From 7869ef2ace9a487abb0b489ca432b0a8878c5083 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 03:19:24 +0100 Subject: First step of the connection skeleton Basic connect, socket creating, polling, recving, etc. --- src/libirc/irc_client.cpp | 102 +++++++++++++++++++++++++++++++++++++++++ src/libirc/irc_client.hpp | 67 +++++++++++++++++++++++++++ src/network/poller.cpp | 96 ++++++++++++++++++++++++++++++++++++++ src/network/poller.hpp | 72 +++++++++++++++++++++++++++++ src/network/socket_handler.hpp | 62 +++++++++++++++++++++++++ src/utils/scopeguard.hpp | 89 +++++++++++++++++++++++++++++++++++ 6 files changed, 488 insertions(+) create mode 100644 src/libirc/irc_client.cpp create mode 100644 src/libirc/irc_client.hpp create mode 100644 src/network/poller.cpp create mode 100644 src/network/poller.hpp create mode 100644 src/network/socket_handler.hpp create mode 100644 src/utils/scopeguard.hpp diff --git a/src/libirc/irc_client.cpp b/src/libirc/irc_client.cpp new file mode 100644 index 0000000..a29e588 --- /dev/null +++ b/src/libirc/irc_client.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +IrcClient::IrcClient() +{ + std::cout << "IrcClient()" << std::endl; + if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) + throw std::runtime_error("Could not create socket"); +} + +IrcClient::~IrcClient() +{ + std::cout << "~IrcClient()" << std::endl; +} + +void IrcClient::on_recv() +{ + char buf[4096]; + + ssize_t size = ::recv(this->socket, buf, 4096, 0); + if (0 == size) + this->on_connection_close(); + else if (-1 == static_cast(size)) + throw std::runtime_error("Error reading from socket"); + else + { + this->in_buf += std::string(buf, size); + this->parse_in_buffer(); + } +} + +void IrcClient::on_send() +{ +} + +socket_t IrcClient::get_socket() const +{ + return this->socket; +} + +void IrcClient::connect(const std::string& address, const std::string& port) +{ + std::cout << "Trying to connect to " << address << ":" << port << std::endl; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + struct addrinfo* addr_res; + const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); + // Make sure the alloced structure is always freed at the end of the + // function + utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); + + if (res != 0) + { + perror("getaddrinfo"); + throw std::runtime_error("getaddrinfo failed"); + } + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) + { + std::cout << "One result" << std::endl; + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) + { + std::cout << "Connection success." << std::endl; + return ; + } + std::cout << "Connection failed:" << std::endl; + perror("connect"); + } + std::cout << "All connection attempts failed." << std::endl; + this->close(); +} + +void IrcClient::on_connection_close() +{ + std::cout << "Connection closed by remote server." << std::endl; + this->close(); +} + +void IrcClient::close() +{ + this->poller->remove_socket_handler(this->get_socket()); + ::close(this->socket); +} + +void IrcClient::parse_in_buffer() +{ + std::cout << "Parsing: [" << this->in_buf << "]" << std::endl; +} diff --git a/src/libirc/irc_client.hpp b/src/libirc/irc_client.hpp new file mode 100644 index 0000000..73e7efd --- /dev/null +++ b/src/libirc/irc_client.hpp @@ -0,0 +1,67 @@ +#ifndef IRC_CLIENT_INCLUDED +# define IRC_CLIENT_INCLUDED + +#include + +#include + +/** + * Represent one IRC client, i.e. an endpoint connected to a single IRC + * server, through a TCP socket, receiving and sending commands to it. + * + * TODO: TLS support, maybe, but that's not high priority + */ +class IrcClient: public SocketHandler +{ +public: + explicit IrcClient(); + ~IrcClient(); + /** + * We read the data, try to parse it and generate some event if + * one or more full message is available. + */ + void on_recv(); + /** + * Just write as much data as possible on the socket. + */ + void on_send(); + socket_t get_socket() const; + /** + * Connect to the remote server + */ + void connect(const std::string& address, const std::string& port); + /** + * Close the connection, remove us from the poller + */ + void close(); + /** + * Called when we detect an orderly close by the remote endpoint. + */ + void on_connection_close(); + /** + * Parse the data we have received so far and try to get one or more + * complete messages from it. + */ + void parse_in_buffer(); + +private: + socket_t socket; + /** + * Where data read from the socket is added, until we can parse a whole + * IRC message, the used data is then removed from that buffer. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Where data is added, when we want to send something to the client. + */ + std::string out_buf; + + IrcClient(const IrcClient&) = delete; + IrcClient(IrcClient&&) = delete; + IrcClient& operator=(const IrcClient&) = delete; + IrcClient& operator=(IrcClient&&) = delete; +}; + +#endif // IRC_CLIENT_INCLUDED diff --git a/src/network/poller.cpp b/src/network/poller.cpp new file mode 100644 index 0000000..c7d9eb2 --- /dev/null +++ b/src/network/poller.cpp @@ -0,0 +1,96 @@ +#include + +#include +#include +#include + + +Poller::Poller() +{ + std::cout << "Poller()" << std::endl; +#if POLLER == POLL + memset(this->fds, 0, sizeof(this->fds)); + this->nfds = 0; +#endif +} + +Poller::~Poller() +{ + std::cout << "~Poller()" << std::endl; +} + +void Poller::add_socket_handler(std::shared_ptr socket_handler) +{ + // Raise an error if that socket is already in the list + const auto it = this->socket_handlers.find(socket_handler->get_socket()); + if (it != this->socket_handlers.end()) + throw std::runtime_error("Trying to insert SocketHandler already managed"); + + this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); + socket_handler->set_poller(this); + + // We always watch all sockets for receive events +#if POLLER == POLL + this->fds[this->nfds].fd = socket_handler->get_socket(); + this->fds[this->nfds].events = POLLIN; + this->nfds++; +#endif +} + +void Poller::remove_socket_handler(const socket_t socket) +{ + const auto it = this->socket_handlers.find(socket); + if (it == this->socket_handlers.end()) + throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); + this->socket_handlers.erase(it); + for (size_t i = 0; i < this->nfds; i++) + { + if (this->fds[i].fd == socket) + { + // Move all subsequent pollfd by one on the left, erasing the + // value of the one we remove + for (size_t j = i; j < this->nfds - 1; ++j) + { + this->fds[j].fd = this->fds[j+1].fd; + this->fds[j].events= this->fds[j+1].events; + } + this->nfds--; + } + } +} + +void Poller::poll() +{ +#if POLLER == POLL + std::cout << "Polling:" << std::endl; + for (size_t i = 0; i < this->nfds; ++i) + std::cout << "pollfd[" << i << "]: (" << this->fds[i].fd << ")" << std::endl; + int res = ::poll(this->fds, this->nfds, -1); + if (res < 0) + { + perror("poll"); + throw std::runtime_error("Poll failed"); + } + // We cannot possibly have more ready events than the number of fds we are + // watching + assert(static_cast(res) <= this->nfds); + for (size_t i = 0; i <= this->nfds && res != 0; ++i) + { + if (this->fds[i].revents == 0) + continue; + else if (this->fds[i].revents & POLLIN) + { + auto socket_handler = this->socket_handlers.at(this->fds[i].fd); + socket_handler->on_recv(); + res--; + } + else if (this->fds[i].revents & POLLOUT) + { + auto socket_handler = this->socket_handlers.at(this->fds[i].fd); + socket_handler->on_send(); + res--; + } + } +#endif +} + diff --git a/src/network/poller.hpp b/src/network/poller.hpp new file mode 100644 index 0000000..46a184e --- /dev/null +++ b/src/network/poller.hpp @@ -0,0 +1,72 @@ +#ifndef POLLER_INCLUDED +# define POLLER_INCLUDED + +#include + +#include +#include + +#define POLL 1 +#define EPOLL 2 +#define KQUEUE 3 + +#define POLLER POLL + +#if POLLER == POLL + #include + // TODO, dynamic size, without artificial limit + #define MAX_POLL_FD_NUMBER 4096 +#endif + +/** + * We pass some SocketHandlers to this the Poller, which uses + * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, + * and call the callbacks when event occurs. + * + * TODO: support for all these pollers: + * - poll(2) (mandatory) + * - epoll(7) + * - kqueue(2) + */ + + +class Poller +{ +public: + explicit Poller(); + ~Poller(); + /** + * Add a SocketHandler to be monitored by this Poller. All receive events + * are always automatically watched. + */ + void add_socket_handler(std::shared_ptr socket_handler); + /** + * Remove (and stop managing) a SocketHandler, designed by the given socket_t. + */ + void remove_socket_handler(const socket_t socket); + /** + * Wait for all watched events, and call the SocketHandlers' callbacks + * when one is ready. + */ + void poll(); + +private: + /** + * A "list" of all the SocketHandlers that we manage, indexed by socket, + * because that's what is returned by select/poll/etc when an event + * occures. + */ + std::unordered_map> socket_handlers; + +#if POLLER == POLL + struct pollfd fds[MAX_POLL_FD_NUMBER]; + nfds_t nfds; +#endif + + Poller(const Poller&) = delete; + Poller(Poller&&) = delete; + Poller& operator=(const Poller&) = delete; + Poller& operator=(Poller&&) = delete; +}; + +#endif // POLLER_INCLUDED diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp new file mode 100644 index 0000000..165f732 --- /dev/null +++ b/src/network/socket_handler.hpp @@ -0,0 +1,62 @@ +#ifndef SOCKET_HANDLER_INCLUDED +# define SOCKET_HANDLER_INCLUDED + +typedef int socket_t; + +class Poller; + +/** + * An interface, with a series of callbacks that should be implemented in + * subclasses that deal with a socket. These callbacks are called on various events + * (read/write/timeout, etc) when they are notified to a poller + * (select/poll/epoll etc) + */ +class SocketHandler +{ +public: + explicit SocketHandler(): + poller(nullptr) + {} + /** + * Set the pointer to the given Poller, to communicate with it. + */ + void set_poller(Poller* poller) + { + this->poller = poller; + }; + /** + * Happens when the socket is ready to be received from. + */ + virtual void on_recv() = 0; + /** + * Happens when the socket is ready to be written to. + */ + virtual void on_send() = 0; + /** + * Returns the socket that should be handled by the poller. + */ + virtual socket_t get_socket() const = 0; + /** + * Close the connection. + */ + virtual void close() = 0; + +protected: + /** + * A pointer to the poller that manages us, because we need to communicate + * with it, sometimes (for example to tell it that he now needs to watch + * write events for us). Do not ever try to delete it. + * + * And a raw pointer because we are not owning it, it is owning us + * (actually it is sharing our ownership with a Bridge). + */ + Poller* poller; + +private: + SocketHandler(const SocketHandler&) = delete; + SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; +}; + +#endif // SOCKET_HANDLER_INCLUDED diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp new file mode 100644 index 0000000..df78831 --- /dev/null +++ b/src/utils/scopeguard.hpp @@ -0,0 +1,89 @@ +#ifndef SCOPEGUARD_HPP +#define SCOPEGUARD_HPP + +#include +#include + +/** + * A class to be used to make sure some functions are called when the scope + * is left, because they will be called in the ScopeGuard's destructor. It + * can for example be used to delete some pointer whenever any exception is + * called. Example: + + * { + * ScopeGuard scope; + * int* number = new int(2); + * scope.add_callback([number]() { delete number; }); + * // Do some other stuff with the number. But these stuff might throw an exception: + * throw std::runtime_error("Some error not caught here, but in our caller"); + * return true; + * } + + * In this example, our pointer will always be deleted, even when the + * exception is thrown. If we want the functions to be called only when the + * scope is left because of an unexpected exception, we can use + * ScopeGuard::disable(); + */ + +namespace utils +{ + +class ScopeGuard +{ +public: + /** + * The constructor can take a callback. But additional callbacks can be + * added later with add_callback() + */ + explicit ScopeGuard(std::function&& func): + enabled(true) + { + this->add_callback(std::move(func)); + } + /** + * default constructor, the scope guard is enabled but empty, use + * add_callback() + */ + explicit ScopeGuard(): + enabled(true) + { + } + /** + * Call all callbacks in the desctructor, unless it has been disabled. + */ + ~ScopeGuard() + { + if (this->enabled) + for (auto& func: this->callbacks) + func(); + } + /** + * Add a callback to be called in our destructor, one scope guard can be + * used for more than one task, if needed. + */ + void add_callback(std::function&& func) + { + this->callbacks.emplace_back(std::move(func)); + } + /** + * Disable that scope guard, nothing will be done when the scope is + * exited. + */ + void disable() + { + this->enabled = false; + } + +private: + bool enabled; + std::vector> callbacks; + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; +}; + +} + +#endif -- cgit v1.2.3 From 64c1b28ce211f899ca0fbcae5049532e129f19c1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 03:23:17 +0100 Subject: Add some dummy main --- src/main.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/main.cpp diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..aeeda34 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,20 @@ +#include +#include + +int main() +{ + Poller p; + // Now I'm the bridge, creating an ircclient because needed. + std::shared_ptr c = std::make_shared(); + p.add_socket_handler(c); + std::shared_ptr d = std::make_shared(); + p.add_socket_handler(d); + std::shared_ptr e = std::make_shared(); + p.add_socket_handler(e); + c->connect("localhost", "7877"); + d->connect("localhost", "7878"); + e->connect("localhost", "7879"); + while (true) + p.poll(); + return 0; +} -- cgit v1.2.3 From 4027ef8c00ee2a5b808c11c7f3ae50cda117d92a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 16:04:10 +0100 Subject: Basic IRC message parsing/sending --- src/libirc/irc_client.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++- src/libirc/irc_client.hpp | 24 +++++++++++++++++ src/network/poller.cpp | 34 +++++++++++++++++++++--- src/network/poller.hpp | 10 +++++++ 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/src/libirc/irc_client.cpp b/src/libirc/irc_client.cpp index a29e588..2780b3c 100644 --- a/src/libirc/irc_client.cpp +++ b/src/libirc/irc_client.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -41,6 +42,18 @@ void IrcClient::on_recv() void IrcClient::on_send() { + const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), 0); + if (res == -1) + { + perror("send"); + this->close(); + } + else + { + this->out_buf = this->out_buf.substr(res, std::string::npos); + if (this->out_buf.empty()) + this->poller->stop_watching_send_events(this); + } } socket_t IrcClient::get_socket() const @@ -75,6 +88,7 @@ void IrcClient::connect(const std::string& address, const std::string& port) if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) { std::cout << "Connection success." << std::endl; + this->on_connected(); return ; } std::cout << "Connection failed:" << std::endl; @@ -84,6 +98,10 @@ void IrcClient::connect(const std::string& address, const std::string& port) this->close(); } +void IrcClient::on_connected() +{ +} + void IrcClient::on_connection_close() { std::cout << "Connection closed by remote server." << std::endl; @@ -98,5 +116,51 @@ void IrcClient::close() void IrcClient::parse_in_buffer() { - std::cout << "Parsing: [" << this->in_buf << "]" << std::endl; + while (true) + { + auto pos = this->in_buf.find("\r\n"); + if (pos == std::string::npos) + break ; + IrcMessage message(this->in_buf.substr(0, pos)); + this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); + std::cout << message << std::endl; + } +} + +void IrcClient::send_message(IrcMessage&& message) +{ + std::string res; + if (!message.prefix.empty()) + res += ":" + std::move(message.prefix) + " "; + res += std::move(message.command); + for (const std::string& arg: message.arguments) + { + if (arg.find(" ") != std::string::npos) + { + res += " :" + arg; + break; + } + res += " " + arg; + } + res += "\r\n"; + this->out_buf += res; + if (!this->out_buf.empty()) + { + this->poller->watch_send_events(this); + } +} + +void IrcClient::send_user_command(const std::string& username, const std::string& realname) +{ + this->send_message(IrcMessage("USER", {username, "NONE", "NONE", realname})); +} + +void IrcClient::send_nick_command(const std::string& nick) +{ + this->send_message(IrcMessage("NICK", {nick})); +} + +void IrcClient::send_join_command(const std::string& chan_name) +{ + this->send_message(IrcMessage("JOIN", {chan_name})); } diff --git a/src/libirc/irc_client.hpp b/src/libirc/irc_client.hpp index 73e7efd..d1ecbd5 100644 --- a/src/libirc/irc_client.hpp +++ b/src/libirc/irc_client.hpp @@ -1,6 +1,8 @@ #ifndef IRC_CLIENT_INCLUDED # define IRC_CLIENT_INCLUDED +#include + #include #include @@ -30,6 +32,10 @@ public: * Connect to the remote server */ void connect(const std::string& address, const std::string& port); + /** + * Called when successfully connected to the server + */ + void on_connected(); /** * Close the connection, remove us from the poller */ @@ -43,6 +49,24 @@ public: * complete messages from it. */ void parse_in_buffer(); + /** + * Serialize the given message into a line, and send that into the socket + * (actually, into our out_buf and signal the poller that we want to wach + * for send events to be ready) + */ + void send_message(IrcMessage&& message); + /** + * Send the USER irc command + */ + void send_user_command(const std::string& username, const std::string& realname); + /** + * Send the NICK irc command + */ + void send_nick_command(const std::string& username); + /** + * Send the JOIN irc command + */ + void send_join_command(const std::string& chan_name); private: socket_t socket; diff --git a/src/network/poller.cpp b/src/network/poller.cpp index c7d9eb2..7ab8bc3 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -59,12 +59,39 @@ void Poller::remove_socket_handler(const socket_t socket) } } +void Poller::watch_send_events(const SocketHandler* const socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i <= this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN|POLLOUT; + return; + } + } +#endif + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +} + +void Poller::stop_watching_send_events(const SocketHandler* const socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i <= this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN; + return; + } + } +#endif + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +} + void Poller::poll() { #if POLLER == POLL - std::cout << "Polling:" << std::endl; - for (size_t i = 0; i < this->nfds; ++i) - std::cout << "pollfd[" << i << "]: (" << this->fds[i].fd << ")" << std::endl; int res = ::poll(this->fds, this->nfds, -1); if (res < 0) { @@ -93,4 +120,3 @@ void Poller::poll() } #endif } - diff --git a/src/network/poller.hpp b/src/network/poller.hpp index 46a184e..64e78e4 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -44,6 +44,16 @@ public: * Remove (and stop managing) a SocketHandler, designed by the given socket_t. */ void remove_socket_handler(const socket_t socket); + /** + * Signal the poller that he needs to watch for send events for the given + * SocketHandler. + */ + void watch_send_events(const SocketHandler* const socket_handler); + /** + * Signal the poller that he needs to stop watching for send events for + * this SocketHandler. + */ + void stop_watching_send_events(const SocketHandler* const socket_handler); /** * Wait for all watched events, and call the SocketHandlers' callbacks * when one is ready. -- cgit v1.2.3 From 7f580dbc0e529d200662e676119a3dcb966f67f9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 16:05:55 +0100 Subject: Add irc_message.hpp --- src/libirc/irc_message.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/libirc/irc_message.hpp | 28 ++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/libirc/irc_message.cpp create mode 100644 src/libirc/irc_message.hpp diff --git a/src/libirc/irc_message.cpp b/src/libirc/irc_message.cpp new file mode 100644 index 0000000..fc691b4 --- /dev/null +++ b/src/libirc/irc_message.cpp @@ -0,0 +1,65 @@ +#include +#include + +IrcMessage::IrcMessage(std::string&& line) +{ + std::string::size_type pos; + + // optional prefix + if (line[0] == ':') + { + pos = line.find(" "); + this->prefix = line.substr(1, pos); + line = line.substr(pos + 1, std::string::npos); + } + // command + pos = line.find(" "); + this->command = line.substr(0, pos); + line = line.substr(pos + 1, std::string::npos); + // arguments + do + { + if (line[0] == ':') + { + this->arguments.emplace_back(line.substr(1, std::string::npos)); + break ; + } + pos = line.find(" "); + this->arguments.emplace_back(line.substr(0, pos)); + line = line.substr(pos + 1, std::string::npos); + } while (pos != std::string::npos); +} + +IrcMessage::IrcMessage(std::string&& prefix, + std::string&& command, + std::vector&& args): + prefix(std::move(prefix)), + command(std::move(command)), + arguments(std::move(args)) +{ +} + +IrcMessage::IrcMessage(std::string&& command, + std::vector&& args): + prefix(), + command(std::move(command)), + arguments(std::move(args)) +{ +} + +IrcMessage::~IrcMessage() +{ +} + +std::ostream& operator<<(std::ostream& os, const IrcMessage& message) +{ + os << "IrcMessage"; + os << "[" << message.command << "]"; + for (const std::string& arg: message.arguments) + { + os << "{" << arg << "}"; + } + if (!message.prefix.empty()) + os << "(from: " << message.prefix << ")"; + return os; +} diff --git a/src/libirc/irc_message.hpp b/src/libirc/irc_message.hpp new file mode 100644 index 0000000..a0bd772 --- /dev/null +++ b/src/libirc/irc_message.hpp @@ -0,0 +1,28 @@ +#ifndef IRC_MESSAGE_INCLUDED +# define IRC_MESSAGE_INCLUDED + +#include +#include +#include + +class IrcMessage +{ +public: + explicit IrcMessage(std::string&& line); + explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); + explicit IrcMessage(std::string&& command, std::vector&& args); + ~IrcMessage(); + + std::string prefix; + std::string command; + std::vector arguments; + + IrcMessage(const IrcMessage&) = delete; + IrcMessage(IrcMessage&&) = delete; + IrcMessage& operator=(const IrcMessage&) = delete; + IrcMessage& operator=(IrcMessage&&) = delete; +}; + +std::ostream& operator<<(std::ostream& os, const IrcMessage& message); + +#endif // IRC_MESSAGE_INCLUDED -- cgit v1.2.3 From 358b53faef288d40ed28532953dc1a4183bc96c9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 23:12:16 +0100 Subject: Move the basic socket implementation into the SocketHandler class (that is, the read/write/connect/etc) Because this code is actually common for both the IrcClient and XmppComponent class. These two classes have to implement some higher level callbacks (parsing the data provided in the buffers, doing stuff when the connection is done) instead of doing the read/write/connect low level things. --- src/libirc/irc_client.cpp | 96 +--------------------------------- src/libirc/irc_client.hpp | 31 ----------- src/network/socket_handler.cpp | 113 +++++++++++++++++++++++++++++++++++++++++ src/network/socket_handler.hpp | 61 +++++++++++++++++----- 4 files changed, 161 insertions(+), 140 deletions(-) create mode 100644 src/network/socket_handler.cpp diff --git a/src/libirc/irc_client.cpp b/src/libirc/irc_client.cpp index 2780b3c..a427026 100644 --- a/src/libirc/irc_client.cpp +++ b/src/libirc/irc_client.cpp @@ -1,13 +1,5 @@ #include #include -#include -#include - -#include -#include -#include -#include -#include #include #include @@ -15,8 +7,6 @@ IrcClient::IrcClient() { std::cout << "IrcClient()" << std::endl; - if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) - throw std::runtime_error("Could not create socket"); } IrcClient::~IrcClient() @@ -24,80 +14,6 @@ IrcClient::~IrcClient() std::cout << "~IrcClient()" << std::endl; } -void IrcClient::on_recv() -{ - char buf[4096]; - - ssize_t size = ::recv(this->socket, buf, 4096, 0); - if (0 == size) - this->on_connection_close(); - else if (-1 == static_cast(size)) - throw std::runtime_error("Error reading from socket"); - else - { - this->in_buf += std::string(buf, size); - this->parse_in_buffer(); - } -} - -void IrcClient::on_send() -{ - const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), 0); - if (res == -1) - { - perror("send"); - this->close(); - } - else - { - this->out_buf = this->out_buf.substr(res, std::string::npos); - if (this->out_buf.empty()) - this->poller->stop_watching_send_events(this); - } -} - -socket_t IrcClient::get_socket() const -{ - return this->socket; -} - -void IrcClient::connect(const std::string& address, const std::string& port) -{ - std::cout << "Trying to connect to " << address << ":" << port << std::endl; - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - struct addrinfo* addr_res; - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - // Make sure the alloced structure is always freed at the end of the - // function - utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); - - if (res != 0) - { - perror("getaddrinfo"); - throw std::runtime_error("getaddrinfo failed"); - } - for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) - { - std::cout << "One result" << std::endl; - if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) - { - std::cout << "Connection success." << std::endl; - this->on_connected(); - return ; - } - std::cout << "Connection failed:" << std::endl; - perror("connect"); - } - std::cout << "All connection attempts failed." << std::endl; - this->close(); -} - void IrcClient::on_connected() { } @@ -108,12 +24,6 @@ void IrcClient::on_connection_close() this->close(); } -void IrcClient::close() -{ - this->poller->remove_socket_handler(this->get_socket()); - ::close(this->socket); -} - void IrcClient::parse_in_buffer() { while (true) @@ -143,11 +53,7 @@ void IrcClient::send_message(IrcMessage&& message) res += " " + arg; } res += "\r\n"; - this->out_buf += res; - if (!this->out_buf.empty()) - { - this->poller->watch_send_events(this); - } + this->send_data(std::move(res)); } void IrcClient::send_user_command(const std::string& username, const std::string& realname) diff --git a/src/libirc/irc_client.hpp b/src/libirc/irc_client.hpp index d1ecbd5..9778876 100644 --- a/src/libirc/irc_client.hpp +++ b/src/libirc/irc_client.hpp @@ -18,20 +18,6 @@ class IrcClient: public SocketHandler public: explicit IrcClient(); ~IrcClient(); - /** - * We read the data, try to parse it and generate some event if - * one or more full message is available. - */ - void on_recv(); - /** - * Just write as much data as possible on the socket. - */ - void on_send(); - socket_t get_socket() const; - /** - * Connect to the remote server - */ - void connect(const std::string& address, const std::string& port); /** * Called when successfully connected to the server */ @@ -39,10 +25,6 @@ public: /** * Close the connection, remove us from the poller */ - void close(); - /** - * Called when we detect an orderly close by the remote endpoint. - */ void on_connection_close(); /** * Parse the data we have received so far and try to get one or more @@ -69,19 +51,6 @@ public: void send_join_command(const std::string& chan_name); private: - socket_t socket; - /** - * Where data read from the socket is added, until we can parse a whole - * IRC message, the used data is then removed from that buffer. - * - * TODO: something more efficient than a string. - */ - std::string in_buf; - /** - * Where data is added, when we want to send something to the client. - */ - std::string out_buf; - IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; IrcClient& operator=(const IrcClient&) = delete; diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp new file mode 100644 index 0000000..e738302 --- /dev/null +++ b/src/network/socket_handler.cpp @@ -0,0 +1,113 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +SocketHandler::SocketHandler(): + poller(nullptr) +{ + if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) + throw std::runtime_error("Could not create socket"); +} + +void SocketHandler::connect(const std::string& address, const std::string& port) +{ + std::cout << "Trying to connect to " << address << ":" << port << std::endl; + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + struct addrinfo* addr_res; + const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); + // Make sure the alloced structure is always freed at the end of the + // function + utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); + + if (res != 0) + { + perror("getaddrinfo"); + throw std::runtime_error("getaddrinfo failed"); + } + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) + { + std::cout << "One result" << std::endl; + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) + { + std::cout << "Connection success." << std::endl; + this->on_connected(); + return ; + } + std::cout << "Connection failed:" << std::endl; + perror("connect"); + } + std::cout << "All connection attempts failed." << std::endl; + this->close(); +} + +void SocketHandler::set_poller(Poller* poller) +{ + this->poller = poller; +} + +void SocketHandler::on_recv() +{ + char buf[4096]; + + ssize_t size = ::recv(this->socket, buf, 4096, 0); + if (0 == size) + this->on_connection_close(); + else if (-1 == static_cast(size)) + throw std::runtime_error("Error reading from socket"); + else + { + this->in_buf += std::string(buf, size); + this->parse_in_buffer(); + } +} + +void SocketHandler::on_send() +{ + const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), 0); + if (res == -1) + { + perror("send"); + this->close(); + } + else + { + this->out_buf = this->out_buf.substr(res, std::string::npos); + if (this->out_buf.empty()) + this->poller->stop_watching_send_events(this); + } +} + +void SocketHandler::close() +{ + this->poller->remove_socket_handler(this->get_socket()); + ::close(this->socket); +} + +socket_t SocketHandler::get_socket() const +{ + return this->socket; +} + +void SocketHandler::send_data(std::string&& data) +{ + this->out_buf += std::move(data); + if (!this->out_buf.empty()) + { + this->poller->watch_send_events(this); + } +} diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 165f732..4152a4e 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,6 +1,8 @@ #ifndef SOCKET_HANDLER_INCLUDED # define SOCKET_HANDLER_INCLUDED +#include + typedef int socket_t; class Poller; @@ -14,34 +16,65 @@ class Poller; class SocketHandler { public: - explicit SocketHandler(): - poller(nullptr) - {} + explicit SocketHandler(); + virtual ~SocketHandler() {} + /** + * Connect to the remote server, and call on_connected() if this succeeds + */ + void connect(const std::string& address, const std::string& port); /** * Set the pointer to the given Poller, to communicate with it. */ - void set_poller(Poller* poller) - { - this->poller = poller; - }; + void set_poller(Poller* poller); + /** + * Reads data in our in_buf and the call parse_in_buf, for the implementor + * to handle the data received so far. + */ + void on_recv(); /** - * Happens when the socket is ready to be received from. + * Write as much data from out_buf as possible, in the socket. */ - virtual void on_recv() = 0; + void on_send(); /** - * Happens when the socket is ready to be written to. + * Add the given data to out_buf and tell our poller that we want to be + * notified when a send event is ready. */ - virtual void on_send() = 0; + void send_data(std::string&& data); /** * Returns the socket that should be handled by the poller. */ - virtual socket_t get_socket() const = 0; + socket_t get_socket() const; + /** + * Close the connection, remove us from the poller + */ + void close(); + /** + * Called when the connection is successful. + */ + virtual void on_connected() = 0; + /** + * Called when we detect a disconnection from the remote host. + */ + virtual void on_connection_close() = 0; /** - * Close the connection. + * Handle/consume (some of) the data received so far. If some data is used, the in_buf + * should be truncated, only the unused data should be left untouched. */ - virtual void close() = 0; + virtual void parse_in_buffer() = 0; protected: + socket_t socket; + /** + * Where data read from the socket is added, until we can parse a whole + * IRC message, the used data is then removed from that buffer. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Where data is added, when we want to send something to the client. + */ + std::string out_buf; /** * A pointer to the poller that manages us, because we need to communicate * with it, sometimes (for example to tell it that he now needs to watch -- cgit v1.2.3 From e332d7a2c0900aec488ab508c3e9e389d2a71e32 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 2 Nov 2013 23:44:45 +0100 Subject: Add initial CMakeLists.txt that compiles the current code --- CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..163e361 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 2.6) + +project(biboumi) + +set(${PROJECT_NAME}_VERSION_MAJOR 0) +set(${PROJECT_NAME}_VERSION_MINOR 1) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -fsanitize=address") + +include_directories("src/") + +# +## network +# +file(GLOB source_network + src/network/*.[hc]pp) +add_library(network STATIC ${source_network}) + +# +## irclib +# +file(GLOB source_libirc + src/libirc/*.[hc]pp) +add_library(libirc STATIC ${source_libirc}) +target_link_libraries(libirc network) + +# +## xmpplib +# +file(GLOB source_libxmpp + src/libxmpp/*.[hc]pp) +add_library(libxmpp STATIC ${source_libxmpp}) +target_link_libraries(libxmpp network) + +add_executable(${PROJECT_NAME} src/main.cpp) +target_link_libraries(${PROJECT_NAME} + libxmpp + libirc) \ No newline at end of file -- cgit v1.2.3 From 87aaacdb420341bf3619922332d58b95249971bc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 00:22:32 +0100 Subject: Rename libirc and libxmpp to irc and xmpp --- CMakeLists.txt | 20 ++++++------- src/irc/irc_client.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++++++ src/irc/irc_client.hpp | 60 ++++++++++++++++++++++++++++++++++++++ src/irc/irc_message.cpp | 65 +++++++++++++++++++++++++++++++++++++++++ src/irc/irc_message.hpp | 28 ++++++++++++++++++ src/libirc/irc_client.cpp | 72 ---------------------------------------------- src/libirc/irc_client.hpp | 60 -------------------------------------- src/libirc/irc_message.cpp | 65 ----------------------------------------- src/libirc/irc_message.hpp | 28 ------------------ src/main.cpp | 2 +- 10 files changed, 236 insertions(+), 236 deletions(-) create mode 100644 src/irc/irc_client.cpp create mode 100644 src/irc/irc_client.hpp create mode 100644 src/irc/irc_message.cpp create mode 100644 src/irc/irc_message.hpp delete mode 100644 src/libirc/irc_client.cpp delete mode 100644 src/libirc/irc_client.hpp delete mode 100644 src/libirc/irc_message.cpp delete mode 100644 src/libirc/irc_message.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 163e361..9f1eef6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,20 +20,20 @@ add_library(network STATIC ${source_network}) # ## irclib # -file(GLOB source_libirc - src/libirc/*.[hc]pp) -add_library(libirc STATIC ${source_libirc}) -target_link_libraries(libirc network) +file(GLOB source_irc + src/irc/*.[hc]pp) +add_library(irc STATIC ${source_irc}) +target_link_libraries(irc network ${CRYPTO++_LIBRARIES}) # ## xmpplib # -file(GLOB source_libxmpp - src/libxmpp/*.[hc]pp) -add_library(libxmpp STATIC ${source_libxmpp}) -target_link_libraries(libxmpp network) +file(GLOB source_xmpp + src/xmpp/*.[hc]pp) +add_library(xmpp STATIC ${source_xmpp}) +target_link_libraries(xmpp network ) add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} - libxmpp - libirc) \ No newline at end of file + xmpp + irc) \ No newline at end of file diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp new file mode 100644 index 0000000..83d1b0d --- /dev/null +++ b/src/irc/irc_client.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include +#include + +IrcClient::IrcClient() +{ + std::cout << "IrcClient()" << std::endl; +} + +IrcClient::~IrcClient() +{ + std::cout << "~IrcClient()" << std::endl; +} + +void IrcClient::on_connected() +{ +} + +void IrcClient::on_connection_close() +{ + std::cout << "Connection closed by remote server." << std::endl; + this->close(); +} + +void IrcClient::parse_in_buffer() +{ + while (true) + { + auto pos = this->in_buf.find("\r\n"); + if (pos == std::string::npos) + break ; + IrcMessage message(this->in_buf.substr(0, pos)); + this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); + std::cout << message << std::endl; + } +} + +void IrcClient::send_message(IrcMessage&& message) +{ + std::string res; + if (!message.prefix.empty()) + res += ":" + std::move(message.prefix) + " "; + res += std::move(message.command); + for (const std::string& arg: message.arguments) + { + if (arg.find(" ") != std::string::npos) + { + res += " :" + arg; + break; + } + res += " " + arg; + } + res += "\r\n"; + this->send_data(std::move(res)); +} + +void IrcClient::send_user_command(const std::string& username, const std::string& realname) +{ + this->send_message(IrcMessage("USER", {username, "NONE", "NONE", realname})); +} + +void IrcClient::send_nick_command(const std::string& nick) +{ + this->send_message(IrcMessage("NICK", {nick})); +} + +void IrcClient::send_join_command(const std::string& chan_name) +{ + this->send_message(IrcMessage("JOIN", {chan_name})); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp new file mode 100644 index 0000000..e380f5b --- /dev/null +++ b/src/irc/irc_client.hpp @@ -0,0 +1,60 @@ +#ifndef IRC_CLIENT_INCLUDED +# define IRC_CLIENT_INCLUDED + +#include + +#include + +#include + +/** + * Represent one IRC client, i.e. an endpoint connected to a single IRC + * server, through a TCP socket, receiving and sending commands to it. + * + * TODO: TLS support, maybe, but that's not high priority + */ +class IrcClient: public SocketHandler +{ +public: + explicit IrcClient(); + ~IrcClient(); + /** + * Called when successfully connected to the server + */ + void on_connected(); + /** + * Close the connection, remove us from the poller + */ + void on_connection_close(); + /** + * Parse the data we have received so far and try to get one or more + * complete messages from it. + */ + void parse_in_buffer(); + /** + * Serialize the given message into a line, and send that into the socket + * (actually, into our out_buf and signal the poller that we want to wach + * for send events to be ready) + */ + void send_message(IrcMessage&& message); + /** + * Send the USER irc command + */ + void send_user_command(const std::string& username, const std::string& realname); + /** + * Send the NICK irc command + */ + void send_nick_command(const std::string& username); + /** + * Send the JOIN irc command + */ + void send_join_command(const std::string& chan_name); + +private: + IrcClient(const IrcClient&) = delete; + IrcClient(IrcClient&&) = delete; + IrcClient& operator=(const IrcClient&) = delete; + IrcClient& operator=(IrcClient&&) = delete; +}; + +#endif // IRC_CLIENT_INCLUDED diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp new file mode 100644 index 0000000..18fa3ec --- /dev/null +++ b/src/irc/irc_message.cpp @@ -0,0 +1,65 @@ +#include +#include + +IrcMessage::IrcMessage(std::string&& line) +{ + std::string::size_type pos; + + // optional prefix + if (line[0] == ':') + { + pos = line.find(" "); + this->prefix = line.substr(1, pos); + line = line.substr(pos + 1, std::string::npos); + } + // command + pos = line.find(" "); + this->command = line.substr(0, pos); + line = line.substr(pos + 1, std::string::npos); + // arguments + do + { + if (line[0] == ':') + { + this->arguments.emplace_back(line.substr(1, std::string::npos)); + break ; + } + pos = line.find(" "); + this->arguments.emplace_back(line.substr(0, pos)); + line = line.substr(pos + 1, std::string::npos); + } while (pos != std::string::npos); +} + +IrcMessage::IrcMessage(std::string&& prefix, + std::string&& command, + std::vector&& args): + prefix(std::move(prefix)), + command(std::move(command)), + arguments(std::move(args)) +{ +} + +IrcMessage::IrcMessage(std::string&& command, + std::vector&& args): + prefix(), + command(std::move(command)), + arguments(std::move(args)) +{ +} + +IrcMessage::~IrcMessage() +{ +} + +std::ostream& operator<<(std::ostream& os, const IrcMessage& message) +{ + os << "IrcMessage"; + os << "[" << message.command << "]"; + for (const std::string& arg: message.arguments) + { + os << "{" << arg << "}"; + } + if (!message.prefix.empty()) + os << "(from: " << message.prefix << ")"; + return os; +} diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp new file mode 100644 index 0000000..a0bd772 --- /dev/null +++ b/src/irc/irc_message.hpp @@ -0,0 +1,28 @@ +#ifndef IRC_MESSAGE_INCLUDED +# define IRC_MESSAGE_INCLUDED + +#include +#include +#include + +class IrcMessage +{ +public: + explicit IrcMessage(std::string&& line); + explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); + explicit IrcMessage(std::string&& command, std::vector&& args); + ~IrcMessage(); + + std::string prefix; + std::string command; + std::vector arguments; + + IrcMessage(const IrcMessage&) = delete; + IrcMessage(IrcMessage&&) = delete; + IrcMessage& operator=(const IrcMessage&) = delete; + IrcMessage& operator=(IrcMessage&&) = delete; +}; + +std::ostream& operator<<(std::ostream& os, const IrcMessage& message); + +#endif // IRC_MESSAGE_INCLUDED diff --git a/src/libirc/irc_client.cpp b/src/libirc/irc_client.cpp deleted file mode 100644 index a427026..0000000 --- a/src/libirc/irc_client.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include - -#include -#include - -IrcClient::IrcClient() -{ - std::cout << "IrcClient()" << std::endl; -} - -IrcClient::~IrcClient() -{ - std::cout << "~IrcClient()" << std::endl; -} - -void IrcClient::on_connected() -{ -} - -void IrcClient::on_connection_close() -{ - std::cout << "Connection closed by remote server." << std::endl; - this->close(); -} - -void IrcClient::parse_in_buffer() -{ - while (true) - { - auto pos = this->in_buf.find("\r\n"); - if (pos == std::string::npos) - break ; - IrcMessage message(this->in_buf.substr(0, pos)); - this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); - std::cout << message << std::endl; - } -} - -void IrcClient::send_message(IrcMessage&& message) -{ - std::string res; - if (!message.prefix.empty()) - res += ":" + std::move(message.prefix) + " "; - res += std::move(message.command); - for (const std::string& arg: message.arguments) - { - if (arg.find(" ") != std::string::npos) - { - res += " :" + arg; - break; - } - res += " " + arg; - } - res += "\r\n"; - this->send_data(std::move(res)); -} - -void IrcClient::send_user_command(const std::string& username, const std::string& realname) -{ - this->send_message(IrcMessage("USER", {username, "NONE", "NONE", realname})); -} - -void IrcClient::send_nick_command(const std::string& nick) -{ - this->send_message(IrcMessage("NICK", {nick})); -} - -void IrcClient::send_join_command(const std::string& chan_name) -{ - this->send_message(IrcMessage("JOIN", {chan_name})); -} diff --git a/src/libirc/irc_client.hpp b/src/libirc/irc_client.hpp deleted file mode 100644 index 9778876..0000000 --- a/src/libirc/irc_client.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef IRC_CLIENT_INCLUDED -# define IRC_CLIENT_INCLUDED - -#include - -#include - -#include - -/** - * Represent one IRC client, i.e. an endpoint connected to a single IRC - * server, through a TCP socket, receiving and sending commands to it. - * - * TODO: TLS support, maybe, but that's not high priority - */ -class IrcClient: public SocketHandler -{ -public: - explicit IrcClient(); - ~IrcClient(); - /** - * Called when successfully connected to the server - */ - void on_connected(); - /** - * Close the connection, remove us from the poller - */ - void on_connection_close(); - /** - * Parse the data we have received so far and try to get one or more - * complete messages from it. - */ - void parse_in_buffer(); - /** - * Serialize the given message into a line, and send that into the socket - * (actually, into our out_buf and signal the poller that we want to wach - * for send events to be ready) - */ - void send_message(IrcMessage&& message); - /** - * Send the USER irc command - */ - void send_user_command(const std::string& username, const std::string& realname); - /** - * Send the NICK irc command - */ - void send_nick_command(const std::string& username); - /** - * Send the JOIN irc command - */ - void send_join_command(const std::string& chan_name); - -private: - IrcClient(const IrcClient&) = delete; - IrcClient(IrcClient&&) = delete; - IrcClient& operator=(const IrcClient&) = delete; - IrcClient& operator=(IrcClient&&) = delete; -}; - -#endif // IRC_CLIENT_INCLUDED diff --git a/src/libirc/irc_message.cpp b/src/libirc/irc_message.cpp deleted file mode 100644 index fc691b4..0000000 --- a/src/libirc/irc_message.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include - -IrcMessage::IrcMessage(std::string&& line) -{ - std::string::size_type pos; - - // optional prefix - if (line[0] == ':') - { - pos = line.find(" "); - this->prefix = line.substr(1, pos); - line = line.substr(pos + 1, std::string::npos); - } - // command - pos = line.find(" "); - this->command = line.substr(0, pos); - line = line.substr(pos + 1, std::string::npos); - // arguments - do - { - if (line[0] == ':') - { - this->arguments.emplace_back(line.substr(1, std::string::npos)); - break ; - } - pos = line.find(" "); - this->arguments.emplace_back(line.substr(0, pos)); - line = line.substr(pos + 1, std::string::npos); - } while (pos != std::string::npos); -} - -IrcMessage::IrcMessage(std::string&& prefix, - std::string&& command, - std::vector&& args): - prefix(std::move(prefix)), - command(std::move(command)), - arguments(std::move(args)) -{ -} - -IrcMessage::IrcMessage(std::string&& command, - std::vector&& args): - prefix(), - command(std::move(command)), - arguments(std::move(args)) -{ -} - -IrcMessage::~IrcMessage() -{ -} - -std::ostream& operator<<(std::ostream& os, const IrcMessage& message) -{ - os << "IrcMessage"; - os << "[" << message.command << "]"; - for (const std::string& arg: message.arguments) - { - os << "{" << arg << "}"; - } - if (!message.prefix.empty()) - os << "(from: " << message.prefix << ")"; - return os; -} diff --git a/src/libirc/irc_message.hpp b/src/libirc/irc_message.hpp deleted file mode 100644 index a0bd772..0000000 --- a/src/libirc/irc_message.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef IRC_MESSAGE_INCLUDED -# define IRC_MESSAGE_INCLUDED - -#include -#include -#include - -class IrcMessage -{ -public: - explicit IrcMessage(std::string&& line); - explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); - explicit IrcMessage(std::string&& command, std::vector&& args); - ~IrcMessage(); - - std::string prefix; - std::string command; - std::vector arguments; - - IrcMessage(const IrcMessage&) = delete; - IrcMessage(IrcMessage&&) = delete; - IrcMessage& operator=(const IrcMessage&) = delete; - IrcMessage& operator=(IrcMessage&&) = delete; -}; - -std::ostream& operator<<(std::ostream& os, const IrcMessage& message); - -#endif // IRC_MESSAGE_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index aeeda34..fe3b2e5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include +#include #include int main() -- cgit v1.2.3 From 5bbd34a3a909fa904ee4402f01dac6bac59211b1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 15:12:50 +0100 Subject: Add an XmppParser, and Stanza classes Generate events on stanza and stream open/close. Create Stanza and serialize them. Note: XML namespaces are not handled yet. --- CMakeLists.txt | 2 +- src/xmpp/xmpp_parser.cpp | 103 ++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_parser.hpp | 107 +++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.cpp | 127 +++++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.hpp | 106 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/xmpp/xmpp_parser.cpp create mode 100644 src/xmpp/xmpp_parser.hpp create mode 100644 src/xmpp/xmpp_stanza.cpp create mode 100644 src/xmpp/xmpp_stanza.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1eef6..1c21b84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ add_library(network STATIC ${source_network}) file(GLOB source_irc src/irc/*.[hc]pp) add_library(irc STATIC ${source_irc}) -target_link_libraries(irc network ${CRYPTO++_LIBRARIES}) +target_link_libraries(irc network ${CRYPTO++_LIBRARIES} expatpp) # ## xmpplib diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp new file mode 100644 index 0000000..00714b3 --- /dev/null +++ b/src/xmpp/xmpp_parser.cpp @@ -0,0 +1,103 @@ +#include +#include + +#include + +XmppParser::XmppParser(): + level(0), + current_node(nullptr) +{ +} + +XmppParser::~XmppParser() +{ + if (this->current_node) + delete this->current_node; +} + +void XmppParser::startElement(const XML_Char* name, const XML_Char** attribute) +{ + level++; + + XmlNode* new_node = new XmlNode(name, this->current_node); + if (this->current_node) + this->current_node->add_child(new_node); + this->current_node = new_node; + for (size_t i = 0; attribute[i]; i += 2) + this->current_node->set_attribute(attribute[i], attribute[i+1]); + if (this->level == 1) + this->stream_open_event(*this->current_node); +} + +void XmppParser::endElement(const XML_Char* name) +{ + assert(name == this->current_node->get_name()); + level--; + this->current_node->close(); + if (level == 1) + { + this->stanza_event(*this->current_node); + } + if (level == 0) + { + this->stream_close_event(*this->current_node); + delete this->current_node; + this->current_node = nullptr; + } + else + this->current_node = this->current_node->get_parent(); + if (level == 1) + this->current_node->delete_all_children(); +} + +void XmppParser::charData(const XML_Char* data, int len) +{ + if (this->current_node->has_children()) + this->current_node->get_last_child()->set_tail(std::string(data, len)); + else + this->current_node->set_inner(std::string(data, len)); +} + +void XmppParser::startNamespace(const XML_Char* prefix, const XML_Char* uri) +{ + std::cout << "startNamespace: " << prefix << ":" << uri << std::endl; + this->namespaces.emplace(std::make_pair(prefix, uri)); +} + +void XmppParser::stanza_event(const Stanza& stanza) const +{ + for (const auto& callback: this->stanza_callbacks) + callback(stanza); +} + +void XmppParser::stream_open_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_open_callbacks) + callback(node); +} + +void XmppParser::stream_close_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_close_callbacks) + callback(node); +} + +void XmppParser::endNamespace(const XML_Char* coucou) +{ + std::cout << "endNamespace: " << coucou << std::endl; +} + +void XmppParser::add_stanza_callback(std::function&& callback) +{ + this->stanza_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_open_callback(std::function&& callback) +{ + this->stream_open_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_close_callback(std::function&& callback) +{ + this->stream_close_callbacks.emplace_back(std::move(callback)); +} diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp new file mode 100644 index 0000000..2e83bc3 --- /dev/null +++ b/src/xmpp/xmpp_parser.hpp @@ -0,0 +1,107 @@ +#ifndef XMPP_PARSER_INCLUDED +# define XMPP_PARSER_INCLUDED + +#include +#include + +#include + +#include + +/** + * A SAX XML parser that builds XML nodes and spawns events when a complete + * stanza is received (an element of level 2), or when the document is + * opened (an element of level 1) + * + * After a stanza_event has been spawned, we delete the whole stanza. This + * means that even with a very long document (in XMPP the document is + * potentially infinite), the memory then is never exhausted as long as each + * stanza is reasonnably short. + * + * TODO: enforce the size-limit for the stanza (limit the number of childs + * it can contain). For example forbid the parser going further than level + * 20 (arbitrary number here), and each XML node to have more than 15 childs + * (arbitrary number again). + */ +class XmppParser: public expatpp +{ +public: + explicit XmppParser(); + ~XmppParser(); + +public: + /** + * Add one callback for the various events that this parser can spawn. + */ + void add_stanza_callback(std::function&& callback); + void add_stream_open_callback(std::function&& callback); + void add_stream_close_callback(std::function&& callback); + +private: + /** + * Called when a new XML element has been opened. We instanciate a new + * XmlNode and set it as our current node. The parent of this new node is + * the previous "current" node. We have all the element's attributes in + * this event. + * + * We spawn a stream_event with this node if this is a level-1 element. + */ + void startElement(const XML_Char* name, const XML_Char** attribute); + /** + * Called when an XML element has been closed. We close the current_node, + * set our current_node as the parent of the current_node, and if that was + * a level-2 element we spawn a stanza_event with this node. + * + * And we then delete the stanza (and everything under it, its children, + * attribute, etc). + */ + void endElement(const XML_Char* name); + /** + * Some inner or tail data has been parsed + */ + void charData(const XML_Char* data, int len); + /** + * TODO use that. + */ + void startNamespace(const XML_Char* prefix, const XML_Char* uri); + /** + * TODO and that. + */ + void endNamespace(const XML_Char* prefix); + /** + * Calls all the stanza_callbacks one by one. + */ + void stanza_event(const Stanza& stanza) const; + /** + * Calls all the stream_open_callbacks one by one. Note: the passed node is not + * closed yet. + */ + void stream_open_event(const XmlNode& node) const; + /** + * Calls all the stream_close_callbacks one by one. + */ + void stream_close_event(const XmlNode& node) const; + + /** + * The current depth in the XML document + */ + size_t level; + /** + * The deepest XML node opened but not yet closed (to which we are adding + * new children, inner or tail) + */ + XmlNode* current_node; + /** + * A list of callbacks to be called on an *_event, receiving the + * concerned Stanza/XmlNode. + */ + std::vector> stanza_callbacks; + std::vector> stream_open_callbacks; + std::vector> stream_close_callbacks; + /** + * TODO: also use that. + */ + std::stack> namespaces; +}; + +#endif // XMPP_PARSER_INCLUDED diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp new file mode 100644 index 0000000..2c98acc --- /dev/null +++ b/src/xmpp/xmpp_stanza.cpp @@ -0,0 +1,127 @@ +#include + +#include + +XmlNode::XmlNode(const std::string& name, XmlNode* parent): + name(name), + parent(parent), + closed(false) +{ +} + +XmlNode::XmlNode(const std::string& name): + XmlNode(name, nullptr) +{ +} + +XmlNode::~XmlNode() +{ + this->delete_all_children(); +} + +void XmlNode::delete_all_children() +{ + for (auto& child: this->children) + { + delete child; + } + this->children.clear(); +} + +void XmlNode::set_attribute(const std::string& name, const std::string& value) +{ + this->attributes[name] = value; +} + +void XmlNode::set_tail(const std::string& data) +{ + this->tail = data; +} + +void XmlNode::set_inner(const std::string& data) +{ + this->inner = data; +} + +void XmlNode::add_child(XmlNode* child) +{ + this->children.push_back(child); +} + +void XmlNode::add_child(XmlNode&& child) +{ + XmlNode* new_node = new XmlNode(std::move(child)); + this->add_child(new_node); +} + +XmlNode* XmlNode::get_last_child() const +{ + return this->children.back(); +} + +void XmlNode::close() +{ + if (this->closed) + throw std::runtime_error("Closing an already closed XmlNode"); + this->closed = true; +} + +XmlNode* XmlNode::get_parent() const +{ + return this->parent; +} + +const std::string& XmlNode::get_name() const +{ + return this->name; +} + +std::string XmlNode::to_string() const +{ + std::string res("<"); + res += this->name; + for (const auto& it: this->attributes) + res += " " + it.first + "='" + it.second + "'"; + if (this->closed && !this->has_children() && this->inner.empty()) + res += "/>"; + else + { + res += ">" + this->inner; + for (const auto& child: this->children) + res += child->to_string(); + if (this->closed) + { + res += "name + ">"; + } + } + res += this->tail; + return res; +} + +void XmlNode::display() const +{ + std::cout << this->to_string() << std::endl; +} + +bool XmlNode::has_children() const +{ + return !this->children.empty(); +} + +const std::string& XmlNode::operator[](const std::string& name) const +{ + try + { + const auto& value = this->attributes.at(name); + return value; + } + catch (const std::out_of_range& e) + { + throw AttributeNotFound(); + } +} + +std::string& XmlNode::operator[](const std::string& name) +{ + return this->attributes[name]; +} diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp new file mode 100644 index 0000000..277b0db --- /dev/null +++ b/src/xmpp/xmpp_stanza.hpp @@ -0,0 +1,106 @@ +#ifndef XMPP_STANZA_INCLUDED +# define XMPP_STANZA_INCLUDED + +#include +#include +#include + +#include + +/** + * Raised on operator[] when the attribute does not exist + */ +class AttributeNotFound: public std::exception +{ +}; + +/** + * Represent an XML node. It has + * - A parent XML node (in the case of the first-level nodes, the parent is + nullptr) + * - zero, one or more children XML nodes + * - A name + * - attributes + * - inner data (inside the node) + * - tail data (just after the node) + */ +class XmlNode +{ +public: + explicit XmlNode(const std::string& name, XmlNode* parent); + explicit XmlNode(const std::string& name); + XmlNode(XmlNode&& node): + name(std::move(node.name)), + parent(std::move(node.parent)), + closed(std::move(node.closed)), + attributes(std::move(node.attributes)), + children(std::move(node.children)), + inner(std::move(node.inner)), + tail(std::move(node.tail)) + { + node.parent = nullptr; + } + + ~XmlNode(); + + void delete_all_children(); + void set_attribute(const std::string& name, const std::string& value); + /** + * Set the content of the tail, that is the text just after this node + */ + void set_tail(const std::string& data); + /** + * Set the content of the inner, that is the text inside this node + */ + void set_inner(const std::string& data); + void add_child(XmlNode* child); + void add_child(XmlNode&& child); + XmlNode* get_last_child() const; + /** + * Mark this node as closed, nothing else + */ + void close(); + XmlNode* get_parent() const; + const std::string& get_name() const; + /** + * Serialize the stanza into a string + */ + std::string to_string() const; + void display() const; + /** + * Whether or not this node has at least one child (if not, this is a leaf + * node) + */ + bool has_children() const; + /** + * Gets the value for the given attribute, raises AttributeNotFound if the + * node as no such attribute. + */ + const std::string& operator[](const std::string& name) const; + /** + * Use this to set an attribute's value, like node["id"] = "12"; + */ + std::string& operator[](const std::string& name); + +private: + std::string name; + XmlNode* parent; + bool closed; + std::unordered_map attributes; + std::vector children; + std::string inner; + std::string tail; + + XmlNode(const XmlNode&) = delete; + XmlNode& operator=(const XmlNode&) = delete; + XmlNode& operator=(XmlNode&&) = delete; +}; + +/** + * An XMPP stanza is just an XML node of level 2 in the XMPP document (the + * level 1 ones are the , and the ones about 2 are just the + * content of the stanzas) + */ +typedef XmlNode Stanza; + +#endif // XMPP_STANZA_INCLUDED -- cgit v1.2.3 From f2f94618fcf87b4fc1ad86902c63a7a48be745b8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 17:51:32 +0100 Subject: Add a basic XMPP component implementation, doing the authentication --- CMakeLists.txt | 9 +++ src/xmpp/xmpp_component.cpp | 137 ++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 80 ++++++++++++++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 src/xmpp/xmpp_component.cpp create mode 100644 src/xmpp/xmpp_component.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c21b84..363eb79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,16 @@ set(${PROJECT_NAME}_VERSION_MINOR 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -fsanitize=address") +# +## Look for external libraries +# +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") +find_package(Cryptopp REQUIRED) + include_directories("src/") +# the SYSTEM flag tells the compiler that we don't care about warnings +# coming from these headers. +include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) # ## network diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp new file mode 100644 index 0000000..0563aa7 --- /dev/null +++ b/src/xmpp/xmpp_component.cpp @@ -0,0 +1,137 @@ +#include + +#include + +// CryptoPP +#include +#include +#include + +XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): + served_hostname(hostname), + secret(secret), + authenticated(false) +{ + this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, + std::placeholders::_1)); + this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this, + std::placeholders::_1)); + this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, + std::placeholders::_1)); + this->stanza_handlers.emplace("handshake", + std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); +} + +XmppComponent::~XmppComponent() +{ +} + +void XmppComponent::start() +{ + this->connect(this->served_hostname, "5347"); +} + +void XmppComponent::send_stanza(const Stanza& stanza) +{ + std::cout << "====== Sending ========" << std::endl; + std::cout << stanza.to_string() << std::endl; + this->send_data(stanza.to_string()); +} + +void XmppComponent::on_connected() +{ + std::cout << "connected to XMPP server" << std::endl; + XmlNode node("stream:stream", nullptr); + node["xmlns"] = "jabber:component:accept"; + node["xmlns:stream"] = "http://etherx.jabber.org/streams"; + node["to"] = "irc.abricot"; + this->send_stanza(node); + +} + +void XmppComponent::on_connection_close() +{ + std::cout << "XMPP server closed connection" << std::endl; +} + +void XmppComponent::parse_in_buffer() +{ + this->parser.XML_Parse(this->in_buf.data(), this->in_buf.size(), false); + this->in_buf.clear(); +} + +void XmppComponent::on_remote_stream_open(const XmlNode& node) +{ + std::cout << "====== DOCUMENT_OPEN =======" << std::endl; + std::cout << node.to_string() << std::endl; + try + { + this->stream_id = node["id"]; + } + catch (const AttributeNotFound& e) + { + std::cout << "Error: no attribute 'id' found" << std::endl; + this->send_stream_error("bad-format", "missing 'id' attribute"); + this->close_document(); + return ; + } + + // Try to authenticate + CryptoPP::SHA1 sha1; + std::string digest; + CryptoPP::StringSource foo(this->stream_id + this->secret, true, + new CryptoPP::HashFilter(sha1, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(digest), false))); + Stanza handshake("handshake", nullptr); + handshake.set_inner(digest); + handshake.close(); + this->send_stanza(handshake); +} + +void XmppComponent::on_remote_stream_close(const XmlNode& node) +{ + std::cout << "====== DOCUMENT_CLOSE =======" << std::endl; + std::cout << node.to_string() << std::endl; +} + +void XmppComponent::on_stanza(const Stanza& stanza) +{ + std::cout << "=========== STANZA ============" << std::endl; + std::cout << stanza.to_string() << std::endl; + try + { + const auto& handler = this->stanza_handlers.at(stanza.get_name()); + handler(stanza); + } + catch (const std::out_of_range& exception) + { + std::cout << "No handler for stanza of type " << stanza.get_name() << std::endl; + return; + } +} + +void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) +{ + XmlNode node("stream:error", nullptr); + XmlNode error(name, nullptr); + error["xmlns"] = "urn:ietf:params:xml:ns:xmpp-streams"; + if (!explanation.empty()) + error.set_inner(explanation); + error.close(); + node.add_child(std::move(error)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::close_document() +{ + std::cout << "====== Sending ========" << std::endl; + std::cout << "" << std::endl; + this->send_data(""); +} + +void XmppComponent::handle_handshake(const Stanza& stanza) +{ + this->authenticated = true; +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp new file mode 100644 index 0000000..464ecaa --- /dev/null +++ b/src/xmpp/xmpp_component.hpp @@ -0,0 +1,80 @@ +#ifndef XMPP_COMPONENT_INCLUDED +# define XMPP_COMPONENT_INCLUDED + +#include + +#include + +#include + +#include + +/** + * An XMPP component, communicating with an XMPP server using the protocole + * described in XEP-0114: Jabber Component Protocol + * + * TODO: implement XEP-0225: Component Connections + */ +class XmppComponent: public SocketHandler +{ +public: + explicit XmppComponent(const std::string& hostname, const std::string& secret); + ~XmppComponent(); + void on_connected(); + void on_connection_close(); + void parse_in_buffer(); + + /** + * Connect to the XMPP server + */ + void start(); + /** + * Serialize the stanza and add it to the out_buf to be sent to the + * server. + */ + void send_stanza(const Stanza& stanza); + /** + * Handle the opening of the remote stream + */ + void on_remote_stream_open(const XmlNode& node); + /** + * Handle the closing of the remote stream + */ + void on_remote_stream_close(const XmlNode& node); + /** + * Handle received stanzas + */ + void on_stanza(const Stanza& stanza); + /** + * Send an error stanza. Message being the name of the element inside the + * stanza, and explanation being a short human-readable sentence + * describing the error. + */ + void send_stream_error(const std::string& message, const std::string& explanation); + /** + * Send the closing signal for our document (not closing the connection though). + */ + void close_document(); + + /** + * Handle the various stanza types + */ + void handle_handshake(const Stanza& stanza); + +private: + XmppParser parser; + std::string stream_id; + std::string served_hostname; + std::string secret; + bool authenticated; + + std::unordered_map> stanza_handlers; + + XmppComponent(const XmppComponent&) = delete; + XmppComponent(XmppComponent&&) = delete; + XmppComponent& operator=(const XmppComponent&) = delete; + XmppComponent& operator=(XmppComponent&&) = delete; +}; + +#endif // XMPP_COMPONENT_INCLUDED + -- cgit v1.2.3 From d834d6ed0647ba7e51e81f600fe259156e2b8070 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 17:54:07 +0100 Subject: Exit the poller when it handles no connection at all --- src/irc/irc_client.cpp | 1 - src/network/poller.cpp | 5 ++++- src/network/poller.hpp | 3 ++- src/network/socket_handler.cpp | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 83d1b0d..80f36ee 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -21,7 +21,6 @@ void IrcClient::on_connected() void IrcClient::on_connection_close() { std::cout << "Connection closed by remote server." << std::endl; - this->close(); } void IrcClient::parse_in_buffer() diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 7ab8bc3..6e86891 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -89,9 +89,11 @@ void Poller::stop_watching_send_events(const SocketHandler* const socket_handler throw std::runtime_error("Cannot watch a non-registered socket for send events"); } -void Poller::poll() +bool Poller::poll() { #if POLLER == POLL + if (this->nfds == 0) + return false; int res = ::poll(this->fds, this->nfds, -1); if (res < 0) { @@ -119,4 +121,5 @@ void Poller::poll() } } #endif + return true; } diff --git a/src/network/poller.hpp b/src/network/poller.hpp index 64e78e4..319236b 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -57,8 +57,9 @@ public: /** * Wait for all watched events, and call the SocketHandlers' callbacks * when one is ready. + * Returns false if there are 0 SocketHandler in the list. */ - void poll(); + bool poll(); private: /** diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index e738302..737bbf5 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -66,7 +66,10 @@ void SocketHandler::on_recv() ssize_t size = ::recv(this->socket, buf, 4096, 0); if (0 == size) - this->on_connection_close(); + { + this->on_connection_close(); + this->close(); + } else if (-1 == static_cast(size)) throw std::runtime_error("Error reading from socket"); else -- cgit v1.2.3 From dea7f60fa1ae6a46228daa36bcb3fec1a6c6ffc3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 3 Nov 2013 17:55:39 +0100 Subject: Add FindCryptoPP to the cmake search stuf --- cmake/Modules/FindCryptopp.cmake | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 cmake/Modules/FindCryptopp.cmake diff --git a/cmake/Modules/FindCryptopp.cmake b/cmake/Modules/FindCryptopp.cmake new file mode 100644 index 0000000..7a8ac31 --- /dev/null +++ b/cmake/Modules/FindCryptopp.cmake @@ -0,0 +1,35 @@ +# - Find Crypto++ + +if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) + set(CRYPTO++_FOUND TRUE) + +else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) + find_path(CRYPTO++_INCLUDE_DIR cryptlib.h + /usr/include/crypto++ + /usr/include/cryptopp + /usr/local/include/crypto++ + /usr/local/include/cryptopp + /opt/local/include/crypto++ + /opt/local/include/cryptopp + $ENV{SystemDrive}/Crypto++/include + ) + + find_library(CRYPTO++_LIBRARIES NAMES cryptopp + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + $ENV{SystemDrive}/Crypto++/lib + ) + + if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) + set(CRYPTO++_FOUND TRUE) + message(STATUS "Found Crypto++: ${CRYPTO++_INCLUDE_DIR}, ${CRYPTO++_LIBRARIES}") + else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) + set(CRYPTO++_FOUND FALSE) + message(STATUS "Crypto++ not found.") + endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) + + mark_as_advanced(CRYPTO++_INCLUDE_DIR CRYPTO++_LIBRARIES) + +endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) -- cgit v1.2.3 From bf7b05ef72bbdac97704d262ddfe418908267535 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 6 Nov 2013 20:51:05 +0100 Subject: Implement the Bridge class to translate between the two protocols MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add all useful classes as well: Jid, Iid, IrcChannel, IrcUser etc to properly keep the informations about what we receive from the IRC server. Only handle the MUC join stanza, and send the list of users in the IRC channel to the XMPP user, and the IRC channel’s topic, for now. --- CMakeLists.txt | 15 ++++-- src/bridge/bridge.cpp | 57 +++++++++++++++++++++++ src/bridge/bridge.hpp | 94 +++++++++++++++++++++++++++++++++++++ src/irc/iid.cpp | 17 +++++++ src/irc/iid.hpp | 34 ++++++++++++++ src/irc/irc_channel.cpp | 24 ++++++++++ src/irc/irc_channel.hpp | 34 ++++++++++++++ src/irc/irc_client.cpp | 105 ++++++++++++++++++++++++++++++++++++++++-- src/irc/irc_client.hpp | 60 +++++++++++++++++++++++- src/irc/irc_user.cpp | 24 ++++++++++ src/irc/irc_user.hpp | 24 ++++++++++ src/main.cpp | 11 +++++ src/xmpp/jid.cpp | 19 ++++++++ src/xmpp/jid.hpp | 26 +++++++++++ src/xmpp/stanza.hpp | 18 ++++++++ src/xmpp/xmpp_component.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++-- src/xmpp/xmpp_component.hpp | 36 +++++++++++++-- 17 files changed, 694 insertions(+), 14 deletions(-) create mode 100644 src/bridge/bridge.cpp create mode 100644 src/bridge/bridge.hpp create mode 100644 src/irc/iid.cpp create mode 100644 src/irc/iid.hpp create mode 100644 src/irc/irc_channel.cpp create mode 100644 src/irc/irc_channel.hpp create mode 100644 src/irc/irc_user.cpp create mode 100644 src/irc/irc_user.hpp create mode 100644 src/xmpp/jid.cpp create mode 100644 src/xmpp/jid.hpp create mode 100644 src/xmpp/stanza.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 363eb79..bff724c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ add_library(network STATIC ${source_network}) file(GLOB source_irc src/irc/*.[hc]pp) add_library(irc STATIC ${source_irc}) -target_link_libraries(irc network ${CRYPTO++_LIBRARIES} expatpp) +target_link_libraries(irc network) # ## xmpplib @@ -40,9 +40,18 @@ target_link_libraries(irc network ${CRYPTO++_LIBRARIES} expatpp) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp network ) +target_link_libraries(xmpp bridge network ${CRYPTO++_LIBRARIES} expatpp) + +# +## bridge +# +file(GLOB source_bridge + src/bridge/*.[hc]pp) +add_library(bridge STATIC ${source_bridge}) +target_link_libraries(bridge xmpp irc) add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} xmpp - irc) \ No newline at end of file + irc + bridge) \ No newline at end of file diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp new file mode 100644 index 0000000..638777d --- /dev/null +++ b/src/bridge/bridge.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): + user_jid(user_jid), + xmpp(xmpp), + poller(poller) +{ +} + +Bridge::~Bridge() +{ +} + +IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string& username) +{ + try + { + return this->irc_clients.at(hostname).get(); + } + catch (const std::out_of_range& exception) + { + this->irc_clients.emplace(hostname, std::make_shared(hostname, username, this)); + std::shared_ptr irc = this->irc_clients.at(hostname); + this->poller->add_socket_handler(irc); + irc->start(); + return irc.get(); + } +} + +void Bridge::join_irc_channel(const Iid& iid, const std::string& username) +{ + IrcClient* irc = this->get_irc_client(iid.server, username); + irc->send_join_command(iid.chan); +} + +void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) +{ + const std::string body = std::string("[") + author + std::string("] ") + msg; + this->xmpp->send_message(from, body, this->user_jid); +} + +void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick) +{ + this->xmpp->send_user_join(chan_name + "%" + hostname, nick, this->user_jid); +} + +void Bridge::send_self_join(const std::string& hostname, const std::string& chan_name, const std::string nick) +{ + this->xmpp->send_self_join(chan_name + "%" + hostname, nick, this->user_jid); +} + +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic) +{ + this->xmpp->send_topic(chan_name + "%" + hostname, topic, this->user_jid); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp new file mode 100644 index 0000000..f9ddcca --- /dev/null +++ b/src/bridge/bridge.hpp @@ -0,0 +1,94 @@ +#ifndef BRIDGE_INCLUDED +# define BRIDGE_INCLUDED + +#include +#include + +#include +#include +#include + +class XmppComponent; +class Poller; + +/** + * One bridge is spawned for each XMPP user that uses the component. The + * bridge spawns IrcClients when needed (when the user wants to join a + * channel on a new server) and does the translation between the two + * protocols. + */ +class Bridge +{ +public: + explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller); + ~Bridge(); + + /*** + ** + ** From XMPP to IRC. + ** + **/ + + void join_irc_channel(const Iid& iid, const std::string& username); + + /*** + ** + ** From IRC to XMPP. + ** + **/ + + /** + * Send a message corresponding to a server NOTICE, the from attribute + * should be juste the server hostname@irc.component. + */ + void send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg); + /** + * Send the presence of a new user in the MUC. + */ + void send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick); + /** + * Send the self presence of an user when the MUC is fully joined. + */ + void send_self_join(const std::string& hostname, const std::string& chan_name, const std::string nick); + /** + * Send the topic of the MUC to the user + */ + void send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic); + +private: + /** + * Returns the client for the given hostname, create one (and use the + * username in this case) if none is found, and connect that newly-created + * client immediately. + */ + IrcClient* get_irc_client(const std::string& hostname, const std::string& username); + /** + * The JID of the user associated with this bridge. Messages from/to this + * JID are only managed by this bridge. + */ + std::string user_jid; + /** + * One IrcClient for each IRC server we need to be connected to. + * The pointer is shared by the bridge and the poller. + */ + std::unordered_map> irc_clients; + /** + * A raw pointer, because we do not own it, the XMPP component owns us, + * but we still need to communicate with it, when sending messages from + * IRC to XMPP. + */ + XmppComponent* xmpp; + /** + * Poller, to give it the IrcClients that we spawn, to make it manage + * their sockets. + * We don't own it. + */ + Poller* poller; + + Bridge(const Bridge&) = delete; + Bridge(Bridge&& other) = delete; + Bridge& operator=(const Bridge&) = delete; + Bridge& operator=(Bridge&&) = delete; +}; + +#endif // BRIDGE_INCLUDED diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp new file mode 100644 index 0000000..ffc8d88 --- /dev/null +++ b/src/irc/iid.cpp @@ -0,0 +1,17 @@ +#include + +Iid::Iid(const std::string& iid) +{ + std::string::size_type sep = iid.find("%"); + if (sep != std::string::npos) + { + this->chan = iid.substr(0, sep); + sep++; + } + else + { + this->chan = iid; + return; + } + this->server = iid.substr(sep); +} diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp new file mode 100644 index 0000000..aacc9e6 --- /dev/null +++ b/src/irc/iid.hpp @@ -0,0 +1,34 @@ +#ifndef IID_INCLUDED +# define IID_INCLUDED + +#include + +/** + * A name representing an IRC channel, on the same model than the XMPP JIDs (but much simpler). + * The separator between the server and the channel name is '%' + * #test%irc.freenode.org has : + * - chan: "#test" (the # is part of the name, it could very well be absent, or & instead + * - server: "irc.freenode.org" + * #test has: + * - chan: "#test" + * - server: "" + * %irc.freenode.org: + * - chan: "" + * - server: "irc.freenode.org" + */ +class Iid +{ +public: + explicit Iid(const std::string& iid); + + std::string chan; + std::string server; + +private: + Iid(const Iid&) = delete; + Iid(Iid&&) = delete; + Iid& operator=(const Iid&) = delete; + Iid& operator=(Iid&&) = delete; +}; + +#endif // IID_INCLUDED diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp new file mode 100644 index 0000000..223305b --- /dev/null +++ b/src/irc/irc_channel.cpp @@ -0,0 +1,24 @@ +#include +#include + +IrcChannel::IrcChannel(): + joined(false), + self(nullptr) +{ +} + +void IrcChannel::set_self(const std::string& name) +{ + this->self = std::make_unique(name); +} + +IrcUser* IrcChannel::add_user(const std::string& name) +{ + this->users.emplace_back(std::make_unique(name)); + return this->users.back().get(); +} + +IrcUser* IrcChannel::get_self() const +{ + return this->self.get(); +} diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp new file mode 100644 index 0000000..da0f298 --- /dev/null +++ b/src/irc/irc_channel.hpp @@ -0,0 +1,34 @@ +#ifndef IRC_CHANNEL_INCLUDED +# define IRC_CHANNEL_INCLUDED + +#include +#include +#include +#include + +/** + * Keep the state of a joined channel (the list of occupants with their + * informations (mode, etc), the modes, etc) + */ +class IrcChannel +{ +public: + explicit IrcChannel(); + + bool joined; + std::string topic; + void set_self(const std::string& name); + IrcUser* get_self() const; + IrcUser* add_user(const std::string& name); + +private: + std::unique_ptr self; + std::vector> users; + + IrcChannel(const IrcChannel&) = delete; + IrcChannel(IrcChannel&&) = delete; + IrcChannel& operator=(const IrcChannel&) = delete; + IrcChannel& operator=(IrcChannel&&) = delete; +}; + +#endif // IRC_CHANNEL_INCLUDED diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 80f36ee..7875b1c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1,10 +1,18 @@ -#include #include +#include +#include +#include + +#include +#include #include #include -IrcClient::IrcClient() +IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge): + hostname(hostname), + username(username), + bridge(bridge) { std::cout << "IrcClient()" << std::endl; } @@ -14,8 +22,15 @@ IrcClient::~IrcClient() std::cout << "~IrcClient()" << std::endl; } +void IrcClient::start() +{ + this->connect(this->hostname, "6667"); +} + void IrcClient::on_connected() { + this->send_nick_command(this->username); + this->send_user_command(this->username, this->username); } void IrcClient::on_connection_close() @@ -23,6 +38,19 @@ void IrcClient::on_connection_close() std::cout << "Connection closed by remote server." << std::endl; } +IrcChannel* IrcClient::get_channel(const std::string& name) +{ + try + { + return this->channels.at(name).get(); + } + catch (const std::out_of_range& exception) + { + this->channels.emplace(name, std::make_unique()); + return this->channels.at(name).get(); + } +} + void IrcClient::parse_in_buffer() { while (true) @@ -33,6 +61,21 @@ void IrcClient::parse_in_buffer() IrcMessage message(this->in_buf.substr(0, pos)); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); std::cout << message << std::endl; + // TODO map function and command name properly + if (message.command == "PING") + this->send_pong_command(); + else if (message.command == "NOTICE" || + message.command == "375" || + message.command == "372") + this->forward_server_message(message); + else if (message.command == "JOIN") + this->on_self_channel_join(message); + else if (message.command == "353") + this->set_and_forward_user_list(message); + else if (message.command == "332") + this->on_topic_received(message); + else if (message.command == "366") + this->on_channel_completely_joined(message); } } @@ -52,6 +95,8 @@ void IrcClient::send_message(IrcMessage&& message) res += " " + arg; } res += "\r\n"; + std::cout << "=== IRC SENDING ===" << std::endl; + std::cout << res << std::endl; this->send_data(std::move(res)); } @@ -67,5 +112,59 @@ void IrcClient::send_nick_command(const std::string& nick) void IrcClient::send_join_command(const std::string& chan_name) { - this->send_message(IrcMessage("JOIN", {chan_name})); + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined == false) + this->send_message(IrcMessage("JOIN", {chan_name})); +} + +void IrcClient::send_pong_command() +{ + this->send_message(IrcMessage("PONG", {})); +} + +void IrcClient::forward_server_message(const IrcMessage& message) +{ + const std::string from = message.prefix; + const std::string body = message.arguments[1]; + + this->bridge->send_xmpp_message(this->hostname, from, body); +} + +void IrcClient::set_and_forward_user_list(const IrcMessage& message) +{ + const std::string chan_name = message.arguments[2]; + IrcChannel* channel = this->get_channel(chan_name); + std::vector nicks = utils::split(message.arguments[3], ' '); + for (const std::string& nick: nicks) + { + IrcUser* user = channel->add_user(nick); + if (user->nick != channel->get_self()->nick) + { + std::cout << "Adding user [" << nick << "] to chan " << chan_name << std::endl; + this->bridge->send_user_join(this->hostname, chan_name, user->nick); + } + } +} + +void IrcClient::on_self_channel_join(const IrcMessage& message) +{ + const std::string chan_name = message.arguments[0]; + IrcChannel* channel = this->get_channel(chan_name); + channel->joined = true; + channel->set_self(message.prefix); +} + +void IrcClient::on_topic_received(const IrcMessage& message) +{ + const std::string chan_name = message.arguments[1]; + IrcChannel* channel = this->get_channel(chan_name); + channel->topic = message.arguments[2]; +} + +void IrcClient::on_channel_completely_joined(const IrcMessage& message) +{ + const std::string chan_name = message.arguments[1]; + IrcChannel* channel = this->get_channel(chan_name); + this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick); + this->bridge->send_topic(this->hostname, chan_name, channel->topic); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index e380f5b..db1b83b 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -2,11 +2,16 @@ # define IRC_CLIENT_INCLUDED #include +#include +#include #include +#include #include +class Bridge; + /** * Represent one IRC client, i.e. an endpoint connected to a single IRC * server, through a TCP socket, receiving and sending commands to it. @@ -16,8 +21,12 @@ class IrcClient: public SocketHandler { public: - explicit IrcClient(); + explicit IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge); ~IrcClient(); + /** + * Connect to the IRC server + */ + void start(); /** * Called when successfully connected to the server */ @@ -31,12 +40,20 @@ public: * complete messages from it. */ void parse_in_buffer(); + /** + * Return the channel with this name, create it if it does not yet exist + */ + IrcChannel* get_channel(const std::string& name); /** * Serialize the given message into a line, and send that into the socket * (actually, into our out_buf and signal the poller that we want to wach * for send events to be ready) */ void send_message(IrcMessage&& message); + /** + * Send the PONG irc command + */ + void send_pong_command(); /** * Send the USER irc command */ @@ -49,8 +66,49 @@ public: * Send the JOIN irc command */ void send_join_command(const std::string& chan_name); + /** + * Forward the server message received from IRC to the XMPP component + */ + void forward_server_message(const IrcMessage& message); + /** + * Forward the join of an other user into an IRC channel, and save the + * IrcUsers in the IrcChannel + */ + void set_and_forward_user_list(const IrcMessage& message); + /** + * Remember our nick and host, when we are joined to the channel. The list + * of user comes after so we do not send the self-presence over XMPP yet. + */ + void on_self_channel_join(const IrcMessage& message); + /** + * Save the topic in the IrcChannel + */ + void on_topic_received(const IrcMessage& message); + /** + * The channel has been completely joined (self presence, topic, all names + * received etc), send the self presence and topic to the XMPP user. + */ + void on_channel_completely_joined(const IrcMessage& message); private: + /** + * The hostname of the server we are connected to. + */ + const std::string hostname; + /** + * The user name used in the USER irc command + */ + const std::string username; + /** + * Raw pointer because the bridge owns us. + */ + Bridge* bridge; + + /** + * The list of joined channels, indexed by name + */ + std::unordered_map> channels; + IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; IrcClient& operator=(const IrcClient&) = delete; diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp new file mode 100644 index 0000000..fc853bc --- /dev/null +++ b/src/irc/irc_user.cpp @@ -0,0 +1,24 @@ +#include + +#include + +IrcUser::IrcUser(const std::string& name) +{ + const std::string::size_type sep = name.find("!"); + if (sep == std::string::npos) + { + if (name[0] == '@' || name[0] == '+') + this->nick = name.substr(1); + else + this->nick = name; + } + else + { + if (name[0] == '@' || name[0] == '+') + this->nick = name.substr(1, sep); + else + this->nick = name.substr(0, sep); + this->host = name.substr(sep+1); + } + std::cout << "Created user: [" << this->nick << "!" << this->host << std::endl; +} diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp new file mode 100644 index 0000000..b76b2ef --- /dev/null +++ b/src/irc/irc_user.hpp @@ -0,0 +1,24 @@ +#ifndef IRC_USER_INCLUDED +# define IRC_USER_INCLUDED + +#include + +/** + * Keeps various information about one IRC channel user + */ +class IrcUser +{ +public: + explicit IrcUser(const std::string& name); + + std::string nick; + std::string host; + +private: + IrcUser(const IrcUser&) = delete; + IrcUser(IrcUser&&) = delete; + IrcUser& operator=(const IrcUser&) = delete; + IrcUser& operator=(IrcUser&&) = delete; +}; + +#endif // IRC_USER_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index fe3b2e5..b0fb140 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,17 @@ #include +#include #include +#include +#include + +#include + +#include +#include + +#include + int main() { Poller p; diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp new file mode 100644 index 0000000..78b28a0 --- /dev/null +++ b/src/xmpp/jid.cpp @@ -0,0 +1,19 @@ +#include + +Jid::Jid(const std::string& jid) +{ + std::string::size_type at = jid.find("@"); + if (at != std::string::npos) + { + this->local = jid.substr(0, at); + at++; + } + else + at = 0; + std::string::size_type slash = jid.find("/", at); + if (slash != std::string::npos) + { + this->resource = jid.substr(slash + 1); + } + this->domain = jid.substr(at, slash - at); +} diff --git a/src/xmpp/jid.hpp b/src/xmpp/jid.hpp new file mode 100644 index 0000000..3027497 --- /dev/null +++ b/src/xmpp/jid.hpp @@ -0,0 +1,26 @@ +#ifndef JID_INCLUDED +# define JID_INCLUDED + +#include + +/** + * Parse a JID into its different subart + */ +class Jid +{ +public: + explicit Jid(const std::string& jid); + + std::string domain; + std::string local; + std::string resource; + +private: + Jid(const Jid&) = delete; + Jid(Jid&&) = delete; + Jid& operator=(const Jid&) = delete; + Jid& operator=(Jid&&) = delete; +}; + + +#endif // JID_INCLUDED diff --git a/src/xmpp/stanza.hpp b/src/xmpp/stanza.hpp new file mode 100644 index 0000000..697bda4 --- /dev/null +++ b/src/xmpp/stanza.hpp @@ -0,0 +1,18 @@ +#ifndef Stanza +# define Stanza + +class Stanza +{ +public: + explicit Stanza(); + ~Stanza(); +private: + Stanza(const Stanza&) = delete; + Stanza(Stanza&&) = delete; + Stanza& operator=(const Stanza&) = delete; + Stanza& operator=(Stanza&&) = delete; +}; + +#endif // Stanza + + diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 0563aa7..3a288c7 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,4 +1,7 @@ +#include + #include +#include #include @@ -20,6 +23,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::placeholders::_1)); this->stanza_handlers.emplace("handshake", std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); + this->stanza_handlers.emplace("presence", + std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); } XmppComponent::~XmppComponent() @@ -46,7 +51,6 @@ void XmppComponent::on_connected() node["xmlns:stream"] = "http://etherx.jabber.org/streams"; node["to"] = "irc.abricot"; this->send_stanza(node); - } void XmppComponent::on_connection_close() @@ -99,16 +103,17 @@ void XmppComponent::on_stanza(const Stanza& stanza) { std::cout << "=========== STANZA ============" << std::endl; std::cout << stanza.to_string() << std::endl; + std::function handler; try { - const auto& handler = this->stanza_handlers.at(stanza.get_name()); - handler(stanza); + handler = this->stanza_handlers.at(stanza.get_name()); } catch (const std::out_of_range& exception) { std::cout << "No handler for stanza of type " << stanza.get_name() << std::endl; return; } + handler(stanza); } void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) @@ -133,5 +138,104 @@ void XmppComponent::close_document() void XmppComponent::handle_handshake(const Stanza& stanza) { + (void)stanza; this->authenticated = true; } + +void XmppComponent::handle_presence(const Stanza& stanza) +{ + Bridge* bridge = this->get_user_bridge(stanza["from"]); + Jid to(stanza["to"]); + Iid iid(to.local); + if (!iid.chan.empty() && !iid.server.empty()) + bridge->join_irc_channel(iid, to.resource); +} + +Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + this->bridges.emplace(user_jid, std::make_unique(user_jid, this, this->poller)); + return this->bridges.at(user_jid).get(); + } +} + +void XmppComponent::send_message(const std::string& from, const std::string& body, const std::string& to) +{ + XmlNode node("message"); + node["to"] = to; + node["from"] = from + "@" + this->served_hostname; + XmlNode body_node("body"); + body_node.set_inner(body); + body_node.close(); + node.add_child(std::move(body_node)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_user_join(const std::string& from, const std::string& nick, const std::string& to) +{ + XmlNode node("presence"); + node["to"] = to; + node["from"] = from + "@" + this->served_hostname + "/" + nick; + + XmlNode x("x"); + x["xmlns"] = "http://jabber.org/protocol/muc#user"; + + // TODO: put real values here + XmlNode item("item"); + item["affiliation"] = "member"; + item["role"] = "participant"; + item.close(); + x.add_child(std::move(item)); + x.close(); + node.add_child(std::move(x)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_self_join(const std::string& from, const std::string& nick, const std::string& to) +{ + XmlNode node("presence"); + node["to"] = to; + node["from"] = from + "@" + this->served_hostname + "/" + nick; + + XmlNode x("x"); + x["xmlns"] = "http://jabber.org/protocol/muc#user"; + + // TODO: put real values here + XmlNode item("item"); + item["affiliation"] = "member"; + item["role"] = "participant"; + item.close(); + x.add_child(std::move(item)); + + XmlNode status("status"); + status["code"] = "110"; + status.close(); + x.add_child(std::move(status)); + + x.close(); + + node.add_child(std::move(x)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_topic(const std::string& from, const std::string& topic, const std::string& to) +{ + XmlNode message("message"); + message["to"] = to; + message["from"] = from + "@" + this->served_hostname; + message["type"] = "groupchat"; + XmlNode subject("subject"); + subject.set_inner(topic); + subject.close(); + message.add_child(std::move(subject)); + message.close(); + this->send_stanza(message); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 464ecaa..725b495 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -1,13 +1,13 @@ #ifndef XMPP_COMPONENT_INCLUDED # define XMPP_COMPONENT_INCLUDED -#include - #include - #include +#include #include +#include +#include /** * An XMPP component, communicating with an XMPP server using the protocole @@ -55,13 +55,35 @@ public: * Send the closing signal for our document (not closing the connection though). */ void close_document(); - + /** + * Send a message from from@served_hostname, with the given body + */ + void send_message(const std::string& from, const std::string& body, const std::string& to); + /** + * Send a join from a new participant + */ + void send_user_join(const std::string& from, const std::string& nick, const std::string& to); + /** + * Send the self join to the user + */ + void send_self_join(const std::string& from, const std::string& nick, const std::string& to); + /** + * Send the MUC topic to the user + */ + void send_topic(const std::string& from, const std::string& topic, const std::string& to); /** * Handle the various stanza types */ void handle_handshake(const Stanza& stanza); + void handle_presence(const Stanza& stanza); private: + /** + * Return the bridge associated with the given full JID. Create a new one + * if none already exist. + */ + Bridge* get_user_bridge(const std::string& user_jid); + XmppParser parser; std::string stream_id; std::string served_hostname; @@ -70,6 +92,12 @@ private: std::unordered_map> stanza_handlers; + /** + * One bridge for each user of the component. Indexed by the user's full + * jid + */ + std::unordered_map> bridges; + XmppComponent(const XmppComponent&) = delete; XmppComponent(XmppComponent&&) = delete; XmppComponent& operator=(const XmppComponent&) = delete; -- cgit v1.2.3 From 4b76a30d0479f366374c7dcf99ac211038722503 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 6 Nov 2013 20:56:59 +0100 Subject: Add make_unique.hpp and split.hpp --- src/utils/make_unique.hpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils/split.hpp | 21 +++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/utils/make_unique.hpp create mode 100644 src/utils/split.hpp diff --git a/src/utils/make_unique.hpp b/src/utils/make_unique.hpp new file mode 100644 index 0000000..67964c8 --- /dev/null +++ b/src/utils/make_unique.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012 Nathan L. Binkert + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __MAKE_UNIQUE_HH__ +#define __MAKE_UNIQUE_HH__ + +#include + +namespace std { +namespace detail { + +// helper to construct a non-array unique_ptr +template +struct make_unique_helper { + typedef std::unique_ptr unique_ptr; + + template + static inline unique_ptr make(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } +}; + +// helper to construct an array unique_ptr +template +struct make_unique_helper { + typedef std::unique_ptr unique_ptr; + + template + static inline unique_ptr make(Args&&... args) { + return unique_ptr(new T[sizeof...(Args)]{std::forward(args)...}); +} +}; + +// helper to construct an array unique_ptr with specified extent +template +struct make_unique_helper { + typedef std::unique_ptr unique_ptr; + + template + static inline unique_ptr make(Args&&... args) { + static_assert(N >= sizeof...(Args), + "For make_unique N must be as largs as the number of arguments"); + return unique_ptr(new T[N]{std::forward(args)...}); + } + +#if __GNUC__ == 4 && __GNUC_MINOR__ <= 6 + // G++ 4.6 has an ICE when you have no arguments + static inline unique_ptr make() { + return unique_ptr(new T[N]); + } +#endif +}; + + +} // namespace detail + +template +inline typename detail::make_unique_helper::unique_ptr +make_unique(Args&&... args) { + return detail::make_unique_helper::make(std::forward(args)...); +} + +} // namespace std + +#endif // __MAKE_UNIQUE_HH__ diff --git a/src/utils/split.hpp b/src/utils/split.hpp new file mode 100644 index 0000000..0067131 --- /dev/null +++ b/src/utils/split.hpp @@ -0,0 +1,21 @@ +#ifndef SPLIT_INCLUDED +# define SPLIT_INCLUDED + +#include +#include +#include + +namespace utils +{ + std::vector split(const std::string &s, const char delim) + { + std::vector ret; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + ret.emplace_back(std::move(item)); + return ret; + } +} + +#endif // SPLIT_INCLUDED -- cgit v1.2.3 From a418b6ed5d70f0e61e71bb1adce2a693ade89e30 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 Nov 2013 01:53:09 +0100 Subject: Send and receive messages Also correctly respond to PING with the id, escape some XML content, but not always --- src/bridge/bridge.cpp | 43 ++++++++++++++++++++++++++++++++- src/bridge/bridge.hpp | 11 ++++++++- src/irc/iid.cpp | 4 +++ src/irc/iid.hpp | 1 + src/irc/irc_client.cpp | 59 +++++++++++++++++++++++++++++++++++++++++---- src/irc/irc_client.hpp | 32 ++++++++++++++++++++++-- src/xmpp/stanza.hpp | 18 -------------- src/xmpp/xmpp_component.cpp | 30 +++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 5 ++++ src/xmpp/xmpp_stanza.cpp | 37 +++++++++++++++++++++++++++- src/xmpp/xmpp_stanza.hpp | 12 +++++++++ 11 files changed, 224 insertions(+), 28 deletions(-) delete mode 100644 src/xmpp/stanza.hpp diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 638777d..5047a78 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -2,6 +2,8 @@ #include #include +#include + Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): user_jid(user_jid), xmpp(xmpp), @@ -29,15 +31,54 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string } } +IrcClient* Bridge::get_irc_client(const std::string& hostname) +{ + try + { + return this->irc_clients.at(hostname).get(); + } + catch (const std::out_of_range& exception) + { + return nullptr; + } +} + + void Bridge::join_irc_channel(const Iid& iid, const std::string& username) { IrcClient* irc = this->get_irc_client(iid.server, username); irc->send_join_command(iid.chan); } +void Bridge::send_channel_message(const Iid& iid, const std::string& body) +{ + if (iid.chan.empty() || iid.server.empty()) + { + std::cout << "Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]" << std::endl; + return; + } + IrcClient* irc = this->get_irc_client(iid.server); + if (!irc) + { + std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; + return; + } + irc->send_channel_message(iid.chan, body); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); +} + +void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) +{ + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, body, this->user_jid); +} + void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) { - const std::string body = std::string("[") + author + std::string("] ") + msg; + std::string body; + if (!author.empty()) + body = std::string("[") + author + std::string("] ") + msg; + else + body = msg; this->xmpp->send_message(from, body, this->user_jid); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f9ddcca..38cf565 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -30,6 +30,7 @@ public: **/ void join_irc_channel(const Iid& iid, const std::string& username); + void send_channel_message(const Iid& iid, const std::string& body); /*** ** @@ -54,7 +55,10 @@ public: * Send the topic of the MUC to the user */ void send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic); - + /** + * Send a MUC message from some participant + */ + void send_muc_message(const Iid& iid, const std::string& nick, const std::string& body); private: /** * Returns the client for the given hostname, create one (and use the @@ -62,6 +66,11 @@ private: * client immediately. */ IrcClient* get_irc_client(const std::string& hostname, const std::string& username); + /** + * This version does not create the IrcClient if it does not exist, and + * returns nullptr in that case + */ + IrcClient* get_irc_client(const std::string& hostname); /** * The JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index ffc8d88..4694c0c 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -15,3 +15,7 @@ Iid::Iid(const std::string& iid) } this->server = iid.substr(sep); } + +Iid::Iid() +{ +} diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index aacc9e6..a62ac71 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -20,6 +20,7 @@ class Iid { public: explicit Iid(const std::string& iid); + explicit Iid(); std::string chan; std::string server; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7875b1c..cf57bd7 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -12,7 +12,9 @@ IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge): hostname(hostname), username(username), - bridge(bridge) + current_nick(username), + bridge(bridge), + welcomed(false) { std::cout << "IrcClient()" << std::endl; } @@ -51,6 +53,11 @@ IrcChannel* IrcClient::get_channel(const std::string& name) } } +std::string IrcClient::get_own_nick() const +{ + return this->current_nick; +} + void IrcClient::parse_in_buffer() { while (true) @@ -63,19 +70,23 @@ void IrcClient::parse_in_buffer() std::cout << message << std::endl; // TODO map function and command name properly if (message.command == "PING") - this->send_pong_command(); + this->send_pong_command(message); else if (message.command == "NOTICE" || message.command == "375" || message.command == "372") this->forward_server_message(message); else if (message.command == "JOIN") this->on_self_channel_join(message); + else if (message.command == "PRIVMSG") + this->on_channel_message(message); else if (message.command == "353") this->set_and_forward_user_list(message); else if (message.command == "332") this->on_topic_received(message); else if (message.command == "366") this->on_channel_completely_joined(message); + else if (message.command == "001") + this->on_welcome_message(message); } } @@ -102,7 +113,7 @@ void IrcClient::send_message(IrcMessage&& message) void IrcClient::send_user_command(const std::string& username, const std::string& realname) { - this->send_message(IrcMessage("USER", {username, "NONE", "NONE", realname})); + this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname})); } void IrcClient::send_nick_command(const std::string& nick) @@ -112,14 +123,32 @@ void IrcClient::send_nick_command(const std::string& nick) void IrcClient::send_join_command(const std::string& chan_name) { + if (this->welcomed == false) + { + this->channels_to_join.push_back(chan_name); + return ; + } IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == false) this->send_message(IrcMessage("JOIN", {chan_name})); } -void IrcClient::send_pong_command() +bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body) { - this->send_message(IrcMessage("PONG", {})); + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined == false) + { + std::cout << "Cannot send message to channel " << chan_name << ", it is not joined" << std::endl; + return false; + } + this->send_message(IrcMessage("PRIVMSG", {chan_name, body})); + return true; +} + +void IrcClient::send_pong_command(const IrcMessage& message) +{ + const std::string id = message.arguments[0]; + this->send_message(IrcMessage("PONG", {id})); } void IrcClient::forward_server_message(const IrcMessage& message) @@ -154,6 +183,17 @@ void IrcClient::on_self_channel_join(const IrcMessage& message) channel->set_self(message.prefix); } +void IrcClient::on_channel_message(const IrcMessage& message) +{ + const IrcUser user(message.prefix); + const std::string nick = user.nick; + Iid iid; + iid.chan = message.arguments[0]; + iid.server = this->hostname; + const std::string body = message.arguments[1]; + this->bridge->send_muc_message(iid, nick, body); +} + void IrcClient::on_topic_received(const IrcMessage& message) { const std::string chan_name = message.arguments[1]; @@ -168,3 +208,12 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick); this->bridge->send_topic(this->hostname, chan_name, channel->topic); } + +void IrcClient::on_welcome_message(const IrcMessage& message) +{ + this->current_nick = message.arguments[0]; + this->welcomed = true; + for (const std::string& chan_name: this->channels_to_join) + this->send_join_command(chan_name); + this->channels_to_join.clear(); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index db1b83b..50f3781 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -44,6 +44,10 @@ public: * Return the channel with this name, create it if it does not yet exist */ IrcChannel* get_channel(const std::string& name); + /** + * Return our own nick + */ + std::string get_own_nick() const; /** * Serialize the given message into a line, and send that into the socket * (actually, into our out_buf and signal the poller that we want to wach @@ -53,7 +57,7 @@ public: /** * Send the PONG irc command */ - void send_pong_command(); + void send_pong_command(const IrcMessage& message); /** * Send the USER irc command */ @@ -66,6 +70,11 @@ public: * Send the JOIN irc command */ void send_join_command(const std::string& chan_name); + /** + * Send a PRIVMSG command for a channel + * Return true if the message was actually sent + */ + bool send_channel_message(const std::string& chan_name, const std::string& body); /** * Forward the server message received from IRC to the XMPP component */ @@ -80,6 +89,10 @@ public: * of user comes after so we do not send the self-presence over XMPP yet. */ void on_self_channel_join(const IrcMessage& message); + /** + * When a channel message is received + */ + void on_channel_message(const IrcMessage& message); /** * Save the topic in the IrcChannel */ @@ -89,6 +102,10 @@ public: * received etc), send the self presence and topic to the XMPP user. */ void on_channel_completely_joined(const IrcMessage& message); + /** + * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname + */ + void on_welcome_message(const IrcMessage& message); private: /** @@ -99,15 +116,26 @@ private: * The user name used in the USER irc command */ const std::string username; + /** + * Our current nickname on the server + */ + std::string current_nick; /** * Raw pointer because the bridge owns us. */ Bridge* bridge; - /** * The list of joined channels, indexed by name */ std::unordered_map> channels; + /** + * A list of chan we want to join, but we need a response 001 from + * the server before sending the actual JOIN commands. So we just keep the + * channel names in a list, and send the JOIN commands for each of them + * whenever the WELCOME message is received. + */ + std::vector channels_to_join; + bool welcomed; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; diff --git a/src/xmpp/stanza.hpp b/src/xmpp/stanza.hpp deleted file mode 100644 index 697bda4..0000000 --- a/src/xmpp/stanza.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef Stanza -# define Stanza - -class Stanza -{ -public: - explicit Stanza(); - ~Stanza(); -private: - Stanza(const Stanza&) = delete; - Stanza(Stanza&&) = delete; - Stanza& operator=(const Stanza&) = delete; - Stanza& operator=(Stanza&&) = delete; -}; - -#endif // Stanza - - diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 3a288c7..cd9cd6f 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -25,6 +25,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); this->stanza_handlers.emplace("presence", std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); + this->stanza_handlers.emplace("message", + std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); } XmppComponent::~XmppComponent() @@ -151,6 +153,20 @@ void XmppComponent::handle_presence(const Stanza& stanza) bridge->join_irc_channel(iid, to.resource); } +void XmppComponent::handle_message(const Stanza& stanza) +{ + Bridge* bridge = this->get_user_bridge(stanza["from"]); + Jid to(stanza["to"]); + Iid iid(to.local); + XmlNode* body = stanza.get_child("body"); + if (stanza["type"] == "groupchat") + { + if (to.resource.empty()) + if (body && !body->get_inner().empty()) + bridge->send_channel_message(iid, body->get_inner()); + } +} + Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) { try @@ -239,3 +255,17 @@ void XmppComponent::send_topic(const std::string& from, const std::string& topic message.close(); this->send_stanza(message); } + +void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to) +{ + Stanza message("message"); + message["to"] = jid_to; + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + message["type"] = "groupchat"; + XmlNode body("body"); + body.set_inner(body_str); + body.close(); + message.add_child(std::move(body)); + message.close(); + this->send_stanza(message); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 725b495..73eadd2 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -71,11 +71,16 @@ public: * Send the MUC topic to the user */ void send_topic(const std::string& from, const std::string& topic, const std::string& to); + /** + * Send a (non-private) message to the MUC + */ + void send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to); /** * Handle the various stanza types */ void handle_handshake(const Stanza& stanza); void handle_presence(const Stanza& stanza); + void handle_message(const Stanza& stanza); private: /** diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 2c98acc..4c0088e 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -2,6 +2,26 @@ #include +std::string xml_escape(const std::string& data) +{ + std::string res; + buffer.reserve(data.size()); + for(size_t pos = 0; pos != data.size(); ++pos) + { + switch(data[pos]) + { + case '&': buffer += "&"; break; + case '\"': buffer += """; break; + case '\'': buffer += "'"; break; + case '<': buffer += "<"; break; + case '>': buffer += ">"; break; + default: buffer += data[pos]; break; + } + } + return buffer; +} + + XmlNode::XmlNode(const std::string& name, XmlNode* parent): name(name), parent(parent), @@ -40,7 +60,22 @@ void XmlNode::set_tail(const std::string& data) void XmlNode::set_inner(const std::string& data) { - this->inner = data; + this->inner = xml_escape(data); +} + +std::string XmlNode::get_inner() const +{ + return this->inner; +} + +XmlNode* XmlNode::get_child(const std::string& name) const +{ + for (auto& child: this->children) + { + if (child->name == name) + return child; + } + return nullptr; } void XmlNode::add_child(XmlNode* child) diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 277b0db..62f152d 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -7,6 +7,8 @@ #include +std::string xml_escape(const std::string& data); + /** * Raised on operator[] when the attribute does not exist */ @@ -51,8 +53,18 @@ public: void set_tail(const std::string& data); /** * Set the content of the inner, that is the text inside this node + * TODO: escape it here. */ void set_inner(const std::string& data); + /** + * Get the content of inner + * TODO: unescape it here. + */ + std::string get_inner() const; + /** + * Get a pointer to the first child element with that name + */ + XmlNode* get_child(const std::string& name) const; void add_child(XmlNode* child); void add_child(XmlNode&& child); XmlNode* get_last_child() const; -- cgit v1.2.3 From ccebe901d7d76dfddc082d994efa54ef2aefee57 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Nov 2013 06:01:47 +0100 Subject: Check UTF-8 encoding, and convert strings to UTF-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle conversion errors properly by inserting � instead. Add a binary header to provide portable way to write binary literals (I like them) Also add a test file. ref #2404 --- CMakeLists.txt | 24 +++++++-- src/main.cpp | 29 +++-------- src/test.cpp | 43 +++++++++++++++ src/utils/binary.hpp | 16 ++++++ src/utils/encoding.cpp | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/encoding.hpp | 21 ++++++++ 6 files changed, 247 insertions(+), 25 deletions(-) create mode 100644 src/test.cpp create mode 100644 src/utils/binary.hpp create mode 100644 src/utils/encoding.cpp create mode 100644 src/utils/encoding.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bff724c..bd8ca76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,13 @@ include_directories("src/") # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) +# +## utils +# +file(GLOB source_utils + src/utils/*.[hc]pp) +add_library(utils STATIC ${source_utils}) + # ## network # @@ -32,7 +39,7 @@ add_library(network STATIC ${source_network}) file(GLOB source_irc src/irc/*.[hc]pp) add_library(irc STATIC ${source_irc}) -target_link_libraries(irc network) +target_link_libraries(irc network utils) # ## xmpplib @@ -40,7 +47,7 @@ target_link_libraries(irc network) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp bridge network ${CRYPTO++_LIBRARIES} expatpp) +target_link_libraries(xmpp bridge network utils ${CRYPTO++_LIBRARIES} expatpp) # ## bridge @@ -54,4 +61,15 @@ add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} xmpp irc - bridge) \ No newline at end of file + bridge) + +# +## Tests +# + +add_executable(test src/test.cpp) +target_link_libraries(test + xmpp + irc + bridge + utils) diff --git a/src/main.cpp b/src/main.cpp index b0fb140..b7fa01e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,31 +1,16 @@ -#include -#include #include - -#include -#include +#include #include -#include -#include - -#include - int main() { Poller p; - // Now I'm the bridge, creating an ircclient because needed. - std::shared_ptr c = std::make_shared(); - p.add_socket_handler(c); - std::shared_ptr d = std::make_shared(); - p.add_socket_handler(d); - std::shared_ptr e = std::make_shared(); - p.add_socket_handler(e); - c->connect("localhost", "7877"); - d->connect("localhost", "7878"); - e->connect("localhost", "7879"); - while (true) - p.poll(); + std::shared_ptr xmpp_component = + std::make_shared("irc.localhost", "secret"); + p.add_socket_handler(xmpp_component); + xmpp_component->start(); + while (p.poll()) + ; return 0; } diff --git a/src/test.cpp b/src/test.cpp new file mode 100644 index 0000000..e3bfa55 --- /dev/null +++ b/src/test.cpp @@ -0,0 +1,43 @@ +/** + * Just a very simple test suite, by hand, using assert() + */ + +#include + +#include + +#include +#include + +#include + +int main() +{ + /** + * Encoding + */ + const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; + assert(utils::is_valid_utf8(valid) == true); + const char* invalid = "\xF0\x0F"; + assert(utils::is_valid_utf8(invalid) == false); + const char* invalid2 = "\xFE\xFE\xFF\xFF"; + assert(utils::is_valid_utf8(invalid2) == false); + + std::string in = "coucou les copains ♥ "; + assert(utils::is_valid_utf8(in.c_str()) == true); + std::string res = utils::convert_to_utf8(in, "UTF-8"); + assert(utils::is_valid_utf8(res.c_str()) == true && res == in); + + std::string original_utf8("couc¥ou"); + std::string original_latin1("couc\xa5ou"); + + // When converting back to utf-8 + std::string from_latin1 = utils::convert_to_utf8(original_latin1.c_str(), "ISO-8859-1"); + assert(from_latin1 == original_utf8); + + // Check the behaviour when the decoding fails (here because we provide a + // wrong charset) + std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); + assert(from_ascii == "couc�ou"); + return 0; +} diff --git a/src/utils/binary.hpp b/src/utils/binary.hpp new file mode 100644 index 0000000..10807bc --- /dev/null +++ b/src/utils/binary.hpp @@ -0,0 +1,16 @@ +#ifndef BINARY_INCLUDED +# define BINARY_INCLUDED + +template struct binary +{ + static_assert(FIRST == '0' || FIRST == '1', "invalid binary digit" ); + enum { value = ((FIRST - '0') << sizeof...(REST)) + binary::value }; +}; + +template<> struct binary<'0'> { enum { value = 0 }; }; +template<> struct binary<'1'> { enum { value = 1 }; }; + +template inline +constexpr unsigned int operator "" _b() { return binary::value; } + +#endif // BINARY_INCLUDED diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp new file mode 100644 index 0000000..a1bc01b --- /dev/null +++ b/src/utils/encoding.cpp @@ -0,0 +1,139 @@ +#include +#include + +#include + +#include +#include +#include + +/** + * The UTF-8-encoded character used as a place holder when a character conversion fails. + * This is U+FFFD � "replacement character" + */ +static const char* invalid_char = "\xef\xbf\xbd"; +static const size_t invalid_char_len = 3; + +namespace utils +{ + /** + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s) + { + if (!s) + return false; + + const unsigned char* str = reinterpret_cast(s); + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 11111000_b) == 11110000_b) + { + if (!str[1] || !str[2] || !str[3] + || ((str[1] & 11000000_b) != 10000000_b) + || ((str[2] & 11000000_b) != 10000000_b) + || ((str[3] & 11000000_b) != 10000000_b)) + return false; + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 11110000_b) == 11100000_b) + { + if (!str[1] || !str[2] + || ((str[1] & 11000000_b) != 10000000_b) + || ((str[2] & 11000000_b) != 10000000_b)) + return false; + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 11100000_b) == 11000000_b) + { + if (!str[1] || + ((str[1] & 11000000_b) != 10000000_b)) + return false; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 10000000_b) != 0) + return false; + else + str++; + } + return true; + } + + std::string convert_to_utf8(const std::string& str, const char* charset) + { + std::string res; + + const iconv_t cd = iconv_open("UTF-8", charset); + if (cd == (iconv_t)-1) + 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); }); + + // iconv will not attempt to modify this buffer, but it still requires + // a char**. + size_t inbytesleft = str.size(); + char* inbuf_ptr = const_cast(str.c_str()); + + size_t outbytesleft = str.size() * 4; + char* outbuf = new char[outbytesleft]; + char* outbuf_ptr = outbuf; + + // Make sure outbuf is always deleted when we leave this function + sg.add_callback([&]{ delete[] outbuf; }); + + bool done = false; + while (done == false) + { + size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); + if ((size_t)-1 == error) + { + switch (errno) + { + case EILSEQ: + // Invalid byte found. Insert a placeholder instead of the + // converted character, jump one byte and continue + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + inbytesleft--; + inbuf_ptr++; + break; + case EINVAL: + // A multibyte sequence is not terminated, but we can't + // provide any more data, so we just add a placeholder to + // indicate that the character is not properly converted, + // and we stop the conversion + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + outbuf_ptr++; + done = true; + break; + case E2BIG: + // This should never happen + done = true; + default: + // This should happen even neverer + done = true; + break; + } + } + else + { + // The conversion finished without any error, stop converting + done = true; + } + } + // Terminate the converted buffer, and copy that buffer it into the + // string we return + *outbuf_ptr = '\0'; + res = outbuf; + return res; + } + +} + diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp new file mode 100644 index 0000000..362f1df --- /dev/null +++ b/src/utils/encoding.hpp @@ -0,0 +1,21 @@ +#ifndef ENCODING_INCLUDED +# define ENCODING_INCLUDED + +#include + +namespace utils +{ + /** + * Returns true if the given null-terminated string is valid utf-8. + * + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s); + /** + * Convert the given string (encoded is "encoding") into valid utf-8. + * If some decoding fails, insert an utf-8 placeholder character instead. + */ + std::string convert_to_utf8(const std::string& str, const char* encoding); +} + +#endif // ENCODING_INCLUDED -- cgit v1.2.3 From af5548897a8395a868f3ff2d716391a0c5ec92fe Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Nov 2013 06:29:15 +0100 Subject: Cosmetic --- src/xmpp/xmpp_stanza.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 4c0088e..d856836 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -5,20 +5,32 @@ std::string xml_escape(const std::string& data) { std::string res; - buffer.reserve(data.size()); - for(size_t pos = 0; pos != data.size(); ++pos) + res.reserve(data.size()); + for (size_t pos = 0; pos != data.size(); ++pos) { switch(data[pos]) { - case '&': buffer += "&"; break; - case '\"': buffer += """; break; - case '\'': buffer += "'"; break; - case '<': buffer += "<"; break; - case '>': buffer += ">"; break; - default: buffer += data[pos]; break; + case '&': + res += "&"; + break; + case '<': + res += "<"; + break; + case '>': + res += ">"; + break; + case '\"': + res += """; + break; + case '\'': + res += "'"; + break; + default: + res += data[pos]; + break; } } - return buffer; + return res; } -- cgit v1.2.3 From f38b31a63ee203e53d1135a87f1b4e9faaf7dd3f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Nov 2013 21:40:29 +0100 Subject: Remove IRC colors from the body when forwarding it to XMPP --- src/bridge/bridge.cpp | 23 +++++++++++++++++++---- src/bridge/bridge.hpp | 1 + src/test.cpp | 5 +++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 5047a78..f028f5b 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,6 +1,9 @@ #include +#include #include #include +#include + #include @@ -15,6 +18,17 @@ Bridge::~Bridge() { } +std::string Bridge::sanitize_for_xmpp(const std::string& str) +{ + std::string res; + if (utils::is_valid_utf8(str.c_str())) + res = str; + else + res = utils::convert_to_utf8(str, "ISO-8859-1"); + remove_irc_colors(res); + return res; +} + IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string& username) { try @@ -43,7 +57,6 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) } } - void Bridge::join_irc_channel(const Iid& iid, const std::string& username) { IrcClient* irc = this->get_irc_client(iid.server, username); @@ -64,12 +77,14 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) return; } irc->send_channel_message(iid.chan, body); + // We do not need to convert body to utf-8: it comes from our XMPP server, + // so it's ok to send it back this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); } void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) { - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, body, this->user_jid); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, this->sanitize_for_xmpp(body), this->user_jid); } void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) @@ -79,7 +94,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho body = std::string("[") + author + std::string("] ") + msg; else body = msg; - this->xmpp->send_message(from, body, this->user_jid); + this->xmpp->send_message(from, this->sanitize_for_xmpp(body), this->user_jid); } void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick) @@ -94,5 +109,5 @@ void Bridge::send_self_join(const std::string& hostname, const std::string& chan void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic) { - this->xmpp->send_topic(chan_name + "%" + hostname, topic, this->user_jid); + this->xmpp->send_topic(chan_name + "%" + hostname, this->sanitize_for_xmpp(topic), this->user_jid); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 38cf565..f7fd7c6 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -23,6 +23,7 @@ public: explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller); ~Bridge(); + static std::string sanitize_for_xmpp(const std::string& str); /*** ** ** From XMPP to IRC. diff --git a/src/test.cpp b/src/test.cpp index e3bfa55..d110868 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -6,6 +6,7 @@ #include +#include #include #include @@ -39,5 +40,9 @@ int main() // wrong charset) std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); assert(from_ascii == "couc�ou"); + + std::string coucou("\u0002\u0002COUCOU\u0003"); + remove_irc_colors(coucou); + assert(coucou == "COUCOU"); return 0; } -- cgit v1.2.3 From 7c671499350e22f8bfba2f72b9827aa5b200f7b0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Nov 2013 23:17:48 +0100 Subject: Implement part and join, both ways --- src/bridge/bridge.cpp | 12 ++++++++++ src/bridge/bridge.hpp | 6 +++++ src/irc/irc_channel.cpp | 24 ++++++++++++++++++++ src/irc/irc_channel.hpp | 2 ++ src/irc/irc_client.cpp | 54 +++++++++++++++++++++++++++++++++++++++------ src/irc/irc_client.hpp | 10 ++++++++- src/irc/irc_user.cpp | 8 +++++-- src/xmpp/xmpp_component.cpp | 38 +++++++++++++++++++++++++++++-- src/xmpp/xmpp_component.hpp | 4 ++++ src/xmpp/xmpp_stanza.cpp | 2 ++ 10 files changed, 148 insertions(+), 12 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f028f5b..9dbea2f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -82,11 +82,23 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); } +void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) +{ + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + irc->send_part_command(iid.chan, status_message); +} + void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) { this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, this->sanitize_for_xmpp(body), this->user_jid); } +void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self) +{ + this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->sanitize_for_xmpp(message), this->user_jid, self); +} + void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) { std::string body; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f7fd7c6..93bb321 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -32,6 +32,7 @@ public: void join_irc_channel(const Iid& iid, const std::string& username); void send_channel_message(const Iid& iid, const std::string& body); + void leave_irc_channel(Iid&& iid, std::string&& status_message); /*** ** @@ -60,6 +61,11 @@ public: * Send a MUC message from some participant */ void send_muc_message(const Iid& iid, const std::string& nick, const std::string& body); + /** + * Send an unavailable presence from this participant + */ + void send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self); + private: /** * Returns the client for the given hostname, create one (and use the diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 223305b..6daf708 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -22,3 +22,27 @@ IrcUser* IrcChannel::get_self() const { return this->self.get(); } + +IrcUser* IrcChannel::find_user(const std::string& name) +{ + IrcUser user(name); + for (const auto& u: this->users) + { + if (u->nick == user.nick) + return u.get(); + } + return nullptr; +} + +void IrcChannel::remove_user(const IrcUser* user) +{ + for (auto it = this->users.begin(); it != this->users.end(); ++it) + { + IrcUser* u = it->get(); + if (u->nick == user->nick) + { + this->users.erase(it); + break ; + } + } +} diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index da0f298..3786697 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -20,6 +20,8 @@ public: void set_self(const std::string& name); IrcUser* get_self() const; IrcUser* add_user(const std::string& name); + IrcUser* find_user(const std::string& name); + void remove_user(const IrcUser* user); private: std::unique_ptr self; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index cf57bd7..e3d7653 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -76,7 +76,7 @@ void IrcClient::parse_in_buffer() message.command == "372") this->forward_server_message(message); else if (message.command == "JOIN") - this->on_self_channel_join(message); + this->on_channel_join(message); else if (message.command == "PRIVMSG") this->on_channel_message(message); else if (message.command == "353") @@ -87,6 +87,8 @@ void IrcClient::parse_in_buffer() this->on_channel_completely_joined(message); else if (message.command == "001") this->on_welcome_message(message); + else if (message.command == "PART") + this->on_part(message); } } @@ -128,9 +130,7 @@ void IrcClient::send_join_command(const std::string& chan_name) this->channels_to_join.push_back(chan_name); return ; } - IrcChannel* channel = this->get_channel(chan_name); - if (channel->joined == false) - this->send_message(IrcMessage("JOIN", {chan_name})); + this->send_message(IrcMessage("JOIN", {chan_name})); } bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body) @@ -145,6 +145,15 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st return true; } +void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) +{ + IrcChannel* channel = this->get_channel(chan_name); + if (channel->joined == true) + { + this->send_message(IrcMessage("PART", {chan_name, status_message})); + } +} + void IrcClient::send_pong_command(const IrcMessage& message) { const std::string id = message.arguments[0]; @@ -175,12 +184,21 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) } } -void IrcClient::on_self_channel_join(const IrcMessage& message) +void IrcClient::on_channel_join(const IrcMessage& message) { const std::string chan_name = message.arguments[0]; IrcChannel* channel = this->get_channel(chan_name); - channel->joined = true; - channel->set_self(message.prefix); + const std::string nick = message.prefix; + if (channel->joined == false) + { + channel->joined = true; + channel->set_self(nick); + } + else + { + IrcUser* user = channel->add_user(nick); + this->bridge->send_user_join(this->hostname, chan_name, user->nick); + } } void IrcClient::on_channel_message(const IrcMessage& message) @@ -217,3 +235,25 @@ void IrcClient::on_welcome_message(const IrcMessage& message) this->send_join_command(chan_name); this->channels_to_join.clear(); } + +void IrcClient::on_part(const IrcMessage& message) +{ + const std::string chan_name = message.arguments[0]; + IrcChannel* channel = this->get_channel(chan_name); + std::string txt; + if (message.arguments.size() >= 2) + txt = message.arguments[1]; + const IrcUser* user = channel->find_user(message.prefix); + if (user) + { + std::string nick = user->nick; + channel->remove_user(user); + Iid iid; + iid.chan = chan_name; + iid.server = this->hostname; + bool self = channel->get_self()->nick == nick; + this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); + if (self) + channel->joined = false; + } +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 50f3781..e58ffbc 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -75,6 +75,10 @@ public: * Return true if the message was actually sent */ bool send_channel_message(const std::string& chan_name, const std::string& body); + /** + * Send the PART irc command + */ + void send_part_command(const std::string& chan_name, const std::string& status_message); /** * Forward the server message received from IRC to the XMPP component */ @@ -88,7 +92,7 @@ public: * Remember our nick and host, when we are joined to the channel. The list * of user comes after so we do not send the self-presence over XMPP yet. */ - void on_self_channel_join(const IrcMessage& message); + void on_channel_join(const IrcMessage& message); /** * When a channel message is received */ @@ -106,6 +110,10 @@ public: * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname */ void on_welcome_message(const IrcMessage& message); + /** + * When a PART message is received + */ + void on_part(const IrcMessage& message); private: /** diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index fc853bc..afe8623 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -7,14 +7,18 @@ IrcUser::IrcUser(const std::string& name) const std::string::size_type sep = name.find("!"); if (sep == std::string::npos) { - if (name[0] == '@' || name[0] == '+') + if (name[0] == '~' || name[0] == '&' + || name[0] == '@' || name[0] == '%' + || name[0] == '+') this->nick = name.substr(1); else this->nick = name; } else { - if (name[0] == '@' || name[0] == '+') + if (name[0] == '~' || name[0] == '&' + || name[0] == '@' || name[0] == '%' + || name[0] == '+') this->nick = name.substr(1, sep); else this->nick = name.substr(0, sep); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index cd9cd6f..83091ba 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -149,8 +149,22 @@ void XmppComponent::handle_presence(const Stanza& stanza) Bridge* bridge = this->get_user_bridge(stanza["from"]); Jid to(stanza["to"]); Iid iid(to.local); - if (!iid.chan.empty() && !iid.server.empty()) - bridge->join_irc_channel(iid, to.resource); + std::string type; + try { + type = stanza["type"]; + } + catch (const AttributeNotFound&) {} + + if (!iid.chan.empty() && !iid.chan.empty()) + { // presence toward a MUC that corresponds to an irc channel + if (type.empty()) + bridge->join_irc_channel(iid, to.resource); + else if (type == "unavailable") + { + XmlNode* status = stanza.get_child("status"); + bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); + } + } } void XmppComponent::handle_message(const Stanza& stanza) @@ -269,3 +283,23 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str message.close(); this->send_stanza(message); } + +void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, std::string&& message, const std::string& jid_to, const bool self) +{ + Stanza presence("presence"); + presence["to"] = jid_to; + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + presence["type"] = "unavailable"; + if (!message.empty() || self) + { + XmlNode status("status"); + if (!message.empty()) + status.set_inner(std::move(message)); + if (self) + status["code"] = "110"; + status.close(); + presence.add_child(std::move(status)); + } + presence.close(); + this->send_stanza(presence); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 73eadd2..a5127a9 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -75,6 +75,10 @@ public: * Send a (non-private) message to the MUC */ void send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to); + /** + * Send an unavailable presence for this nick + */ + void send_muc_leave(std::string&& muc_name, std::string&& nick, std::string&& message, const std::string& jid_to, const bool self); /** * Handle the various stanza types */ diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index d856836..ab26304 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -1,5 +1,7 @@ #include +#include + #include std::string xml_escape(const std::string& data) -- cgit v1.2.3 From 8acd7a02aeda01c0ac828b05c36f10bbacaea70e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Nov 2013 23:20:12 +0100 Subject: Aaaand, I forgot to add files --- src/bridge/colors.cpp | 20 ++++++++++++++++++++ src/bridge/colors.hpp | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 src/bridge/colors.cpp create mode 100644 src/bridge/colors.hpp diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp new file mode 100644 index 0000000..f49f31f --- /dev/null +++ b/src/bridge/colors.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include + +void remove_irc_colors(std::string& str) +{ + auto it = std::remove_if(str.begin(), str.end(), + [](const char c) + { + if (c == IRC_COLOR_BOLD_CHAR || c == IRC_COLOR_COLOR_CHAR || + c == IRC_COLOR_FIXED_CHAR || c == IRC_COLOR_RESET_CHAR || + c == IRC_COLOR_REVERSE_CHAR || c == IRC_COLOR_REVERSE2_CHAR || + c == IRC_COLOR_UNDERLINE_CHAR || c == IRC_COLOR_ITALIC_CHAR) + return true; + return false; + } + ); + str.erase(it, str.end()); +} diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp new file mode 100644 index 0000000..a4775e1 --- /dev/null +++ b/src/bridge/colors.hpp @@ -0,0 +1,21 @@ +#ifndef COLORS_INCLUDED +# define COLORS_INCLUDED + +#include + +/** + * A module handling the conversion between IRC colors and XHTML-IM, and vice versa. + */ + +#define IRC_COLOR_BOLD_CHAR '\x02' +#define IRC_COLOR_COLOR_CHAR '\x03' +#define IRC_COLOR_RESET_CHAR '\x0F' +#define IRC_COLOR_FIXED_CHAR '\x11' +#define IRC_COLOR_REVERSE_CHAR '\x12' +#define IRC_COLOR_REVERSE2_CHAR '\x16' +#define IRC_COLOR_ITALIC_CHAR '\x1D' +#define IRC_COLOR_UNDERLINE_CHAR '\x1F' + +void remove_irc_colors(std::string& str); + +#endif // COLORS_INCLUDED -- cgit v1.2.3 From 3d92360310d8e35394109058ff723da57af5b380 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 02:26:07 +0100 Subject: Use the Expat library directly instead of relying on expatpp And now we handle namespaces, yay. And a nice little test. --- CMakeLists.txt | 6 ++++- src/test.cpp | 20 ++++++++++++++- src/xmpp/xmpp_component.cpp | 31 ++++++++++++++--------- src/xmpp/xmpp_parser.cpp | 60 +++++++++++++++++++++++++++++++-------------- src/xmpp/xmpp_parser.hpp | 46 +++++++++++++++++----------------- src/xmpp/xmpp_stanza.cpp | 15 ++++++++++++ src/xmpp/xmpp_stanza.hpp | 20 ++++++++++++--- 7 files changed, 140 insertions(+), 58 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bd8ca76..2ec9dd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,14 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -fsanitize=address") # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") find_package(Cryptopp REQUIRED) +include(FindEXPAT) +find_package(EXPAT REQUIRED) include_directories("src/") # the SYSTEM flag tells the compiler that we don't care about warnings # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) +include_directories(SYSTEM ${EXPAT_INCLUDE_DIRS}) # ## utils @@ -47,7 +50,8 @@ target_link_libraries(irc network utils) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp bridge network utils ${CRYPTO++_LIBRARIES} expatpp) +target_link_libraries(xmpp bridge network utils + ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES}) # ## bridge diff --git a/src/test.cpp b/src/test.cpp index d110868..674be98 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include int main() { @@ -44,5 +44,23 @@ int main() std::string coucou("\u0002\u0002COUCOU\u0003"); remove_irc_colors(coucou); assert(coucou == "COUCOU"); + + /** + * XML parsing + */ + XmppParser xml; + const std::string doc = "innertail"; + xml.add_stanza_callback([](const Stanza& stanza) + { + assert(stanza.get_name() == "stream_ns:stanza"); + assert(stanza["b"] == "c"); + assert(stanza.get_inner() == "inner"); + assert(stanza.get_tail() == ""); + assert(stanza.get_child("stream_ns:child1") != nullptr); + assert(stanza.get_child("stream_ns:child2") == nullptr); + assert(stanza.get_child("child2_ns:child2") != nullptr); + assert(stanza.get_child("child2_ns:child2")->get_tail() == "tail"); + }); + xml.feed(doc.data(), doc.size(), true); return 0; } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 83091ba..4d981fa 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -10,6 +10,15 @@ #include #include +#define STREAM_NS "http://etherx.jabber.org/streams" +#define COMPONENT_NS "jabber:component:accept" +#define MUC_NS "http://jabber.org/protocol/muc" +#define MUC_USER_NS MUC_NS"#user" +#define DISCO_NS "http://jabber.org/protocol/disco" +#define DISCO_ITEMS_NS DISCO_NS"#items" +#define DISCO_INFO_NS DISCO_NS"#info" + + XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): served_hostname(hostname), secret(secret), @@ -21,11 +30,11 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::placeholders::_1)); this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, std::placeholders::_1)); - this->stanza_handlers.emplace("handshake", + this->stanza_handlers.emplace(COMPONENT_NS":handshake", std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); - this->stanza_handlers.emplace("presence", + this->stanza_handlers.emplace(COMPONENT_NS":presence", std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); - this->stanza_handlers.emplace("message", + this->stanza_handlers.emplace(COMPONENT_NS":message", std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); } @@ -49,8 +58,8 @@ void XmppComponent::on_connected() { std::cout << "connected to XMPP server" << std::endl; XmlNode node("stream:stream", nullptr); - node["xmlns"] = "jabber:component:accept"; - node["xmlns:stream"] = "http://etherx.jabber.org/streams"; + node["xmlns"] = COMPONENT_NS; + node["xmlns:stream"] = STREAM_NS; node["to"] = "irc.abricot"; this->send_stanza(node); } @@ -62,7 +71,7 @@ void XmppComponent::on_connection_close() void XmppComponent::parse_in_buffer() { - this->parser.XML_Parse(this->in_buf.data(), this->in_buf.size(), false); + this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); this->in_buf.clear(); } @@ -122,7 +131,7 @@ void XmppComponent::send_stream_error(const std::string& name, const std::string { XmlNode node("stream:error", nullptr); XmlNode error(name, nullptr); - error["xmlns"] = "urn:ietf:params:xml:ns:xmpp-streams"; + error["xmlns"] = STREAM_NS; if (!explanation.empty()) error.set_inner(explanation); error.close(); @@ -161,7 +170,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) bridge->join_irc_channel(iid, to.resource); else if (type == "unavailable") { - XmlNode* status = stanza.get_child("status"); + XmlNode* status = stanza.get_child(MUC_USER_NS":status"); bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); } } @@ -172,7 +181,7 @@ void XmppComponent::handle_message(const Stanza& stanza) Bridge* bridge = this->get_user_bridge(stanza["from"]); Jid to(stanza["to"]); Iid iid(to.local); - XmlNode* body = stanza.get_child("body"); + XmlNode* body = stanza.get_child(COMPONENT_NS":body"); if (stanza["type"] == "groupchat") { if (to.resource.empty()) @@ -214,7 +223,7 @@ void XmppComponent::send_user_join(const std::string& from, const std::string& n node["from"] = from + "@" + this->served_hostname + "/" + nick; XmlNode x("x"); - x["xmlns"] = "http://jabber.org/protocol/muc#user"; + x["xmlns"] = MUC_USER_NS; // TODO: put real values here XmlNode item("item"); @@ -235,7 +244,7 @@ void XmppComponent::send_self_join(const std::string& from, const std::string& n node["from"] = from + "@" + this->served_hostname + "/" + nick; XmlNode x("x"); - x["xmlns"] = "http://jabber.org/protocol/muc#user"; + x["xmlns"] = MUC_USER_NS; // TODO: put real values here XmlNode item("item"); diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 00714b3..6e4809d 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -1,21 +1,56 @@ #include #include -#include +/** + * Expat handlers. Called by the Expat library, never by ourself. + * They just forward the call to the XmppParser corresponding methods. + */ + +static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts) +{ + static_cast(user_data)->start_element(name, atts); +} + +static void end_element_handler(void* user_data, const XML_Char* name) +{ + static_cast(user_data)->end_element(name); +} + +static void character_data_handler(void *user_data, const XML_Char *s, int len) +{ + static_cast(user_data)->char_data(s, len); +} + +/** + * XmppParser class + */ XmppParser::XmppParser(): level(0), current_node(nullptr) { + // Create the expat parser + this->parser = XML_ParserCreateNS("UTF-8", ':'); + XML_SetUserData(this->parser, static_cast(this)); + + // Install Expat handlers + XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler); + XML_SetCharacterDataHandler(this->parser, &character_data_handler); } XmppParser::~XmppParser() { if (this->current_node) delete this->current_node; + XML_ParserFree(this->parser); } -void XmppParser::startElement(const XML_Char* name, const XML_Char** attribute) +void XmppParser::feed(const char* data, const int len, const bool is_final) +{ + XML_Parse(this->parser, data, len, is_final); +} + +void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) { level++; @@ -29,9 +64,9 @@ void XmppParser::startElement(const XML_Char* name, const XML_Char** attribute) this->stream_open_event(*this->current_node); } -void XmppParser::endElement(const XML_Char* name) +void XmppParser::end_element(const XML_Char* name) { - assert(name == this->current_node->get_name()); + (void)name; level--; this->current_node->close(); if (level == 1) @@ -50,18 +85,12 @@ void XmppParser::endElement(const XML_Char* name) this->current_node->delete_all_children(); } -void XmppParser::charData(const XML_Char* data, int len) +void XmppParser::char_data(const XML_Char* data, int len) { if (this->current_node->has_children()) - this->current_node->get_last_child()->set_tail(std::string(data, len)); + this->current_node->get_last_child()->add_to_tail(std::string(data, len)); else - this->current_node->set_inner(std::string(data, len)); -} - -void XmppParser::startNamespace(const XML_Char* prefix, const XML_Char* uri) -{ - std::cout << "startNamespace: " << prefix << ":" << uri << std::endl; - this->namespaces.emplace(std::make_pair(prefix, uri)); + this->current_node->add_to_inner(std::string(data, len)); } void XmppParser::stanza_event(const Stanza& stanza) const @@ -82,11 +111,6 @@ void XmppParser::stream_close_event(const XmlNode& node) const callback(node); } -void XmppParser::endNamespace(const XML_Char* coucou) -{ - std::cout << "endNamespace: " << coucou << std::endl; -} - void XmppParser::add_stanza_callback(std::function&& callback) { this->stanza_callbacks.emplace_back(std::move(callback)); diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp index 2e83bc3..afdfdfa 100644 --- a/src/xmpp/xmpp_parser.hpp +++ b/src/xmpp/xmpp_parser.hpp @@ -1,35 +1,43 @@ #ifndef XMPP_PARSER_INCLUDED # define XMPP_PARSER_INCLUDED -#include -#include - #include -#include +#include + +#include /** * A SAX XML parser that builds XML nodes and spawns events when a complete * stanza is received (an element of level 2), or when the document is - * opened (an element of level 1) + * opened/closed (an element of level 1) * * After a stanza_event has been spawned, we delete the whole stanza. This * means that even with a very long document (in XMPP the document is - * potentially infinite), the memory then is never exhausted as long as each + * potentially infinite), the memory is never exhausted as long as each * stanza is reasonnably short. * + * The element names generated by expat contain the namespace of the + * element, a colon (':') and then the actual name of the element. To get + * an element "x" with a namespace of "http://jabber.org/protocol/muc", you + * just look for an XmlNode named "http://jabber.org/protocol/muc:x" + * * TODO: enforce the size-limit for the stanza (limit the number of childs * it can contain). For example forbid the parser going further than level * 20 (arbitrary number here), and each XML node to have more than 15 childs * (arbitrary number again). */ -class XmppParser: public expatpp +class XmppParser { public: explicit XmppParser(); ~XmppParser(); public: + /** + * Feed the parser with some XML data + */ + void feed(const char* data, const int len, const bool is_final); /** * Add one callback for the various events that this parser can spawn. */ @@ -37,7 +45,6 @@ public: void add_stream_open_callback(std::function&& callback); void add_stream_close_callback(std::function&& callback); -private: /** * Called when a new XML element has been opened. We instanciate a new * XmlNode and set it as our current node. The parent of this new node is @@ -46,7 +53,7 @@ private: * * We spawn a stream_event with this node if this is a level-1 element. */ - void startElement(const XML_Char* name, const XML_Char** attribute); + void start_element(const XML_Char* name, const XML_Char** attribute); /** * Called when an XML element has been closed. We close the current_node, * set our current_node as the parent of the current_node, and if that was @@ -55,19 +62,11 @@ private: * And we then delete the stanza (and everything under it, its children, * attribute, etc). */ - void endElement(const XML_Char* name); + void end_element(const XML_Char* name); /** * Some inner or tail data has been parsed */ - void charData(const XML_Char* data, int len); - /** - * TODO use that. - */ - void startNamespace(const XML_Char* prefix, const XML_Char* uri); - /** - * TODO and that. - */ - void endNamespace(const XML_Char* prefix); + void char_data(const XML_Char* data, int len); /** * Calls all the stanza_callbacks one by one. */ @@ -82,6 +81,11 @@ private: */ void stream_close_event(const XmlNode& node) const; +private: + /** + * Expat structure. + */ + XML_Parser parser; /** * The current depth in the XML document */ @@ -98,10 +102,6 @@ private: std::vector> stanza_callbacks; std::vector> stream_open_callbacks; std::vector> stream_close_callbacks; - /** - * TODO: also use that. - */ - std::stack> namespaces; }; #endif // XMPP_PARSER_INCLUDED diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index ab26304..a1a04ba 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -72,16 +72,31 @@ void XmlNode::set_tail(const std::string& data) this->tail = data; } +void XmlNode::add_to_tail(const std::string& data) +{ + this->tail += data; +} + void XmlNode::set_inner(const std::string& data) { this->inner = xml_escape(data); } +void XmlNode::add_to_inner(const std::string& data) +{ + this->inner += xml_escape(data); +} + std::string XmlNode::get_inner() const { return this->inner; } +std::string XmlNode::get_tail() const +{ + return this->tail; +} + XmlNode* XmlNode::get_child(const std::string& name) const { for (auto& child: this->children) diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 62f152d..d2fe8c8 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -5,8 +5,6 @@ #include #include -#include - std::string xml_escape(const std::string& data); /** @@ -52,15 +50,29 @@ public: */ void set_tail(const std::string& data); /** - * Set the content of the inner, that is the text inside this node - * TODO: escape it here. + * Append the given data to the content of the tail. This exists because + * the expat library may provide the complete text of an element in more + * than one call + */ + void add_to_tail(const std::string& data); + /** + * Set the content of the inner, that is the text inside this node. */ void set_inner(const std::string& data); + /** + * Append the given data to the content of the inner. For the reason + * described in add_to_tail comment. + */ + void add_to_inner(const std::string& data); /** * Get the content of inner * TODO: unescape it here. */ std::string get_inner() const; + /** + * Get the content of the tail + */ + std::string get_tail() const; /** * Get a pointer to the first child element with that name */ -- cgit v1.2.3 From 6a43d350bed472f8e52525d9afc2d40ee72cef7e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 03:08:45 +0100 Subject: Add include for perror --- src/network/poller.cpp | 3 ++- src/network/socket_handler.cpp | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 6e86891..e790e60 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -1,10 +1,11 @@ #include #include +#include + #include #include - Poller::Poller() { std::cout << "Poller()" << std::endl; diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 737bbf5..d75b505 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -3,11 +3,12 @@ #include #include -#include #include +#include +#include #include #include -#include +#include #include -- cgit v1.2.3 From ef014f7ddf8fd603a4238f5ed4878d7038ce162d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 03:18:08 +0100 Subject: Properly detect iconv features to compile --- CMakeLists.txt | 5 +++- cmake/Modules/FindIconv.cmake | 57 +++++++++++++++++++++++++++++++++++++++++++ src/utils/encoding.cpp | 9 +++++-- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 cmake/Modules/FindIconv.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ec9dd0..f651723 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,14 +13,16 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -fsanitize=address") # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") find_package(Cryptopp REQUIRED) +find_package(Iconv REQUIRED) include(FindEXPAT) find_package(EXPAT REQUIRED) include_directories("src/") +include_directories(${EXPAT_INCLUDE_DIRS}) +include_directories(${ICONV_INCLUDE_DIR}) # the SYSTEM flag tells the compiler that we don't care about warnings # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) -include_directories(SYSTEM ${EXPAT_INCLUDE_DIRS}) # ## utils @@ -28,6 +30,7 @@ include_directories(SYSTEM ${EXPAT_INCLUDE_DIRS}) file(GLOB source_utils src/utils/*.[hc]pp) add_library(utils STATIC ${source_utils}) +target_link_libraries(utils ${ICONV_LIBRARIES}) # ## network diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake new file mode 100644 index 0000000..c957af6 --- /dev/null +++ b/cmake/Modules/FindIconv.cmake @@ -0,0 +1,57 @@ +# - Try to find Iconv +# Once done this will define +# +# ICONV_FOUND - system has Iconv +# ICONV_INCLUDE_DIR - the Iconv include directory +# ICONV_LIBRARIES - Link these to use Iconv +# ICONV_SECOND_ARGUMENT_IS_CONST - the second argument for iconv() is const +# +include(CheckCXXSourceCompiles) + +IF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + # Already in cache, be silent + SET(ICONV_FIND_QUIETLY TRUE) +ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + +FIND_PATH(ICONV_INCLUDE_DIR iconv.h) + +FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) + +IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + SET(ICONV_FOUND TRUE) +ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) + +set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) +set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) +IF(ICONV_FOUND) + check_cxx_source_compiles(" + #include + int main(){ + iconv_t conv = 0; + const char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0; + } +" ICONV_SECOND_ARGUMENT_IS_CONST ) +ENDIF(ICONV_FOUND) +set(CMAKE_REQUIRED_INCLUDES) +set(CMAKE_REQUIRED_LIBRARIES) + +IF(ICONV_FOUND) + IF(NOT ICONV_FIND_QUIETLY) + MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}") + ENDIF(NOT ICONV_FIND_QUIETLY) +ELSE(ICONV_FOUND) + IF(Iconv_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find Iconv") + ENDIF(Iconv_FIND_REQUIRED) +ENDIF(ICONV_FOUND) + +MARK_AS_ADVANCED( + ICONV_INCLUDE_DIR + ICONV_LIBRARIES + ICONV_SECOND_ARGUMENT_IS_CONST +) \ No newline at end of file diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index a1bc01b..2a6aecb 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -75,10 +75,15 @@ namespace utils // Make sure cd is always closed when we leave this function ScopeGuard sg([&]{ iconv_close(cd); }); - // iconv will not attempt to modify this buffer, but it still requires - // a char**. size_t inbytesleft = str.size(); + + // iconv will not attempt to modify this buffer, but some plateform + // require a char** anyway +#ifdef ICONV_SECOND_ARGUMENT_IS_CONST + const char* inbuf_ptr = str.c_str(); +#else char* inbuf_ptr = const_cast(str.c_str()); +#endif size_t outbytesleft = str.size() * 4; char* outbuf = new char[outbytesleft]; -- cgit v1.2.3 From 5cb81cace08b016f50708bb8ef718e07865b435a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 03:29:12 +0100 Subject: And actually use the values found by cmake --- CMakeLists.txt | 2 ++ config.h.cmake | 1 + src/utils/encoding.cpp | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 config.h.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f651723..b0d2801 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,3 +80,5 @@ target_link_libraries(test irc bridge utils) + +CONFIGURE_FILE(config.h.cmake config.h @ONLY) diff --git a/config.h.cmake b/config.h.cmake new file mode 100644 index 0000000..5f0a3cb --- /dev/null +++ b/config.h.cmake @@ -0,0 +1 @@ +#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index 2a6aecb..2d95132 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -7,6 +7,8 @@ #include #include +#include "config.h" + /** * The UTF-8-encoded character used as a place holder when a character conversion fails. * This is U+FFFD � "replacement character" -- cgit v1.2.3 From af4fc92c215e48cf13be36a1f8e8e1a821dabb5a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 03:35:31 +0100 Subject: Fix the include of the config.h --- CMakeLists.txt | 2 +- src/utils/encoding.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0d2801..a7ff1d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,4 +81,4 @@ target_link_libraries(test bridge utils) -CONFIGURE_FILE(config.h.cmake config.h @ONLY) +CONFIGURE_FILE(config.h.cmake src/config.h @ONLY) diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index 2d95132..634964b 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -7,7 +7,7 @@ #include #include -#include "config.h" +#include /** * The UTF-8-encoded character used as a place holder when a character conversion fails. -- cgit v1.2.3 From 0bb7ee0127a625ca8b6c25d9f593bfaa3d5af84b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 05:08:50 +0100 Subject: Handle IRC QUIT command --- src/irc/irc_client.cpp | 24 ++++++++++++++++++++++++ src/irc/irc_client.hpp | 4 ++++ 2 files changed, 28 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e3d7653..8de7c8f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -89,6 +89,8 @@ void IrcClient::parse_in_buffer() this->on_welcome_message(message); else if (message.command == "PART") this->on_part(message); + else if (message.command == "QUIT") + this->on_quit(message); } } @@ -257,3 +259,25 @@ void IrcClient::on_part(const IrcMessage& message) channel->joined = false; } } + +void IrcClient::on_quit(const IrcMessage& message) +{ + std::string txt; + if (message.arguments.size() >= 1) + txt = message.arguments[0]; + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + const std::string chan_name = it->first; + IrcChannel* channel = it->second.get(); + const IrcUser* user = channel->find_user(message.prefix); + if (user) + { + std::string nick = user->nick; + channel->remove_user(user); + Iid iid; + iid.chan = chan_name; + iid.server = this->hostname; + this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), false); + } + } +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index e58ffbc..33ab894 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -114,6 +114,10 @@ public: * When a PART message is received */ void on_part(const IrcMessage& message); + /** + * When a QUIT message is received + */ + void on_quit(const IrcMessage& message); private: /** -- cgit v1.2.3 From a992e281c4a8eb0542abf8475fcd62f297527447 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 05:10:54 +0100 Subject: Connect to localhost instead of the served hostname Because the XMPP component connection is only available locally, the XMPP servers proably only listen on 127.0.0.1 instead of 0.0.0.0. --- src/xmpp/xmpp_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 4d981fa..ba45fe4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -44,7 +44,7 @@ XmppComponent::~XmppComponent() void XmppComponent::start() { - this->connect(this->served_hostname, "5347"); + this->connect("127.0.0.1", "5347"); } void XmppComponent::send_stanza(const Stanza& stanza) @@ -60,7 +60,7 @@ void XmppComponent::on_connected() XmlNode node("stream:stream", nullptr); node["xmlns"] = COMPONENT_NS; node["xmlns:stream"] = STREAM_NS; - node["to"] = "irc.abricot"; + node["to"] = this->served_hostname; this->send_stanza(node); } -- cgit v1.2.3 From 7ba2d0fb45e7466e6fb38002bf1866d1f5e39c28 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 05:41:22 +0100 Subject: Handle the ACTION (/me) IRC command, both ways --- src/bridge/bridge.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9dbea2f..89a6231 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -4,9 +4,11 @@ #include #include - #include +static const char* action_prefix = "\01ACTION "; +static const size_t action_prefix_len = 8; + Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): user_jid(user_jid), xmpp(xmpp), @@ -76,7 +78,10 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; return; } - irc->send_channel_message(iid.chan, body); + if (body.substr(0, 4) == "/me ") + irc->send_channel_message(iid.chan, action_prefix + body.substr(4) + "\01"); + else + irc->send_channel_message(iid.chan, body); // We do not need to convert body to utf-8: it comes from our XMPP server, // so it's ok to send it back this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); @@ -91,7 +96,16 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) { - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, this->sanitize_for_xmpp(body), this->user_jid); + const std::string& utf8_body = this->sanitize_for_xmpp(body); + if (utf8_body.substr(0, action_prefix_len) == action_prefix) + { // Special case for ACTION (/me) messages: + // "\01ACTION goes out\01" == "/me goes out" + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, + std::string("/me ") + utf8_body.substr(action_prefix_len, utf8_body.size() - action_prefix_len - 1), + this->user_jid); + } + else + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, utf8_body, this->user_jid); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self) -- cgit v1.2.3 From 10d528717723a72dd3240c634980a461cf9fa2df Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 06:33:04 +0100 Subject: Handle private messages, both ways --- src/bridge/bridge.cpp | 22 ++++++++++++++++------ src/bridge/bridge.hpp | 3 ++- src/irc/irc_client.cpp | 13 ++++++++++++- src/irc/irc_client.hpp | 4 ++++ src/xmpp/xmpp_component.cpp | 5 +++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 89a6231..1f394bf 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -87,6 +87,15 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); } +void Bridge::send_private_message(const Iid& iid, const std::string& body) +{ + if (iid.chan.empty() || iid.server.empty()) + return ; + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + irc->send_private_message(iid.chan, body); +} + void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) { IrcClient* irc = this->get_irc_client(iid.server); @@ -94,18 +103,19 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) irc->send_part_command(iid.chan, status_message); } -void Bridge::send_muc_message(const Iid& iid, const std::string& nick, const std::string& body) +void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { - const std::string& utf8_body = this->sanitize_for_xmpp(body); + std::string utf8_body = this->sanitize_for_xmpp(body); if (utf8_body.substr(0, action_prefix_len) == action_prefix) { // Special case for ACTION (/me) messages: // "\01ACTION goes out\01" == "/me goes out" - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, - std::string("/me ") + utf8_body.substr(action_prefix_len, utf8_body.size() - action_prefix_len - 1), - this->user_jid); + utf8_body = std::string("/me ") + + utf8_body.substr(action_prefix_len, utf8_body.size() - action_prefix_len - 1); } - else + if (muc) this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, utf8_body, this->user_jid); + else + this->xmpp->send_message(iid.chan + "%" + iid.server, utf8_body, this->user_jid); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 93bb321..e0f4598 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -32,6 +32,7 @@ public: void join_irc_channel(const Iid& iid, const std::string& username); void send_channel_message(const Iid& iid, const std::string& body); + void send_private_message(const Iid& iid, const std::string& body); void leave_irc_channel(Iid&& iid, std::string&& status_message); /*** @@ -60,7 +61,7 @@ public: /** * Send a MUC message from some participant */ - void send_muc_message(const Iid& iid, const std::string& nick, const std::string& body); + void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc); /** * Send an unavailable presence from this participant */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 8de7c8f..71b0fe2 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -147,6 +147,11 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st return true; } +void IrcClient::send_private_message(const std::string& username, const std::string& body) +{ + this->send_message(IrcMessage("PRIVMSG", {username, body})); +} + void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) { IrcChannel* channel = this->get_channel(chan_name); @@ -211,7 +216,13 @@ void IrcClient::on_channel_message(const IrcMessage& message) iid.chan = message.arguments[0]; iid.server = this->hostname; const std::string body = message.arguments[1]; - this->bridge->send_muc_message(iid, nick, body); + bool muc = true; + if (!this->get_channel(iid.chan)->joined) + { + iid.chan = nick; + muc = false; + } + this->bridge->send_message(iid, nick, body, muc); } void IrcClient::on_topic_received(const IrcMessage& message) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 33ab894..bb51a4e 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -75,6 +75,10 @@ public: * Return true if the message was actually sent */ bool send_channel_message(const std::string& chan_name, const std::string& body); + /** + * Send a PRIVMSG command for an user + */ + void send_private_message(const std::string& username, const std::string& body); /** * Send the PART irc command */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index ba45fe4..7cf2ce0 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -188,6 +188,11 @@ void XmppComponent::handle_message(const Stanza& stanza) if (body && !body->get_inner().empty()) bridge->send_channel_message(iid, body->get_inner()); } + else + { + if (body && !body->get_inner().empty()) + bridge->send_private_message(iid, body->get_inner()); + } } Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) -- cgit v1.2.3 From f0d9273da61ce154dbe460cf58c98de851d30615 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 10 Nov 2013 20:47:11 +0100 Subject: Add a Config module, and use it to get the password from a file --- CMakeLists.txt | 18 ++++++- src/config/config.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/config/config.hpp | 111 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 27 +++++++++-- src/test.cpp | 26 +++++++++- 5 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 src/config/config.cpp create mode 100644 src/config/config.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a7ff1d4..b0fae9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,14 @@ file(GLOB source_utils add_library(utils STATIC ${source_utils}) target_link_libraries(utils ${ICONV_LIBRARIES}) +# +## config +# +file(GLOB source_config + src/config/*.[hc]pp) +add_library(config STATIC ${source_config}) +target_link_libraries(config utils) + # ## network # @@ -64,11 +72,16 @@ file(GLOB source_bridge add_library(bridge STATIC ${source_bridge}) target_link_libraries(bridge xmpp irc) +# +## Main executable +# add_executable(${PROJECT_NAME} src/main.cpp) target_link_libraries(${PROJECT_NAME} xmpp irc - bridge) + bridge + utils + config) # ## Tests @@ -79,6 +92,7 @@ target_link_libraries(test xmpp irc bridge - utils) + utils + config) CONFIGURE_FILE(config.h.cmake src/config.h @ONLY) diff --git a/src/config/config.cpp b/src/config/config.cpp new file mode 100644 index 0000000..cad8216 --- /dev/null +++ b/src/config/config.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include +#include + +std::string Config::filename = "./biboumi.cfg"; +bool Config::file_must_exist = false; + +std::string Config::get(const std::string& option, const std::string& def) +{ + Config* self = Config::instance().get(); + auto it = self->values.find(option); + + if (it == self->values.end()) + return def; + return it->second; +} + +int Config::get_int(const std::string& option, const int& def) +{ + Config* self = Config::instance().get(); + std::string res = self->get(option, ""); + if (!res.empty()) + return atoi(res.c_str()); + else + return def; +} + +void Config::set_int(const std::string& option, const int& value, bool save) +{ + std::ostringstream os; + os << value; + Config::set(option, os.str(), save); +} + +void Config::set(const std::string& option, const std::string& value, bool save) +{ + Config* self = Config::instance().get(); + self->values[option] = value; + if (save) + { + self->save_to_file(); + self->trigger_configuration_change(); + } +} + +void Config::connect(t_config_changed_callback callback) +{ + Config* self = Config::instance().get(); + self->callbacks.push_back(callback); +} + +void Config::close() +{ + Config* self = Config::instance().get(); + self->save_to_file(); + self->values.clear(); + Config::instance().reset(); +} + +/** + * Private methods + */ + +void Config::trigger_configuration_change() +{ + std::vector::iterator it; + for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it) + (*it)(); +} + +std::unique_ptr& Config::instance() +{ + static std::unique_ptr instance; + + if (!instance) + { + instance = std::make_unique(); + instance->read_conf(); + } + return instance; +} + +bool Config::read_conf() +{ + std::ifstream file; + file.open(filename.data()); + if (!file.is_open()) + { + if (Config::file_must_exist) + { + perror(("Error while opening file " + filename + " for reading.").c_str()); + file.exceptions(std::ifstream::failbit); + } + return false; + } + + std::string line; + size_t pos; + std::string option; + std::string value; + while (file.good()) + { + std::getline(file, line); + if (line == "" || line[0] == '#') + continue ; + pos = line.find('='); + if (pos == std::string::npos) + continue ; + option = line.substr(0, pos); + value = line.substr(pos+1); + this->values[option] = value; + } + return true; +} + +void Config::save_to_file() const +{ + std::ofstream file(this->filename.data()); + if (file.fail()) + { + std::cerr << "Could not save config file." << std::endl; + return ; + } + for (auto& it: this->values) + file << it.first << "=" << it.second << std::endl; + file.close(); +} diff --git a/src/config/config.hpp b/src/config/config.hpp new file mode 100644 index 0000000..ea28388 --- /dev/null +++ b/src/config/config.hpp @@ -0,0 +1,111 @@ +/** + * Read the config file and save all the values in a map. + * Also, a singleton. + * + * Use Config::filename = "bla" to set the filename you want to use. + * + * If you want to exit if the file does not exist when it is open for + * reading, set Config::file_must_exist = true. + * + * Config::get() can the be used to access the values in the conf. + * + * Use Config::close() when you're done getting/setting value. This will + * save the config into the file. + */ + +#ifndef CONFIG_INCLUDED +# define CONFIG_INCLUDED + +#include +#include +#include +#include +#include +#include + +typedef std::function t_config_changed_callback; + +class Config +{ +public: + Config(){}; + ~Config(){}; + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + * @param option The option we want + * @param def The default value in case the option does not exist + */ + static std::string get(const std::string&, const std::string&); + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + * @param option The option we want + * @param def The default value in case the option does not exist + */ + static int get_int(const std::string&, const int&); + /** + * Set a value for the given option. And write all the config + * in the file from which it was read if boolean is set. + * @param option The option to set + * @param value The value to use + * @param save if true, save the config file + */ + static void set(const std::string&, const std::string&, bool save = false); + /** + * Set a value for the given option. And write all the config + * in the file from which it was read if boolean is set. + * @param option The option to set + * @param value The value to use + * @param save if true, save the config file + */ + static void set_int(const std::string&, const int&, bool save = false); + /** + * Adds a function to a list. This function will be called whenever a + * configuration change occurs. + */ + static void connect(t_config_changed_callback); + /** + * Close the config file, saving it to the file is save == true. + */ + static void close(); + + /** + * Set the value of the filename to use, before calling any method. + */ + static std::string filename; + /** + * Set to true if you want an exception to be raised if the file does not + * exist when reading it. + */ + static bool file_must_exist; + +private: + /** + * Get the singleton instance + */ + static std::unique_ptr& instance(); + /** + * Read the configuration file at the given path. + */ + bool read_conf(); + /** + * Write all the config values into the configuration file + */ + void save_to_file() const; + /** + * Call all the callbacks previously registered using connect(). + * This is used to notify any class that a configuration change occured. + */ + void trigger_configuration_change(); + + std::map values; + std::vector callbacks; + + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; +}; + +#endif // CONFIG_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index b7fa01e..80c8436 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,32 @@ -#include #include +#include +#include +#include #include -int main() +int main(int ac, char** av) { - Poller p; + if (ac > 1) + Config::filename = av[1]; + Config::file_must_exist = true; + + std::string password; + try { // The file must exist + password = Config::get("password", ""); + } + catch (const std::ios::failure& e) { + return 1; + } + if (password.empty()) + { + std::cerr << "No password provided." << std::endl; + return 1; + } std::shared_ptr xmpp_component = - std::make_shared("irc.localhost", "secret"); + std::make_shared("irc.abricot", password); + + Poller p; p.add_socket_handler(xmpp_component); xmpp_component->start(); while (p.poll()) diff --git a/src/test.cpp b/src/test.cpp index 674be98..99454a5 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include int main() @@ -49,7 +51,7 @@ int main() * XML parsing */ XmppParser xml; - const std::string doc = "innertail"; + const std::string doc = "innertail"; xml.add_stanza_callback([](const Stanza& stanza) { assert(stanza.get_name() == "stream_ns:stanza"); @@ -62,5 +64,27 @@ int main() assert(stanza.get_child("child2_ns:child2")->get_tail() == "tail"); }); xml.feed(doc.data(), doc.size(), true); + + /** + * Config + */ + Config::filename = "test.cfg"; + Config::file_must_exist = false; + Config::set("coucou", "bonjour"); + Config::close(); + + bool error = false; + try + { + Config::file_must_exist = true; + assert(Config::get("coucou", "") == "bonjour"); + assert(Config::get("does not exist", "default") == "default"); + Config::close(); + } + catch (const std::ios::failure& e) + { + error = true; + } + assert(error == false); return 0; } -- cgit v1.2.3 From 096a4e3bafe6e2d238e4592f57f22f19f363fcbd Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 Nov 2013 00:24:34 +0100 Subject: Handle nick changes, both ways --- src/bridge/bridge.cpp | 32 +++++++++++++++++++++++++++++--- src/bridge/bridge.hpp | 19 +++++++++++++++++-- src/irc/irc_client.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ src/irc/irc_client.hpp | 10 +++++++++- src/xmpp/xmpp_component.cpp | 42 +++++++++++++++++++++++++++++++++++++++++- src/xmpp/xmpp_component.hpp | 4 ++++ 6 files changed, 139 insertions(+), 13 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1f394bf..d292af3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -59,10 +59,15 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) } } -void Bridge::join_irc_channel(const Iid& iid, const std::string& username) +bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) { IrcClient* irc = this->get_irc_client(iid.server, username); - irc->send_join_command(iid.chan); + if (irc->is_channel_joined(iid.chan) == false) + { + irc->send_join_command(iid.chan); + return true; + } + return false; } void Bridge::send_channel_message(const Iid& iid, const std::string& body) @@ -103,6 +108,13 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) irc->send_part_command(iid.chan, status_message); } +void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) +{ + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + irc->send_nick_command(new_nick); +} + void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { std::string utf8_body = this->sanitize_for_xmpp(body); @@ -118,11 +130,17 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st this->xmpp->send_message(iid.chan + "%" + iid.server, utf8_body, this->user_jid); } -void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self) +void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->sanitize_for_xmpp(message), this->user_jid, self); } +void Bridge::send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self) +{ + this->xmpp->send_nick_change(std::move(iid.chan) + "%" + std::move(iid.server), + old_nick, new_nick, this->user_jid, self); +} + void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) { std::string body; @@ -147,3 +165,11 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam { this->xmpp->send_topic(chan_name + "%" + hostname, this->sanitize_for_xmpp(topic), this->user_jid); } + +std::string Bridge::get_own_nick(const Iid& iid) +{ + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + return irc->get_own_nick(); + return ""; +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index e0f4598..0466ee1 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -30,10 +30,15 @@ public: ** **/ - void join_irc_channel(const Iid& iid, const std::string& username); + /** + * Try to join an irc_channel, does nothing and return true if the channel + * was already joined. + */ + bool join_irc_channel(const Iid& iid, const std::string& username); void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body); void leave_irc_channel(Iid&& iid, std::string&& status_message); + void send_irc_nick_change(const Iid& iid, const std::string& new_nick); /*** ** @@ -65,7 +70,17 @@ public: /** * Send an unavailable presence from this participant */ - void send_muc_leave(Iid&& iid, std::string&& nick, std::string&& message, const bool self); + void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self); + /** + * Send presences to indicate that an user old_nick (ourself if self == + * true) changed his nick to new_nick + */ + void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self); + + /** + * Misc + */ + std::string get_own_nick(const Iid& iid); private: /** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 71b0fe2..30b1204 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -53,6 +53,12 @@ IrcChannel* IrcClient::get_channel(const std::string& name) } } +bool IrcClient::is_channel_joined(const std::string& name) +{ + IrcChannel* client = this->get_channel(name); + return client->joined; +} + std::string IrcClient::get_own_nick() const { return this->current_nick; @@ -91,6 +97,8 @@ void IrcClient::parse_in_buffer() this->on_part(message); else if (message.command == "QUIT") this->on_quit(message); + else if (message.command == "NICK") + this->on_nick(message); } } @@ -128,11 +136,9 @@ void IrcClient::send_nick_command(const std::string& nick) void IrcClient::send_join_command(const std::string& chan_name) { if (this->welcomed == false) - { - this->channels_to_join.push_back(chan_name); - return ; - } - this->send_message(IrcMessage("JOIN", {chan_name})); + this->channels_to_join.push_back(chan_name); + else + this->send_message(IrcMessage("JOIN", {chan_name})); } bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body) @@ -288,7 +294,34 @@ void IrcClient::on_quit(const IrcMessage& message) Iid iid; iid.chan = chan_name; iid.server = this->hostname; - this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), false); + this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false); } } } + +void IrcClient::on_nick(const IrcMessage& message) +{ + const std::string new_nick = message.arguments[0]; + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + const std::string chan_name = it->first; + IrcChannel* channel = it->second.get(); + IrcUser* user = channel->find_user(message.prefix); + if (user) + { + std::string old_nick = user->nick; + Iid iid; + iid.chan = chan_name; + iid.server = this->hostname; + bool self = channel->get_self()->nick == old_nick; + this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, self); + user->nick = new_nick; + if (self) + { + channel->get_self()->nick = new_nick; + this->current_nick = new_nick; + } + } + } +} + diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index bb51a4e..d9ea069 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -44,6 +44,10 @@ public: * Return the channel with this name, create it if it does not yet exist */ IrcChannel* get_channel(const std::string& name); + /** + * Returns true if the channel is joined + */ + bool is_channel_joined(const std::string& name); /** * Return our own nick */ @@ -67,7 +71,7 @@ public: */ void send_nick_command(const std::string& username); /** - * Send the JOIN irc command + * Send the JOIN irc command. */ void send_join_command(const std::string& chan_name); /** @@ -118,6 +122,10 @@ public: * When a PART message is received */ void on_part(const IrcMessage& message); + /** + * When a NICK message is received + */ + void on_nick(const IrcMessage& message); /** * When a QUIT message is received */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 7cf2ce0..e2063ba 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -167,7 +167,13 @@ void XmppComponent::handle_presence(const Stanza& stanza) if (!iid.chan.empty() && !iid.chan.empty()) { // presence toward a MUC that corresponds to an irc channel if (type.empty()) - bridge->join_irc_channel(iid, to.resource); + { + const std::string own_nick = bridge->get_own_nick(iid); + if (!own_nick.empty() && own_nick != to.resource) + bridge->send_irc_nick_change(iid, to.resource); + else + bridge->join_irc_channel(iid, to.resource); + } else if (type == "unavailable") { XmlNode* status = stanza.get_child(MUC_USER_NS":status"); @@ -317,3 +323,37 @@ void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, s presence.close(); this->send_stanza(presence); } + +void XmppComponent::send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self) +{ + Stanza presence("presence"); + presence["to"] = jid_to; + presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick; + presence["type"] = "unavailable"; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["nick"] = new_nick; + item.close(); + x.add_child(std::move(item)); + XmlNode status("status"); + status["code"] = "303"; + status.close(); + x.add_child(std::move(status)); + if (self) + { + XmlNode status2("status"); + status2["code"] = "110"; + status2.close(); + x.add_child(std::move(status2)); + } + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); + + if (self) + this->send_self_join(muc_name, new_nick, jid_to); + else + this->send_user_join(muc_name, new_nick, jid_to); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index a5127a9..84b19a9 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -79,6 +79,10 @@ public: * Send an unavailable presence for this nick */ void send_muc_leave(std::string&& muc_name, std::string&& nick, std::string&& message, const std::string& jid_to, const bool self); + /** + * Indicate that a participant changed his nick + */ + void send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self); /** * Handle the various stanza types */ -- cgit v1.2.3 From 2be4811d14f921f92e7f976b6e3c9ceb5404086b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 Nov 2013 00:29:44 +0100 Subject: Unescape XML before sending messages over IRC --- src/xmpp/xmpp_stanza.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++++-- src/xmpp/xmpp_stanza.hpp | 2 +- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index a1a04ba..169a8ef 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -4,6 +4,8 @@ #include +#include + std::string xml_escape(const std::string& data) { std::string res; @@ -35,6 +37,49 @@ std::string xml_escape(const std::string& data) return res; } +std::string xml_unescape(const std::string& data) +{ + std::string res; + res.reserve(data.size()); + const char* str = data.c_str(); + while (str && *str && (str - data.c_str()) < data.size()) + { + if (*str == '&') + { + if (strncmp(str+1, "amp;", 4) == 0) + { + res += "&"; + str += 4; + } + else if (strncmp(str+1, "lt;", 3) == 0) + { + res += "<"; + str += 3; + } + else if (strncmp(str+1, "gt;", 3) == 0) + { + res += ">"; + str += 3; + } + else if (strncmp(str+1, "quot;", 5) == 0) + { + res += "\""; + str += 5; + } + else if (strncmp(str+1, "apos;", 5) == 0) + { + res += "'"; + str += 5; + } + else + res += "&"; + } + else + res += *str; + str++; + } + return res; +} XmlNode::XmlNode(const std::string& name, XmlNode* parent): name(name), @@ -89,12 +134,12 @@ void XmlNode::add_to_inner(const std::string& data) std::string XmlNode::get_inner() const { - return this->inner; + return xml_unescape(this->inner); } std::string XmlNode::get_tail() const { - return this->tail; + return xml_unescape(this->tail); } XmlNode* XmlNode::get_child(const std::string& name) const diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index d2fe8c8..2ce8ce2 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -6,6 +6,7 @@ #include std::string xml_escape(const std::string& data); +std::string xml_unescape(const std::string& data); /** * Raised on operator[] when the attribute does not exist @@ -66,7 +67,6 @@ public: void add_to_inner(const std::string& data); /** * Get the content of inner - * TODO: unescape it here. */ std::string get_inner() const; /** -- cgit v1.2.3 From b60cbda4f93bb83e36b29f5cba975b94b833663d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 12 Nov 2013 22:35:27 +0100 Subject: Read the served hostname from the config file --- src/main.cpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 80c8436..71c93f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,26 +5,42 @@ #include #include +/** + * Provide an helpful message to help the user write a minimal working + * configuration file. + */ +int config_help(const std::string& missing_option) +{ + if (!missing_option.empty()) + std::cerr << "Error: empty value for option " << missing_option << "." << std::endl; + std::cerr << + "Please provide a configuration file filled like this:\n\n" + "hostname=irc.example.com\npassword=S3CR3T" + << std::endl; + return 1; +} + int main(int ac, char** av) { if (ac > 1) Config::filename = av[1]; Config::file_must_exist = true; + std::cerr << "Using configuration file: " << Config::filename << std::endl; std::string password; try { // The file must exist password = Config::get("password", ""); } catch (const std::ios::failure& e) { - return 1; + return config_help(""); } + const std::string hostname = Config::get("hostname", ""); if (password.empty()) - { - std::cerr << "No password provided." << std::endl; - return 1; - } + return config_help("hostname"); + if (hostname.empty()) + return config_help("password"); std::shared_ptr xmpp_component = - std::make_shared("irc.abricot", password); + std::make_shared(hostname, password); Poller p; p.add_socket_handler(xmpp_component); -- cgit v1.2.3 From 5817a95b5ee89480788832be35679dfcd2ed833b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 12 Nov 2013 23:43:43 +0100 Subject: Basic handling of modes, both ways --- CMakeLists.txt | 2 +- src/bridge/bridge.cpp | 7 +++++++ src/irc/irc_channel.hpp | 1 - src/irc/irc_client.cpp | 43 ++++++++++++++++++++++++++++++++++++++++--- src/irc/irc_client.hpp | 19 +++++++++++++------ src/utils/split.cpp | 18 ++++++++++++++++++ src/utils/split.hpp | 10 +--------- src/xmpp/xmpp_component.cpp | 5 ++++- 8 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 src/utils/split.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b0fae9e..7f7633b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ target_link_libraries(xmpp bridge network utils file(GLOB source_bridge src/bridge/*.[hc]pp) add_library(bridge STATIC ${source_bridge}) -target_link_libraries(bridge xmpp irc) +target_link_libraries(bridge xmpp irc utils) # ## Main executable diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d292af3..0b26a7f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -4,6 +4,7 @@ #include #include +#include #include static const char* action_prefix = "\01ACTION "; @@ -83,6 +84,12 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; return; } + if (body.substr(0, 6) == "/mode ") + { + std::vector args = utils::split(body.substr(6), ' ', false); + irc->send_mode_command(iid.chan, args); + return; + } if (body.substr(0, 4) == "/me ") irc->send_channel_message(iid.chan, action_prefix + body.substr(4) + "\01"); else diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 3786697..c4b6d2c 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -26,7 +26,6 @@ public: private: std::unique_ptr self; std::vector> users; - IrcChannel(const IrcChannel&) = delete; IrcChannel(IrcChannel&&) = delete; IrcChannel& operator=(const IrcChannel&) = delete; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 30b1204..0d8c614 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -162,9 +162,15 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin { IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == true) - { - this->send_message(IrcMessage("PART", {chan_name, status_message})); - } + this->send_message(IrcMessage("PART", {chan_name, status_message})); +} + +void IrcClient::send_mode_command(const std::string& chan_name, const std::vector& arguments) +{ + std::vector args(arguments); + args.insert(args.begin(), chan_name); + IrcMessage m("MODE", std::move(args)); + this->send_message(std::move(m)); } void IrcClient::send_pong_command(const IrcMessage& message) @@ -325,3 +331,34 @@ void IrcClient::on_nick(const IrcMessage& message) } } +void IrcClient::on_mode(const IrcMessage& message) +{ + const std::string target = message.arguments[0]; + if (target[0] == '&' || target[0] == '#' || + target[0] == '!' || target[0] == '+') + this->on_channel_mode(message); + else + this->on_user_mode(message); +} + +void IrcClient::on_channel_mode(const IrcMessage& message) +{ + // For now, just transmit the modes so the user can know what happens + // TODO, actually interprete the mode. + Iid iid; + iid.chan = message.arguments[0]; + iid.server = this->hostname; + IrcUser user(message.prefix); + this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan + + " [" + message.arguments[1] + + (message.arguments.size() > 2 ? (" " + message.arguments[2]): "") + + "] by " + user.nick, + true); +} + +void IrcClient::on_user_mode(const IrcMessage& message) +{ + this->bridge->send_xmpp_message(this->hostname, "", + std::string("User mode for ") + message.arguments[0] + + " is [" + message.arguments[1] + "]"); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index d9ea069..722f850 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -87,6 +87,10 @@ public: * Send the PART irc command */ void send_part_command(const std::string& chan_name, const std::string& status_message); + /** + * Send the MODE irc command + */ + void send_mode_command(const std::string& chan_name, const std::vector& arguments); /** * Forward the server message received from IRC to the XMPP component */ @@ -118,17 +122,20 @@ public: * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname */ void on_welcome_message(const IrcMessage& message); - /** - * When a PART message is received - */ void on_part(const IrcMessage& message); + void on_nick(const IrcMessage& message); + void on_mode(const IrcMessage& message); /** - * When a NICK message is received + * A mode towards our own user is received (note, that is different from a + * channel mode towards or own nick, see + * http://tools.ietf.org/html/rfc2812#section-3.1.5 VS #section-3.2.3) */ - void on_nick(const IrcMessage& message); + void on_user_mode(const IrcMessage& message); /** - * When a QUIT message is received + * A mode towards a channel. Note that this can change the mode of the + * channel itself or an IrcUser in it. */ + void on_channel_mode(const IrcMessage& message); void on_quit(const IrcMessage& message); private: diff --git a/src/utils/split.cpp b/src/utils/split.cpp new file mode 100644 index 0000000..82852ee --- /dev/null +++ b/src/utils/split.cpp @@ -0,0 +1,18 @@ +#include + +namespace utils +{ + std::vector split(const std::string &s, const char delim, const bool allow_empty) + { + std::vector ret; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + { + if (item.empty() && !allow_empty) + continue ; + ret.emplace_back(std::move(item)); + } + return ret; + } +} diff --git a/src/utils/split.hpp b/src/utils/split.hpp index 0067131..9fee90a 100644 --- a/src/utils/split.hpp +++ b/src/utils/split.hpp @@ -7,15 +7,7 @@ namespace utils { - std::vector split(const std::string &s, const char delim) - { - std::vector ret; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) - ret.emplace_back(std::move(item)); - return ret; - } + std::vector split(const std::string &s, const char delim, const bool allow_empty=true); } #endif // SPLIT_INCLUDED diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index e2063ba..c36a141 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -294,7 +294,10 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str { Stanza message("message"); message["to"] = jid_to; - message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + if (!nick.empty()) + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else // Message from the room itself + message["from"] = muc_name + "@" + this->served_hostname; message["type"] = "groupchat"; XmlNode body("body"); body.set_inner(body_str); -- cgit v1.2.3 From 3cfaab9a2debe03829b1ff26fe94e775e1d18e0a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 12 Nov 2013 23:47:00 +0100 Subject: Map irc commands to callbacks, in a clean way --- src/irc/irc_client.cpp | 30 +++++------------------------- src/irc/irc_client.hpp | 24 +++++++++++++++++++++++- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0d8c614..1d2e487 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -74,31 +74,11 @@ void IrcClient::parse_in_buffer() IrcMessage message(this->in_buf.substr(0, pos)); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); std::cout << message << std::endl; - // TODO map function and command name properly - if (message.command == "PING") - this->send_pong_command(message); - else if (message.command == "NOTICE" || - message.command == "375" || - message.command == "372") - this->forward_server_message(message); - else if (message.command == "JOIN") - this->on_channel_join(message); - else if (message.command == "PRIVMSG") - this->on_channel_message(message); - else if (message.command == "353") - this->set_and_forward_user_list(message); - else if (message.command == "332") - this->on_topic_received(message); - else if (message.command == "366") - this->on_channel_completely_joined(message); - else if (message.command == "001") - this->on_welcome_message(message); - else if (message.command == "PART") - this->on_part(message); - else if (message.command == "QUIT") - this->on_quit(message); - else if (message.command == "NICK") - this->on_nick(message); + auto cb = irc_callbacks.find(message.command); + if (cb != irc_callbacks.end()) + (this->*(cb->second))(message); + else + std::cout << "No handler for command " << message.command << std::endl; } } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 722f850..07ff02c 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -167,11 +167,33 @@ private: */ std::vector channels_to_join; bool welcomed; - IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; IrcClient& operator=(const IrcClient&) = delete; IrcClient& operator=(IrcClient&&) = delete; }; +/** + * Define a map of functions to be called for each IRC command we can + * handle. + */ +typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); + +static const std::unordered_map irc_callbacks = { + {"NOTICE", &IrcClient::forward_server_message}, + {"375", &IrcClient::forward_server_message}, + {"372", &IrcClient::forward_server_message}, + {"JOIN", &IrcClient::on_channel_join}, + {"PRIVMSG", &IrcClient::on_channel_message}, + {"353", &IrcClient::set_and_forward_user_list}, + {"332", &IrcClient::on_topic_received}, + {"366", &IrcClient::on_channel_completely_joined}, + {"001", &IrcClient::on_welcome_message}, + {"PART", &IrcClient::on_part}, + {"QUIT", &IrcClient::on_quit}, + {"NICK", &IrcClient::on_nick}, + {"MODE", &IrcClient::on_mode}, + {"PING", &IrcClient::send_pong_command}, +}; + #endif // IRC_CLIENT_INCLUDED -- cgit v1.2.3 From 0859801230f999889d0f7356864888e8c5936cda Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Nov 2013 01:24:36 +0100 Subject: Handle KICK in irc channel, both ways --- src/bridge/bridge.cpp | 12 +++++++ src/bridge/bridge.hpp | 2 ++ src/irc/irc_client.cpp | 20 ++++++++++++ src/irc/irc_client.hpp | 6 ++++ src/xmpp/xmpp_component.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 9 ++++++ 6 files changed, 128 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 0b26a7f..e39cdd3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -122,6 +122,13 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) irc->send_nick_command(new_nick); } +void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason) +{ + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + irc->send_kick_command(iid.chan, target, reason); +} + void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { std::string utf8_body = this->sanitize_for_xmpp(body); @@ -180,3 +187,8 @@ std::string Bridge::get_own_nick(const Iid& iid) return irc->get_own_nick(); return ""; } + +void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) +{ + this->xmpp->kick_user(iid.chan + "%" + iid.server, target, reason, author, this->user_jid); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 0466ee1..b2124bd 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -39,6 +39,7 @@ public: void send_private_message(const Iid& iid, const std::string& body); void leave_irc_channel(Iid&& iid, std::string&& status_message); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); + void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason); /*** ** @@ -76,6 +77,7 @@ public: * true) changed his nick to new_nick */ void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self); + void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); /** * Misc diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1d2e487..82abbd9 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -113,6 +113,11 @@ void IrcClient::send_nick_command(const std::string& nick) this->send_message(IrcMessage("NICK", {nick})); } +void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason) +{ + this->send_message(IrcMessage("KICK", {chan_name, target, reason})); +} + void IrcClient::send_join_command(const std::string& chan_name) { if (this->welcomed == false) @@ -311,6 +316,21 @@ void IrcClient::on_nick(const IrcMessage& message) } } +void IrcClient::on_kick(const IrcMessage& message) +{ + const std::string target = message.arguments[1]; + const std::string reason = message.arguments[2]; + const std::string chan_name = message.arguments[0]; + IrcChannel* channel = this->get_channel(chan_name); + if (channel->get_self()->nick == target) + channel->joined = false; + IrcUser author(message.prefix); + Iid iid; + iid.chan = chan_name; + iid.server = this->hostname; + this->bridge->kick_muc_user(std::move(iid), target, reason, author.nick); +} + void IrcClient::on_mode(const IrcMessage& message) { const std::string target = message.arguments[0]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 07ff02c..3b22fa0 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -91,6 +91,10 @@ public: * Send the MODE irc command */ void send_mode_command(const std::string& chan_name, const std::vector& arguments); + /** + * Send the KICK irc command + */ + void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason); /** * Forward the server message received from IRC to the XMPP component */ @@ -124,6 +128,7 @@ public: void on_welcome_message(const IrcMessage& message); void on_part(const IrcMessage& message); void on_nick(const IrcMessage& message); + void on_kick(const IrcMessage& message); void on_mode(const IrcMessage& message); /** * A mode towards our own user is received (note, that is different from a @@ -194,6 +199,7 @@ static const std::unordered_map irc_callbacks = { {"NICK", &IrcClient::on_nick}, {"MODE", &IrcClient::on_mode}, {"PING", &IrcClient::send_pong_command}, + {"KICK", &IrcClient::on_kick}, }; #endif // IRC_CLIENT_INCLUDED diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index c36a141..2d891bc 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -14,6 +14,7 @@ #define COMPONENT_NS "jabber:component:accept" #define MUC_NS "http://jabber.org/protocol/muc" #define MUC_USER_NS MUC_NS"#user" +#define MUC_ADMIN_NS MUC_NS"#admin" #define DISCO_NS "http://jabber.org/protocol/disco" #define DISCO_ITEMS_NS DISCO_NS"#items" #define DISCO_INFO_NS DISCO_NS"#info" @@ -36,6 +37,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); this->stanza_handlers.emplace(COMPONENT_NS":message", std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); + this->stanza_handlers.emplace(COMPONENT_NS":iq", + std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1)); } XmppComponent::~XmppComponent() @@ -201,6 +204,46 @@ void XmppComponent::handle_message(const Stanza& stanza) } } +void XmppComponent::handle_iq(const Stanza& stanza) +{ + Bridge* bridge = this->get_user_bridge(stanza["from"]); + Jid to(stanza["to"]); + std::string type; + try { + type = stanza["type"]; + } + catch (const AttributeNotFound&) + { return; } + if (type == "set") + { + XmlNode* query; + if ((query = stanza.get_child(MUC_ADMIN_NS":query"))) + { + XmlNode* child; + if ((child = query->get_child(MUC_ADMIN_NS":item"))) + { + std::string nick; + std::string role; + try { + nick = (*child)["nick"]; + role = (*child)["role"]; + } + catch (const AttributeNotFound&) + { return; } + if (!nick.empty() && role == "none") + { + std::string reason; + XmlNode* reason_el = child->get_child(MUC_ADMIN_NS":reason"); + if (reason_el) + reason = reason_el->get_inner(); + Iid iid(to.local); + bridge->send_irc_kick(iid, nick, reason); + } + } + } + } +} + Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) { try @@ -360,3 +403,39 @@ void XmppComponent::send_nick_change(const std::string& muc_name, const std::str else this->send_user_join(muc_name, new_nick, 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) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; + presence["to"] = jid_to; + presence["type"] = "unavailable"; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["affiliation"] = "none"; + item["role"] = "none"; + XmlNode actor("actor"); + actor["nick"] = author; + actor["jid"] = author; // backward compatibility with old clients + actor.close(); + item.add_child(std::move(actor)); + XmlNode reason("reason"); + reason.set_inner(txt); + reason.close(); + item.add_child(std::move(reason)); + item.close(); + x.add_child(std::move(item)); + XmlNode status("status"); + status["code"] = "307"; + status.close(); + x.add_child(std::move(status)); + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 84b19a9..0c68497 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -83,12 +83,21 @@ public: * Indicate that a participant changed his nick */ void send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self); + /** + * 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); /** * Handle the various stanza types */ void handle_handshake(const Stanza& stanza); void handle_presence(const Stanza& stanza); void handle_message(const Stanza& stanza); + void handle_iq(const Stanza& stanza); private: /** -- cgit v1.2.3 From abce2fc92ec80e95066f6362492351b85ad8aef1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Nov 2013 01:36:30 +0100 Subject: Do not crash on special chars in the content of message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit That’s ugly, and we need to sanitize everything properly, and also handle these special messages. --- src/bridge/colors.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index f49f31f..b34ab4a 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -11,7 +11,11 @@ void remove_irc_colors(std::string& str) if (c == IRC_COLOR_BOLD_CHAR || c == IRC_COLOR_COLOR_CHAR || c == IRC_COLOR_FIXED_CHAR || c == IRC_COLOR_RESET_CHAR || c == IRC_COLOR_REVERSE_CHAR || c == IRC_COLOR_REVERSE2_CHAR || - c == IRC_COLOR_UNDERLINE_CHAR || c == IRC_COLOR_ITALIC_CHAR) + c == IRC_COLOR_UNDERLINE_CHAR || c == IRC_COLOR_ITALIC_CHAR || + // HACK: until we properly handle things + // like ^AVERSION^A, remove the ^A chars + // here. + c == '\u0001') return true; return false; } -- cgit v1.2.3 From 8c33a01e68125fbc76be1f455f5ca85bcb952e65 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Nov 2013 01:39:26 +0100 Subject: Include stdlib.h for atoi --- src/config/config.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config/config.cpp b/src/config/config.cpp index cad8216..5f937e6 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -4,6 +4,8 @@ #include #include +#include + std::string Config::filename = "./biboumi.cfg"; bool Config::file_must_exist = false; -- cgit v1.2.3 From 83219052c32a2073f52aae8e4b1c15822343f04f Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 14 Nov 2013 15:59:05 +0100 Subject: Fix JID parsing --- src/test.cpp | 18 ++++++++++++++++++ src/xmpp/jid.cpp | 16 +++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 99454a5..7818451 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -12,6 +12,7 @@ #include +#include #include int main() @@ -65,6 +66,23 @@ int main() }); xml.feed(doc.data(), doc.size(), true); + /** + * JID parsing + */ + // Full JID + Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); + std::cerr << jid1.local << " @ " << jid1.domain << " / " << jid1.resource << std::endl; + assert(jid1.local == "♥"); + assert(jid1.domain == "ツ.coucou"); + assert(jid1.resource == "coucou@coucou/coucou"); + + // Domain and resource + Jid jid2("ツ.coucou/coucou@coucou/coucou"); + std::cerr << jid2.local << " @ " << jid2.domain << " / " << jid2.resource << std::endl; + assert(jid2.local == ""); + assert(jid2.domain == "ツ.coucou"); + assert(jid2.resource == "coucou@coucou/coucou"); + /** * Config */ diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index 78b28a0..29a5302 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -2,18 +2,20 @@ Jid::Jid(const std::string& jid) { - std::string::size_type at = jid.find("@"); - if (at != std::string::npos) + std::string::size_type slash = jid.find('/'); + if (slash != std::string::npos) + { + this->resource = jid.substr(slash + 1); + } + + std::string::size_type at = jid.find('@'); + if (at != std::string::npos && at < slash) { this->local = jid.substr(0, at); at++; } else at = 0; - std::string::size_type slash = jid.find("/", at); - if (slash != std::string::npos) - { - this->resource = jid.substr(slash + 1); - } + this->domain = jid.substr(at, slash - at); } -- cgit v1.2.3 From 641166b037f73e47fe29eb9d7542c39349c28428 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Nov 2013 23:34:58 +0100 Subject: Link with pthread, required by cryptopp --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f7633b..21e82b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,7 +62,7 @@ file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp bridge network utils - ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES}) + ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES} pthread) # ## bridge -- cgit v1.2.3 From a85e1abaaa5fe4f6cd9da7276088d14bf5f2c41f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Nov 2013 23:39:28 +0100 Subject: Fix a warning --- src/xmpp/xmpp_stanza.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 169a8ef..348d73c 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -42,7 +42,7 @@ std::string xml_unescape(const std::string& data) std::string res; res.reserve(data.size()); const char* str = data.c_str(); - while (str && *str && (str - data.c_str()) < data.size()) + while (str && *str && static_cast(str - data.c_str()) < data.size()) { if (*str == '&') { -- cgit v1.2.3 From e7a441e798e0a32fc4bb4021e058f3dc080adc80 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 16 Nov 2013 12:07:16 +0100 Subject: Add a test for xml escape/unescape --- src/test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test.cpp b/src/test.cpp index 7818451..9cb48b7 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -66,6 +66,13 @@ int main() }); xml.feed(doc.data(), doc.size(), true); + /** + * XML escape/escape + */ + const std::string unescaped = "'coucou'/&\"gaga\""; + assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); + assert(xml_unescape(xml_escape(unescaped)) == unescaped); + /** * JID parsing */ -- cgit v1.2.3 From 1e122d3342ef4336f17bd5606be7101748627415 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 17 Nov 2013 11:35:43 +0100 Subject: Send the motd as one single big message We append each line to a string, and when the MOTD is complete, we send that string at once. --- src/irc/irc_client.cpp | 21 +++++++++++++++++++++ src/irc/irc_client.hpp | 25 +++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 82abbd9..f339580 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -222,6 +222,27 @@ void IrcClient::on_channel_message(const IrcMessage& message) this->bridge->send_message(iid, nick, body, muc); } +void IrcClient::empty_motd(const IrcMessage& message) +{ + (void)message; + this->motd.erase(); +} + +void IrcClient::on_motd_line(const IrcMessage& message) +{ + const std::string body = message.arguments[1]; + // We could send the MOTD without a line break between each IRC-message, + // but sometimes it contains some ASCII art, we use line breaks to keep + // them intact. + this->motd += body+"\n"; +} + +void IrcClient::send_motd(const IrcMessage& message) +{ + (void)message; + this->bridge->send_xmpp_message(this->hostname, "", this->motd); +} + void IrcClient::on_topic_received(const IrcMessage& message) { const std::string chan_name = message.arguments[1]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 3b22fa0..aa420f4 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -99,6 +99,18 @@ public: * Forward the server message received from IRC to the XMPP component */ void forward_server_message(const IrcMessage& message); + /** + * Just empty the motd we kept as a string + */ + void empty_motd(const IrcMessage& message); + /** + * Send the MOTD string as one single "big" message + */ + void send_motd(const IrcMessage& message); + /** + * Append this line to the MOTD + */ + void on_motd_line(const IrcMessage& message); /** * Forward the join of an other user into an IRC channel, and save the * IrcUsers in the IrcChannel @@ -172,6 +184,11 @@ private: */ std::vector channels_to_join; bool welcomed; + /** + * Each motd line received is appended to this string, which we send when + * the motd is completely received + */ + std::string motd; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; IrcClient& operator=(const IrcClient&) = delete; @@ -186,8 +203,12 @@ typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); static const std::unordered_map irc_callbacks = { {"NOTICE", &IrcClient::forward_server_message}, - {"375", &IrcClient::forward_server_message}, - {"372", &IrcClient::forward_server_message}, + {"RPL_MOTDSTART", &IrcClient::empty_motd}, + {"375", &IrcClient::empty_motd}, + {"RPL_MOTD", &IrcClient::on_motd_line}, + {"372", &IrcClient::on_motd_line}, + {"RPL_MOTDEND", &IrcClient::send_motd}, + {"376", &IrcClient::send_motd}, {"JOIN", &IrcClient::on_channel_join}, {"PRIVMSG", &IrcClient::on_channel_message}, {"353", &IrcClient::set_and_forward_user_list}, -- cgit v1.2.3 From b569240a55a0df3a78d3cb3e1e673e9347e531c0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 17 Nov 2013 12:55:45 +0100 Subject: Use epoll --- src/network/poller.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++--- src/network/poller.hpp | 22 ++++++++------- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index e790e60..023fb12 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -12,6 +12,13 @@ Poller::Poller() #if POLLER == POLL memset(this->fds, 0, sizeof(this->fds)); this->nfds = 0; +#elif POLLER == EPOLL + this->epfd = ::epoll_create1(0); + if (this->epfd == -1) + { + perror("epoll"); + throw std::runtime_error("Could not create epoll instance"); + } #endif } @@ -36,6 +43,17 @@ void Poller::add_socket_handler(std::shared_ptr socket_handler) this->fds[this->nfds].events = POLLIN; this->nfds++; #endif +#if POLLER == EPOLL + struct epoll_event event; + event.data.ptr = socket_handler.get(); + event.events = EPOLLIN; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); + if (res == -1) + { + perror("epoll_ctl"); + throw std::runtime_error("Could not add socket to epoll"); + } +#endif } void Poller::remove_socket_handler(const socket_t socket) @@ -44,6 +62,8 @@ void Poller::remove_socket_handler(const socket_t socket) if (it == this->socket_handlers.end()) throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); this->socket_handlers.erase(it); + +#if POLLER == POLL for (size_t i = 0; i < this->nfds; i++) { if (this->fds[i].fd == socket) @@ -58,9 +78,17 @@ void Poller::remove_socket_handler(const socket_t socket) this->nfds--; } } +#elif POLLER == EPOLL + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); + if (res == -1) + { + perror("epoll_ctl"); + throw std::runtime_error("Could not remove socket from epoll"); + } +#endif } -void Poller::watch_send_events(const SocketHandler* const socket_handler) +void Poller::watch_send_events(SocketHandler* socket_handler) { #if POLLER == POLL for (size_t i = 0; i <= this->nfds; ++i) @@ -71,11 +99,21 @@ void Poller::watch_send_events(const SocketHandler* const socket_handler) return; } } -#endif throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event; + event.data.ptr = socket_handler; + event.events = EPOLLIN|EPOLLOUT; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + perror("epoll_ctl"); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif } -void Poller::stop_watching_send_events(const SocketHandler* const socket_handler) +void Poller::stop_watching_send_events(SocketHandler* socket_handler) { #if POLLER == POLL for (size_t i = 0; i <= this->nfds; ++i) @@ -86,8 +124,18 @@ void Poller::stop_watching_send_events(const SocketHandler* const socket_handler return; } } -#endif throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event; + event.data.ptr = socket_handler; + event.events = EPOLLIN; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + perror("epoll_ctl"); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif } bool Poller::poll() @@ -121,6 +169,23 @@ bool Poller::poll() res--; } } +#elif POLLER == EPOLL + static const size_t max_events = 12; + struct epoll_event revents[max_events]; + const int nb_events = epoll_wait(this->epfd, revents, max_events, -1); + if (nb_events == -1) + { + perror("epoll_wait"); + throw std::runtime_error("Epoll_wait failed"); + } + for (int i = 0; i < nb_events; ++i) + { + auto socket_handler = static_cast(revents[i].data.ptr); + if (revents[i].events & EPOLLIN) + socket_handler->on_recv(); + if (revents[i].events & EPOLLOUT) + socket_handler->on_send(); + } #endif return true; } diff --git a/src/network/poller.hpp b/src/network/poller.hpp index 319236b..e6ce7f2 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -10,26 +10,28 @@ #define EPOLL 2 #define KQUEUE 3 -#define POLLER POLL +#include +#ifndef POLLER + // Default standard poller + #define POLLER EPOLL +#endif #if POLLER == POLL #include - // TODO, dynamic size, without artificial limit #define MAX_POLL_FD_NUMBER 4096 +#elif POLLER == EPOLL + #include #endif /** - * We pass some SocketHandlers to this the Poller, which uses + * We pass some SocketHandlers to this Poller, which uses * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, * and call the callbacks when event occurs. * - * TODO: support for all these pollers: - * - poll(2) (mandatory) - * - epoll(7) + * TODO: support these pollers: * - kqueue(2) */ - class Poller { public: @@ -48,12 +50,12 @@ public: * Signal the poller that he needs to watch for send events for the given * SocketHandler. */ - void watch_send_events(const SocketHandler* const socket_handler); + void watch_send_events(SocketHandler* socket_handler); /** * Signal the poller that he needs to stop watching for send events for * this SocketHandler. */ - void stop_watching_send_events(const SocketHandler* const socket_handler); + void stop_watching_send_events(SocketHandler* socket_handler); /** * Wait for all watched events, and call the SocketHandlers' callbacks * when one is ready. @@ -72,6 +74,8 @@ private: #if POLLER == POLL struct pollfd fds[MAX_POLL_FD_NUMBER]; nfds_t nfds; +#elif POLLER == EPOLL + int epfd; #endif Poller(const Poller&) = delete; -- cgit v1.2.3 From b72908548dc841de65dc9288a96c1abe648acc46 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Nov 2013 00:21:32 +0100 Subject: Let the user choose the poller to use through cmake POLLER option Use ccmake, or cmake -i, or cmake -DPOLLER=EPOLL, for example --- CMakeLists.txt | 8 +++++++- config.h.cmake | 1 + src/network/poller.hpp | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21e82b4..2c01ee3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,12 @@ include_directories(${ICONV_INCLUDE_DIR}) # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) +set(POLLER "POLL" CACHE STRING + "Choose the poller between POLL and EPOLL (Linux-only)") +if((NOT ${POLLER} MATCHES "POLL") AND + (NOT ${POLLER} MATCHES "EPOLL")) + message(FATAL_ERROR "POLLER must be either POLL or EPOLL") +endif() # ## utils # @@ -95,4 +101,4 @@ target_link_libraries(test utils config) -CONFIGURE_FILE(config.h.cmake src/config.h @ONLY) +configure_file(config.h.cmake src/config.h) diff --git a/config.h.cmake b/config.h.cmake index 5f0a3cb..e17cc80 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1 +1,2 @@ #cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST +#cmakedefine POLLER ${POLLER} diff --git a/src/network/poller.hpp b/src/network/poller.hpp index e6ce7f2..fe52fda 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -9,11 +9,9 @@ #define POLL 1 #define EPOLL 2 #define KQUEUE 3 - #include #ifndef POLLER - // Default standard poller - #define POLLER EPOLL + #define POLLER POLL #endif #if POLLER == POLL @@ -21,6 +19,8 @@ #define MAX_POLL_FD_NUMBER 4096 #elif POLLER == EPOLL #include +#else + #error Invalid POLLER value #endif /** -- cgit v1.2.3 From bfcc9cdc7462c515c308592735bc661103fb92b5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Nov 2013 00:58:14 +0100 Subject: Send XMPP multi-line messages as multiple IRC messages --- src/bridge/bridge.cpp | 17 ++++++++++++----- src/test.cpp | 31 +++++++++++++++++++++++-------- src/utils/split.cpp | 2 +- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e39cdd3..e24ab88 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -73,6 +73,10 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) void Bridge::send_channel_message(const Iid& iid, const std::string& body) { + std::vector lines = utils::split(body, '\n', true); + if (lines.empty()) + return ; + const std::string first_line = lines[0]; if (iid.chan.empty() || iid.server.empty()) { std::cout << "Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]" << std::endl; @@ -84,16 +88,19 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; return; } - if (body.substr(0, 6) == "/mode ") + if (first_line.substr(0, 6) == "/mode ") { - std::vector args = utils::split(body.substr(6), ' ', false); + std::vector args = utils::split(first_line.substr(6), ' ', false); irc->send_mode_command(iid.chan, args); return; } - if (body.substr(0, 4) == "/me ") - irc->send_channel_message(iid.chan, action_prefix + body.substr(4) + "\01"); + if (first_line.substr(0, 4) == "/me ") + irc->send_channel_message(iid.chan, action_prefix + first_line.substr(4) + "\01"); else - irc->send_channel_message(iid.chan, body); + irc->send_channel_message(iid.chan, first_line); + // Send each of the other lines of the message as a separate IRC message + for (std::vector::const_iterator it = lines.begin() + 1; it != lines.end(); ++it) + irc->send_channel_message(iid.chan, *it); // We do not need to convert body to utf-8: it comes from our XMPP server, // so it's ok to send it back this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); diff --git a/src/test.cpp b/src/test.cpp index 9cb48b7..aa1ccde 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -2,18 +2,18 @@ * Just a very simple test suite, by hand, using assert() */ -#include - -#include - -#include +#include #include +#include +#include +#include +#include #include -#include +#include +#include -#include -#include +#include int main() { @@ -48,6 +48,21 @@ int main() remove_irc_colors(coucou); assert(coucou == "COUCOU"); + /** + * Utils + */ + std::vector splitted = utils::split("a::a", ':', false); + assert(splitted.size() == 2); + splitted = utils::split("a::a", ':', true); + assert(splitted.size() == 3); + assert(splitted[0] == "a"); + assert(splitted[1] == ""); + assert(splitted[2] == "a"); + splitted = utils::split("\na", '\n', true); + assert(splitted.size() == 2); + assert(splitted[0] == ""); + assert(splitted[1] == "a"); + /** * XML parsing */ diff --git a/src/utils/split.cpp b/src/utils/split.cpp index 82852ee..afe4300 100644 --- a/src/utils/split.cpp +++ b/src/utils/split.cpp @@ -2,7 +2,7 @@ namespace utils { - std::vector split(const std::string &s, const char delim, const bool allow_empty) + std::vector split(const std::string& s, const char delim, const bool allow_empty) { std::vector ret; std::stringstream ss(s); -- cgit v1.2.3 From 12a18ca748a27111bb24a7f6518f831494e5ca47 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Nov 2013 19:38:31 +0100 Subject: TIL override and final --- src/irc/irc_client.hpp | 6 +++--- src/xmpp/xmpp_component.hpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index aa420f4..4749cac 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -30,16 +30,16 @@ public: /** * Called when successfully connected to the server */ - void on_connected(); + void on_connected() override final; /** * Close the connection, remove us from the poller */ - void on_connection_close(); + void on_connection_close() override final; /** * Parse the data we have received so far and try to get one or more * complete messages from it. */ - void parse_in_buffer(); + void parse_in_buffer() override final; /** * Return the channel with this name, create it if it does not yet exist */ diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 0c68497..e45c64b 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -20,9 +20,9 @@ class XmppComponent: public SocketHandler public: explicit XmppComponent(const std::string& hostname, const std::string& secret); ~XmppComponent(); - void on_connected(); - void on_connection_close(); - void parse_in_buffer(); + void on_connected() override final; + void on_connection_close() override final; + void parse_in_buffer() override final; /** * Connect to the XMPP server -- cgit v1.2.3 From 959291afe69de05aaa7ceeb61b3b0ede7a54bfea Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 22 Nov 2013 20:44:27 +0100 Subject: Set the parent of a node passed to add_child, and return it for conveniance --- src/xmpp/xmpp_stanza.cpp | 8 +++++--- src/xmpp/xmpp_stanza.hpp | 11 +++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 348d73c..85bd6b5 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -152,15 +152,17 @@ XmlNode* XmlNode::get_child(const std::string& name) const return nullptr; } -void XmlNode::add_child(XmlNode* child) +XmlNode* XmlNode::add_child(XmlNode* child) { + child->parent = this; this->children.push_back(child); + return child; } -void XmlNode::add_child(XmlNode&& child) +XmlNode* XmlNode::add_child(XmlNode&& child) { XmlNode* new_node = new XmlNode(std::move(child)); - this->add_child(new_node); + return this->add_child(new_node); } XmlNode* XmlNode::get_last_child() const diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 2ce8ce2..ca21ab4 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -77,8 +77,15 @@ public: * Get a pointer to the first child element with that name */ XmlNode* get_child(const std::string& name) const; - void add_child(XmlNode* child); - void add_child(XmlNode&& child); + /** + * Add a node child to this node. Assign this node to the child’s parent. + * Returns a pointer to the newly added child. + */ + XmlNode* add_child(XmlNode* child); + XmlNode* add_child(XmlNode&& child); + /** + * Returns the last of the children + */ XmlNode* get_last_child() const; /** * Mark this node as closed, nothing else -- cgit v1.2.3 From fba01f46468050d4f3b8eb35373ed49a3584868e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 22 Nov 2013 21:00:07 +0100 Subject: Remove incomplete implementation of remove_irc_colors --- src/bridge/bridge.cpp | 1 - src/bridge/colors.cpp | 17 ----------------- src/bridge/colors.hpp | 1 - src/test.cpp | 4 ---- 4 files changed, 23 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e24ab88..e08e2a4 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -28,7 +28,6 @@ std::string Bridge::sanitize_for_xmpp(const std::string& str) res = str; else res = utils::convert_to_utf8(str, "ISO-8859-1"); - remove_irc_colors(res); return res; } diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index b34ab4a..2f30354 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -3,22 +3,5 @@ #include -void remove_irc_colors(std::string& str) { - auto it = std::remove_if(str.begin(), str.end(), - [](const char c) - { - if (c == IRC_COLOR_BOLD_CHAR || c == IRC_COLOR_COLOR_CHAR || - c == IRC_COLOR_FIXED_CHAR || c == IRC_COLOR_RESET_CHAR || - c == IRC_COLOR_REVERSE_CHAR || c == IRC_COLOR_REVERSE2_CHAR || - c == IRC_COLOR_UNDERLINE_CHAR || c == IRC_COLOR_ITALIC_CHAR || - // HACK: until we properly handle things - // like ^AVERSION^A, remove the ^A chars - // here. - c == '\u0001') - return true; - return false; - } - ); - str.erase(it, str.end()); } diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index a4775e1..da4498c 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -16,6 +16,5 @@ #define IRC_COLOR_ITALIC_CHAR '\x1D' #define IRC_COLOR_UNDERLINE_CHAR '\x1F' -void remove_irc_colors(std::string& str); #endif // COLORS_INCLUDED diff --git a/src/test.cpp b/src/test.cpp index aa1ccde..1f2d185 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -44,10 +44,6 @@ int main() std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); assert(from_ascii == "couc�ou"); - std::string coucou("\u0002\u0002COUCOU\u0003"); - remove_irc_colors(coucou); - assert(coucou == "COUCOU"); - /** * Utils */ -- cgit v1.2.3 From 31e18e49b699f606a8aeb1f529642a004781e704 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 24 Nov 2013 15:37:48 +0100 Subject: =?UTF-8?q?fsanitize=3Daddress=20requires=20libasan,=20that?= =?UTF-8?q?=E2=80=99s=20a=20useless=20dependency.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also we cannot use both this feature and valgrind at the same time. So, I’ll just specify this flag myself when I need it, this doesn’t need to be there by default --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c01ee3..daa6cf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og -fsanitize=address") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") # ## Look for external libraries -- cgit v1.2.3 From e6f20d3c0fd4ba8696a4410a366741c9b9f3562d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 22 Nov 2013 21:00:32 +0100 Subject: Implement IRC format to xhtml-im conversion The generated XML is very verbose because each IRC formatting tag makes us close a element and reopen it with the new style applied. However, this works quite well and is easy to implement. --- src/bridge/bridge.cpp | 26 +++---- src/bridge/bridge.hpp | 3 +- src/bridge/colors.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++ src/bridge/colors.hpp | 56 ++++++++++++--- src/test.cpp | 25 +++++++ src/xmpp/xmpp_component.cpp | 32 ++++++--- src/xmpp/xmpp_component.hpp | 8 +-- src/xmpp/xmpp_stanza.hpp | 10 +-- 8 files changed, 275 insertions(+), 47 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e08e2a4..7e6f801 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -21,14 +21,14 @@ Bridge::~Bridge() { } -std::string Bridge::sanitize_for_xmpp(const std::string& str) +Xmpp::body Bridge::make_xmpp_body(const std::string& str) { std::string res; if (utils::is_valid_utf8(str.c_str())) res = str; else res = utils::convert_to_utf8(str, "ISO-8859-1"); - return res; + return irc_format_to_xhtmlim(res); } IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string& username) @@ -102,7 +102,8 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.chan, *it); // We do not need to convert body to utf-8: it comes from our XMPP server, // so it's ok to send it back - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), body, this->user_jid); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), + this->make_xmpp_body(body), this->user_jid); } void Bridge::send_private_message(const Iid& iid, const std::string& body) @@ -137,22 +138,17 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { - std::string utf8_body = this->sanitize_for_xmpp(body); - if (utf8_body.substr(0, action_prefix_len) == action_prefix) - { // Special case for ACTION (/me) messages: - // "\01ACTION goes out\01" == "/me goes out" - utf8_body = std::string("/me ") + - utf8_body.substr(action_prefix_len, utf8_body.size() - action_prefix_len - 1); - } if (muc) - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, utf8_body, this->user_jid); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, + this->make_xmpp_body(body), this->user_jid); else - this->xmpp->send_message(iid.chan + "%" + iid.server, utf8_body, this->user_jid); + this->xmpp->send_message(iid.chan + "%" + iid.server, + this->make_xmpp_body(body), this->user_jid); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { - this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->sanitize_for_xmpp(message), this->user_jid, self); + this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); } void Bridge::send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self) @@ -168,7 +164,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho body = std::string("[") + author + std::string("] ") + msg; else body = msg; - this->xmpp->send_message(from, this->sanitize_for_xmpp(body), this->user_jid); + this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid); } void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick) @@ -183,7 +179,7 @@ void Bridge::send_self_join(const std::string& hostname, const std::string& chan void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic) { - this->xmpp->send_topic(chan_name + "%" + hostname, this->sanitize_for_xmpp(topic), this->user_jid); + this->xmpp->send_topic(chan_name + "%" + hostname, this->make_xmpp_body(topic), this->user_jid); } std::string Bridge::get_own_nick(const Iid& iid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b2124bd..1443191 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -2,6 +2,7 @@ # define BRIDGE_INCLUDED #include +#include #include #include @@ -23,7 +24,7 @@ public: explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller); ~Bridge(); - static std::string sanitize_for_xmpp(const std::string& str); + static Xmpp::body make_xmpp_body(const std::string& str); /*** ** ** From XMPP to IRC. diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 2f30354..024121b 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -1,7 +1,169 @@ #include +#include +#include + #include #include +#include + +static const char IRC_NUM_COLORS = 16; + +static const char* irc_colors_to_css[IRC_NUM_COLORS] = { + "white", + "black", + "blue", + "green", + "indianred", + "red", + "magenta", + "brown", + "yellow", + "lightgreen", + "cyan", + "lightcyan", + "lightblue", + "lightmagenta", + "gray", + "white", +}; + +#define XHTML_NS "http://www.w3.org/1999/xhtml" + +struct styles_t { + bool strong; + bool underline; + bool italic; + int fg; + int bg; +}; + +/** We keep the currently-applied CSS styles in a structure. Each time a tag + * is found, update this style list, then close the current span XML element + * (if it is open), then reopen it with all the new styles in it. This is + * done this way because IRC formatting does not map well with XML + * (hierarchical tags), it’s a lot easier and cleaner to remove all styles + * and reapply them for each tag, instead of trying to keep a consistent + * hierarchy of span, strong, em etc tags. The generated XML is one-level + * deep only. +*/ +Xmpp::body irc_format_to_xhtmlim(const std::string& s) +{ + if (s.find_first_of(irc_format_char) == std::string::npos) + // there is no special formatting at all + return std::make_tuple(s, nullptr); + + std::string cleaned; + + styles_t styles = {false, false, false, -1, -1}; + + std::unique_ptr result = std::make_unique("body"); + (*result)["xmlns"] = XHTML_NS; + + XmlNode* current_node = result.get(); + std::string::size_type pos_start = 0; + std::string::size_type pos_end; + + while ((pos_end = s.find_first_of(irc_format_char, pos_start)) != std::string::npos) + { + const std::string txt = s.substr(pos_start, pos_end-pos_start); + cleaned += txt; + if (current_node->has_children()) + current_node->get_last_child()->set_tail(txt); + else + current_node->set_inner(txt); + + if (s[pos_end] == IRC_FORMAT_BOLD_CHAR) + styles.strong = !styles.strong; + else if (s[pos_end] == IRC_FORMAT_UNDERLINE_CHAR) + styles.underline = !styles.underline; + else if (s[pos_end] == IRC_FORMAT_ITALIC_CHAR) + styles.italic = !styles.italic; + else if (s[pos_end] == IRC_FORMAT_RESET_CHAR) + styles = {false, false, false, -1, -1}; + else if (s[pos_end] == IRC_FORMAT_REVERSE_CHAR) + { } // TODO + else if (s[pos_end] == IRC_FORMAT_REVERSE2_CHAR) + { } // TODO + else if (s[pos_end] == IRC_FORMAT_FIXED_CHAR) + { } // TODO + else if (s[pos_end] == IRC_FORMAT_COLOR_CHAR) + { + size_t pos = pos_end + 1; + styles.fg = -1; + styles.bg = -1; + // get the first number following the format char + if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9') + { // first digit + styles.fg = s[pos++] - '0'; + if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9') + // second digit + styles.fg = styles.fg * 10 + s[pos++] - '0'; + } + if (pos < s.size() && s[pos] == ',') + { // get bg color after the comma + pos++; + if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9') + { // first digit + styles.bg = s[pos++] - '0'; + if (pos < s.size() && s[pos] >= '0' && s[pos] <= '9') + // second digit + styles.bg = styles.bg * 10 + s[pos++] - '0'; + } + } + pos_end = pos - 1; + } + + // close opened span, if any + if (current_node != result.get()) + { + current_node->close(); + result->add_child(current_node); + current_node = result.get(); + } + // Take all currently-applied style and create a new span with it + std::string styles_str; + if (styles.strong) + styles_str += "font-weight:bold;"; + if (styles.underline) + styles_str += "text-decoration:underline;"; + if (styles.italic) + styles_str += "font-style:italic;"; + if (styles.fg != -1) + styles_str += std::string("color:") + + irc_colors_to_css[styles.fg % IRC_NUM_COLORS] + ";"; + if (styles.bg != -1) + styles_str += std::string("background-color:") + + irc_colors_to_css[styles.bg % IRC_NUM_COLORS] + ";"; + if (!styles_str.empty()) + { + current_node = new XmlNode("span"); + (*current_node)["style"] = styles_str; + } + + pos_start = pos_end + 1; + } + + // If some text remains, without any format char, just append that text at + // the end of the current node + const std::string txt = s.substr(pos_start, pos_end-pos_start); + cleaned += txt; + if (current_node->has_children()) + current_node->get_last_child()->set_tail(txt); + else + current_node->set_inner(txt); + + if (current_node != result.get()) + { + current_node->close(); + result->add_child(current_node); + current_node = result.get(); + } + + + result->close(); + Xmpp::body body_res = std::make_tuple(cleaned, std::move(result)); + return body_res; } diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index da4498c..82e6faf 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -1,20 +1,54 @@ #ifndef COLORS_INCLUDED # define COLORS_INCLUDED -#include - /** - * A module handling the conversion between IRC colors and XHTML-IM, and vice versa. + * A module handling the conversion between IRC colors and XHTML-IM, and + * vice versa. */ -#define IRC_COLOR_BOLD_CHAR '\x02' -#define IRC_COLOR_COLOR_CHAR '\x03' -#define IRC_COLOR_RESET_CHAR '\x0F' -#define IRC_COLOR_FIXED_CHAR '\x11' -#define IRC_COLOR_REVERSE_CHAR '\x12' -#define IRC_COLOR_REVERSE2_CHAR '\x16' -#define IRC_COLOR_ITALIC_CHAR '\x1D' -#define IRC_COLOR_UNDERLINE_CHAR '\x1F' +#include +#include +#include + +class XmlNode; + +namespace Xmpp +{ +// Contains: +// - an XMPP-valid UTF-8 body +// - an XML node representing the XHTML-IM body, or null + typedef std::tuple> body; +} +#define IRC_FORMAT_BOLD_CHAR '\x02' // done +#define IRC_FORMAT_COLOR_CHAR '\x03' // done +#define IRC_FORMAT_RESET_CHAR '\x0F' // done +#define IRC_FORMAT_FIXED_CHAR '\x11' // ?? +#define IRC_FORMAT_REVERSE_CHAR '\x12' // maybe one day +#define IRC_FORMAT_REVERSE2_CHAR '\x16' // wat +#define IRC_FORMAT_ITALIC_CHAR '\x1D' // done +#define IRC_FORMAT_UNDERLINE_CHAR '\x1F' // done + +static const char irc_format_char[] = { + IRC_FORMAT_BOLD_CHAR, + IRC_FORMAT_COLOR_CHAR, + IRC_FORMAT_RESET_CHAR, + IRC_FORMAT_FIXED_CHAR, + IRC_FORMAT_REVERSE_CHAR, + IRC_FORMAT_REVERSE2_CHAR, + IRC_FORMAT_ITALIC_CHAR, + IRC_FORMAT_UNDERLINE_CHAR, + '\x00' +}; + +/** + * Convert the passed string into an XML tree representing the XHTML version + * of the message, converting the IRC colors symbols into xhtml-im + * formatting. + * + * Returns the body cleaned from any IRC formatting (but without any xhtml), + * and the body as XHTML-IM + */ +Xmpp::body irc_format_to_xhtmlim(const std::string& str); #endif // COLORS_INCLUDED diff --git a/src/test.cpp b/src/test.cpp index 1f2d185..b33ff1d 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -84,6 +84,31 @@ int main() assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); assert(xml_unescape(xml_escape(unescaped)) == unescaped); + /** + * Colors conversion + */ + std::unique_ptr xhtml; + std::string cleaned_up; + + std::tie(cleaned_up, xhtml) = + irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" + "5red,5default-on-red10,2cyan-on-blue"); + assert(xhtml); + assert(xhtml->to_string() == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + assert(cleaned_up == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normal"); + assert(!xhtml && cleaned_up == "normal"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(""); + assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(",a"); + assert(xhtml && !xhtml->has_children() && cleaned_up == "a"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); + assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); + /** * JID parsing */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 2d891bc..9245fde 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -18,7 +18,7 @@ #define DISCO_NS "http://jabber.org/protocol/disco" #define DISCO_ITEMS_NS DISCO_NS"#items" #define DISCO_INFO_NS DISCO_NS"#info" - +#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): served_hostname(hostname), @@ -257,13 +257,13 @@ Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) } } -void XmppComponent::send_message(const std::string& from, const std::string& body, const std::string& to) +void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to) { XmlNode node("message"); node["to"] = to; node["from"] = from + "@" + this->served_hostname; XmlNode body_node("body"); - body_node.set_inner(body); + body_node.set_inner(std::get<0>(body)); body_node.close(); node.add_child(std::move(body_node)); node.close(); @@ -319,21 +319,21 @@ void XmppComponent::send_self_join(const std::string& from, const std::string& n this->send_stanza(node); } -void XmppComponent::send_topic(const std::string& from, const std::string& topic, const std::string& to) +void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) { XmlNode message("message"); message["to"] = to; message["from"] = from + "@" + this->served_hostname; message["type"] = "groupchat"; XmlNode subject("subject"); - subject.set_inner(topic); + subject.set_inner(std::get<0>(topic)); subject.close(); message.add_child(std::move(subject)); message.close(); this->send_stanza(message); } -void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to) +void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to) { Stanza message("message"); message["to"] = jid_to; @@ -343,24 +343,34 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str message["from"] = muc_name + "@" + this->served_hostname; message["type"] = "groupchat"; XmlNode body("body"); - body.set_inner(body_str); + body.set_inner(std::get<0>(xmpp_body)); body.close(); message.add_child(std::move(body)); + if (std::get<1>(xmpp_body)) + { + XmlNode html("html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::get<1>(xmpp_body).release()); + html.close(); + message.add_child(std::move(html)); + } message.close(); this->send_stanza(message); } -void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, std::string&& message, const std::string& jid_to, const bool self) +void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) { Stanza presence("presence"); presence["to"] = jid_to; presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; presence["type"] = "unavailable"; - if (!message.empty() || self) + const std::string message_str = std::get<0>(message); + if (message_str.empty() || self) { XmlNode status("status"); - if (!message.empty()) - status.set_inner(std::move(message)); + if (!message_str.empty()) + status.set_inner(message_str); if (self) status["code"] = "110"; status.close(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index e45c64b..bf85536 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -58,7 +58,7 @@ public: /** * Send a message from from@served_hostname, with the given body */ - void send_message(const std::string& from, const std::string& body, const std::string& to); + void send_message(const std::string& from, Xmpp::body&& body, const std::string& to); /** * Send a join from a new participant */ @@ -70,15 +70,15 @@ public: /** * Send the MUC topic to the user */ - void send_topic(const std::string& from, const std::string& topic, const std::string& to); + void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to); /** * Send a (non-private) message to the MUC */ - void send_muc_message(const std::string& muc_name, const std::string& nick, const std::string body_str, const std::string& jid_to); + void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); /** * Send an unavailable presence for this nick */ - void send_muc_leave(std::string&& muc_name, std::string&& nick, std::string&& message, const std::string& jid_to, const bool self); + void send_muc_leave(std::string&& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); /** * Indicate that a participant changed his nick */ diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index ca21ab4..87a80e9 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -21,9 +21,9 @@ class AttributeNotFound: public std::exception nullptr) * - zero, one or more children XML nodes * - A name - * - attributes - * - inner data (inside the node) - * - tail data (just after the node) + * - A map of attributes + * - inner data (text inside the node) + * - tail data (text just after the node) */ class XmlNode { @@ -32,8 +32,8 @@ public: explicit XmlNode(const std::string& name); XmlNode(XmlNode&& node): name(std::move(node.name)), - parent(std::move(node.parent)), - closed(std::move(node.closed)), + parent(node.parent), + closed(node.closed), attributes(std::move(node.attributes)), children(std::move(node.children)), inner(std::move(node.inner)), -- cgit v1.2.3 From 3c5cdec1d11fc03899f7068279cf5430d274124c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 27 Nov 2013 00:43:37 +0100 Subject: Add some documentation --- COPYING | 20 ++++++ INSTALL | 75 +++++++++++++++++++++++ README | 51 ++++++++++++++++ doc/biboumi.1 | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 338 insertions(+) create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 README create mode 100644 doc/biboumi.1 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8806baf --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2013 Florent Le Coz + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..309ecc0 --- /dev/null +++ b/INSTALL @@ -0,0 +1,75 @@ +============== + Dependencies +============== + +Build and runtime dependencies: + +Libraries: +- crypto++ + Sometimes packaged as cryptopp. This library is used to generate MD5 + hashes. + http://www.cryptopp.com/ +- expat + Used to parse XML from the XMPP server. + http://expat.sourceforge.net/ +- libiconv + Encoding from anything into UTF-8 + http://www.gnu.org/software/libiconv/ + +Tools: +- A C++11 compiler. +- CMake + + +============== + Configure +============== + +Configure the build system using cmake, there are many solutions to do + that, the simplest is to just run + +% cmake . + +in the current directory + +You can also configure many parameters of the build (like personnalize +CFLAGS, the install path, choose the compiler, or enabling some options +like the POLLER to use), using: + +% ccmake . + +In ccmake, first use 'c' to configure the build system, edit the values you +need and finaly use 'g' to generate the Makefiles to build the system and +quit ccmake. + +or + +% cmake -i . + +and respond to the questions when you are prompted to. + +You can select the poller used by biboumi, at compile-time, using the POLLER +cmake option. Available values are: + POLL: use the standard poll(2). This is the default value because it works on all supported plateforms + EPOLL: use the Linux-specific epoll(7) + +Example, configure the poller with cmake: +% cmake . -DPOLLER=EPOLL + + +============== + Build +============== + +- Build the project + +% make + + +============= + Install +============= + +- Install the software system-wide + +# make install diff --git a/README b/README new file mode 100644 index 0000000..8a4f3b3 --- /dev/null +++ b/README @@ -0,0 +1,51 @@ + _ _ _ _ +| |__ (_) |__ ___ _ _ _ __ ___ (_) +| '_ \| | '_ \ / _ \| | | | '_ ` _ \| | +| |_) | | |_) | (_) | |_| | | | | | | | +|_.__/|_|_.__/ \___/ \__,_|_| |_| |_|_| + + +Homepage: http://dev.louiz.org/projects/biboumi + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +It is written in modern C++11 and make great efforts to have as little +dependencies and to be as simple as possible. + +The goal is to provide a way to access most of IRC features using any XMPP +client. It doesn’t however try to provide a complete mapping of the +features of both worlds simply because this is not useful and most probably +impossible. For example all IRC modes are not all translatable into an XMPP +features. Some of theme are (like +m (mute) or +o (operator) modes), but +some others are IRC-specific. If IRC is the limiting factor (for example +you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to +work around this issue: it just enforces the rules of the IRC server by +telling the user that he/she must choose an ASCII nickname only. An +important goal is to keep the software (and its code) light and simple. + +================ + Install +================ +Refer to the INSTALL file. + +================ + Authors +================ +Florent Le Coz (louiz’) + +================= + Contact/Support +================= +Jabber ChatRoom: biboumi@muc.poezio.eu +Report a bug: http://dev.louiz.org/projects/biboumi/issues/new + +================= + License +================= +Biboumi is Free Software. +(learn more: http://www.gnu.org/philosophy/free-sw.html) + +Biboumi is released under the zlib license. +Please read the COPYING file for details. diff --git a/doc/biboumi.1 b/doc/biboumi.1 new file mode 100644 index 0000000..c874062 --- /dev/null +++ b/doc/biboumi.1 @@ -0,0 +1,192 @@ +.TH biboumi 1 2013-11-21 +. +.SH NAME +. +\fBbiboumi\fR - XMPP gateway to IRC +. +.SH SYNOPSIS +. +\fBbiboumi\fR [\fIconfig_filename\fR] +. +.SH DESCRIPTION +. +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. +. +.SH OPTIONS +. +Available options: +.TP +\fBconfig_filename\fR +Specify the file to read for configuration. See \fBCONFIG\fR section for +more details on its content. +. +.SH CONFIG +. +The configuration file uses a simple format of the form +\fB"option=value"\fR. Here is a description of each possible option: +.TP +\fBhostname\fR (mandatory) +The hostname served by the XMPP gateway. This domain must be configured in +the XMPP server as an external component. See the manual for your XMPP +server for more information. +.TP +\fBpassword\fR (mandatory) +The password used to authenticate the XMPP component to your XMPP server. +This password must be configured in the XMPP server, associated with the +external component on \fBhostname\fR. +. +.SH USAGE +. +When started, biboumi connects, without encryption (see \fBSECURITY\fR), to +the local XMPP server on the port \fI5347\fR and provides the configured +password to authenticate. Biboumi then serves the configured +\fIhostname\fR, this means that all XMPP stanza with a \fIto\fR JID on that +domain will be sent to biboumi, and biboumi will send only send messages +coming from this hostname. +. +When an user joins an IRC channel on an IRC server (see \fBJoin an IRC +channel\fR), biboumi connects to the remote IRC server, sets the user’s nick +as requested, and then tries to join the specified channel. If the same +user subsequently tries to connect to an other channel on the same server, +the same IRC connection is used. If, however, an other user wants to join +an IRC channel on that same IRC server, biboumi opens a new connection to +that server. Biboumi connects once to each IRC server, for each user on it. +. +.SS "Addressing" +. +IRC entities are represented by XMPP JIDs. The domain part of the JID is +the domain served by biboumi, and the local part depends on the concerned +entity. +. +IRC channels and IRC users JIDs have a localpart formed like this: \fIname\fR, +the '\fI%\fR' separator and the \fIirc_server\fR. +. +For an IRC channel, the name starts with '\fI&\fR', '\fI#\fR', '\fI+\fR' +or '\fI!\fR'. Some other gateway implementations, as well as some IRC +clients, do not require them to be started by one of these characters, +adding an implicit '\fI#\fR' in that case. Biboumi does not do that because +this gets confusing when trying to understand the difference between +\fIfoo\fR, \fI#foo\fR, and \fI##foo\fR. +. +If the name starts with any other character, this represents an IRC user. +. +.SS "Join an IRC channel" +. +To join an IRC channel \fI#foo\fR on the IRC server \fIirc.example.com\fR, +join the XMPP MUC \fI#foo%irc.example.com@hostname\fR. +. +.SS "Channel messages" +. +On XMPP, unlike on IRC, the displayed order of the messages is the same for +all participants of a MUC. Biboumi can not however provide this feature, as +it cannot know whether the IRC server has received and forwarded the +messages to other users. This means that the order of the messages +displayed in your XMPP may not be the same than the order on other IRC +users’. +. +.SS "Nicknames" +. +On IRC, nicknames are server-wide. This means that one user only has one +single nickname at one given time on all the channels of a server. This is +different from XMPP where an user can have a different nick on each MUC, +even if these MUCs are on the same server. +. +This means that the nick you choose when joining your first IRC channel on a +given IRC server will be your nickname in all other channels that you join +on that same IRC server. +If you explicitely change your nickname on one channel, your nickname will +be changed on all channels on the same server as well. +. +.SS "Private messages" +. +Private messages are handled differently on IRC and on XMPP. On IRC, you +talk directly to one server-user: toto on the channel #foo is the same user +than toto on the channel #bar (as long as these two channels are on the same +IRC server). Using biboumi, there is no way to receive a message from a +room participant (from a jid like \fI#test%irc.example.com/\fBnickname\fR). +Instead, private messages are received from and sent to the user (using a +jid like \fBnickname\fI%irc.example.com\fR). For conveniance and +compatibility with XMPP clients sending private messages to the MUC +participants, a message sent to +\fB#chan%irc.example.com@irc.example.net/Nickname\fR will be redirected to +\fBNickname%irc.example.com@irc.example.net\fR, although this is not the +prefered way to do it. +. +.SS "Notices" +. +Notices are received exactly like private messages. It is not possible to +send a notice. +. +.SS "Kicks and bans" +. +Kicks are transparently translated from one protocol to another. However +banning an XMPP participant has no effect. To ban an user you need to set a +mode +b on that user nick or host (see \fBMODES\fR) and then kick it. +. +.SS "Encoding" +. +On XMPP, the encoding is always \fIUTF-8\fR, whereas on IRC the encoding of +each message can be anything. +. +This means that biboumi has to convert everything coming from IRC into UTF-8 +without knowing the encoding of the received messages. To do so, it checks +if each message is UTF-8 valid, if not it tries to convert from +\fIiso_8859-1\fR (because this appears to be the most common case, at least +on the channels I visit) to \fIUTF-8\fR. If that conversion fails at some +point, a placeholder character '\f�\fR' is inserted to indicate this +decoding error. +. +Messages are always sent in UTF-8 over IRC, no conversion is done in that +direction. +. +.SS "IRC modes" +. +One feature that doesn’t exist on XMPP but does on IRC is the \fImodes\fR. +Although some of these modes have a correspondance in the XMPP world (for +example the \fI+o\fR mode on an user corresponds to the \fImoderator\fR role +in XMPP), it is impossible to map all these modes to an XMPP feature. To +circumvent this problem, biboumi provides a raw notification when modes are +changed, and lets the user change the modes directly. +. +To change modes, simply send a message starting with “\fB/mode\fR” followed +by the modes and the arguments you want to send to the IRC server. For +example “/mode +aho louiz”. Note that your XMPP client may +inteprete messages begining with “/” like a command. To actually send a +message starting with a slash, you may need to start your message with +“//mode” or “/say /mode”, depending on your client. +. +When a mode is changed, the user is notified by a message coming from the +MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if +the mode change can be translated to an XMPP feature, the user will be +notified of this XMPP event as well. For example if a mode “+o toto” is +received, then toto’s role will be changed to moderator. The mapping +between IRC modes and XMPP features is as follow: +. +.TP +.B +o +Sets the participant’s role to \fImoderator\fR. +. +.TP +.B +a +Sets the participant’s role to \admin\fR. +. +.TP +.B +v +Sets the participant’s affiliation to \fImember\fR. +. +.SH SECURITY +. +Biboumi does not provide any encryption mechanism: connection to the XMPP +server MUST be made on localhost. The XMPP server is not supposed to accept +non-local connection from components, thus encryption is useless. IRC +SSL/TLS is also not implemented although this could be useful for some +users, this is however not a high priority feature. +. +Biboumi also does not check if JIDs are properly formatted using nodeprep. +This must be done by the XMPP server to which biboumi is directly connected. +. +.SH AUTHORS +. +Written by Florent Le Coz -- cgit v1.2.3 From 61ecaab40078901800e3e20282f1ae7852a7c938 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 Nov 2013 00:55:20 +0100 Subject: Re-add support for /me messages from IRC It was recently removed because it was handled in the old "convert irc colors" code. It now is in the right place. --- src/irc/irc_client.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f339580..72eec02 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -219,7 +219,14 @@ void IrcClient::on_channel_message(const IrcMessage& message) iid.chan = nick; muc = false; } - this->bridge->send_message(iid, nick, body, muc); + if (!body.empty() && body[0] == '\01') + { + if (body.substr(1, 6) == "ACTION") + this->bridge->send_message(iid, nick, + std::string("/me") + body.substr(7, body.size() - 8), muc); + } + else + this->bridge->send_message(iid, nick, body, muc); } void IrcClient::empty_motd(const IrcMessage& message) -- cgit v1.2.3 From 2921f53657a78a73ca0dc8af95219ca27653fe55 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 Nov 2013 01:17:37 +0100 Subject: Print some stuff when ./test is running --- src/test.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index b33ff1d..bed8829 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -15,11 +15,15 @@ #include +static const std::string color(""); +static const std::string reset(""); + int main() { /** * Encoding */ + std::cout << color << "Testing encoding…" << reset << std::endl; const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; assert(utils::is_valid_utf8(valid) == true); const char* invalid = "\xF0\x0F"; @@ -27,7 +31,8 @@ int main() const char* invalid2 = "\xFE\xFE\xFF\xFF"; assert(utils::is_valid_utf8(invalid2) == false); - std::string in = "coucou les copains ♥ "; + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + std::cout << in << std::endl; assert(utils::is_valid_utf8(in.c_str()) == true); std::string res = utils::convert_to_utf8(in, "UTF-8"); assert(utils::is_valid_utf8(res.c_str()) == true && res == in); @@ -43,10 +48,12 @@ int main() // wrong charset) std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); assert(from_ascii == "couc�ou"); + std::cout << from_ascii << std::endl; /** * Utils */ + std::cout << color << "Testing utils…" << reset << std::endl; std::vector splitted = utils::split("a::a", ':', false); assert(splitted.size() == 2); splitted = utils::split("a::a", ':', true); @@ -62,10 +69,12 @@ int main() /** * XML parsing */ + std::cout << color << "Testing XML parsing…" << reset << std::endl; XmppParser xml; const std::string doc = "innertail"; xml.add_stanza_callback([](const Stanza& stanza) { + std::cout << stanza.to_string() << std::endl; assert(stanza.get_name() == "stream_ns:stanza"); assert(stanza["b"] == "c"); assert(stanza.get_inner() == "inner"); @@ -80,6 +89,7 @@ int main() /** * XML escape/escape */ + std::cout << color << "Testing XML escaping…" << reset << std::endl; const std::string unescaped = "'coucou'/&\"gaga\""; assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); assert(xml_unescape(xml_escape(unescaped)) == unescaped); @@ -87,9 +97,14 @@ int main() /** * Colors conversion */ + std::cout << color << "Testing IRC colors conversion…" << reset << std::endl; std::unique_ptr xhtml; std::string cleaned_up; + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); + std::cout << xhtml->to_string() << std::endl; + assert(xhtml && xhtml->to_string() == "bold"); + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" "5red,5default-on-red10,2cyan-on-blue"); @@ -112,16 +127,17 @@ int main() /** * JID parsing */ + std::cout << color << "Testing JID parsing…" << reset << std::endl; // Full JID Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); - std::cerr << jid1.local << " @ " << jid1.domain << " / " << jid1.resource << std::endl; + std::cout << jid1.local << "@" << jid1.domain << "/" << jid1.resource << std::endl; assert(jid1.local == "♥"); assert(jid1.domain == "ツ.coucou"); assert(jid1.resource == "coucou@coucou/coucou"); // Domain and resource Jid jid2("ツ.coucou/coucou@coucou/coucou"); - std::cerr << jid2.local << " @ " << jid2.domain << " / " << jid2.resource << std::endl; + std::cout << jid2.local << "@" << jid2.domain << "/" << jid2.resource << std::endl; assert(jid2.local == ""); assert(jid2.domain == "ツ.coucou"); assert(jid2.resource == "coucou@coucou/coucou"); @@ -129,6 +145,7 @@ int main() /** * Config */ + std::cout << color << "Testing JID parsing…" << reset << std::endl; Config::filename = "test.cfg"; Config::file_must_exist = false; Config::set("coucou", "bonjour"); @@ -147,5 +164,6 @@ int main() error = true; } assert(error == false); + return 0; } -- cgit v1.2.3 From 1151c26c363e736a98c5fcb723c753658fe35b9b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 Nov 2013 02:14:42 +0100 Subject: Channel names are case insensitive But some servers (epiknet for example) send channel names with an uppercase --- src/irc/irc_client.cpp | 13 +++++++------ src/test.cpp | 5 +++++ src/utils/tolower.hpp | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 src/utils/tolower.hpp diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 72eec02..dc0986f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -174,7 +175,7 @@ void IrcClient::forward_server_message(const IrcMessage& message) void IrcClient::set_and_forward_user_list(const IrcMessage& message) { - const std::string chan_name = message.arguments[2]; + const std::string chan_name = utils::tolower(message.arguments[2]); IrcChannel* channel = this->get_channel(chan_name); std::vector nicks = utils::split(message.arguments[3], ' '); for (const std::string& nick: nicks) @@ -190,7 +191,7 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) void IrcClient::on_channel_join(const IrcMessage& message) { - const std::string chan_name = message.arguments[0]; + const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); const std::string nick = message.prefix; if (channel->joined == false) @@ -252,14 +253,14 @@ void IrcClient::send_motd(const IrcMessage& message) void IrcClient::on_topic_received(const IrcMessage& message) { - const std::string chan_name = message.arguments[1]; + const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->topic = message.arguments[2]; } void IrcClient::on_channel_completely_joined(const IrcMessage& message) { - const std::string chan_name = message.arguments[1]; + const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick); this->bridge->send_topic(this->hostname, chan_name, channel->topic); @@ -276,7 +277,7 @@ void IrcClient::on_welcome_message(const IrcMessage& message) void IrcClient::on_part(const IrcMessage& message) { - const std::string chan_name = message.arguments[0]; + const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); std::string txt; if (message.arguments.size() >= 2) @@ -348,7 +349,7 @@ void IrcClient::on_kick(const IrcMessage& message) { const std::string target = message.arguments[1]; const std::string reason = message.arguments[2]; - const std::string chan_name = message.arguments[0]; + const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); if (channel->get_self()->nick == target) channel->joined = false; diff --git a/src/test.cpp b/src/test.cpp index bed8829..234ab2d 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,10 @@ int main() assert(splitted[0] == ""); assert(splitted[1] == "a"); + const std::string lowercase = utils::tolower("CoUcOu LeS CoPaiNs ♥"); + std::cout << lowercase << std::endl; + assert(lowercase == "coucou les copains ♥"); + /** * XML parsing */ diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp new file mode 100644 index 0000000..22d2b8f --- /dev/null +++ b/src/utils/tolower.hpp @@ -0,0 +1,18 @@ +#ifndef TOLOWER_INCLUDED +# define TOLOWER_INCLUDED + +#include + +namespace utils +{ + std::string tolower(const std::string& original) + { + std::string res; + res.reserve(original.size()); + for (const char c: original) + res += static_cast(std::tolower(c)); + return res; + } +} + +#endif // SPLIT_INCLUDED -- cgit v1.2.3 From d6832fbcc98557952387f3ce2899bbebacd7c204 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 Nov 2013 21:09:27 +0100 Subject: :3 --- src/irc/irc_client.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index dc0986f..2d158fb 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -91,7 +91,8 @@ void IrcClient::send_message(IrcMessage&& message) res += std::move(message.command); for (const std::string& arg: message.arguments) { - if (arg.find(" ") != std::string::npos) + if (arg.find(" ") != std::string::npos || + (!arg.empty()) && arg[0] == ':') { res += " :" + arg; break; -- cgit v1.2.3 From 050cf1fd606a04c4734d66e996a52a907001bd12 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Dec 2013 12:39:42 +0100 Subject: xml-escape the tail in an XmlNode --- src/xmpp/xmpp_stanza.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 85bd6b5..5af53e1 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -114,12 +114,12 @@ void XmlNode::set_attribute(const std::string& name, const std::string& value) void XmlNode::set_tail(const std::string& data) { - this->tail = data; + this->tail = xml_escape(data); } void XmlNode::add_to_tail(const std::string& data) { - this->tail += data; + this->tail += xml_escape(data); } void XmlNode::set_inner(const std::string& data) -- cgit v1.2.3 From 6bd176f15ebf146874bc7f4525870e52921cc2fe Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 30 Nov 2013 18:53:45 +0100 Subject: Fix a parenthesis ambiguity --- src/irc/irc_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 2d158fb..0061561 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -92,7 +92,7 @@ void IrcClient::send_message(IrcMessage&& message) for (const std::string& arg: message.arguments) { if (arg.find(" ") != std::string::npos || - (!arg.empty()) && arg[0] == ':') + (!arg.empty() && arg[0] == ':')) { res += " :" + arg; break; -- cgit v1.2.3 From 2662ed89e2cd41477582140e482f1ddbbfdb235e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 3 Dec 2013 18:27:20 +0100 Subject: Add a logger class --- CMakeLists.txt | 15 ++++++++++- src/irc/irc_client.cpp | 1 + src/logger/logger.cpp | 41 +++++++++++++++++++++++++++++ src/logger/logger.hpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/test.cpp | 9 +++++++ 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/logger/logger.cpp create mode 100644 src/logger/logger.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index daa6cf6..51253cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,10 @@ set(${PROJECT_NAME}_VERSION_MINOR 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +# Define a __FILENAME__ macro to get the filename of each file, instead of +# the full path as in __FILE__ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'") + # ## Look for external libraries # @@ -46,6 +50,14 @@ file(GLOB source_config add_library(config STATIC ${source_config}) target_link_libraries(config utils) +# +## logger +# +file(GLOB source_logger + src/logger/*.[hc]pp) +add_library(logger STATIC ${source_logger}) +target_link_libraries(logger config) + # ## network # @@ -99,6 +111,7 @@ target_link_libraries(test irc bridge utils - config) + config + logger) configure_file(config.h.cmake src/config.h) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0061561..4e5efe1 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -4,6 +4,7 @@ #include #include +// #include #include #include diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp new file mode 100644 index 0000000..eac833f --- /dev/null +++ b/src/logger/logger.cpp @@ -0,0 +1,41 @@ +#include +#include +#include + +Logger::Logger(const int log_level): + log_level(log_level), + stream(std::cout.rdbuf()) +{ + std::cout << "Logger(1)" << std::endl; +} + +Logger::Logger(const int log_level, const std::string& log_file): + log_level(log_level), + ofstream(log_file.data(), std::ios_base::app), + stream(ofstream.rdbuf()) +{ + std::cout << "Logger(" << this->log_level << ")" << std::endl; +} + +std::unique_ptr& Logger::instance() +{ + static std::unique_ptr instance; + + if (!instance) + { + const std::string log_file = Config::get("log_file", ""); + const int log_level = Config::get_int("log_level", 0); + if (log_file.empty()) + instance = std::make_unique(log_level); + else + instance = std::make_unique(log_level, log_file); + } + return instance; +} + +std::ostream& Logger::get_stream(const int lvl) +{ + if (lvl >= this->log_level) + return this->stream; + return this->null_stream; +} diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp new file mode 100644 index 0000000..182c517 --- /dev/null +++ b/src/logger/logger.hpp @@ -0,0 +1,70 @@ +#ifndef LOGGER_INCLUDED +# define LOGGER_INCLUDED + +/** + * Singleton used in logger macros to write into files or stdout, with + * various levels of severity. + * Only the macros should be used. + * @class Logger + */ + +#include +#include +#include + +#define debug_lvl 0 +#define info_lvl 1 +#define warning_lvl 2 +#define error_lvl 3 + +// Macro defined to get the filename instead of the full path. But if it is +// not properly defined by the build system, we fallback to __FILE__ +#ifndef __FILENAME__ +# define __FILENAME__ __FILE__ +#endif + +#define WHERE\ + __FILENAME__ << ":" << __LINE__ + +#define log_debug(text)\ + Logger::instance()->get_stream(debug_lvl) << "[DEBUG]:" << WHERE << ":\t" << text << std::endl; + +#define log_info(text)\ + Logger::instance()->get_stream(info_lvl) << "[INFO]:" << WHERE << ":\t" << text << std::endl; + +#define log_warning(text)\ + Logger::instance()->get_stream(warning_lvl) << "[WARNING]:" << WHERE << ":\t" << text << std::endl; + +#define log_error(text)\ + Logger::instance()->get_stream(error_lvl) << "[ERROR]:" << WHERE << ":\t" << text << std::endl; + +/** + * Juste a structure representing a stream doing nothing with its input. + */ +class nullstream: public std::ostream +{ +public: + nullstream(): + std::ostream(0) + { } +}; + +class Logger +{ +public: + static std::unique_ptr& instance(); + std::ostream& get_stream(const int); + Logger(const int log_level, const std::string& log_file); + Logger(const int log_level); + +private: + Logger(const Logger&); + Logger& operator=(const Logger&); + + const int log_level; + std::ofstream ofstream; + nullstream null_stream; + std::ostream stream; +}; + +#endif // LOGGER_INCLUDED diff --git a/src/test.cpp b/src/test.cpp index 234ab2d..ac1a85d 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -170,5 +171,13 @@ int main() } assert(error == false); + Config::set("log_level", "3"); + Config::set("log_file", ""); + + log_debug("coucou"); + log_info("coucou"); + log_warning("coucou"); + log_error("coucou"); + return 0; } -- cgit v1.2.3 From a4c845ab6c54172ea305f33734c83238c75d421a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 3 Dec 2013 19:10:44 +0100 Subject: Use the logger everywhere --- CMakeLists.txt | 7 ++++--- src/bridge/bridge.cpp | 6 +++--- src/irc/irc_client.cpp | 17 +++++++---------- src/irc/irc_user.cpp | 1 - src/logger/logger.cpp | 2 -- src/network/poller.cpp | 2 -- src/network/socket_handler.cpp | 10 +++++----- src/xmpp/xmpp_component.cpp | 27 ++++++++++++--------------- src/xmpp/xmpp_stanza.cpp | 5 ----- src/xmpp/xmpp_stanza.hpp | 1 - 10 files changed, 31 insertions(+), 47 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51253cc..ea51db7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ target_link_libraries(logger config) file(GLOB source_network src/network/*.[hc]pp) add_library(network STATIC ${source_network}) +target_link_libraries(network logger) # ## irclib @@ -71,7 +72,7 @@ add_library(network STATIC ${source_network}) file(GLOB source_irc src/irc/*.[hc]pp) add_library(irc STATIC ${source_irc}) -target_link_libraries(irc network utils) +target_link_libraries(irc network utils logger) # ## xmpplib @@ -79,7 +80,7 @@ target_link_libraries(irc network utils) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp bridge network utils +target_link_libraries(xmpp bridge network utils logger ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES} pthread) # @@ -88,7 +89,7 @@ target_link_libraries(xmpp bridge network utils file(GLOB source_bridge src/bridge/*.[hc]pp) add_library(bridge STATIC ${source_bridge}) -target_link_libraries(bridge xmpp irc utils) +target_link_libraries(bridge xmpp irc utils logger) # ## Main executable diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7e6f801..2ad6af8 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -3,7 +3,7 @@ #include #include #include - +#include #include #include @@ -78,13 +78,13 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) const std::string first_line = lines[0]; if (iid.chan.empty() || iid.server.empty()) { - std::cout << "Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]" << std::endl; + log_warning("Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]"); return; } IrcClient* irc = this->get_irc_client(iid.server); if (!irc) { - std::cout << "Cannot send message: no client exist for server " << iid.server << std::endl; + log_warning("Cannot send message: no client exist for server " << iid.server); return; } if (first_line.substr(0, 6) == "/mode ") diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4e5efe1..1a9e61b 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -4,7 +4,7 @@ #include #include -// #include +#include #include #include @@ -18,12 +18,10 @@ IrcClient::IrcClient(const std::string& hostname, const std::string& username, B bridge(bridge), welcomed(false) { - std::cout << "IrcClient()" << std::endl; } IrcClient::~IrcClient() { - std::cout << "~IrcClient()" << std::endl; } void IrcClient::start() @@ -39,7 +37,7 @@ void IrcClient::on_connected() void IrcClient::on_connection_close() { - std::cout << "Connection closed by remote server." << std::endl; + log_warning("Connection closed by remote server."); } IrcChannel* IrcClient::get_channel(const std::string& name) @@ -74,18 +72,19 @@ void IrcClient::parse_in_buffer() if (pos == std::string::npos) break ; IrcMessage message(this->in_buf.substr(0, pos)); + log_debug("IRC RECEIVING: " << message); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); - std::cout << message << std::endl; auto cb = irc_callbacks.find(message.command); if (cb != irc_callbacks.end()) (this->*(cb->second))(message); else - std::cout << "No handler for command " << message.command << std::endl; + log_info("No handler for command " << message.command); } } void IrcClient::send_message(IrcMessage&& message) { + log_debug("IRC SENDING: " << message); std::string res; if (!message.prefix.empty()) res += ":" + std::move(message.prefix) + " "; @@ -101,8 +100,6 @@ void IrcClient::send_message(IrcMessage&& message) res += " " + arg; } res += "\r\n"; - std::cout << "=== IRC SENDING ===" << std::endl; - std::cout << res << std::endl; this->send_data(std::move(res)); } @@ -134,7 +131,7 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == false) { - std::cout << "Cannot send message to channel " << chan_name << ", it is not joined" << std::endl; + log_warning("Cannot send message to channel " << chan_name << ", it is not joined"); return false; } this->send_message(IrcMessage("PRIVMSG", {chan_name, body})); @@ -185,7 +182,7 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) IrcUser* user = channel->add_user(nick); if (user->nick != channel->get_self()->nick) { - std::cout << "Adding user [" << nick << "] to chan " << chan_name << std::endl; + log_debug("Adding user [" << nick << "] to chan " << chan_name); this->bridge->send_user_join(this->hostname, chan_name, user->nick); } } diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index afe8623..f9866ef 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -24,5 +24,4 @@ IrcUser::IrcUser(const std::string& name) this->nick = name.substr(0, sep); this->host = name.substr(sep+1); } - std::cout << "Created user: [" << this->nick << "!" << this->host << std::endl; } diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp index eac833f..22d83bd 100644 --- a/src/logger/logger.cpp +++ b/src/logger/logger.cpp @@ -6,7 +6,6 @@ Logger::Logger(const int log_level): log_level(log_level), stream(std::cout.rdbuf()) { - std::cout << "Logger(1)" << std::endl; } Logger::Logger(const int log_level, const std::string& log_file): @@ -14,7 +13,6 @@ Logger::Logger(const int log_level, const std::string& log_file): ofstream(log_file.data(), std::ios_base::app), stream(ofstream.rdbuf()) { - std::cout << "Logger(" << this->log_level << ")" << std::endl; } std::unique_ptr& Logger::instance() diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 023fb12..71c7172 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -8,7 +8,6 @@ Poller::Poller() { - std::cout << "Poller()" << std::endl; #if POLLER == POLL memset(this->fds, 0, sizeof(this->fds)); this->nfds = 0; @@ -24,7 +23,6 @@ Poller::Poller() Poller::~Poller() { - std::cout << "~Poller()" << std::endl; } void Poller::add_socket_handler(std::shared_ptr socket_handler) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index d75b505..246d8d3 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -21,7 +22,7 @@ SocketHandler::SocketHandler(): void SocketHandler::connect(const std::string& address, const std::string& port) { - std::cout << "Trying to connect to " << address << ":" << port << std::endl; + log_info("Trying to connect to " << address << ":" << port); struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = 0; @@ -42,17 +43,16 @@ void SocketHandler::connect(const std::string& address, const std::string& port) } for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) { - std::cout << "One result" << std::endl; if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) { - std::cout << "Connection success." << std::endl; + log_info("Connection success."); this->on_connected(); return ; } - std::cout << "Connection failed:" << std::endl; + log_info("Connection failed:"); perror("connect"); } - std::cout << "All connection attempts failed." << std::endl; + log_error("All connection attempts failed."); this->close(); } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9245fde..5e65595 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -52,14 +53,14 @@ void XmppComponent::start() void XmppComponent::send_stanza(const Stanza& stanza) { - std::cout << "====== Sending ========" << std::endl; - std::cout << stanza.to_string() << std::endl; - this->send_data(stanza.to_string()); + std::string str = stanza.to_string(); + log_debug("XMPP SENDING: " << str); + this->send_data(std::move(str)); } void XmppComponent::on_connected() { - std::cout << "connected to XMPP server" << std::endl; + log_info("connected to XMPP server"); XmlNode node("stream:stream", nullptr); node["xmlns"] = COMPONENT_NS; node["xmlns:stream"] = STREAM_NS; @@ -69,7 +70,7 @@ void XmppComponent::on_connected() void XmppComponent::on_connection_close() { - std::cout << "XMPP server closed connection" << std::endl; + log_info("XMPP server closed connection"); } void XmppComponent::parse_in_buffer() @@ -80,15 +81,14 @@ void XmppComponent::parse_in_buffer() void XmppComponent::on_remote_stream_open(const XmlNode& node) { - std::cout << "====== DOCUMENT_OPEN =======" << std::endl; - std::cout << node.to_string() << std::endl; + log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); try { this->stream_id = node["id"]; } catch (const AttributeNotFound& e) { - std::cout << "Error: no attribute 'id' found" << std::endl; + log_error("Error: no attribute 'id' found"); this->send_stream_error("bad-format", "missing 'id' attribute"); this->close_document(); return ; @@ -109,14 +109,12 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) void XmppComponent::on_remote_stream_close(const XmlNode& node) { - std::cout << "====== DOCUMENT_CLOSE =======" << std::endl; - std::cout << node.to_string() << std::endl; + log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); } void XmppComponent::on_stanza(const Stanza& stanza) { - std::cout << "=========== STANZA ============" << std::endl; - std::cout << stanza.to_string() << std::endl; + log_debug("XMPP RECEIVING: " << stanza.to_string()); std::function handler; try { @@ -124,7 +122,7 @@ void XmppComponent::on_stanza(const Stanza& stanza) } catch (const std::out_of_range& exception) { - std::cout << "No handler for stanza of type " << stanza.get_name() << std::endl; + log_warning("No handler for stanza of type " << stanza.get_name()); return; } handler(stanza); @@ -145,8 +143,7 @@ void XmppComponent::send_stream_error(const std::string& name, const std::string void XmppComponent::close_document() { - std::cout << "====== Sending ========" << std::endl; - std::cout << "" << std::endl; + log_debug("XMPP SENDING: "); this->send_data(""); } diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 5af53e1..34ba85f 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -209,11 +209,6 @@ std::string XmlNode::to_string() const return res; } -void XmlNode::display() const -{ - std::cout << this->to_string() << std::endl; -} - bool XmlNode::has_children() const { return !this->children.empty(); diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 87a80e9..d9bf81d 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -97,7 +97,6 @@ public: * Serialize the stanza into a string */ std::string to_string() const; - void display() const; /** * Whether or not this node has at least one child (if not, this is a leaf * node) -- cgit v1.2.3 From 6fa548a194082648724078cc777da34c30dd40a1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 3 Dec 2013 19:56:57 +0100 Subject: Display all the MODE arguments in the message --- src/irc/irc_client.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1a9e61b..af3df62 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -377,10 +377,18 @@ void IrcClient::on_channel_mode(const IrcMessage& message) iid.chan = message.arguments[0]; iid.server = this->hostname; IrcUser user(message.prefix); + std::string mode_arguments; + for (size_t i = 1; i < message.arguments.size(); ++i) + { + if (!message.arguments[i].empty()) + { + if (i != 1) + mode_arguments += " "; + mode_arguments += message.arguments[i]; + } + } this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan + - " [" + message.arguments[1] + - (message.arguments.size() > 2 ? (" " + message.arguments[2]): "") - + "] by " + user.nick, + " [" + mode_arguments + "] by " + user.nick, true); } -- cgit v1.2.3 From e2117bcb0abfde30ad503b99da58699cf0f2a95b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 04:44:46 +0100 Subject: Enforce a simple limit of 400 bytes for IRC messages body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The limit for the whole message is 512 bytes, we limit the body to 400 (instead of doing a calculation based on the command name and the other parameters), because it's simple, easy and that’s enough. fixes #2416 --- src/irc/irc_client.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index af3df62..0d4d102 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -134,13 +134,27 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st log_warning("Cannot send message to channel " << chan_name << ", it is not joined"); return false; } - this->send_message(IrcMessage("PRIVMSG", {chan_name, body})); + // Cut the message body into 400-bytes parts (because the whole command + // must fit into 512 bytes, that's an easy way to make sure the chan name + // + body fits. I’m lazy.) + std::string::size_type pos = 0; + while (pos < body.size()) + { + this->send_message(IrcMessage("PRIVMSG", {chan_name, body.substr(pos, 400)})); + pos += 400; + } return true; } void IrcClient::send_private_message(const std::string& username, const std::string& body) { - this->send_message(IrcMessage("PRIVMSG", {username, body})); + std::string::size_type pos = 0; + while (pos < body.size()) + { + this->send_message(IrcMessage("PRIVMSG", {username, body.substr(pos, 400)})); + pos += 400; + } + } void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) -- cgit v1.2.3 From 2c9680bc0392a33d32b90723228ec60753070a9f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 15:31:20 +0100 Subject: Rewrite the FindCryptopp cmake module cleanly --- CMakeLists.txt | 2 +- cmake/Modules/FindCryptopp.cmake | 84 ++++++++++++++++++++++++---------------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea51db7..81ecaaf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIR}) # the SYSTEM flag tells the compiler that we don't care about warnings # coming from these headers. -include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIR}) +include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIRS}) set(POLLER "POLL" CACHE STRING "Choose the poller between POLL and EPOLL (Linux-only)") diff --git a/cmake/Modules/FindCryptopp.cmake b/cmake/Modules/FindCryptopp.cmake index 7a8ac31..9835b6f 100644 --- a/cmake/Modules/FindCryptopp.cmake +++ b/cmake/Modules/FindCryptopp.cmake @@ -1,35 +1,53 @@ # - Find Crypto++ +# Find the Crypto++ library +# +# This module defines the following variables: +# CRYPTO++_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# CRYPTO++_LIBRARIES - Where to find the library file +# CRYPTO++_INCLUDE_DIRS - The directory where to find the header files +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to way to write them. +# +# CRYPTOPP_FOUND +# +# CRYPTO++_LIBRARY +# CRYPTOPP_LIBRARY +# CRYPTOPP_LIBRARIES +# +# CRYPTO++_INCLUDE_DIR +# CRYPTOPP_INCLUDE_DIRS +# CRYPTOPP_INCLUDE_DIR +# +# This file is in the public domain. + +find_path(CRYPTO++_INCLUDE_DIRS NAMES cryptlib.h + PATH_SUFFIXES "crypto++" "cryptopp" + DOC "The Crypto++ include directory") + +find_library(CRYPTO++_LIBRARIES NAMES cryptopp + DOC "The Crypto++ library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set CRYPTO++_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Crypto++ REQUIRED_VARS CRYPTO++_LIBRARIES CRYPTO++_INCLUDE_DIRS) + +# Compatibility for all the ways of writing these variables +if(CRYPTO++_FOUND) + set(CRYPTOPP_FOUND ${CRYPTO++_FOUND}) + + set(CRYPTO++_LIBRARY ${CRYPTO++_LIBRARIES}) + set(CRYPTOPP_LIBRARY ${CRYPTO++_LIBRARIES}) + set(CRYPTOPP_LIBRARIES ${CRYPTO++_LIBRARIES}) + + set(CRYPTO++_INCLUDE_DIR ${CRYPTO++_INCLUDE_DIRS}) + set(CRYPTOPP_INCLUDE_DIR ${CRYPTO++_INCLUDE_DIRS}) + set(CRYPTOPP_INCLUDE_DIRS ${CRYPTO++_INCLUDE_DIRS}) +endif() + +mark_as_advanced(CRYPTO++_INCLUDE_DIRS CRYPTO++_LIBRARIES) + -if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND TRUE) - -else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - find_path(CRYPTO++_INCLUDE_DIR cryptlib.h - /usr/include/crypto++ - /usr/include/cryptopp - /usr/local/include/crypto++ - /usr/local/include/cryptopp - /opt/local/include/crypto++ - /opt/local/include/cryptopp - $ENV{SystemDrive}/Crypto++/include - ) - - find_library(CRYPTO++_LIBRARIES NAMES cryptopp - PATHS - /usr/lib - /usr/local/lib - /opt/local/lib - $ENV{SystemDrive}/Crypto++/lib - ) - - if(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND TRUE) - message(STATUS "Found Crypto++: ${CRYPTO++_INCLUDE_DIR}, ${CRYPTO++_LIBRARIES}") - else(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - set(CRYPTO++_FOUND FALSE) - message(STATUS "Crypto++ not found.") - endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) - - mark_as_advanced(CRYPTO++_INCLUDE_DIR CRYPTO++_LIBRARIES) - -endif(CRYPTO++_INCLUDE_DIR AND CRYPTO++_LIBRARIES) -- cgit v1.2.3 From fb01f78b0ac840387613bf671e980cead27f8fc0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 15:32:14 +0100 Subject: Rewrite the FindIconv module cleanly --- CMakeLists.txt | 2 +- cmake/Modules/FindIconv.cmake | 78 +++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ecaaf..3011fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ find_package(EXPAT REQUIRED) include_directories("src/") include_directories(${EXPAT_INCLUDE_DIRS}) -include_directories(${ICONV_INCLUDE_DIR}) +include_directories(${ICONV_INCLUDE_DIRS}) # the SYSTEM flag tells the compiler that we don't care about warnings # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIRS}) diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake index c957af6..ac81a2c 100644 --- a/cmake/Modules/FindIconv.cmake +++ b/cmake/Modules/FindIconv.cmake @@ -1,29 +1,40 @@ -# - Try to find Iconv -# Once done this will define +# - Find iconv +# Find the iconv (character set conversion) library # -# ICONV_FOUND - system has Iconv -# ICONV_INCLUDE_DIR - the Iconv include directory -# ICONV_LIBRARIES - Link these to use Iconv -# ICONV_SECOND_ARGUMENT_IS_CONST - the second argument for iconv() is const +# This module defines the following variables: +# ICONV_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# ICONV_INCLUDE_DIRS - The directory where to find the header file +# ICONV_LIBRARIES - Where to find the library file +# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const # -include(CheckCXXSourceCompiles) - -IF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - # Already in cache, be silent - SET(ICONV_FIND_QUIETLY TRUE) -ENDIF (ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to way to write them. +# ICONV_LIBRARY +# ICONV_INCLUDE_DIR +# +# This file is in the public domain -FIND_PATH(ICONV_INCLUDE_DIR iconv.h) +find_path(ICONV_INCLUDE_DIRS NAMES iconv.h + DOC "The iconv include directory") -FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) +find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c + DOC "The iconv library") -IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) - SET(ICONV_FOUND TRUE) -ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARIES) +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set ICONV_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS) -set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) -set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) -IF(ICONV_FOUND) +# Check if the prototype is +# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +# or +# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +if(ICONV_FOUND) + include(CheckCXXSourceCompiles) check_cxx_source_compiles(" #include int main(){ @@ -33,25 +44,12 @@ IF(ICONV_FOUND) char* out = 0; size_t olen = 0; iconv(conv, &in, &ilen, &out, &olen); - return 0; - } -" ICONV_SECOND_ARGUMENT_IS_CONST ) -ENDIF(ICONV_FOUND) -set(CMAKE_REQUIRED_INCLUDES) -set(CMAKE_REQUIRED_LIBRARIES) + return 0;}" + ICONV_SECOND_ARGUMENT_IS_CONST) -IF(ICONV_FOUND) - IF(NOT ICONV_FIND_QUIETLY) - MESSAGE(STATUS "Found Iconv: ${ICONV_LIBRARIES}") - ENDIF(NOT ICONV_FIND_QUIETLY) -ELSE(ICONV_FOUND) - IF(Iconv_FIND_REQUIRED) - MESSAGE(FATAL_ERROR "Could not find Iconv") - ENDIF(Iconv_FIND_REQUIRED) -ENDIF(ICONV_FOUND) +# Compatibility for all the ways of writing these variables + set(ICONV_LIBRARY ${ICONV_LIBRARIES}) + set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS}) +endif() -MARK_AS_ADVANCED( - ICONV_INCLUDE_DIR - ICONV_LIBRARIES - ICONV_SECOND_ARGUMENT_IS_CONST -) \ No newline at end of file +mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST) \ No newline at end of file -- cgit v1.2.3 From cb718def0cb51aac4c2125e3864522740fcb2573 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 18:02:24 +0100 Subject: Put utils::tolower definition in its own cpp file --- CMakeLists.txt | 1 + src/utils/tolower.cpp | 13 +++++++++++++ src/utils/tolower.hpp | 9 +-------- 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 src/utils/tolower.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3011fd3..8a8ae9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,7 @@ if((NOT ${POLLER} MATCHES "POLL") AND (NOT ${POLLER} MATCHES "EPOLL")) message(FATAL_ERROR "POLLER must be either POLL or EPOLL") endif() + # ## utils # diff --git a/src/utils/tolower.cpp b/src/utils/tolower.cpp new file mode 100644 index 0000000..3e518bd --- /dev/null +++ b/src/utils/tolower.cpp @@ -0,0 +1,13 @@ +#include + +namespace utils +{ + std::string tolower(const std::string& original) + { + std::string res; + res.reserve(original.size()); + for (const char c: original) + res += static_cast(std::tolower(c)); + return res; + } +} diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp index 22d2b8f..0019182 100644 --- a/src/utils/tolower.hpp +++ b/src/utils/tolower.hpp @@ -5,14 +5,7 @@ namespace utils { - std::string tolower(const std::string& original) - { - std::string res; - res.reserve(original.size()); - for (const char c: original) - res += static_cast(std::tolower(c)); - return res; - } + std::string tolower(const std::string& original); } #endif // SPLIT_INCLUDED -- cgit v1.2.3 From b11126a19dbaadf4c32fb8dbec22754ad0712c26 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 20:14:12 +0100 Subject: Provide a JID for IRC users, and add a stringprep dependency for this --- CMakeLists.txt | 11 ++++++++++ cmake/Modules/FindLibidn.cmake | 36 +++++++++++++++++++++++++++++++++ config.h.cmake | 1 + src/bridge/bridge.cpp | 6 ++++-- src/bridge/bridge.hpp | 5 ++++- src/irc/irc_client.cpp | 8 ++++---- src/irc/irc_message.cpp | 2 +- src/test.cpp | 5 +++++ src/xmpp/jid.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++ src/xmpp/jid.hpp | 10 +++++++++ src/xmpp/xmpp_component.cpp | 13 ++++++++++-- src/xmpp/xmpp_component.hpp | 5 ++++- 12 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 cmake/Modules/FindLibidn.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a8ae9c..ac93ff1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ find_package(Cryptopp REQUIRED) find_package(Iconv REQUIRED) include(FindEXPAT) find_package(EXPAT REQUIRED) +find_package(Libidn) include_directories("src/") include_directories(${EXPAT_INCLUDE_DIRS}) @@ -28,6 +29,12 @@ include_directories(${ICONV_INCLUDE_DIRS}) # coming from these headers. include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIRS}) +if(LIBIDN_FOUND) + include_directories(${LIBIDN_INCLUDE_DIRS}) +else() + message("Building without stringprep support.") +endif() + set(POLLER "POLL" CACHE STRING "Choose the poller between POLL and EPOLL (Linux-only)") if((NOT ${POLLER} MATCHES "POLL") AND @@ -35,6 +42,7 @@ if((NOT ${POLLER} MATCHES "POLL") AND message(FATAL_ERROR "POLLER must be either POLL or EPOLL") endif() + # ## utils # @@ -83,6 +91,9 @@ file(GLOB source_xmpp add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp bridge network utils logger ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES} pthread) +if(LIBIDN_FOUND) + target_link_libraries(xmpp ${LIBIDN_LIBRARIES}) +endif() # ## bridge diff --git a/cmake/Modules/FindLibidn.cmake b/cmake/Modules/FindLibidn.cmake new file mode 100644 index 0000000..4c0b2c5 --- /dev/null +++ b/cmake/Modules/FindLibidn.cmake @@ -0,0 +1,36 @@ +# - Find libidn +# Find the libidn library, and more particularly the stringprep header. +# +# This module defines the following variables: +# LIBIDN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBIDN_INCLUDE_DIRS - The directory where to find the header file +# LIBIDN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to way to write them. +# LIBIDN_INCLUDE_DIR +# LIBIDN_LIBRARY +# +# This file is in the public domain + +find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h + DOC "The libidn include directory") + +# The library containing the stringprep module is libidn +find_library(LIBIDN_LIBRARIES NAMES idn + DOC "The libidn library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set LIBIDN_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libidn REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) + +# Compatibility for all the ways of writing these variables +if(LIBIDN_FOUND) + set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) + set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) +endif() + +mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/config.h.cmake b/config.h.cmake index e17cc80..8ee0fd3 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,2 +1,3 @@ #cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST +#cmakedefine LIBIDN_FOUND #cmakedefine POLLER ${POLLER} diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 2ad6af8..973e095 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -167,9 +167,11 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid); } -void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick) +void Bridge::send_user_join(const std::string& hostname, + const std::string& chan_name, + const IrcUser* user) { - this->xmpp->send_user_join(chan_name + "%" + hostname, nick, this->user_jid); + this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host, this->user_jid); } void Bridge::send_self_join(const std::string& hostname, const std::string& chan_name, const std::string nick) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1443191..bbbca95 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -56,7 +57,9 @@ public: /** * Send the presence of a new user in the MUC. */ - void send_user_join(const std::string& hostname, const std::string& chan_name, const std::string nick); + void send_user_join(const std::string& hostname, + const std::string& chan_name, + const IrcUser* user); /** * Send the self presence of an user when the MUC is fully joined. */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0d4d102..0f29a2b 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -193,11 +193,11 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) std::vector nicks = utils::split(message.arguments[3], ' '); for (const std::string& nick: nicks) { - IrcUser* user = channel->add_user(nick); + const IrcUser* user = channel->add_user(nick); if (user->nick != channel->get_self()->nick) { log_debug("Adding user [" << nick << "] to chan " << chan_name); - this->bridge->send_user_join(this->hostname, chan_name, user->nick); + this->bridge->send_user_join(this->hostname, chan_name, user); } } } @@ -214,8 +214,8 @@ void IrcClient::on_channel_join(const IrcMessage& message) } else { - IrcUser* user = channel->add_user(nick); - this->bridge->send_user_join(this->hostname, chan_name, user->nick); + const IrcUser* user = channel->add_user(nick); + this->bridge->send_user_join(this->hostname, chan_name, user); } } diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp index 18fa3ec..eb3eb47 100644 --- a/src/irc/irc_message.cpp +++ b/src/irc/irc_message.cpp @@ -9,7 +9,7 @@ IrcMessage::IrcMessage(std::string&& line) if (line[0] == ':') { pos = line.find(" "); - this->prefix = line.substr(1, pos); + this->prefix = line.substr(1, pos - 1); line = line.substr(pos + 1, std::string::npos); } // command diff --git a/src/test.cpp b/src/test.cpp index ac1a85d..a8c0276 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -148,6 +148,11 @@ int main() assert(jid2.domain == "ツ.coucou"); assert(jid2.resource == "coucou@coucou/coucou"); + // Nodeprep + const std::string& badjid("~louiz@EpiK-7D9D1FDE.poez.io"); + const std::string correctjid = jidprep(badjid); + assert(correctjid == "~louiz@epik-7d9d1fde.poez.io"); + /** * Config */ diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index 29a5302..4f9917b 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -1,4 +1,12 @@ #include +#include +#include + +#ifdef LIBIDN_FOUND + #include +#endif + +#include Jid::Jid(const std::string& jid) { @@ -19,3 +27,41 @@ Jid::Jid(const std::string& jid) this->domain = jid.substr(at, slash - at); } + +#include + +static constexpr size_t max_jid_part_len = 1023; + +std::string jidprep(const std::string& original) +{ +#ifdef LIBIDN_FOUND + // TODO: cache the result + const std::string error_msg("Failed to convert " + original + " into a valid JID:"); + Jid jid(original); + + char local[max_jid_part_len] = {}; + memcpy(local, jid.local.data(), jid.local.size()); + Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, + static_cast(0), stringprep_xmpp_nodeprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + + char domain[max_jid_part_len] = {}; + memcpy(domain, jid.domain.data(), jid.domain.size()); + rc = static_cast(::stringprep(domain, max_jid_part_len, + static_cast(0), stringprep_nameprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + + return std::string(local) + "@" + domain; +#else + (void)original; + return ""; +#endif +} diff --git a/src/xmpp/jid.hpp b/src/xmpp/jid.hpp index 3027497..b6975a2 100644 --- a/src/xmpp/jid.hpp +++ b/src/xmpp/jid.hpp @@ -22,5 +22,15 @@ private: Jid& operator=(Jid&&) = delete; }; +/** + * Prepare the given UTF-8 string according to the XMPP node stringprep + * identifier profile. This is used to send properly-formed JID to the XMPP + * server. + * + * If the stringprep library is not found, we return an empty string. When + * this function is used, the result must always be checked for an empty + * value, and if this is the case it must not be used as a JID. + */ +std::string jidprep(const std::string& original); #endif // JID_INCLUDED diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 5e65595..6424d74 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -267,7 +267,10 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con this->send_stanza(node); } -void XmppComponent::send_user_join(const std::string& from, const std::string& nick, const std::string& to) +void XmppComponent::send_user_join(const std::string& from, + const std::string& nick, + const std::string& realjid, + const std::string& to) { XmlNode node("presence"); node["to"] = to; @@ -280,6 +283,12 @@ void XmppComponent::send_user_join(const std::string& from, const std::string& n XmlNode item("item"); item["affiliation"] = "member"; item["role"] = "participant"; + if (!realjid.empty()) + { + const std::string preped_jid = jidprep(realjid); + if (!preped_jid.empty()) + item["jid"] = preped_jid; + } item.close(); x.add_child(std::move(item)); x.close(); @@ -408,7 +417,7 @@ void XmppComponent::send_nick_change(const std::string& muc_name, const std::str if (self) this->send_self_join(muc_name, new_nick, jid_to); else - this->send_user_join(muc_name, new_nick, jid_to); + this->send_user_join(muc_name, new_nick, "", jid_to); } void XmppComponent::kick_user(const std::string& muc_name, diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index bf85536..15f8f2f 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -62,7 +62,10 @@ public: /** * Send a join from a new participant */ - void send_user_join(const std::string& from, const std::string& nick, const std::string& to); + void send_user_join(const std::string& from, + const std::string& nick, + const std::string& realjid, + const std::string& to); /** * Send the self join to the user */ -- cgit v1.2.3 From 8e2dd573bb01f1caa66674a58c2917592256b41f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 20:23:35 +0100 Subject: Provide the doc in markdown format The included man page is now generated from this markdown file, using ronn --- doc/biboumi.1 | 219 +++++++++++++++++++-------------------------------------- doc/biboumi.md | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+), 145 deletions(-) create mode 100644 doc/biboumi.md diff --git a/doc/biboumi.1 b/doc/biboumi.1 index c874062..5f88a16 100644 --- a/doc/biboumi.1 +++ b/doc/biboumi.1 @@ -1,192 +1,121 @@ -.TH biboumi 1 2013-11-21 +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.SH NAME +.TH "BIBOUMI" "" "December 2013" "" "" . -\fBbiboumi\fR - XMPP gateway to IRC +.SH "NAME" +Bbiboumi \- XMPP gateway to IRC . -.SH SYNOPSIS +.SH "SYNOPSIS" +\fBbiboumi\fR [\fBconfig_file\fR] . -\fBbiboumi\fR [\fIconfig_filename\fR] -. -.SH DESCRIPTION -. -Biboumi is an XMPP gateway that connects to IRC servers and translates -between the two protocols. It can be used to access IRC channels using any -XMPP client as if these channels were XMPP MUCs. -. -.SH OPTIONS +.SH "DESCRIPTION" +Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols\. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs\. . +.SH "OPTIONS" Available options: -.TP +. +.P \fBconfig_filename\fR -Specify the file to read for configuration. See \fBCONFIG\fR section for -more details on its content. . -.SH CONFIG +.P +Specify the file to read for configuration\. See \fICONFIG\fR section for more details on its content\. +. +.SH "CONFIG" +The configuration file uses a simple format of the form \fB"option=value"\fR\. Here is a description of each possible option: . -The configuration file uses a simple format of the form -\fB"option=value"\fR. Here is a description of each possible option: -.TP +.P \fBhostname\fR (mandatory) -The hostname served by the XMPP gateway. This domain must be configured in -the XMPP server as an external component. See the manual for your XMPP -server for more information. -.TP +. +.P +The hostname served by the XMPP gateway\. This domain must be configured in the XMPP server as an external component\. See the manual for your XMPP server for more information\. +. +.P \fBpassword\fR (mandatory) -The password used to authenticate the XMPP component to your XMPP server. -This password must be configured in the XMPP server, associated with the -external component on \fBhostname\fR. -. -.SH USAGE -. -When started, biboumi connects, without encryption (see \fBSECURITY\fR), to -the local XMPP server on the port \fI5347\fR and provides the configured -password to authenticate. Biboumi then serves the configured -\fIhostname\fR, this means that all XMPP stanza with a \fIto\fR JID on that -domain will be sent to biboumi, and biboumi will send only send messages -coming from this hostname. -. -When an user joins an IRC channel on an IRC server (see \fBJoin an IRC -channel\fR), biboumi connects to the remote IRC server, sets the user’s nick -as requested, and then tries to join the specified channel. If the same -user subsequently tries to connect to an other channel on the same server, -the same IRC connection is used. If, however, an other user wants to join -an IRC channel on that same IRC server, biboumi opens a new connection to -that server. Biboumi connects once to each IRC server, for each user on it. . -.SS "Addressing" +.P +The password used to authenticate the XMPP component to your XMPP server\. This password must be configured in the XMPP server, associated with the external component on \fIhostname\fR\. +. +.SH "USAGE" +When started, biboumi connects, without encryption (see \fISECURITY\fR), to the local XMPP server on the port \fB5347\fR and provides the configured password to authenticate\. Biboumi then serves the configured \fBhostname\fR, this means that all XMPP stanza with a \fBto\fR JID on that domain will be sent to biboumi, and biboumi will send only send messages coming from this hostname\. . -IRC entities are represented by XMPP JIDs. The domain part of the JID is -the domain served by biboumi, and the local part depends on the concerned -entity. +.P +When an user joins an IRC channel on an IRC server (see \fIJoin an IRC channel\fR), biboumi connects to the remote IRC server, sets the user’s nick as requested, and then tries to join the specified channel\. If the same user subsequently tries to connect to an other channel on the same server, the same IRC connection is used\. If, however, an other user wants to join an IRC channel on that same IRC server, biboumi opens a new connection to that server\. Biboumi connects once to each IRC server, for each user on it\. . -IRC channels and IRC users JIDs have a localpart formed like this: \fIname\fR, -the '\fI%\fR' separator and the \fIirc_server\fR. +.SS "Addressing" +IRC entities are represented by XMPP JIDs\. The domain part of the JID is the domain served by biboumi, and the local part depends on the concerned entity\. . -For an IRC channel, the name starts with '\fI&\fR', '\fI#\fR', '\fI+\fR' -or '\fI!\fR'. Some other gateway implementations, as well as some IRC -clients, do not require them to be started by one of these characters, -adding an implicit '\fI#\fR' in that case. Biboumi does not do that because -this gets confusing when trying to understand the difference between -\fIfoo\fR, \fI#foo\fR, and \fI##foo\fR. +.P +IRC channels and IRC users JIDs have a localpart formed like this: \fBname\fR, the \fB\'%\'\fR separator and the \fBirc_server\fR\. . -If the name starts with any other character, this represents an IRC user. +.P +For an IRC channel, the name starts with \fB\'&\'\fR, \fB\'#\'\fR, \fB\'+\'\fR or \fB\'!\'\fR\. Some other gateway implementations, as well as some IRC clients, do not require them to be started by one of these characters, adding an implicit \fB\'#\'\fR in that case\. Biboumi does not do that because this gets confusing when trying to understand the difference between \fIfoo\fR, \fI#foo\fR, and \fI##foo\fR\. . -.SS "Join an IRC channel" +.P +If the name starts with any other character, this represents an IRC user\. . -To join an IRC channel \fI#foo\fR on the IRC server \fIirc.example.com\fR, -join the XMPP MUC \fI#foo%irc.example.com@hostname\fR. +.SS "Join an IRC channel" +To join an IRC channel \fB#foo\fR on the IRC server \fBirc\.example\.com\fR, join the XMPP MUC \fB#foo%irc\.example\.com@hostname\fR\. . .SS "Channel messages" -. -On XMPP, unlike on IRC, the displayed order of the messages is the same for -all participants of a MUC. Biboumi can not however provide this feature, as -it cannot know whether the IRC server has received and forwarded the -messages to other users. This means that the order of the messages -displayed in your XMPP may not be the same than the order on other IRC -users’. +On XMPP, unlike on IRC, the displayed order of the messages is the same for all participants of a MUC\. Biboumi can not however provide this feature, as it cannot know whether the IRC server has received and forwarded the messages to other users\. This means that the order of the messages displayed in your XMPP may not be the same than the order on other IRC users’\. . .SS "Nicknames" +On IRC, nicknames are server\-wide\. This means that one user only has one single nickname at one given time on all the channels of a server\. This is different from XMPP where an user can have a different nick on each MUC, even if these MUCs are on the same server\. . -On IRC, nicknames are server-wide. This means that one user only has one -single nickname at one given time on all the channels of a server. This is -different from XMPP where an user can have a different nick on each MUC, -even if these MUCs are on the same server. -. -This means that the nick you choose when joining your first IRC channel on a -given IRC server will be your nickname in all other channels that you join -on that same IRC server. -If you explicitely change your nickname on one channel, your nickname will -be changed on all channels on the same server as well. +.P +This means that the nick you choose when joining your first IRC channel on a given IRC server will be your nickname in all other channels that you join on that same IRC server\. If you explicitely change your nickname on one channel, your nickname will be changed on all channels on the same server as well\. . .SS "Private messages" -. -Private messages are handled differently on IRC and on XMPP. On IRC, you -talk directly to one server-user: toto on the channel #foo is the same user -than toto on the channel #bar (as long as these two channels are on the same -IRC server). Using biboumi, there is no way to receive a message from a -room participant (from a jid like \fI#test%irc.example.com/\fBnickname\fR). -Instead, private messages are received from and sent to the user (using a -jid like \fBnickname\fI%irc.example.com\fR). For conveniance and -compatibility with XMPP clients sending private messages to the MUC -participants, a message sent to -\fB#chan%irc.example.com@irc.example.net/Nickname\fR will be redirected to -\fBNickname%irc.example.com@irc.example.net\fR, although this is not the -prefered way to do it. +Private messages are handled differently on IRC and on XMPP\. On IRC, you talk directly to one server\-user: toto on the channel #foo is the same user than toto on the channel #bar (as long as these two channels are on the same IRC server)\. Using biboumi, there is no way to receive a message from a room participant (from a jid like #test%irc\.example\.com/nickname)\. Instead, private messages are received from and sent to the user (using a jid like nickname%irc\.example\.com)\. For conveniance and compatibility with XMPP clients sending private messages to the MUC participants, a message sent toNickname%irc\.example\.com@irc\.example\.net, although this is not the prefered way to do it\. . .SS "Notices" -. -Notices are received exactly like private messages. It is not possible to -send a notice. +Notices are received exactly like private messages\. It is not possible to send a notice\. . .SS "Kicks and bans" -. -Kicks are transparently translated from one protocol to another. However -banning an XMPP participant has no effect. To ban an user you need to set a -mode +b on that user nick or host (see \fBMODES\fR) and then kick it. +Kicks are transparently translated from one protocol to another\. However banning an XMPP participant has no effect\. To ban an user you need to set a mode +b on that user nick or host (see \fIMODES\fR) and then kick it\. . .SS "Encoding" +On XMPP, the encoding is always \fBUTF\-8\fR, whereas on IRC the encoding of each message can be anything\. . -On XMPP, the encoding is always \fIUTF-8\fR, whereas on IRC the encoding of -each message can be anything. -. -This means that biboumi has to convert everything coming from IRC into UTF-8 -without knowing the encoding of the received messages. To do so, it checks -if each message is UTF-8 valid, if not it tries to convert from -\fIiso_8859-1\fR (because this appears to be the most common case, at least -on the channels I visit) to \fIUTF-8\fR. If that conversion fails at some -point, a placeholder character '\f�\fR' is inserted to indicate this -decoding error. +.P +This means that biboumi has to convert everything coming from IRC into UTF\-8 without knowing the encoding of the received messages\. To do so, it checks if each message is UTF\-8 valid, if not it tries to convert from \fBiso_8859\-1\fR (because this appears to be the most common case, at least on the channels I visit) to \fBUTF\-8\fR\. If that conversion fails at some point, a placeholder character \fB\'�\'\fR is inserted to indicate this decoding error\. . -Messages are always sent in UTF-8 over IRC, no conversion is done in that -direction. +.P +Messages are always sent in UTF\-8 over IRC, no conversion is done in that direction\. . .SS "IRC modes" +One feature that doesn’t exist on XMPP but does on IRC is the \fBmodes\fR\. Although some of these modes have a correspondance in the XMPP world (for example the \fB+o\fR mode on an user corresponds to the \fBmoderator\fR role in XMPP), it is impossible to map all these modes to an XMPP feature\. To circumvent this problem, biboumi provides a raw notification when modes are changed, and lets the user change the modes directly\. . -One feature that doesn’t exist on XMPP but does on IRC is the \fImodes\fR. -Although some of these modes have a correspondance in the XMPP world (for -example the \fI+o\fR mode on an user corresponds to the \fImoderator\fR role -in XMPP), it is impossible to map all these modes to an XMPP feature. To -circumvent this problem, biboumi provides a raw notification when modes are -changed, and lets the user change the modes directly. +.P +To change modes, simply send a message starting with “\fB/mode\fR” followed by the modes and the arguments you want to send to the IRC server\. For example “/mode +aho louiz”\. Note that your XMPP client may inteprete messages begining with “/” like a command\. To actually send a message starting with a slash, you may need to start your message with “//mode” or “/say /mode”, depending on your client\. . -To change modes, simply send a message starting with “\fB/mode\fR” followed -by the modes and the arguments you want to send to the IRC server. For -example “/mode +aho louiz”. Note that your XMPP client may -inteprete messages begining with “/” like a command. To actually send a -message starting with a slash, you may need to start your message with -“//mode” or “/say /mode”, depending on your client. +.P +When a mode is changed, the user is notified by a message coming from the MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”\. In addition, if the mode change can be translated to an XMPP feature, the user will be notified of this XMPP event as well\. For example if a mode “+o toto” is received, then toto’s role will be changed to moderator\. The mapping between IRC modes and XMPP features is as follow: . -When a mode is changed, the user is notified by a message coming from the -MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if -the mode change can be translated to an XMPP feature, the user will be -notified of this XMPP event as well. For example if a mode “+o toto” is -received, then toto’s role will be changed to moderator. The mapping -between IRC modes and XMPP features is as follow: +.P +\fB+o\fR . -.TP -.B +o -Sets the participant’s role to \fImoderator\fR. +.P +Sets the participant’s role to \fBmoderator\fR\. . -.TP -.B +a -Sets the participant’s role to \admin\fR. +.P +\fB+a\fR . -.TP -.B +v -Sets the participant’s affiliation to \fImember\fR. +.P +Sets the participant’s role to \fBadmin\fR\. . -.SH SECURITY +.P +\fB+v\fR . -Biboumi does not provide any encryption mechanism: connection to the XMPP -server MUST be made on localhost. The XMPP server is not supposed to accept -non-local connection from components, thus encryption is useless. IRC -SSL/TLS is also not implemented although this could be useful for some -users, this is however not a high priority feature. +.P +Sets the participant’s affiliation to \fBmember\fR\. . -Biboumi also does not check if JIDs are properly formatted using nodeprep. -This must be done by the XMPP server to which biboumi is directly connected. +.SH "SECURITY" +Biboumi does not provide any encryption mechanism: connection to the XMPP server MUST be made on localhost\. The XMPP server is not supposed to accept non\-local connection from components, thus encryption is useless\. IRC SSL/TLS is also not implemented although this could be useful for some users, this is however not a high priority feature\. . -.SH AUTHORS +.P +Biboumi also does not check if JIDs are properly formatted using nodeprep\. This must be done by the XMPP server to which biboumi is directly connected\. . +.SH "AUTHORS" Written by Florent Le Coz diff --git a/doc/biboumi.md b/doc/biboumi.md new file mode 100644 index 0000000..a251801 --- /dev/null +++ b/doc/biboumi.md @@ -0,0 +1,202 @@ +BIBOUMI 1 "2013-11-21" +====================== + +NAME +---- + +Bbiboumi - XMPP gateway to IRC + +SYNOPSIS +-------- + +`biboumi` [`config_file`] + +DESCRIPTION +----------- + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +OPTIONS +------- + +Available options: + +`config_filename` + + Specify the file to read for configuration. See *CONFIG* section for more + details on its content. + +CONFIG +------ + +The configuration file uses a simple format of the form +`"option=value"`. Here is a description of each possible option: + +`hostname` (mandatory) + + The hostname served by the XMPP gateway. This domain must be configured in + the XMPP server as an external component. See the manual for your XMPP + server for more information. + +`password` (mandatory) + + The password used to authenticate the XMPP component to your XMPP server. + This password must be configured in the XMPP server, associated with the + external component on *hostname*. + +USAGE +----- + +When started, biboumi connects, without encryption (see *SECURITY*), to the +local XMPP server on the port `5347` and provides the configured password to +authenticate. Biboumi then serves the configured `hostname`, this means +that all XMPP stanza with a `to` JID on that domain will be sent to biboumi, +and biboumi will send only send messages coming from this hostname. + +When an user joins an IRC channel on an IRC server (see *Join an IRC +channel*), biboumi connects to the remote IRC server, sets the user’s nick +as requested, and then tries to join the specified channel. If the same +user subsequently tries to connect to an other channel on the same server, +the same IRC connection is used. If, however, an other user wants to join +an IRC channel on that same IRC server, biboumi opens a new connection to +that server. Biboumi connects once to each IRC server, for each user on it. + +### Addressing + +IRC entities are represented by XMPP JIDs. The domain part of the JID is +the domain served by biboumi, and the local part depends on the concerned +entity. + +IRC channels and IRC users JIDs have a localpart formed like this: +`name`, the `'%'` separator and the `irc_server`. + +For an IRC channel, the name starts with `'&'`, `'#'`, `'+'` +or `'!'`. Some other gateway implementations, as well as some IRC +clients, do not require them to be started by one of these characters, +adding an implicit `'#'` in that case. Biboumi does not do that because +this gets confusing when trying to understand the difference between +*foo*, *#foo*, and *##foo*. + +If the name starts with any other character, this represents an IRC user. + +### Join an IRC channel + +To join an IRC channel `#foo` on the IRC server `irc.example.com`, +join the XMPP MUC `#foo%irc.example.com@hostname`. + +### Channel messages + +On XMPP, unlike on IRC, the displayed order of the messages is the same for +all participants of a MUC. Biboumi can not however provide this feature, as +it cannot know whether the IRC server has received and forwarded the +messages to other users. This means that the order of the messages +displayed in your XMPP may not be the same than the order on other IRC +users’. + +### Nicknames + +On IRC, nicknames are server-wide. This means that one user only has one +single nickname at one given time on all the channels of a server. This is +different from XMPP where an user can have a different nick on each MUC, +even if these MUCs are on the same server. + +This means that the nick you choose when joining your first IRC channel on a +given IRC server will be your nickname in all other channels that you join +on that same IRC server. +If you explicitely change your nickname on one channel, your nickname will +be changed on all channels on the same server as well. + +### Private messages + +Private messages are handled differently on IRC and on XMPP. On IRC, you +talk directly to one server-user: toto on the channel #foo is the same user +than toto on the channel #bar (as long as these two channels are on the same +IRC server). Using biboumi, there is no way to receive a message from a +room participant (from a jid like #test%irc.example.com/nickname). Instead, +private messages are received from and sent to the user (using a jid like +nickname%irc.example.com). For conveniance and compatibility with XMPP +clients sending private messages to the MUC participants, a message sent to +#chan%irc.example.com@irc.example.net/Nickname will be redirected to +Nickname%irc.example.com@irc.example.net, although this is not the prefered +way to do it. + +### Notices + +Notices are received exactly like private messages. It is not possible to +send a notice. + +### Kicks and bans + +Kicks are transparently translated from one protocol to another. However +banning an XMPP participant has no effect. To ban an user you need to set a +mode +b on that user nick or host (see *MODES*) and then kick it. + +### Encoding + +On XMPP, the encoding is always `UTF-8`, whereas on IRC the encoding of +each message can be anything. + +This means that biboumi has to convert everything coming from IRC into UTF-8 +without knowing the encoding of the received messages. To do so, it checks +if each message is UTF-8 valid, if not it tries to convert from +`iso_8859-1` (because this appears to be the most common case, at least +on the channels I visit) to `UTF-8`. If that conversion fails at some +point, a placeholder character `'�'` is inserted to indicate this +decoding error. + +Messages are always sent in UTF-8 over IRC, no conversion is done in that +direction. + +### IRC modes + +One feature that doesn’t exist on XMPP but does on IRC is the `modes`. +Although some of these modes have a correspondance in the XMPP world (for +example the `+o` mode on an user corresponds to the `moderator` role +in XMPP), it is impossible to map all these modes to an XMPP feature. To +circumvent this problem, biboumi provides a raw notification when modes are +changed, and lets the user change the modes directly. + +To change modes, simply send a message starting with “`/mode`” followed +by the modes and the arguments you want to send to the IRC server. For +example “/mode +aho louiz”. Note that your XMPP client may +inteprete messages begining with “/” like a command. To actually send a +message starting with a slash, you may need to start your message with +“//mode” or “/say /mode”, depending on your client. + +When a mode is changed, the user is notified by a message coming from the +MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if +the mode change can be translated to an XMPP feature, the user will be +notified of this XMPP event as well. For example if a mode “+o toto” is +received, then toto’s role will be changed to moderator. The mapping +between IRC modes and XMPP features is as follow: + +`+o` + + Sets the participant’s role to `moderator`. + +`+a` + + Sets the participant’s role to `admin`. + +`+v` + + Sets the participant’s affiliation to `member`. + +SECURITY +-------- + +Biboumi does not provide any encryption mechanism: connection to the XMPP +server MUST be made on localhost. The XMPP server is not supposed to accept +non-local connection from components, thus encryption is useless. IRC +SSL/TLS is also not implemented although this could be useful for some +users, this is however not a high priority feature. + +Biboumi also does not check if JIDs are properly formatted using nodeprep. +This must be done by the XMPP server to which biboumi is directly connected. + +AUTHORS +------- + +Written by Florent Le Coz -- cgit v1.2.3 From 2aa868c7cc3a11b66f3326519f8d67b67b33b986 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 20:28:57 +0100 Subject: Document the bare-JID-for-IRC-users feature --- INSTALL | 10 ++++++++++ doc/biboumi.1 | 2 +- doc/biboumi.md | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 309ecc0..94b62bc 100644 --- a/INSTALL +++ b/INSTALL @@ -4,18 +4,28 @@ Build and runtime dependencies: + Libraries: + - crypto++ Sometimes packaged as cryptopp. This library is used to generate MD5 hashes. http://www.cryptopp.com/ + - expat Used to parse XML from the XMPP server. http://expat.sourceforge.net/ + - libiconv Encoding from anything into UTF-8 http://www.gnu.org/software/libiconv/ +- libidn (optionnal, but recommended) + Provides the stringprep functionality. Without it, JIDs for IRC users are + not provided. + http://www.gnu.org/software/libidn/ + + Tools: - A C++11 compiler. - CMake diff --git a/doc/biboumi.1 b/doc/biboumi.1 index 5f88a16..013ab77 100644 --- a/doc/biboumi.1 +++ b/doc/biboumi.1 @@ -52,7 +52,7 @@ IRC channels and IRC users JIDs have a localpart formed like this: \fBname\fR, t For an IRC channel, the name starts with \fB\'&\'\fR, \fB\'#\'\fR, \fB\'+\'\fR or \fB\'!\'\fR\. Some other gateway implementations, as well as some IRC clients, do not require them to be started by one of these characters, adding an implicit \fB\'#\'\fR in that case\. Biboumi does not do that because this gets confusing when trying to understand the difference between \fIfoo\fR, \fI#foo\fR, and \fI##foo\fR\. . .P -If the name starts with any other character, this represents an IRC user\. +If the name starts with any other character, this represents an IRC user\. If compiled with Libidn, an IRC user has a bare JID representing the “hostname” provided by the IRC server\. . .SS "Join an IRC channel" To join an IRC channel \fB#foo\fR on the IRC server \fBirc\.example\.com\fR, join the XMPP MUC \fB#foo%irc\.example\.com@hostname\fR\. diff --git a/doc/biboumi.md b/doc/biboumi.md index a251801..355445e 100644 --- a/doc/biboumi.md +++ b/doc/biboumi.md @@ -80,6 +80,8 @@ this gets confusing when trying to understand the difference between *foo*, *#foo*, and *##foo*. If the name starts with any other character, this represents an IRC user. +If compiled with Libidn, an IRC user has a bare JID representing the +“hostname” provided by the IRC server. ### Join an IRC channel -- cgit v1.2.3 From bb094e5058f0ee4b22e73b10166d0ca90fab559b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Dec 2013 20:33:36 +0100 Subject: Document the log_file and log_level configuration options --- doc/biboumi.1 | 12 ++++++++++++ doc/biboumi.md | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/doc/biboumi.1 b/doc/biboumi.1 index 013ab77..3e472c8 100644 --- a/doc/biboumi.1 +++ b/doc/biboumi.1 @@ -36,6 +36,18 @@ The hostname served by the XMPP gateway\. This domain must be configured in th .P The password used to authenticate the XMPP component to your XMPP server\. This password must be configured in the XMPP server, associated with the external component on \fIhostname\fR\. . +.P +\fBlog_file\fR +. +.P +A filename into which logs are written\. If none is provided, the logs are written on standard output +. +.P +\fBlog_level\fR +. +.P +Indicate what type of log messages to write in the logs\. Values can be from 0 to 3\. 0 is debug, 1 is info, 2 is warnig, 3 is error\. +. .SH "USAGE" When started, biboumi connects, without encryption (see \fISECURITY\fR), to the local XMPP server on the port \fB5347\fR and provides the configured password to authenticate\. Biboumi then serves the configured \fBhostname\fR, this means that all XMPP stanza with a \fBto\fR JID on that domain will be sent to biboumi, and biboumi will send only send messages coming from this hostname\. . diff --git a/doc/biboumi.md b/doc/biboumi.md index 355445e..811b5c0 100644 --- a/doc/biboumi.md +++ b/doc/biboumi.md @@ -46,6 +46,16 @@ The configuration file uses a simple format of the form This password must be configured in the XMPP server, associated with the external component on *hostname*. +`log_file` + + A filename into which logs are written. If none is provided, the logs are + written on standard output + +`log_level` + + Indicate what type of log messages to write in the logs. Values can be from 0 to 3. + 0 is debug, 1 is info, 2 is warnig, 3 is error. + USAGE ----- -- cgit v1.2.3 From b29290f73a24f2d5af7bde45c9ff5332c7a1f5a6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 9 Dec 2013 03:53:01 +0100 Subject: Lowercase the chan names in two missing cases --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0f29a2b..ed98653 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -224,7 +224,7 @@ void IrcClient::on_channel_message(const IrcMessage& message) const IrcUser user(message.prefix); const std::string nick = user.nick; Iid iid; - iid.chan = message.arguments[0]; + iid.chan = utils::tolower(message.arguments[0]); iid.server = this->hostname; const std::string body = message.arguments[1]; bool muc = true; @@ -388,7 +388,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) // For now, just transmit the modes so the user can know what happens // TODO, actually interprete the mode. Iid iid; - iid.chan = message.arguments[0]; + iid.chan = utils::tolower(message.arguments[0]); iid.server = this->hostname; IrcUser user(message.prefix); std::string mode_arguments; -- cgit v1.2.3 From 3960e4d5afa09c299f595b411ee8522db30580fd Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Dec 2013 21:07:39 +0100 Subject: Functions to provide xml-valid strings By removing invalid chars, see http://www.w3.org/TR/xml/#charsets --- src/test.cpp | 7 ++++- src/utils/encoding.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/encoding.hpp | 8 ++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/test.cpp b/src/test.cpp index a8c0276..958e894 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -52,6 +52,11 @@ int main() assert(from_ascii == "couc�ou"); std::cout << from_ascii << std::endl; + std::string without_ctrl_char("𤭢€¢$"); + assert(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); + assert(utils::remove_invalid_xml_chars(in) == in); + assert(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); + /** * Utils */ @@ -156,7 +161,7 @@ int main() /** * Config */ - std::cout << color << "Testing JID parsing…" << reset << std::endl; + std::cout << color << "Testing config…" << reset << std::endl; Config::filename = "test.cfg"; Config::file_must_exist = false; Config::set("coucou", "bonjour"); diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index 634964b..76d1922 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -9,6 +9,8 @@ #include +#include + /** * The UTF-8-encoded character used as a place holder when a character conversion fails. * This is U+FFFD � "replacement character" @@ -66,6 +68,77 @@ namespace utils return true; } + 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;}); + + // pointer where we write valid chars + unsigned char* r = res; + + const unsigned char* str = reinterpret_cast(original.c_str()); + std::bitset<20> codepoint; + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 11111000_b) == 11110000_b) + { + codepoint = ((str[0] & 00000111_b) << 18); + codepoint |= ((str[1] & 00111111_b) << 12); + codepoint |= ((str[2] & 00111111_b) << 6 ); + codepoint |= ((str[3] & 00111111_b) << 0 ); + if (codepoint.to_ulong() <= 0x10FFFF) + { + ::memcpy(r, str, 4); + r += 4; + } + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 11110000_b) == 11100000_b) + { + codepoint = ((str[0] & 00001111_b) << 12); + codepoint |= ((str[1] & 00111111_b) << 6); + codepoint |= ((str[2] & 00111111_b) << 0 ); + if (codepoint.to_ulong() <= 0xD7FF || + (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) + { + ::memcpy(r, str, 3); + r += 3; + } + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 11100000_b) == 11000000_b) + { + // All 2 bytes char are valid, don't even bother calculating + // the codepoint + ::memcpy(r, str, 2); + r += 2; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 10000000_b) == 0) + { + codepoint = ((str[0] & 01111111_b)); + if (codepoint.to_ulong() == 0x09 || + codepoint.to_ulong() == 0x0A || + codepoint.to_ulong() == 0x0D || + codepoint.to_ulong() >= 0x20) + { + ::memcpy(r, str, 1); + r += 1; + } + str += 1; + } + else + throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); + } + return std::string(reinterpret_cast(res), r-res); + } + std::string convert_to_utf8(const std::string& str, const char* charset) { std::string res; diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp index 362f1df..a3bccfc 100644 --- a/src/utils/encoding.hpp +++ b/src/utils/encoding.hpp @@ -11,6 +11,14 @@ namespace utils * Based on http://en.wikipedia.org/wiki/UTF-8#Description */ bool is_valid_utf8(const char* s); + /** + * Remove all invalid codepoints from the given utf-8-encoded string. + * The value returned is a copy of the string, without the removed chars. + * + * See http://www.w3.org/TR/xml/#charsets for the list of valid characters + * in XML. + */ + std::string remove_invalid_xml_chars(const std::string& original); /** * Convert the given string (encoded is "encoding") into valid utf-8. * If some decoding fails, insert an utf-8 placeholder character instead. -- cgit v1.2.3 From 7d8ce3b5638b8e09313a6218014a307ab98e6289 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Dec 2013 21:22:06 +0100 Subject: Use XML-sanitized strings when serializing stanzas for the XMPP server --- src/test.cpp | 8 ++++++++ src/xmpp/xmpp_stanza.cpp | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 958e894..c9427f0 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -97,6 +97,14 @@ int main() }); xml.feed(doc.data(), doc.size(), true); + const std::string doc2 = "coucou\r\n\a"; + xml.add_stanza_callback([](const Stanza& stanza) + { + std::cout << stanza.to_string() << std::endl; + assert(stanza.get_inner() == "coucou\r\n"); + }); + xml.feed(doc2.data(), doc.size(), true); + /** * XML escape/escape */ diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 34ba85f..b6f5a23 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -192,12 +192,13 @@ std::string XmlNode::to_string() const std::string res("<"); res += this->name; for (const auto& it: this->attributes) - res += " " + it.first + "='" + it.second + "'"; + res += " " + utils::remove_invalid_xml_chars(it.first) + "='" + + utils::remove_invalid_xml_chars(it.second) + "'"; if (this->closed && !this->has_children() && this->inner.empty()) res += "/>"; else { - res += ">" + this->inner; + res += ">" + utils::remove_invalid_xml_chars(this->inner); for (const auto& child: this->children) res += child->to_string(); if (this->closed) @@ -205,7 +206,7 @@ std::string XmlNode::to_string() const res += "name + ">"; } } - res += this->tail; + res += utils::remove_invalid_xml_chars(this->tail); return res; } -- cgit v1.2.3 From e35472a6cbc5ffecf0413c2c7da1e2552230d227 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 23 Dec 2013 17:02:46 +0100 Subject: Correctly send the part message to IRC By fixing a namespace when looking for an XML element containing that message --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 6424d74..7810499 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -176,7 +176,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) } else if (type == "unavailable") { - XmlNode* status = stanza.get_child(MUC_USER_NS":status"); + XmlNode* status = stanza.get_child(COMPONENT_NS":status"); bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); } } -- cgit v1.2.3 From 2ecb637f0c4a97643962d1703f208d1b1baf7e9b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Dec 2013 21:04:31 +0100 Subject: Read a variable number of bytes, 4096 by default --- src/network/socket_handler.cpp | 4 ++-- src/network/socket_handler.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 246d8d3..e3973f1 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -61,11 +61,11 @@ void SocketHandler::set_poller(Poller* poller) this->poller = poller; } -void SocketHandler::on_recv() +void SocketHandler::on_recv(const size_t nb) { char buf[4096]; - ssize_t size = ::recv(this->socket, buf, 4096, 0); + ssize_t size = ::recv(this->socket, buf, nb, 0); if (0 == size) { this->on_connection_close(); diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 4152a4e..6678722 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -30,7 +30,7 @@ public: * Reads data in our in_buf and the call parse_in_buf, for the implementor * to handle the data received so far. */ - void on_recv(); + void on_recv(const size_t nb = 4096); /** * Write as much data from out_buf as possible, in the socket. */ -- cgit v1.2.3 From 983477084cbb78b00da249a301480175324e93fc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Dec 2013 21:04:39 +0100 Subject: connect() returns a boolean --- src/network/socket_handler.cpp | 5 +++-- src/network/socket_handler.hpp | 2 +- src/xmpp/xmpp_component.cpp | 4 ++-- src/xmpp/xmpp_component.hpp | 5 +++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index e3973f1..c1ad8ae 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -20,7 +20,7 @@ SocketHandler::SocketHandler(): throw std::runtime_error("Could not create socket"); } -void SocketHandler::connect(const std::string& address, const std::string& port) +bool SocketHandler::connect(const std::string& address, const std::string& port) { log_info("Trying to connect to " << address << ":" << port); struct addrinfo hints; @@ -47,13 +47,14 @@ void SocketHandler::connect(const std::string& address, const std::string& port) { log_info("Connection success."); this->on_connected(); - return ; + return true; } log_info("Connection failed:"); perror("connect"); } log_error("All connection attempts failed."); this->close(); + return false; } void SocketHandler::set_poller(Poller* poller) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 6678722..675d247 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -21,7 +21,7 @@ public: /** * Connect to the remote server, and call on_connected() if this succeeds */ - void connect(const std::string& address, const std::string& port); + bool connect(const std::string& address, const std::string& port); /** * Set the pointer to the given Poller, to communicate with it. */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 7810499..433f87a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -46,9 +46,9 @@ XmppComponent::~XmppComponent() { } -void XmppComponent::start() +bool XmppComponent::start() { - this->connect("127.0.0.1", "5347"); + return this->connect("127.0.0.1", "5347"); } void XmppComponent::send_stanza(const Stanza& stanza) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 15f8f2f..1a7fc6b 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -25,9 +25,10 @@ public: void parse_in_buffer() override final; /** - * Connect to the XMPP server + * Connect to the XMPP server. + * Returns false if we failed to connect */ - void start(); + bool start(); /** * Serialize the stanza and add it to the out_buf to be sent to the * server. -- cgit v1.2.3 From df59a09163bd988ad4da533c4f39de057a3701ba Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Dec 2013 21:04:46 +0100 Subject: Do not mismatch password and hostname in the config error help message --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 71c93f9..2da180b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,9 +36,9 @@ int main(int ac, char** av) } const std::string hostname = Config::get("hostname", ""); if (password.empty()) - return config_help("hostname"); - if (hostname.empty()) return config_help("password"); + if (hostname.empty()) + return config_help("hostname"); std::shared_ptr xmpp_component = std::make_shared(hostname, password); -- cgit v1.2.3 From 3afb63a650b8b925ce1ba722dd42b7418f623713 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Dec 2013 21:04:41 +0100 Subject: Shutdown cleanly on SIGINT --- src/bridge/bridge.cpp | 8 +++++++ src/bridge/bridge.hpp | 4 ++++ src/irc/irc_client.cpp | 20 +++++++++++++++++ src/irc/irc_client.hpp | 6 +++++ src/main.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++--- src/network/poller.cpp | 45 ++++++++++++++++++++------------------ src/network/poller.hpp | 14 +++++++++--- src/xmpp/xmpp_component.cpp | 18 ++++++++++++++- src/xmpp/xmpp_component.hpp | 13 ++++++++++- 9 files changed, 152 insertions(+), 29 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 973e095..606cb02 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -21,6 +21,14 @@ Bridge::~Bridge() { } +void Bridge::shutdown() +{ + for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) + { + it->second->send_quit_command(); + } +} + Xmpp::body Bridge::make_xmpp_body(const std::string& str) { std::string res; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index bbbca95..7a36b59 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -24,6 +24,10 @@ class Bridge public: explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller); ~Bridge(); + /** + * QUIT all connected IRC servers. + */ + void shutdown(); static Xmpp::body make_xmpp_body(const std::string& str); /*** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ed98653..2115bdc 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -118,6 +118,11 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin this->send_message(IrcMessage("KICK", {chan_name, target, reason})); } +void IrcClient::send_quit_command() +{ + this->send_message(IrcMessage("QUIT", {"gateway shutdown"})); +} + void IrcClient::send_join_command(const std::string& chan_name) { if (this->welcomed == false) @@ -310,6 +315,21 @@ void IrcClient::on_part(const IrcMessage& message) } } +void IrcClient::on_error(const IrcMessage& message) +{ + const std::string leave_message = message.arguments[0]; + // The user is out of all the channels + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + Iid iid; + iid.chan = it->first; + iid.server = this->hostname; + IrcChannel* channel = it->second.get(); + std::string own_nick = channel->get_self()->nick; + this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); + } +} + void IrcClient::on_quit(const IrcMessage& message) { std::string txt; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4749cac..4038cdf 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -95,6 +95,10 @@ public: * Send the KICK irc command */ void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason); + /** + * Send the QUIT irc command + */ + void send_quit_command(); /** * Forward the server message received from IRC to the XMPP component */ @@ -139,6 +143,7 @@ public: */ void on_welcome_message(const IrcMessage& message); void on_part(const IrcMessage& message); + void on_error(const IrcMessage& message); void on_nick(const IrcMessage& message); void on_kick(const IrcMessage& message); void on_mode(const IrcMessage& message); @@ -216,6 +221,7 @@ static const std::unordered_map irc_callbacks = { {"366", &IrcClient::on_channel_completely_joined}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, + {"ERROR", &IrcClient::on_error}, {"QUIT", &IrcClient::on_quit}, {"NICK", &IrcClient::on_nick}, {"MODE", &IrcClient::on_mode}, diff --git a/src/main.cpp b/src/main.cpp index 2da180b..6c9560c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,19 @@ #include #include #include +#include #include #include +#include + +#include + +// A flag set by the SIGINT signal handler. +volatile std::atomic stop(false); +// A flag indicating that we are wanting to exit the process. i.e: if this +// flag is set and all connections are closed, we can exit properly. +static bool exiting = false; /** * Provide an helpful message to help the user write a minimal working @@ -20,6 +30,11 @@ int config_help(const std::string& missing_option) return 1; } +static void sigint_handler(int, siginfo_t*, void*) +{ + stop = true; +} + int main(int ac, char** av) { if (ac > 1) @@ -44,8 +59,40 @@ int main(int ac, char** av) Poller p; p.add_socket_handler(xmpp_component); - xmpp_component->start(); - while (p.poll()) - ; + if (!xmpp_component->start()) + { + log_info("Exiting"); + return -1; + } + + // Install the signals used to exit the process cleanly, or reload the + // config + sigset_t mask; + sigemptyset(&mask); + struct sigaction on_sig; + on_sig.sa_sigaction = &sigint_handler; + on_sig.sa_mask = mask; + // we want to catch that signal only once. + // Sending SIGINT again will "force" an exit + on_sig.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &on_sig, nullptr); + sigaction(SIGTERM, &on_sig, nullptr); + + const std::chrono::milliseconds timeout(-1); + while (p.poll(timeout) != -1 || !exiting) + { + if (stop) + { + log_info("Signal received, exiting..."); + exiting = true; + stop = false; + xmpp_component->shutdown(); + } + // If the only existing connection is the one to the XMPP component: + // close the XMPP stream. + if (exiting && p.size() == 1 && xmpp_component->is_document_open()) + xmpp_component->close_document(); + } + log_info("All connection cleanely closed, have a nice day."); return 0; } diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 71c7172..919ceb0 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -9,7 +9,6 @@ Poller::Poller() { #if POLLER == POLL - memset(this->fds, 0, sizeof(this->fds)); this->nfds = 0; #elif POLLER == EPOLL this->epfd = ::epoll_create1(0); @@ -42,9 +41,7 @@ void Poller::add_socket_handler(std::shared_ptr socket_handler) this->nfds++; #endif #if POLLER == EPOLL - struct epoll_event event; - event.data.ptr = socket_handler.get(); - event.events = EPOLLIN; + struct epoll_event event = {EPOLLIN, {socket_handler.get()}}; const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); if (res == -1) { @@ -99,9 +96,7 @@ void Poller::watch_send_events(SocketHandler* socket_handler) } throw std::runtime_error("Cannot watch a non-registered socket for send events"); #elif POLLER == EPOLL - struct epoll_event event; - event.data.ptr = socket_handler; - event.events = EPOLLIN|EPOLLOUT; + struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}}; const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { @@ -124,9 +119,7 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) } throw std::runtime_error("Cannot watch a non-registered socket for send events"); #elif POLLER == EPOLL - struct epoll_event event; - event.data.ptr = socket_handler; - event.events = EPOLLIN; + struct epoll_event event = {EPOLLIN, {socket_handler}}; const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { @@ -136,21 +129,23 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) #endif } -bool Poller::poll() +int Poller::poll(const std::chrono::milliseconds& timeout) { + if (this->socket_handlers.size() == 0) + return -1; #if POLLER == POLL - if (this->nfds == 0) - return false; - int res = ::poll(this->fds, this->nfds, -1); - if (res < 0) + int nb_events = ::poll(this->fds, this->nfds, timeout.count()); + if (nb_events < 0) { + if (errno == EINTR) + return true; perror("poll"); throw std::runtime_error("Poll failed"); } // We cannot possibly have more ready events than the number of fds we are // watching - assert(static_cast(res) <= this->nfds); - for (size_t i = 0; i <= this->nfds && res != 0; ++i) + assert(static_cast(nb_events) <= this->nfds); + for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) { if (this->fds[i].revents == 0) continue; @@ -158,21 +153,24 @@ bool Poller::poll() { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); socket_handler->on_recv(); - res--; + nb_events--; } else if (this->fds[i].revents & POLLOUT) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); socket_handler->on_send(); - res--; + nb_events--; } } + return 1; #elif POLLER == EPOLL static const size_t max_events = 12; struct epoll_event revents[max_events]; - const int nb_events = epoll_wait(this->epfd, revents, max_events, -1); + const int nb_events = ::epoll_wait(this->epfd, revents, max_events, timeout.count()); if (nb_events == -1) { + if (errno == EINTR) + return 0; perror("epoll_wait"); throw std::runtime_error("Epoll_wait failed"); } @@ -184,6 +182,11 @@ bool Poller::poll() if (revents[i].events & EPOLLOUT) socket_handler->on_send(); } + return nb_events; #endif - return true; +} + +size_t Poller::size() const +{ + return this->socket_handlers.size(); } diff --git a/src/network/poller.hpp b/src/network/poller.hpp index fe52fda..dc087a2 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -5,6 +5,7 @@ #include #include +#include #define POLL 1 #define EPOLL 2 @@ -58,10 +59,17 @@ public: void stop_watching_send_events(SocketHandler* socket_handler); /** * Wait for all watched events, and call the SocketHandlers' callbacks - * when one is ready. - * Returns false if there are 0 SocketHandler in the list. + * when one is ready. Returns if nothing happened before the provided + * timeout. If the timeout is 0, it waits forever. If there is no + * watched event, returns -1 immediately, ignoring the timeout value. + * Otherwise, returns the number of event handled. If 0 is returned this + * means that we were interrupted by a signal, or the timeout occured. */ - bool poll(); + int poll(const std::chrono::milliseconds& timeout); + /** + * Returns the number of SocketHandlers managed by the poller. + */ + size_t size() const; private: /** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 433f87a..dc77934 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -24,7 +24,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): served_hostname(hostname), secret(secret), - authenticated(false) + authenticated(false), + doc_open(false) { this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, std::placeholders::_1)); @@ -51,6 +52,11 @@ bool XmppComponent::start() return this->connect("127.0.0.1", "5347"); } +bool XmppComponent::is_document_open() const +{ + return this->doc_open; +} + void XmppComponent::send_stanza(const Stanza& stanza) { std::string str = stanza.to_string(); @@ -66,6 +72,7 @@ void XmppComponent::on_connected() node["xmlns:stream"] = STREAM_NS; node["to"] = this->served_hostname; this->send_stanza(node); + this->doc_open = true; } void XmppComponent::on_connection_close() @@ -79,6 +86,14 @@ void XmppComponent::parse_in_buffer() this->in_buf.clear(); } +void XmppComponent::shutdown() +{ + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + { + it->second->shutdown(); + } +} + void XmppComponent::on_remote_stream_open(const XmlNode& node) { log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); @@ -145,6 +160,7 @@ void XmppComponent::close_document() { log_debug("XMPP SENDING: "); this->send_data(""); + this->doc_open = false; } void XmppComponent::handle_handshake(const Stanza& stanza) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 1a7fc6b..1952e19 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -23,7 +23,14 @@ public: void on_connected() override final; void on_connection_close() override final; void parse_in_buffer() override final; - + /** + * Send a "close" message to all our connected peers. That message + * depends on the protocol used (this may be a QUIT irc message, or a + * , etc). We may also directly close the connection, or we may + * wait for the remote peer to acknowledge it before closing. + */ + void shutdown(); + bool is_document_open() const; /** * Connect to the XMPP server. * Returns false if we failed to connect @@ -115,6 +122,10 @@ private: std::string served_hostname; std::string secret; bool authenticated; + /** + * Whether or not OUR XMPP document is open + */ + bool doc_open; std::unordered_map> stanza_handlers; -- cgit v1.2.3 From b8ce9ed43d809daefe17714b36aa0874ca5f6cc8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Dec 2013 14:30:07 +0100 Subject: Check that channels are joined before acting on objects in it --- src/irc/irc_client.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 2115bdc..bde9973 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -297,6 +297,8 @@ void IrcClient::on_part(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); + if (!channel->joined) + return ; std::string txt; if (message.arguments.size() >= 2) txt = message.arguments[1]; @@ -325,6 +327,8 @@ void IrcClient::on_error(const IrcMessage& message) iid.chan = it->first; iid.server = this->hostname; IrcChannel* channel = it->second.get(); + if (!channel->joined) + continue; std::string own_nick = channel->get_self()->nick; this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); } @@ -384,6 +388,8 @@ void IrcClient::on_kick(const IrcMessage& message) const std::string reason = message.arguments[2]; const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); + if (!channel->joined) + return ; if (channel->get_self()->nick == target) channel->joined = false; IrcUser author(message.prefix); -- cgit v1.2.3 From 9df757fc6737b59f56d5b808ef48baba760b142e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Dec 2013 14:57:13 +0100 Subject: Handle topic changes --- src/irc/irc_client.cpp | 2 ++ src/irc/irc_client.hpp | 1 + 2 files changed, 3 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index bde9973..bdeb2f8 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -274,6 +274,8 @@ void IrcClient::on_topic_received(const IrcMessage& message) const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->topic = message.arguments[2]; + if (channel->joined) + this->bridge->send_topic(this->hostname, chan_name, channel->topic); } void IrcClient::on_channel_completely_joined(const IrcMessage& message) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4038cdf..99ee6c0 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -218,6 +218,7 @@ static const std::unordered_map irc_callbacks = { {"PRIVMSG", &IrcClient::on_channel_message}, {"353", &IrcClient::set_and_forward_user_list}, {"332", &IrcClient::on_topic_received}, + {"TOPIC", &IrcClient::on_topic_received}, {"366", &IrcClient::on_channel_completely_joined}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, -- cgit v1.2.3 From 483e17020dc4f19223001ab36da0dc48a15a0d3e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Dec 2013 11:49:28 +0100 Subject: Be verbose about the connection status, and some errors --- src/irc/irc_client.cpp | 25 ++++++++++++++++++++++++- src/irc/irc_client.hpp | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index bdeb2f8..881de96 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -33,11 +33,14 @@ void IrcClient::on_connected() { this->send_nick_command(this->username); this->send_user_command(this->username, this->username); + this->send_gateway_message("Connected to IRC server."); } void IrcClient::on_connection_close() { - log_warning("Connection closed by remote server."); + static const std::string message = "Connection closed by remote server."; + this->send_gateway_message(message); + log_warning(message); } IrcChannel* IrcClient::get_channel(const std::string& name) @@ -191,6 +194,11 @@ void IrcClient::forward_server_message(const IrcMessage& message) this->bridge->send_xmpp_message(this->hostname, from, body); } +void IrcClient::send_gateway_message(const std::string& message, const std::string& from) +{ + this->bridge->send_xmpp_message(this->hostname, from, message); +} + void IrcClient::set_and_forward_user_list(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[2]); @@ -286,6 +294,20 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) this->bridge->send_topic(this->hostname, chan_name, channel->topic); } +void IrcClient::on_erroneous_nickname(const IrcMessage& message) +{ + const std::string error_msg = message.arguments.size() >= 3 ? + message.arguments[2]: "Erroneous nickname"; + this->send_gateway_message(error_msg + ": " + message.arguments[1], message.prefix); +} + +void IrcClient::on_generic_error(const IrcMessage& message) +{ + const std::string error_msg = message.arguments.size() >= 3 ? + message.arguments[2]: "Unspecified error"; + this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix); +} + void IrcClient::on_welcome_message(const IrcMessage& message) { this->current_nick = message.arguments[0]; @@ -334,6 +356,7 @@ void IrcClient::on_error(const IrcMessage& message) std::string own_nick = channel->get_self()->nick; this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); } + this->send_gateway_message(std::string("ERROR: ") + leave_message); } void IrcClient::on_quit(const IrcMessage& message) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 99ee6c0..e695e53 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -99,6 +99,12 @@ public: * Send the QUIT irc command */ void send_quit_command(); + /** + * Send a message to the gateway user, not generated by the IRC server, + * but that might be useful because we want to be verbose (for example we + * might want to notify the user about the connexion state) + */ + void send_gateway_message(const std::string& message, const std::string& from=""); /** * Forward the server message received from IRC to the XMPP component */ @@ -138,6 +144,14 @@ public: * received etc), send the self presence and topic to the XMPP user. */ void on_channel_completely_joined(const IrcMessage& message); + /** + * We tried to set an invalid nickname + */ + void on_erroneous_nickname(const IrcMessage& message); + /** + * Handles most errors from the server by just forwarding the message to the user. + */ + void on_generic_error(const IrcMessage& message); /** * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname */ @@ -208,6 +222,8 @@ typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); static const std::unordered_map irc_callbacks = { {"NOTICE", &IrcClient::forward_server_message}, + {"002", &IrcClient::forward_server_message}, + {"003", &IrcClient::forward_server_message}, {"RPL_MOTDSTART", &IrcClient::empty_motd}, {"375", &IrcClient::empty_motd}, {"RPL_MOTD", &IrcClient::on_motd_line}, @@ -220,6 +236,8 @@ static const std::unordered_map irc_callbacks = { {"332", &IrcClient::on_topic_received}, {"TOPIC", &IrcClient::on_topic_received}, {"366", &IrcClient::on_channel_completely_joined}, + {"432", &IrcClient::on_erroneous_nickname}, + {"461", &IrcClient::on_generic_error}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, {"ERROR", &IrcClient::on_error}, -- cgit v1.2.3 From aa53276859a5fc80071d24111a311297e058e603 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Dec 2013 11:53:29 +0100 Subject: Keep a "connected" state in the SocketHandler class --- src/bridge/bridge.cpp | 1 - src/irc/irc_client.cpp | 2 ++ src/network/socket_handler.cpp | 13 ++++++++++++- src/network/socket_handler.hpp | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 606cb02..3a755a3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -50,7 +50,6 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string this->irc_clients.emplace(hostname, std::make_shared(hostname, username, this)); std::shared_ptr irc = this->irc_clients.at(hostname); this->poller->add_socket_handler(irc); - irc->start(); return irc.get(); } } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 881de96..4e37a63 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -128,6 +128,8 @@ void IrcClient::send_quit_command() void IrcClient::send_join_command(const std::string& chan_name) { + if (!this->connected) + this->start(); if (this->welcomed == false) this->channels_to_join.push_back(chan_name); else diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index c1ad8ae..484aa8f 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -14,7 +14,8 @@ #include SocketHandler::SocketHandler(): - poller(nullptr) + poller(nullptr), + connected(false) { if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) throw std::runtime_error("Could not create socket"); @@ -46,6 +47,7 @@ bool SocketHandler::connect(const std::string& address, const std::string& port) if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) { log_info("Connection success."); + this->connected = true; this->on_connected(); return true; } @@ -99,8 +101,12 @@ void SocketHandler::on_send() void SocketHandler::close() { + this->connected = false; this->poller->remove_socket_handler(this->get_socket()); ::close(this->socket); + // recreate the socket for a potential future usage + if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) + throw std::runtime_error("Could not create socket"); } socket_t SocketHandler::get_socket() const @@ -116,3 +122,8 @@ void SocketHandler::send_data(std::string&& data) this->poller->watch_send_events(this); } } + +bool SocketHandler::is_connected() const +{ + return this->connected; +} diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 675d247..ec63ea7 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -61,6 +61,7 @@ public: * should be truncated, only the unused data should be left untouched. */ virtual void parse_in_buffer() = 0; + bool is_connected() const; protected: socket_t socket; @@ -84,6 +85,7 @@ protected: * (actually it is sharing our ownership with a Bridge). */ Poller* poller; + bool connected; private: SocketHandler(const SocketHandler&) = delete; -- cgit v1.2.3 From e8e592d1ace5413a1e7d8b59b9467c78d8d68ea9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Dec 2013 12:01:26 +0100 Subject: Remove disconnected IrcClients --- src/bridge/bridge.cpp | 13 +++++++++++++ src/bridge/bridge.hpp | 5 ++++- src/irc/irc_client.cpp | 10 ++++++++-- src/main.cpp | 3 +++ src/xmpp/xmpp_component.cpp | 8 ++++++++ src/xmpp/xmpp_component.hpp | 5 +++++ 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 3a755a3..c93d710 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -29,6 +29,19 @@ void Bridge::shutdown() } } +void Bridge::clean() +{ + auto it = this->irc_clients.begin(); + while (it != this->irc_clients.end()) + { + IrcClient* client = it->second.get(); + if (!client->is_connected()) + it = this->irc_clients.erase(it); + else + ++it; + } +} + Xmpp::body Bridge::make_xmpp_body(const std::string& str) { std::string res; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 7a36b59..1e1149b 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -28,7 +28,10 @@ public: * QUIT all connected IRC servers. */ void shutdown(); - + /** + * Remove all inactive IrcClients + */ + void clean(); static Xmpp::body make_xmpp_body(const std::string& str); /*** ** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4e37a63..afdc629 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -58,8 +58,8 @@ IrcChannel* IrcClient::get_channel(const std::string& name) bool IrcClient::is_channel_joined(const std::string& name) { - IrcChannel* client = this->get_channel(name); - return client->joined; + IrcChannel* channel = this->get_channel(name); + return channel->joined; } std::string IrcClient::get_own_nick() const @@ -339,7 +339,12 @@ void IrcClient::on_part(const IrcMessage& message) bool self = channel->get_self()->nick == nick; this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); if (self) + { channel->joined = false; + this->channels.erase(chan_name); + // channel pointer is now invalid + channel = nullptr; + } } } @@ -358,6 +363,7 @@ void IrcClient::on_error(const IrcMessage& message) std::string own_nick = channel->get_self()->nick; this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); } + this->channels.clear(); this->send_gateway_message(std::string("ERROR: ") + leave_message); } diff --git a/src/main.cpp b/src/main.cpp index 6c9560c..28f5a76 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -81,6 +81,9 @@ int main(int ac, char** av) const std::chrono::milliseconds timeout(-1); while (p.poll(timeout) != -1 || !exiting) { + // Check for empty irc_clients (not connected, or with no joined + // channel) and remove them + xmpp_component->clean(); if (stop) { log_info("Signal received, exiting..."); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index dc77934..3c37eb1 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -94,6 +94,14 @@ void XmppComponent::shutdown() } } +void XmppComponent::clean() +{ + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + { + it->second->clean(); + } +} + void XmppComponent::on_remote_stream_open(const XmlNode& node) { log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 1952e19..d76a2c3 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -31,6 +31,11 @@ public: */ void shutdown(); bool is_document_open() const; + /** + * Run a check on all bridges, to remove all disconnected (socket is + * closed, or no channel is joined) IrcClients. Some kind of garbage collector. + */ + void clean(); /** * Connect to the XMPP server. * Returns false if we failed to connect -- cgit v1.2.3 From 43cc60e4a9e2859fdf67c89e58ee18cf7571f186 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Dec 2013 15:34:58 +0100 Subject: Handle nickname conflicts by sending the correct XMPP error presence --- src/bridge/bridge.cpp | 5 +++++ src/bridge/bridge.hpp | 1 + src/irc/irc_client.cpp | 13 +++++++++++++ src/irc/irc_client.hpp | 6 ++++++ src/xmpp/xmpp_component.cpp | 29 +++++++++++++++++++++++++++-- src/xmpp/xmpp_component.hpp | 6 ++++++ 6 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index c93d710..7f245db 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -216,3 +216,8 @@ void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::stri { this->xmpp->kick_user(iid.chan + "%" + iid.server, target, reason, author, this->user_jid); } + +void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) +{ + this->xmpp->send_nickname_conflict_error(iid.chan + "%" + iid.server, nickname, this->user_jid); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1e1149b..b5bee9e 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -89,6 +89,7 @@ public: */ void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self); void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); + void send_nickname_conflict_error(const Iid& iid, const std::string& nickname); /** * Misc diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index afdc629..10a5b12 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -303,6 +303,19 @@ void IrcClient::on_erroneous_nickname(const IrcMessage& message) this->send_gateway_message(error_msg + ": " + message.arguments[1], message.prefix); } +void IrcClient::on_nickname_conflict(const IrcMessage& message) +{ + const std::string nickname = message.arguments[1]; + this->on_generic_error(message); + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + Iid iid; + iid.chan = it->first; + iid.server = this->hostname; + this->bridge->send_nickname_conflict_error(iid, nickname); + } +} + void IrcClient::on_generic_error(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index e695e53..eced4b6 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -148,6 +148,11 @@ public: * We tried to set an invalid nickname */ void on_erroneous_nickname(const IrcMessage& message); + /** + * When the IRC servers denies our nickname because of a conflict. Send a + * presence conflict from all channels, because the name is server-wide. + */ + void on_nickname_conflict(const IrcMessage& message); /** * Handles most errors from the server by just forwarding the message to the user. */ @@ -237,6 +242,7 @@ static const std::unordered_map irc_callbacks = { {"TOPIC", &IrcClient::on_topic_received}, {"366", &IrcClient::on_channel_completely_joined}, {"432", &IrcClient::on_erroneous_nickname}, + {"433", &IrcClient::on_nickname_conflict}, {"461", &IrcClient::on_generic_error}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 3c37eb1..1cc4c25 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -20,6 +20,7 @@ #define DISCO_ITEMS_NS DISCO_NS"#items" #define DISCO_INFO_NS DISCO_NS"#info" #define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" +#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): served_hostname(hostname), @@ -195,8 +196,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) const std::string own_nick = bridge->get_own_nick(iid); if (!own_nick.empty() && own_nick != to.resource) bridge->send_irc_nick_change(iid, to.resource); - else - bridge->join_irc_channel(iid, to.resource); + bridge->join_irc_channel(iid, to.resource); } else if (type == "unavailable") { @@ -479,3 +479,28 @@ void XmppComponent::kick_user(const std::string& muc_name, presence.close(); this->send_stanza(presence); } + +void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; + presence["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_NS; + x.close(); + presence.add_child(std::move(x)); + XmlNode error("error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = "cancel"; + error["code"] = "409"; + XmlNode conflict("conflict"); + conflict["xmlns"] = STANZA_NS; + conflict.close(); + error.add_child(std::move(conflict)); + error.close(); + presence.add_child(std::move(error)); + presence.close(); + this->send_stanza(presence); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index d76a2c3..63eb88f 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -107,6 +107,12 @@ public: const std::string& reason, const std::string& author, const std::string& jid_to); + /** + * Send a presence type=error with a conflict element + */ + void send_nickname_conflict_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to); /** * Handle the various stanza types */ -- cgit v1.2.3 From a075517824da7e82e1be7b67d615834851482861 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Dec 2013 17:42:48 +0100 Subject: Basic isupport support CHANMODES and PREFIX only --- src/irc/irc_client.cpp | 29 ++++++++++++++++++++++++++++- src/irc/irc_client.hpp | 21 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 10a5b12..912ee45 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -16,7 +16,8 @@ IrcClient::IrcClient(const std::string& hostname, const std::string& username, B username(username), current_nick(username), bridge(bridge), - welcomed(false) + welcomed(false), + chanmodes({"", "", "", ""}) { } @@ -196,6 +197,32 @@ void IrcClient::forward_server_message(const IrcMessage& message) this->bridge->send_xmpp_message(this->hostname, from, body); } +void IrcClient::on_isupport_message(const IrcMessage& message) +{ + const size_t len = message.arguments.size(); + for (size_t i = 1; i < len; ++i) + { + const std::string token = message.arguments[i]; + if (token.substr(0, 10) == "CHANMODES=") + { + this->chanmodes = utils::split(token.substr(11), ','); + // make sure we have 4 strings + this->chanmodes.resize(4); + } + else if (token.substr(0, 7) == "PREFIX=") + { + size_t i = 8; // jump PREFIX=( + size_t j = 9; + // Find the ) char + while (j < token.size() && token[j] != ')') + j++; + j++; + while (j < token.size() && token[i] != ')') + this->prefix_to_mode[token[j++]] = token[i++]; + } + } +} + void IrcClient::send_gateway_message(const std::string& message, const std::string& from) { this->bridge->send_xmpp_message(this->hostname, from, message); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index eced4b6..a0258ee 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -8,7 +8,9 @@ #include #include +#include #include +#include class Bridge; @@ -109,6 +111,11 @@ public: * Forward the server message received from IRC to the XMPP component */ void forward_server_message(const IrcMessage& message); + /** + * When receiving the isupport informations. See + * http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt + */ + void on_isupport_message(const IrcMessage& message); /** * Just empty the motd we kept as a string */ @@ -208,11 +215,24 @@ private: */ std::vector channels_to_join; bool welcomed; + /** + * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.3 + * We store the possible chanmodes in this object. + * chanmodes[0] contains modes of type A, [1] of type B etc + */ + std::vector chanmodes; /** * Each motd line received is appended to this string, which we send when * the motd is completely received */ std::string motd; + /** + * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14 + * The example given would be transformed into + * modes_to_prefix = {{'a', '&'}, {'b', '*'}} + */ + std::map prefix_to_mode; + IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; IrcClient& operator=(const IrcClient&) = delete; @@ -229,6 +249,7 @@ static const std::unordered_map irc_callbacks = { {"NOTICE", &IrcClient::forward_server_message}, {"002", &IrcClient::forward_server_message}, {"003", &IrcClient::forward_server_message}, + {"005", &IrcClient::on_isupport_message}, {"RPL_MOTDSTART", &IrcClient::empty_motd}, {"375", &IrcClient::empty_motd}, {"RPL_MOTD", &IrcClient::on_motd_line}, -- cgit v1.2.3 From acf769d83a40e971ccc1346225688841465b36ee Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 29 Dec 2013 19:57:43 +0100 Subject: Use isupport informations to know the user modes when joining Also remove the duplicate send_self_join methods, user only send_user_join --- src/bridge/bridge.cpp | 18 ++++++++++------- src/bridge/bridge.hpp | 7 ++----- src/irc/irc_channel.cpp | 5 +++-- src/irc/irc_channel.hpp | 4 +++- src/irc/irc_client.cpp | 15 +++++++++----- src/irc/irc_user.cpp | 30 ++++++++++++++-------------- src/irc/irc_user.hpp | 6 +++++- src/xmpp/xmpp_component.cpp | 48 +++++++++++++-------------------------------- src/xmpp/xmpp_component.hpp | 9 ++++----- 9 files changed, 67 insertions(+), 75 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7f245db..fb3afc7 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -189,14 +189,18 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, - const IrcUser* user) + const IrcUser* user, + const bool self) { - this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host, this->user_jid); -} - -void Bridge::send_self_join(const std::string& hostname, const std::string& chan_name, const std::string nick) -{ - this->xmpp->send_self_join(chan_name + "%" + hostname, nick, this->user_jid); + std::string affiliation = "participant"; + std::string role = "none"; + if (user->modes.find('o') != user->modes.end()) + { + affiliation = "admin"; + role = "moderator"; + } + this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host, + affiliation, role, this->user_jid, self); } void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b5bee9e..a4eeaa4 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -66,11 +66,8 @@ public: */ void send_user_join(const std::string& hostname, const std::string& chan_name, - const IrcUser* user); - /** - * Send the self presence of an user when the MUC is fully joined. - */ - void send_self_join(const std::string& hostname, const std::string& chan_name, const std::string nick); + const IrcUser* user, + const bool self); /** * Send the topic of the MUC to the user */ diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 6daf708..80e9b24 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -12,9 +12,10 @@ void IrcChannel::set_self(const std::string& name) this->self = std::make_unique(name); } -IrcUser* IrcChannel::add_user(const std::string& name) +IrcUser* IrcChannel::add_user(const std::string& name, + const std::map prefix_to_mode) { - this->users.emplace_back(std::make_unique(name)); + this->users.emplace_back(std::make_unique(name, prefix_to_mode)); return this->users.back().get(); } diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index c4b6d2c..edb779a 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -5,6 +5,7 @@ #include #include #include +#include /** * Keep the state of a joined channel (the list of occupants with their @@ -19,7 +20,8 @@ public: std::string topic; void set_self(const std::string& name); IrcUser* get_self() const; - IrcUser* add_user(const std::string& name); + IrcUser* add_user(const std::string& name, + const std::map prefix_to_mode); IrcUser* find_user(const std::string& name); void remove_user(const IrcUser* user); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 912ee45..cdda2b5 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -235,11 +235,16 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) std::vector nicks = utils::split(message.arguments[3], ' '); for (const std::string& nick: nicks) { - const IrcUser* user = channel->add_user(nick); + const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); if (user->nick != channel->get_self()->nick) { log_debug("Adding user [" << nick << "] to chan " << chan_name); - this->bridge->send_user_join(this->hostname, chan_name, user); + this->bridge->send_user_join(this->hostname, chan_name, user, false); + } + else + { + // we now know the modes of self, so copy the modes into self + channel->get_self()->modes = user->modes; } } } @@ -256,8 +261,8 @@ void IrcClient::on_channel_join(const IrcMessage& message) } else { - const IrcUser* user = channel->add_user(nick); - this->bridge->send_user_join(this->hostname, chan_name, user); + const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); + this->bridge->send_user_join(this->hostname, chan_name, user, false); } } @@ -319,7 +324,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); - this->bridge->send_self_join(this->hostname, chan_name, channel->get_self()->nick); + this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(), true); this->bridge->send_topic(this->hostname, chan_name, channel->topic); } diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index f9866ef..934988a 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -2,26 +2,26 @@ #include -IrcUser::IrcUser(const std::string& name) +IrcUser::IrcUser(const std::string& name, + const std::map& prefix_to_mode) { + if (name.empty()) + return ; const std::string::size_type sep = name.find("!"); + const std::map::const_iterator prefix = prefix_to_mode.find(name[0]); + const size_t name_begin = prefix == prefix_to_mode.end()? 0: 1; if (sep == std::string::npos) - { - if (name[0] == '~' || name[0] == '&' - || name[0] == '@' || name[0] == '%' - || name[0] == '+') - this->nick = name.substr(1); - else - this->nick = name; - } + this->nick = name.substr(name_begin); else { - if (name[0] == '~' || name[0] == '&' - || name[0] == '@' || name[0] == '%' - || name[0] == '+') - this->nick = name.substr(1, sep); - else - this->nick = name.substr(0, sep); + this->nick = name.substr(name_begin, sep); this->host = name.substr(sep+1); } + if (prefix != prefix_to_mode.end()) + this->modes.insert(prefix->second); +} + +IrcUser::IrcUser(const std::string& name): + IrcUser(name, {}) +{ } diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp index b76b2ef..f30da4d 100644 --- a/src/irc/irc_user.hpp +++ b/src/irc/irc_user.hpp @@ -2,6 +2,8 @@ # define IRC_USER_INCLUDED #include +#include +#include /** * Keeps various information about one IRC channel user @@ -9,10 +11,12 @@ class IrcUser { public: + explicit IrcUser(const std::string& name, + const std::map& prefix_to_mode); explicit IrcUser(const std::string& name); - std::string nick; std::string host; + std::set modes; private: IrcUser(const IrcUser&) = delete; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1cc4c25..f0d1d3a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -294,7 +294,10 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con void XmppComponent::send_user_join(const std::string& from, const std::string& nick, const std::string& realjid, - const std::string& to) + const std::string& affiliation, + const std::string& role, + const std::string& to, + const bool self) { XmlNode node("presence"); node["to"] = to; @@ -305,8 +308,8 @@ void XmppComponent::send_user_join(const std::string& from, // TODO: put real values here XmlNode item("item"); - item["affiliation"] = "member"; - item["role"] = "participant"; + item["affiliation"] = affiliation; + item["role"] = role; if (!realjid.empty()) { const std::string preped_jid = jidprep(realjid); @@ -315,35 +318,15 @@ void XmppComponent::send_user_join(const std::string& from, } item.close(); x.add_child(std::move(item)); - x.close(); - node.add_child(std::move(x)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_self_join(const std::string& from, const std::string& nick, const std::string& to) -{ - XmlNode node("presence"); - node["to"] = to; - node["from"] = from + "@" + this->served_hostname + "/" + nick; - - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - - // TODO: put real values here - XmlNode item("item"); - item["affiliation"] = "member"; - item["role"] = "participant"; - item.close(); - x.add_child(std::move(item)); - - XmlNode status("status"); - status["code"] = "110"; - status.close(); - x.add_child(std::move(status)); + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + status.close(); + x.add_child(std::move(status)); + } x.close(); - node.add_child(std::move(x)); node.close(); this->send_stanza(node); @@ -438,10 +421,7 @@ void XmppComponent::send_nick_change(const std::string& muc_name, const std::str presence.close(); this->send_stanza(presence); - if (self) - this->send_self_join(muc_name, new_nick, jid_to); - else - this->send_user_join(muc_name, new_nick, "", jid_to); + this->send_user_join(muc_name, new_nick, "", "participant", "none", jid_to, self); } void XmppComponent::kick_user(const std::string& muc_name, diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 63eb88f..c10f10a 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -78,11 +78,10 @@ public: void send_user_join(const std::string& from, const std::string& nick, const std::string& realjid, - const std::string& to); - /** - * Send the self join to the user - */ - void send_self_join(const std::string& from, const std::string& nick, const std::string& to); + const std::string& affiliation, + const std::string& role, + const std::string& to, + const bool self); /** * Send the MUC topic to the user */ -- cgit v1.2.3 From baf03a7e20d30698a06ccf03cd93b15317de340e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 29 Dec 2013 19:59:50 +0100 Subject: Do not throw an exception when recv returns and error, just close the socket --- src/network/socket_handler.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 484aa8f..4dab214 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -75,7 +75,11 @@ void SocketHandler::on_recv(const size_t nb) this->close(); } else if (-1 == static_cast(size)) - throw std::runtime_error("Error reading from socket"); + { + log_warning("Error while reading from socket: " << strerror(errno)); + this->on_connection_close(); + this->close(); + } else { this->in_buf += std::string(buf, size); -- cgit v1.2.3 From e840704b58a984351971e8034e74f5e9fdfaf114 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 4 Jan 2014 01:30:03 +0100 Subject: Convert received modes into roles and affiliations --- doc/biboumi.md | 12 +++++--- src/bridge/bridge.cpp | 32 +++++++++++++++++++ src/bridge/bridge.hpp | 4 +++ src/irc/irc_channel.cpp | 2 +- src/irc/irc_channel.hpp | 2 +- src/irc/irc_client.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++- src/irc/irc_client.hpp | 7 ++++- src/irc/irc_user.cpp | 20 ++++++++++++ src/irc/irc_user.hpp | 4 +++ src/xmpp/xmpp_component.cpp | 22 +++++++++++++ src/xmpp/xmpp_component.hpp | 9 ++++++ 11 files changed, 181 insertions(+), 8 deletions(-) diff --git a/doc/biboumi.md b/doc/biboumi.md index 811b5c0..35755ef 100644 --- a/doc/biboumi.md +++ b/doc/biboumi.md @@ -184,17 +184,21 @@ notified of this XMPP event as well. For example if a mode “+o toto” is received, then toto’s role will be changed to moderator. The mapping between IRC modes and XMPP features is as follow: +`+a` + + Sets the participant’s role to `moderator` and its affiliation to `owner`. + `+o` - Sets the participant’s role to `moderator`. + Sets the participant’s role to `moderator` and its affiliation to `admin`. -`+a` +`+h` - Sets the participant’s role to `admin`. + Sets the participant’s role to `moderator` and its affiliation to `member`. `+v` - Sets the participant’s affiliation to `member`. + Sets the participant’s role to `participant` and its affiliation to `member`. SECURITY -------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index fb3afc7..d034bcd 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -225,3 +225,35 @@ void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nic { this->xmpp->send_nickname_conflict_error(iid.chan + "%" + iid.server, nickname, this->user_jid); } + +void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) +{ + std::string role; + std::string affiliation; + if (mode == 0) + { + role = "participant"; + affiliation = "none"; + } + else if (mode == 'a') + { + role = "moderator"; + affiliation = "owner"; + } + else if (mode == 'o') + { + role = "moderator"; + affiliation = "admin"; + } + else if (mode == 'h') + { + role = "moderator"; + affiliation = "member"; + } + else if (mode == 'v') + { + role = "participant"; + affiliation = "member"; + } + this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index a4eeaa4..7e881d9 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -87,6 +87,10 @@ public: void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self); void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); void send_nickname_conflict_error(const Iid& iid, const std::string& nickname); + /** + * Send a role/affiliation change, matching the change of mode for that user + */ + void send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode); /** * Misc diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 80e9b24..99783fa 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -24,7 +24,7 @@ IrcUser* IrcChannel::get_self() const return this->self.get(); } -IrcUser* IrcChannel::find_user(const std::string& name) +IrcUser* IrcChannel::find_user(const std::string& name) const { IrcUser user(name); for (const auto& u: this->users) diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index edb779a..0160469 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -22,7 +22,7 @@ public: IrcUser* get_self() const; IrcUser* add_user(const std::string& name, const std::map prefix_to_mode); - IrcUser* find_user(const std::string& name); + IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); private: diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index cdda2b5..8a57371 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -218,7 +218,10 @@ void IrcClient::on_isupport_message(const IrcMessage& message) j++; j++; while (j < token.size() && token[i] != ')') - this->prefix_to_mode[token[j++]] = token[i++]; + { + this->sorted_user_modes.push_back(token[i]); + this->prefix_to_mode[token[j++]] = token[i++]; + } } } } @@ -508,6 +511,76 @@ void IrcClient::on_channel_mode(const IrcMessage& message) this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan + " [" + mode_arguments + "] by " + user.nick, true); + const IrcChannel* channel = this->get_channel(iid.chan); + if (!channel) + return; + + // parse the received modes, we need to handle things like "+m-oo coucou toutou" + const std::string modes = message.arguments[1]; + // a list of modified IrcUsers. When we applied all modes, we check the + // modes that now applies to each of them, and send a notification for + // each one. This is to disallow sending two notifications or more when a + // single MODE command changes two or more modes on the same participant + std::set modified_users; + // If it is true, the modes are added, if it’s false they are + // removed. When we encounter the '+' char, the value is changed to true, + // and with '-' it is changed to false. + bool add = true; + bool use_arg; + size_t arg_pos = 2; + for (const char c: modes) + { + if (c == '+') + add = true; + else if (c == '-') + add = false; + else + { // lookup the mode symbol in the 4 chanmodes lists, depending on + // the list where it is found, it takes an argument or not + size_t type; + for (type = 0; type < 4; ++type) + if (this->chanmodes[type].find(c) != std::string::npos) + break; + if (type == 4) // if mode was not found + { + // That mode can also be of type B if it is present in the + // prefix_to_mode map + for (const std::pair& pair: this->prefix_to_mode) + if (pair.second == c) + { + type = 1; + break; + } + } + // modes of type A, B or C (but only with add == true) + if (type == 0 || type == 1 || + (type == 2 && add == true)) + use_arg = true; + else // modes of type C (but only with add == false), D, or unknown + use_arg = false; + if (use_arg == true && message.arguments.size() > arg_pos) + { + const std::string target = message.arguments[arg_pos++]; + IrcUser* user = channel->find_user(target); + if (!user) + { + log_warning("Trying to set mode for non-existing user '" << target + << "' in channel" << iid.chan); + return; + } + if (add) + user->add_mode(c); + else + user->remove_mode(c); + modified_users.insert(user); + } + } + } + for (const IrcUser* u: modified_users) + { + char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes); + this->bridge->send_affiliation_role_change(iid, u->nick, most_significant_mode); + } } void IrcClient::on_user_mode(const IrcMessage& message) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index a0258ee..96ded44 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -229,9 +229,14 @@ private: /** * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14 * The example given would be transformed into - * modes_to_prefix = {{'a', '&'}, {'b', '*'}} + * modes_to_prefix = {{'&', 'a'}, {'*', 'b'}} */ std::map prefix_to_mode; + /** + * Available user modes, sorted from most significant to least significant + * (for example 'ahov' is a common order). + */ + std::vector sorted_user_modes; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index 934988a..0f1b1ee 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -25,3 +25,23 @@ IrcUser::IrcUser(const std::string& name): IrcUser(name, {}) { } + +void IrcUser::add_mode(const char mode) +{ + this->modes.insert(mode); +} + +void IrcUser::remove_mode(const char mode) +{ + this->modes.erase(mode); +} + +char IrcUser::get_most_significant_mode(const std::vector& modes) const +{ + for (const char mode: modes) + { + if (this->modes.find(mode) != this->modes.end()) + return mode; + } + return 0; +} diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp index f30da4d..d3397ca 100644 --- a/src/irc/irc_user.hpp +++ b/src/irc/irc_user.hpp @@ -1,6 +1,7 @@ #ifndef IRC_USER_INCLUDED # define IRC_USER_INCLUDED +#include #include #include #include @@ -14,6 +15,9 @@ public: explicit IrcUser(const std::string& name, const std::map& prefix_to_mode); explicit IrcUser(const std::string& name); + void add_mode(const char mode); + void remove_mode(const char mode); + char get_most_significant_mode(const std::vector& sorted_user_modes) const; std::string nick; std::string host; std::set modes; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index f0d1d3a..a0054ea 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -484,3 +484,25 @@ void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, presence.close(); this->send_stanza(presence); } + +void XmppComponent::send_affiliation_role_change(const std::string& muc_name, + const std::string& target, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; + presence["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["affiliation"] = affiliation; + item["role"] = role; + item.close(); + x.add_child(std::move(item)); + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index c10f10a..f86d930 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -112,6 +112,15 @@ public: void send_nickname_conflict_error(const std::string& muc_name, const std::string& nickname, const std::string& jid_to); + /** + * Send a presence from the MUC indicating a change in the role and/or + * affiliation of a participant + */ + void send_affiliation_role_change(const std::string& muc_name, + const std::string& target, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to); /** * Handle the various stanza types */ -- cgit v1.2.3 From fbec16f1a208881ea49923287aae27978d79681e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 4 Jan 2014 01:53:50 +0100 Subject: Possibility to change a channel's topic --- src/bridge/bridge.cpp | 7 +++++++ src/bridge/bridge.hpp | 1 + src/irc/irc_client.cpp | 5 +++++ src/irc/irc_client.hpp | 1 + src/xmpp/xmpp_component.cpp | 3 +++ 5 files changed, 17 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d034bcd..dae5a72 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -156,6 +156,13 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: irc->send_kick_command(iid.chan, target, reason); } +void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) +{ + IrcClient* irc = this->get_irc_client(iid.server); + if (irc) + irc->send_topic_command(iid.chan, subject); +} + void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 7e881d9..75708e5 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -49,6 +49,7 @@ public: void leave_irc_channel(Iid&& iid, std::string&& status_message); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason); + void set_channel_topic(const Iid& iid, const std::string& subject); /*** ** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 8a57371..644cfd1 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -122,6 +122,11 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin this->send_message(IrcMessage("KICK", {chan_name, target, reason})); } +void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic) +{ + this->send_message(IrcMessage("TOPIC", {chan_name, topic})); +} + void IrcClient::send_quit_command() { this->send_message(IrcMessage("QUIT", {"gateway shutdown"})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 96ded44..a058b19 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -97,6 +97,7 @@ public: * Send the KICK irc command */ void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason); + void send_topic_command(const std::string& chan_name, const std::string& topic); /** * Send the QUIT irc command */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index a0054ea..b370daa 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -217,6 +217,9 @@ void XmppComponent::handle_message(const Stanza& stanza) if (to.resource.empty()) if (body && !body->get_inner().empty()) bridge->send_channel_message(iid, body->get_inner()); + XmlNode* subject = stanza.get_child(COMPONENT_NS":subject"); + if (subject) + bridge->set_channel_topic(iid, subject->get_inner()); } else { -- cgit v1.2.3 From d90c1978def803390203f0fada3621de8abba0e5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 4 Jan 2014 01:54:09 +0100 Subject: Fix a bug when receiving a topic change The number of arguments is not always the same --- src/irc/irc_client.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 644cfd1..1e8e326 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -321,9 +321,11 @@ void IrcClient::send_motd(const IrcMessage& message) void IrcClient::on_topic_received(const IrcMessage& message) { - const std::string chan_name = utils::tolower(message.arguments[1]); + if (message.arguments.size() < 2) + return; + const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]); IrcChannel* channel = this->get_channel(chan_name); - channel->topic = message.arguments[2]; + channel->topic = message.arguments[message.arguments.size() - 1]; if (channel->joined) this->bridge->send_topic(this->hostname, chan_name, channel->topic); } -- cgit v1.2.3 From b51368304140ea227dbbb71357a2fe9d7fd9057a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 4 Jan 2014 02:04:38 +0100 Subject: Document the shutdown procedure --- doc/biboumi.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/biboumi.md b/doc/biboumi.md index 35755ef..4bbbcf7 100644 --- a/doc/biboumi.md +++ b/doc/biboumi.md @@ -73,6 +73,14 @@ the same IRC connection is used. If, however, an other user wants to join an IRC channel on that same IRC server, biboumi opens a new connection to that server. Biboumi connects once to each IRC server, for each user on it. +To cleanly shutdown the component, send the SIGINT or SIGTERM signals to it. +It will send messages to all connected IRC and XMPP servers to indicate a +reason why the users are being disconnected. Biboumi exits when all +connections are closed because the remote aknowledged the end of +communication. If the remote server does not respond, biboumi does not +exit, unless SIGINT or SIGTERM is received again, in which case biboumi +closes the TCP connections and exits immediately. + ### Addressing IRC entities are represented by XMPP JIDs. The domain part of the JID is -- cgit v1.2.3 From 7701c907636d79f55e033aa7adaf7ffae4150c91 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 4 Jan 2014 02:48:08 +0100 Subject: Add missing errno.h includes --- src/network/poller.cpp | 1 + src/network/socket_handler.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 919ceb0..4c72192 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 4dab214..998b10d 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From fef9c8193ddf8cdf81978874be788af9441e2286 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 6 Jan 2014 01:22:43 +0100 Subject: Also set the role and affiliation of users already in the chan --- src/bridge/bridge.cpp | 56 ++++++++++++++++++++++---------------------------- src/bridge/bridge.hpp | 1 + src/irc/irc_client.cpp | 12 ++++++++--- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index dae5a72..be0d270 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -6,6 +6,7 @@ #include #include #include +#include static const char* action_prefix = "\01ACTION "; static const size_t action_prefix_len = 8; @@ -21,6 +22,22 @@ Bridge::~Bridge() { } +/** + * Return the role and affiliation, corresponding to the given irc mode */ +static std::tuple get_role_affiliation_from_irc_mode(const char mode) +{ + if (mode == 'a' || mode == 'q') + return std::make_tuple("moderator", "owner"); + else if (mode == 'o') + return std::make_tuple("moderator", "admin"); + else if (mode == 'h') + return std::make_tuple("moderator", "member"); + else if (mode == 'v') + return std::make_tuple("participant", "member"); + else + return std::make_tuple("participant", "none"); +} + void Bridge::shutdown() { for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) @@ -197,15 +214,13 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, const IrcUser* user, + const char user_mode, const bool self) { - std::string affiliation = "participant"; - std::string role = "none"; - if (user->modes.find('o') != user->modes.end()) - { - affiliation = "admin"; - role = "moderator"; - } + std::string affiliation; + std::string role; + std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); + this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host, affiliation, role, this->user_jid, self); } @@ -237,30 +252,7 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar { std::string role; std::string affiliation; - if (mode == 0) - { - role = "participant"; - affiliation = "none"; - } - else if (mode == 'a') - { - role = "moderator"; - affiliation = "owner"; - } - else if (mode == 'o') - { - role = "moderator"; - affiliation = "admin"; - } - else if (mode == 'h') - { - role = "moderator"; - affiliation = "member"; - } - else if (mode == 'v') - { - role = "participant"; - affiliation = "member"; - } + + std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 75708e5..c43b049 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -68,6 +68,7 @@ public: void send_user_join(const std::string& hostname, const std::string& chan_name, const IrcUser* user, + const char user_mode, const bool self); /** * Send the topic of the MUC to the user diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1e8e326..644351e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -247,7 +247,9 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) if (user->nick != channel->get_self()->nick) { log_debug("Adding user [" << nick << "] to chan " << chan_name); - this->bridge->send_user_join(this->hostname, chan_name, user, false); + this->bridge->send_user_join(this->hostname, chan_name, user, + user->get_most_significant_mode(this->sorted_user_modes), + false); } else { @@ -270,7 +272,9 @@ void IrcClient::on_channel_join(const IrcMessage& message) else { const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); - this->bridge->send_user_join(this->hostname, chan_name, user, false); + this->bridge->send_user_join(this->hostname, chan_name, user, + user->get_most_significant_mode(this->sorted_user_modes), + false); } } @@ -334,7 +338,9 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); - this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(), true); + this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(), + channel->get_self()->get_most_significant_mode(this->sorted_user_modes), + true); this->bridge->send_topic(this->hostname, chan_name, channel->topic); } -- cgit v1.2.3 From 48b4f7b83f9f8e6d89d56f5e24f0ed1d7c4676a1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 12 Jan 2014 04:12:27 +0100 Subject: Remove cryptopp dependency, directly include a simple sha1 implementation --- CMakeLists.txt | 6 +- INSTALL | 5 -- cmake/Modules/FindCryptopp.cmake | 53 -------------- src/utils/sha1.cpp | 154 +++++++++++++++++++++++++++++++++++++++ src/utils/sha1.hpp | 35 +++++++++ src/xmpp/xmpp_component.cpp | 25 ++++--- 6 files changed, 204 insertions(+), 74 deletions(-) delete mode 100644 cmake/Modules/FindCryptopp.cmake create mode 100644 src/utils/sha1.cpp create mode 100644 src/utils/sha1.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ac93ff1..433b965 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE ## Look for external libraries # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") -find_package(Cryptopp REQUIRED) find_package(Iconv REQUIRED) include(FindEXPAT) find_package(EXPAT REQUIRED) @@ -25,9 +24,6 @@ find_package(Libidn) include_directories("src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) -# the SYSTEM flag tells the compiler that we don't care about warnings -# coming from these headers. -include_directories(SYSTEM ${CRYPTO++_INCLUDE_DIRS}) if(LIBIDN_FOUND) include_directories(${LIBIDN_INCLUDE_DIRS}) @@ -90,7 +86,7 @@ file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp bridge network utils logger - ${CRYPTO++_LIBRARIES} ${EXPAT_LIBRARIES} pthread) + ${EXPAT_LIBRARIES} pthread) if(LIBIDN_FOUND) target_link_libraries(xmpp ${LIBIDN_LIBRARIES}) endif() diff --git a/INSTALL b/INSTALL index 94b62bc..8de0fa6 100644 --- a/INSTALL +++ b/INSTALL @@ -7,11 +7,6 @@ Build and runtime dependencies: Libraries: -- crypto++ - Sometimes packaged as cryptopp. This library is used to generate MD5 - hashes. - http://www.cryptopp.com/ - - expat Used to parse XML from the XMPP server. http://expat.sourceforge.net/ diff --git a/cmake/Modules/FindCryptopp.cmake b/cmake/Modules/FindCryptopp.cmake deleted file mode 100644 index 9835b6f..0000000 --- a/cmake/Modules/FindCryptopp.cmake +++ /dev/null @@ -1,53 +0,0 @@ -# - Find Crypto++ -# Find the Crypto++ library -# -# This module defines the following variables: -# CRYPTO++_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# CRYPTO++_LIBRARIES - Where to find the library file -# CRYPTO++_INCLUDE_DIRS - The directory where to find the header files -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to way to write them. -# -# CRYPTOPP_FOUND -# -# CRYPTO++_LIBRARY -# CRYPTOPP_LIBRARY -# CRYPTOPP_LIBRARIES -# -# CRYPTO++_INCLUDE_DIR -# CRYPTOPP_INCLUDE_DIRS -# CRYPTOPP_INCLUDE_DIR -# -# This file is in the public domain. - -find_path(CRYPTO++_INCLUDE_DIRS NAMES cryptlib.h - PATH_SUFFIXES "crypto++" "cryptopp" - DOC "The Crypto++ include directory") - -find_library(CRYPTO++_LIBRARIES NAMES cryptopp - DOC "The Crypto++ library") - -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set CRYPTO++_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Crypto++ REQUIRED_VARS CRYPTO++_LIBRARIES CRYPTO++_INCLUDE_DIRS) - -# Compatibility for all the ways of writing these variables -if(CRYPTO++_FOUND) - set(CRYPTOPP_FOUND ${CRYPTO++_FOUND}) - - set(CRYPTO++_LIBRARY ${CRYPTO++_LIBRARIES}) - set(CRYPTOPP_LIBRARY ${CRYPTO++_LIBRARIES}) - set(CRYPTOPP_LIBRARIES ${CRYPTO++_LIBRARIES}) - - set(CRYPTO++_INCLUDE_DIR ${CRYPTO++_INCLUDE_DIRS}) - set(CRYPTOPP_INCLUDE_DIR ${CRYPTO++_INCLUDE_DIRS}) - set(CRYPTOPP_INCLUDE_DIRS ${CRYPTO++_INCLUDE_DIRS}) -endif() - -mark_as_advanced(CRYPTO++_INCLUDE_DIRS CRYPTO++_LIBRARIES) - - diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp new file mode 100644 index 0000000..76476df --- /dev/null +++ b/src/utils/sha1.cpp @@ -0,0 +1,154 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include "sha1.hpp" + +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +const uint8_t sha1InitState[] = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void sha1_init(sha1nfo *s) { + memcpy(s->state.b,sha1InitState,HASH_LENGTH); + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state.w[0]; + b=s->state.w[1]; + c=s->state.w[2]; + d=s->state.w[3]; + e=s->state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; + s->buffer.w[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state.w[0] += a; + s->state.w[1] += b; + s->state.w[2] += c; + s->state.w[3] += d; + s->state.w[4] += e; +} + +void sha1_addUncounted(sha1nfo *s, uint8_t data) { + s->buffer.b[s->bufferOffset ^ 3] = data; + s->bufferOffset++; + if (s->bufferOffset == BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + int i; + // Pad to complete the last block + sha1_pad(s); + + // Swap byte order back + for (i=0; i<5; i++) { + uint32_t a,b; + a=s->state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + s->state.w[i]=b; + } + + // 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; ikeyBuffer[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; ikeyBuffer[i] ^ HMAC_OPAD); + for (i=0; iinnerHash[i]); + return sha1_result(s); +} diff --git a/src/utils/sha1.hpp b/src/utils/sha1.hpp new file mode 100644 index 0000000..d02de75 --- /dev/null +++ b/src/utils/sha1.hpp @@ -0,0 +1,35 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include +#include + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; + +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +typedef struct sha1nfo { + union _buffer buffer; + uint8_t bufferOffset; + union _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +} sha1nfo; + +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/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index b370daa..1ef2f37 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -4,12 +4,11 @@ #include #include +#include + #include -// CryptoPP -#include -#include -#include +#include #define STREAM_NS "http://etherx.jabber.org/streams" #define COMPONENT_NS "jabber:component:accept" @@ -119,13 +118,17 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) } // Try to authenticate - CryptoPP::SHA1 sha1; - std::string digest; - CryptoPP::StringSource foo(this->stream_id + this->secret, true, - new CryptoPP::HashFilter(sha1, - new CryptoPP::HexEncoder( - new CryptoPP::StringSink(digest), false))); - Stanza handshake("handshake", nullptr); + 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'; + + Stanza handshake("handshake"); handshake.set_inner(digest); handshake.close(); this->send_stanza(handshake); -- cgit v1.2.3 From af2d232059cd1ede246019f951ee9de89ea4b767 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 12 Jan 2014 04:13:14 +0100 Subject: Little README fixes --- README | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README b/README index 8a4f3b3..eec7480 100644 --- a/README +++ b/README @@ -4,14 +4,13 @@ | |_) | | |_) | (_) | |_| | | | | | | | |_.__/|_|_.__/ \___/ \__,_|_| |_| |_|_| - -Homepage: http://dev.louiz.org/projects/biboumi +Homepage: http://biboumi.louiz.org Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. -It is written in modern C++11 and make great efforts to have as little +It is written in modern C++11 and makes great efforts to have as little dependencies and to be as simple as possible. The goal is to provide a way to access most of IRC features using any XMPP @@ -42,7 +41,7 @@ Jabber ChatRoom: biboumi@muc.poezio.eu Report a bug: http://dev.louiz.org/projects/biboumi/issues/new ================= - License + Licence ================= Biboumi is Free Software. (learn more: http://www.gnu.org/philosophy/free-sw.html) -- cgit v1.2.3 From feb77bbdadf6658e9dfdd7c7eac05850ea5fa7b8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 13 Jan 2014 20:14:12 +0100 Subject: Do not change the affiliation/role to "none"/"participant" when changing nick fixes #2436 --- src/xmpp/xmpp_component.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1ef2f37..6fec72c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -312,10 +312,11 @@ void XmppComponent::send_user_join(const std::string& from, XmlNode x("x"); x["xmlns"] = MUC_USER_NS; - // TODO: put real values here XmlNode item("item"); - item["affiliation"] = affiliation; - item["role"] = role; + if (!affiliation.empty()) + item["affiliation"] = affiliation; + if (!role.empty()) + item["role"] = role; if (!realjid.empty()) { const std::string preped_jid = jidprep(realjid); @@ -427,7 +428,7 @@ void XmppComponent::send_nick_change(const std::string& muc_name, const std::str presence.close(); this->send_stanza(presence); - this->send_user_join(muc_name, new_nick, "", "participant", "none", jid_to, self); + this->send_user_join(muc_name, new_nick, "", "", "", jid_to, self); } void XmppComponent::kick_user(const std::string& muc_name, -- cgit v1.2.3 From d1c6d64546f5b139ec7946696602be5fe9fca66e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 13 Jan 2014 20:19:10 +0100 Subject: Add missing stdexcept includes --- src/network/poller.cpp | 1 + src/network/socket_handler.cpp | 1 + src/utils/encoding.cpp | 2 ++ src/xmpp/xmpp_stanza.cpp | 1 + 4 files changed, 5 insertions(+) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 4c72192..66b73f8 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -6,6 +6,7 @@ #include #include +#include Poller::Poller() { diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 998b10d..67adacd 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index 76d1922..fa4958b 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -3,6 +3,8 @@ #include +#include + #include #include #include diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index b6f5a23..23b2d25 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -2,6 +2,7 @@ #include +#include #include #include -- cgit v1.2.3 From b71547a32b9beefbe24465a5ba092d2f9b3b04e5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 14 Jan 2014 21:45:56 +0100 Subject: Fix the build system to correctly include config.h --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 433b965..222c609 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ include(FindEXPAT) find_package(EXPAT REQUIRED) find_package(Libidn) +# To be able to include the config.h file generated by cmake +include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories("src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) @@ -123,4 +125,4 @@ target_link_libraries(test config logger) -configure_file(config.h.cmake src/config.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) \ No newline at end of file -- cgit v1.2.3 From de7b3503a47ea7d5a6177359f6c799c2dd5e586e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 28 Jan 2014 03:26:18 +0100 Subject: Jidprep also handles the resource part --- src/test.cpp | 11 ++++++++--- src/xmpp/jid.cpp | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index c9427f0..b95b379 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -161,10 +161,15 @@ int main() assert(jid2.domain == "ツ.coucou"); assert(jid2.resource == "coucou@coucou/coucou"); - // Nodeprep - const std::string& badjid("~louiz@EpiK-7D9D1FDE.poez.io"); + // Jidprep + const std::string& badjid("~zigougou@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt"); const std::string correctjid = jidprep(badjid); - assert(correctjid == "~louiz@epik-7d9d1fde.poez.io"); + assert(correctjid == "~zigougou@epik-7d9d1fde.poez.io/Boujour/coucou/slt"); + + const std::string& badjid2("Zigougou@poez.io"); + const std::string correctjid2 = jidprep(badjid2); + std::cout << correctjid2 << std::endl; + assert(correctjid2 == "zigougou@poez.io"); /** * Config diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index 4f9917b..e3b0c8f 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -59,7 +59,22 @@ std::string jidprep(const std::string& original) return ""; } - return std::string(local) + "@" + domain; + // If there is no resource, stop here + if (jid.resource.empty()) + return std::string(local) + "@" + domain; + + // Otherwise, also process the resource part + char resource[max_jid_part_len] = {}; + memcpy(resource, jid.resource.data(), jid.resource.size()); + rc = static_cast(::stringprep(resource, max_jid_part_len, + static_cast(0), stringprep_xmpp_resourceprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + return std::string(local) + "@" + domain + "/" + resource; + #else (void)original; return ""; -- cgit v1.2.3 From c8b41d5e350354881caa933ab363a2e90f991524 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 07:52:22 +0100 Subject: Be verbose about IRC server connection failures, and handle them properly --- src/irc/irc_client.cpp | 7 ++++++- src/network/socket_handler.cpp | 19 +++++++++++-------- src/network/socket_handler.hpp | 3 ++- src/xmpp/xmpp_component.cpp | 2 +- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 644351e..a84f96e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -27,7 +27,12 @@ IrcClient::~IrcClient() void IrcClient::start() { - this->connect(this->hostname, "6667"); + this->bridge->send_xmpp_message(this->hostname, "", std::string("Connecting to ") + + this->hostname + ":" + "6667"); + std::pair res = this->connect(this->hostname, "6667"); + if (!res.first) + this->bridge->send_xmpp_message(this->hostname, "", + std::string("Connection failed: ") + res.second); } void IrcClient::on_connected() diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 67adacd..ea674d1 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -23,7 +23,7 @@ SocketHandler::SocketHandler(): throw std::runtime_error("Could not create socket"); } -bool SocketHandler::connect(const std::string& address, const std::string& port) +std::pair SocketHandler::connect(const std::string& address, const std::string& port) { log_info("Trying to connect to " << address << ":" << port); struct addrinfo hints; @@ -35,15 +35,18 @@ bool SocketHandler::connect(const std::string& address, const std::string& port) struct addrinfo* addr_res; const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - // Make sure the alloced structure is always freed at the end of the - // function - utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); if (res != 0) { - perror("getaddrinfo"); - throw std::runtime_error("getaddrinfo failed"); + log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); + this->close(); + return std::make_pair(false, gai_strerror(res)); } + + // Make sure the alloced structure is always freed at the end of the + // function + utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) { if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) @@ -51,14 +54,14 @@ bool SocketHandler::connect(const std::string& address, const std::string& port) log_info("Connection success."); this->connected = true; this->on_connected(); - return true; + return std::make_pair(true, ""); } log_info("Connection failed:"); perror("connect"); } log_error("All connection attempts failed."); this->close(); - return false; + return std::make_pair(false, ""); } void SocketHandler::set_poller(Poller* poller) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index ec63ea7..c27d44c 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -2,6 +2,7 @@ # define SOCKET_HANDLER_INCLUDED #include +#include typedef int socket_t; @@ -21,7 +22,7 @@ public: /** * Connect to the remote server, and call on_connected() if this succeeds */ - bool connect(const std::string& address, const std::string& port); + std::pair connect(const std::string& address, const std::string& port); /** * Set the pointer to the given Poller, to communicate with it. */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 6fec72c..92c614c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -49,7 +49,7 @@ XmppComponent::~XmppComponent() bool XmppComponent::start() { - return this->connect("127.0.0.1", "5347"); + return this->connect("127.0.0.1", "5347").first; } bool XmppComponent::is_document_open() const -- cgit v1.2.3 From da85617428af0c35e9c5e7db21ec8a613ddd8b7c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 07:59:02 +0100 Subject: Use strerror and log_error instead of perrror --- src/network/poller.cpp | 14 +++++++------- src/network/socket_handler.cpp | 5 ++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 66b73f8..a157da9 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -16,7 +16,7 @@ Poller::Poller() this->epfd = ::epoll_create1(0); if (this->epfd == -1) { - perror("epoll"); + log_error("epoll failed: " << strerror(errno)); throw std::runtime_error("Could not create epoll instance"); } #endif @@ -47,7 +47,7 @@ void Poller::add_socket_handler(std::shared_ptr socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); if (res == -1) { - perror("epoll_ctl"); + log_error("epoll_ctl failed: " << strerror(errno)); throw std::runtime_error("Could not add socket to epoll"); } #endif @@ -79,7 +79,7 @@ void Poller::remove_socket_handler(const socket_t socket) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); if (res == -1) { - perror("epoll_ctl"); + log_error("epoll_ctl failed: " << strerror(errno)); throw std::runtime_error("Could not remove socket from epoll"); } #endif @@ -102,7 +102,7 @@ void Poller::watch_send_events(SocketHandler* socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { - perror("epoll_ctl"); + log_error("epoll_ctl failed: " << strerror(errno)); throw std::runtime_error("Could not modify socket flags in epoll"); } #endif @@ -125,7 +125,7 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { - perror("epoll_ctl"); + log_error("epoll_ctl failed: " << strerror(errno)); throw std::runtime_error("Could not modify socket flags in epoll"); } #endif @@ -141,7 +141,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) { if (errno == EINTR) return true; - perror("poll"); + log_error("poll failed: " << strerror(errno)); throw std::runtime_error("Poll failed"); } // We cannot possibly have more ready events than the number of fds we are @@ -173,7 +173,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) { if (errno == EINTR) return 0; - perror("epoll_wait"); + log_error("epoll wait: " << strerror(errno)); throw std::runtime_error("Epoll_wait failed"); } for (int i = 0; i < nb_events; ++i) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index ea674d1..3fe423e 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -56,8 +56,7 @@ std::pair SocketHandler::connect(const std::string& address, this->on_connected(); return std::make_pair(true, ""); } - log_info("Connection failed:"); - perror("connect"); + log_info("Connection failed:" << strerror(errno)); } log_error("All connection attempts failed."); this->close(); @@ -97,7 +96,7 @@ void SocketHandler::on_send() const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), 0); if (res == -1) { - perror("send"); + log_error("send failed: " << strerror(errno)); this->close(); } else -- cgit v1.2.3 From e3b91475ffd7c1c76868964614f66060ba9e3a85 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 08:04:33 +0100 Subject: Do not include an empty item XML element in join presences --- src/xmpp/xmpp_component.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 92c614c..ef86213 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -324,7 +324,8 @@ void XmppComponent::send_user_join(const std::string& from, item["jid"] = preped_jid; } item.close(); - x.add_child(std::move(item)); + if (item.has_children()) + x.add_child(std::move(item)); if (self) { -- cgit v1.2.3 From eb2a2e75c257735c3aa0548ad5a374931ea7da65 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 08:14:08 +0100 Subject: Add missing logger include --- src/network/poller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index a157da9..75e7e6a 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -1,4 +1,5 @@ #include +#include #include #include -- cgit v1.2.3 From d46e7ee8c90a800e6f3c2f1db949e88635983b6b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 08:14:24 +0100 Subject: Enable TCP keepalive on sockets --- src/network/socket_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 3fe423e..ae9489d 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -21,6 +21,9 @@ SocketHandler::SocketHandler(): { if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) throw std::runtime_error("Could not create socket"); + int optval = 1; + if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) + log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); } std::pair SocketHandler::connect(const std::string& address, const std::string& port) -- cgit v1.2.3 From 274859c096b25444d475d1319e9296a868ec258c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 8 Feb 2014 08:22:42 +0100 Subject: Handle most generic error IRC messages --- src/irc/irc_client.hpp | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index a058b19..7380a8d 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -270,7 +270,6 @@ static const std::unordered_map irc_callbacks = { {"366", &IrcClient::on_channel_completely_joined}, {"432", &IrcClient::on_erroneous_nickname}, {"433", &IrcClient::on_nickname_conflict}, - {"461", &IrcClient::on_generic_error}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, {"ERROR", &IrcClient::on_error}, @@ -279,6 +278,55 @@ static const std::unordered_map irc_callbacks = { {"MODE", &IrcClient::on_mode}, {"PING", &IrcClient::send_pong_command}, {"KICK", &IrcClient::on_kick}, + + {"401", &IrcClient::on_generic_error}, + {"402", &IrcClient::on_generic_error}, + {"403", &IrcClient::on_generic_error}, + {"404", &IrcClient::on_generic_error}, + {"405", &IrcClient::on_generic_error}, + {"406", &IrcClient::on_generic_error}, + {"407", &IrcClient::on_generic_error}, + {"408", &IrcClient::on_generic_error}, + {"409", &IrcClient::on_generic_error}, + {"410", &IrcClient::on_generic_error}, + {"411", &IrcClient::on_generic_error}, + {"412", &IrcClient::on_generic_error}, + {"414", &IrcClient::on_generic_error}, + {"421", &IrcClient::on_generic_error}, + {"422", &IrcClient::on_generic_error}, + {"423", &IrcClient::on_generic_error}, + {"424", &IrcClient::on_generic_error}, + {"431", &IrcClient::on_generic_error}, + {"436", &IrcClient::on_generic_error}, + {"441", &IrcClient::on_generic_error}, + {"442", &IrcClient::on_generic_error}, + {"443", &IrcClient::on_generic_error}, + {"444", &IrcClient::on_generic_error}, + {"446", &IrcClient::on_generic_error}, + {"451", &IrcClient::on_generic_error}, + {"461", &IrcClient::on_generic_error}, + {"462", &IrcClient::on_generic_error}, + {"463", &IrcClient::on_generic_error}, + {"464", &IrcClient::on_generic_error}, + {"465", &IrcClient::on_generic_error}, + {"467", &IrcClient::on_generic_error}, + {"470", &IrcClient::on_generic_error}, + {"471", &IrcClient::on_generic_error}, + {"472", &IrcClient::on_generic_error}, + {"473", &IrcClient::on_generic_error}, + {"474", &IrcClient::on_generic_error}, + {"475", &IrcClient::on_generic_error}, + {"476", &IrcClient::on_generic_error}, + {"477", &IrcClient::on_generic_error}, + {"481", &IrcClient::on_generic_error}, + {"482", &IrcClient::on_generic_error}, + {"483", &IrcClient::on_generic_error}, + {"484", &IrcClient::on_generic_error}, + {"485", &IrcClient::on_generic_error}, + {"487", &IrcClient::on_generic_error}, + {"491", &IrcClient::on_generic_error}, + {"501", &IrcClient::on_generic_error}, + {"502", &IrcClient::on_generic_error}, }; #endif // IRC_CLIENT_INCLUDED -- cgit v1.2.3 From 6dbf36b974cd641f4541599496b04ad48ab41c87 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 01:21:38 +0100 Subject: Revert "Do not include an empty item XML element in join presences" This reverts commit e3b91475ffd7c1c76868964614f66060ba9e3a85. --- src/xmpp/xmpp_component.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index ef86213..92c614c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -324,8 +324,7 @@ void XmppComponent::send_user_join(const std::string& from, item["jid"] = preped_jid; } item.close(); - if (item.has_children()) - x.add_child(std::move(item)); + x.add_child(std::move(item)); if (self) { -- cgit v1.2.3 From abcf16bc28a5f72d9e7f964a5f2b432f55dfe5b6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 01:31:19 +0100 Subject: Log (xmpp) stream-level errors --- src/xmpp/xmpp_component.cpp | 13 +++++++++++++ src/xmpp/xmpp_component.hpp | 1 + 2 files changed, 14 insertions(+) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 92c614c..bf06101 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -20,6 +20,7 @@ #define DISCO_INFO_NS DISCO_NS"#info" #define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" #define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): served_hostname(hostname), @@ -41,6 +42,8 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); this->stanza_handlers.emplace(COMPONENT_NS":iq", std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1)); + this->stanza_handlers.emplace(STREAM_NS":error", + std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); } XmppComponent::~XmppComponent() @@ -179,6 +182,7 @@ void XmppComponent::handle_handshake(const Stanza& stanza) { (void)stanza; this->authenticated = true; + log_info("Authenticated with the XMPP server"); } void XmppComponent::handle_presence(const Stanza& stanza) @@ -271,6 +275,15 @@ void XmppComponent::handle_iq(const Stanza& stanza) } } +void XmppComponent::handle_error(const Stanza& stanza) +{ + XmlNode* text = stanza.get_child(STREAMS_NS":text"); + std::string error_message("Unspecified error"); + if (text) + error_message = text->get_inner(); + log_error("Stream error received from the XMPP server: " << error_message); +} + Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) { try diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index f86d930..fb9282b 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -128,6 +128,7 @@ public: void handle_presence(const Stanza& stanza); void handle_message(const Stanza& stanza); void handle_iq(const Stanza& stanza); + void handle_error(const Stanza& stanza); private: /** -- cgit v1.2.3 From cf9f3a1f2855b358aa9bbc31f234801e9e1efc28 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 01:43:58 +0100 Subject: Include role and affiliation in the join presence of the nick change process --- src/bridge/bridge.cpp | 12 ++++++++++-- src/bridge/bridge.hpp | 12 +++++++++--- src/irc/irc_client.cpp | 5 +++-- src/xmpp/xmpp_component.cpp | 10 ++++++++-- src/xmpp/xmpp_component.hpp | 8 +++++++- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index be0d270..6460e6b 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -195,10 +195,18 @@ void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& me this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); } -void Bridge::send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self) +void Bridge::send_nick_change(Iid&& iid, + const std::string& old_nick, + const std::string& new_nick, + const char user_mode, + const bool self) { + std::string affiliation; + std::string role; + std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); + this->xmpp->send_nick_change(std::move(iid.chan) + "%" + std::move(iid.server), - old_nick, new_nick, this->user_jid, self); + old_nick, new_nick, affiliation, role, this->user_jid, self); } void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c43b049..b3a5d02 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -84,9 +84,15 @@ public: void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self); /** * Send presences to indicate that an user old_nick (ourself if self == - * true) changed his nick to new_nick - */ - void send_nick_change(Iid&& iid, const std::string& old_nick, const std::string& new_nick, const bool self); + * true) changed his nick to new_nick. The user_mode is needed because + * the xmpp presence needs ton contain the role and affiliation of the + * user. + */ + void send_nick_change(Iid&& iid, + const std::string& old_nick, + const std::string& new_nick, + const char user_mode, + const bool self); void kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author); void send_nickname_conflict_error(const Iid& iid, const std::string& nickname); /** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index a84f96e..bb2fc75 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -469,8 +469,9 @@ void IrcClient::on_nick(const IrcMessage& message) Iid iid; iid.chan = chan_name; iid.server = this->hostname; - bool self = channel->get_self()->nick == old_nick; - this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, self); + const bool self = channel->get_self()->nick == old_nick; + const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); + this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); user->nick = new_nick; if (self) { diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index bf06101..facdbd4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -413,7 +413,13 @@ void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, X this->send_stanza(presence); } -void XmppComponent::send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self) +void XmppComponent::send_nick_change(const std::string& muc_name, + const std::string& old_nick, + const std::string& new_nick, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to, + const bool self) { Stanza presence("presence"); presence["to"] = jid_to; @@ -441,7 +447,7 @@ void XmppComponent::send_nick_change(const std::string& muc_name, const std::str presence.close(); this->send_stanza(presence); - this->send_user_join(muc_name, new_nick, "", "", "", jid_to, self); + this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); } void XmppComponent::kick_user(const std::string& muc_name, diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index fb9282b..27a735a 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -97,7 +97,13 @@ public: /** * Indicate that a participant changed his nick */ - void send_nick_change(const std::string& muc_name, const std::string& old_nick, const std::string& new_nick, const std::string& jid_to, const bool self); + void send_nick_change(const std::string& muc_name, + const std::string& old_nick, + const std::string& new_nick, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to, + const bool self); /** * An user is kicked from a room */ -- cgit v1.2.3 From 6700d5daf29bf4117ea964f2863652be104e7405 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 01:56:22 +0100 Subject: Fix quit messages not being sent to XMPP --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index facdbd4..2fb8a23 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -399,7 +399,7 @@ void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, X presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; presence["type"] = "unavailable"; const std::string message_str = std::get<0>(message); - if (message_str.empty() || self) + if (!message_str.empty() || self) { XmlNode status("status"); if (!message_str.empty()) -- cgit v1.2.3 From 7cfc79179c8dda5c4f8dbde467fe8c8a30010f5f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 02:12:12 +0100 Subject: Make install rule --- CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 222c609..660f238 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,4 +125,11 @@ target_link_libraries(test config logger) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) \ No newline at end of file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +# +## Install target +# +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin) +install(FILES doc/${PROJECT_NAME}.1 DESTINATION man/man1) -- cgit v1.2.3 From c5b4be0e17a21449d226ebcc3a9313a020c0c8b4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 02:31:14 +0100 Subject: Little man page update --- doc/biboumi.md | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/doc/biboumi.md b/doc/biboumi.md index 4bbbcf7..334e65e 100644 --- a/doc/biboumi.md +++ b/doc/biboumi.md @@ -1,15 +1,15 @@ -BIBOUMI 1 "2013-11-21" +BIBOUMI 1 "2014-02-17" ====================== NAME ---- -Bbiboumi - XMPP gateway to IRC +biboumi - XMPP gateway to IRC SYNOPSIS -------- -`biboumi` [`config_file`] +`biboumi` [`config_filename`] DESCRIPTION ----------- @@ -53,8 +53,9 @@ The configuration file uses a simple format of the form `log_level` - Indicate what type of log messages to write in the logs. Values can be from 0 to 3. - 0 is debug, 1 is info, 2 is warnig, 3 is error. + Indicate what type of log messages to write in the logs. Values can be + from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The + default is 0, but a more practical value for production use is 1. USAGE ----- @@ -76,7 +77,7 @@ that server. Biboumi connects once to each IRC server, for each user on it. To cleanly shutdown the component, send the SIGINT or SIGTERM signals to it. It will send messages to all connected IRC and XMPP servers to indicate a reason why the users are being disconnected. Biboumi exits when all -connections are closed because the remote aknowledged the end of +connections are closed because the remote acknowledged the end of communication. If the remote server does not respond, biboumi does not exit, unless SIGINT or SIGTERM is received again, in which case biboumi closes the TCP connections and exits immediately. @@ -132,7 +133,7 @@ be changed on all channels on the same server as well. Private messages are handled differently on IRC and on XMPP. On IRC, you talk directly to one server-user: toto on the channel #foo is the same user -than toto on the channel #bar (as long as these two channels are on the same +as toto on the channel #bar (as long as these two channels are on the same IRC server). Using biboumi, there is no way to receive a message from a room participant (from a jid like #test%irc.example.com/nickname). Instead, private messages are received from and sent to the user (using a jid like @@ -178,12 +179,12 @@ in XMPP), it is impossible to map all these modes to an XMPP feature. To circumvent this problem, biboumi provides a raw notification when modes are changed, and lets the user change the modes directly. -To change modes, simply send a message starting with “`/mode`” followed -by the modes and the arguments you want to send to the IRC server. For -example “/mode +aho louiz”. Note that your XMPP client may -inteprete messages begining with “/” like a command. To actually send a -message starting with a slash, you may need to start your message with -“//mode” or “/say /mode”, depending on your client. +To change modes, simply send a message starting with “`/mode`” followed by +the modes and the arguments you want to send to the IRC server. For example +“/mode +aho louiz”. Note that your XMPP client may interprete messages +begining with “/” like a command. To actually send a message starting with +a slash, you may need to start your message with “//mode” or “/say /mode”, +depending on your client. When a mode is changed, the user is notified by a message coming from the MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if @@ -192,6 +193,10 @@ notified of this XMPP event as well. For example if a mode “+o toto” is received, then toto’s role will be changed to moderator. The mapping between IRC modes and XMPP features is as follow: +`+q` + + Sets the participant’s role to `moderator` and its affiliation to `owner`. + `+a` Sets the participant’s role to `moderator` and its affiliation to `owner`. @@ -214,13 +219,18 @@ SECURITY Biboumi does not provide any encryption mechanism: connection to the XMPP server MUST be made on localhost. The XMPP server is not supposed to accept non-local connection from components, thus encryption is useless. IRC -SSL/TLS is also not implemented although this could be useful for some -users, this is however not a high priority feature. +SSL/TLS is also not yet implemented. -Biboumi also does not check if JIDs are properly formatted using nodeprep. -This must be done by the XMPP server to which biboumi is directly connected. +Biboumi also does not check if received JIDs are properly formatted using +nodeprep. This must be done by the XMPP server to which biboumi is directly +connected. AUTHORS ------- -Written by Florent Le Coz +This software and man page are both written by Florent Le Coz. + +LICENSE +------- + +Biboumi is released under the zlib license. -- cgit v1.2.3 From e43de640e9158ef396c9dc71a13bdcbfef740e7d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 17 Feb 2014 03:07:27 +0100 Subject: Build the man page as part of the build process, if ronn is found --- CMakeLists.txt | 19 ++++- INSTALL | 1 + doc/biboumi.1 | 133 ------------------------------- doc/biboumi.1.md | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/biboumi.md | 236 ------------------------------------------------------- 5 files changed, 255 insertions(+), 370 deletions(-) delete mode 100644 doc/biboumi.1 create mode 100644 doc/biboumi.1.md delete mode 100644 doc/biboumi.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 660f238..edbc87e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,18 @@ if((NOT ${POLLER} MATCHES "POLL") AND message(FATAL_ERROR "POLLER must be either POLL or EPOLL") endif() +# +## Documentation +# +find_program(RONN_EXECUTABLE NAMES ronn + DOC "The ronn software, to build the man page from the markdown documentation") +if(RONN_EXECUTABLE) + set(WITH_DOC true) + add_custom_target(doc + ${RONN_SOFTWARE} --roff ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1.md + COMMENT "Generate the man page" VERBATIM) +endif() + # ## utils @@ -111,6 +123,9 @@ target_link_libraries(${PROJECT_NAME} bridge utils config) +if(WITH_DOC) + add_dependencies(${PROJECT_NAME} doc) +endif() # ## Tests @@ -132,4 +147,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY # install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) -install(FILES doc/${PROJECT_NAME}.1 DESTINATION man/man1) +if(WITH_DOC) + install(FILES doc/${PROJECT_NAME}.1 DESTINATION man/man1) +endif() \ No newline at end of file diff --git a/INSTALL b/INSTALL index 8de0fa6..986ee84 100644 --- a/INSTALL +++ b/INSTALL @@ -24,6 +24,7 @@ Libraries: Tools: - A C++11 compiler. - CMake +- ronn (optional) to build the man page ============== diff --git a/doc/biboumi.1 b/doc/biboumi.1 deleted file mode 100644 index 3e472c8..0000000 --- a/doc/biboumi.1 +++ /dev/null @@ -1,133 +0,0 @@ -.\" generated with Ronn/v0.7.3 -.\" http://github.com/rtomayko/ronn/tree/0.7.3 -. -.TH "BIBOUMI" "" "December 2013" "" "" -. -.SH "NAME" -Bbiboumi \- XMPP gateway to IRC -. -.SH "SYNOPSIS" -\fBbiboumi\fR [\fBconfig_file\fR] -. -.SH "DESCRIPTION" -Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols\. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs\. -. -.SH "OPTIONS" -Available options: -. -.P -\fBconfig_filename\fR -. -.P -Specify the file to read for configuration\. See \fICONFIG\fR section for more details on its content\. -. -.SH "CONFIG" -The configuration file uses a simple format of the form \fB"option=value"\fR\. Here is a description of each possible option: -. -.P -\fBhostname\fR (mandatory) -. -.P -The hostname served by the XMPP gateway\. This domain must be configured in the XMPP server as an external component\. See the manual for your XMPP server for more information\. -. -.P -\fBpassword\fR (mandatory) -. -.P -The password used to authenticate the XMPP component to your XMPP server\. This password must be configured in the XMPP server, associated with the external component on \fIhostname\fR\. -. -.P -\fBlog_file\fR -. -.P -A filename into which logs are written\. If none is provided, the logs are written on standard output -. -.P -\fBlog_level\fR -. -.P -Indicate what type of log messages to write in the logs\. Values can be from 0 to 3\. 0 is debug, 1 is info, 2 is warnig, 3 is error\. -. -.SH "USAGE" -When started, biboumi connects, without encryption (see \fISECURITY\fR), to the local XMPP server on the port \fB5347\fR and provides the configured password to authenticate\. Biboumi then serves the configured \fBhostname\fR, this means that all XMPP stanza with a \fBto\fR JID on that domain will be sent to biboumi, and biboumi will send only send messages coming from this hostname\. -. -.P -When an user joins an IRC channel on an IRC server (see \fIJoin an IRC channel\fR), biboumi connects to the remote IRC server, sets the user’s nick as requested, and then tries to join the specified channel\. If the same user subsequently tries to connect to an other channel on the same server, the same IRC connection is used\. If, however, an other user wants to join an IRC channel on that same IRC server, biboumi opens a new connection to that server\. Biboumi connects once to each IRC server, for each user on it\. -. -.SS "Addressing" -IRC entities are represented by XMPP JIDs\. The domain part of the JID is the domain served by biboumi, and the local part depends on the concerned entity\. -. -.P -IRC channels and IRC users JIDs have a localpart formed like this: \fBname\fR, the \fB\'%\'\fR separator and the \fBirc_server\fR\. -. -.P -For an IRC channel, the name starts with \fB\'&\'\fR, \fB\'#\'\fR, \fB\'+\'\fR or \fB\'!\'\fR\. Some other gateway implementations, as well as some IRC clients, do not require them to be started by one of these characters, adding an implicit \fB\'#\'\fR in that case\. Biboumi does not do that because this gets confusing when trying to understand the difference between \fIfoo\fR, \fI#foo\fR, and \fI##foo\fR\. -. -.P -If the name starts with any other character, this represents an IRC user\. If compiled with Libidn, an IRC user has a bare JID representing the “hostname” provided by the IRC server\. -. -.SS "Join an IRC channel" -To join an IRC channel \fB#foo\fR on the IRC server \fBirc\.example\.com\fR, join the XMPP MUC \fB#foo%irc\.example\.com@hostname\fR\. -. -.SS "Channel messages" -On XMPP, unlike on IRC, the displayed order of the messages is the same for all participants of a MUC\. Biboumi can not however provide this feature, as it cannot know whether the IRC server has received and forwarded the messages to other users\. This means that the order of the messages displayed in your XMPP may not be the same than the order on other IRC users’\. -. -.SS "Nicknames" -On IRC, nicknames are server\-wide\. This means that one user only has one single nickname at one given time on all the channels of a server\. This is different from XMPP where an user can have a different nick on each MUC, even if these MUCs are on the same server\. -. -.P -This means that the nick you choose when joining your first IRC channel on a given IRC server will be your nickname in all other channels that you join on that same IRC server\. If you explicitely change your nickname on one channel, your nickname will be changed on all channels on the same server as well\. -. -.SS "Private messages" -Private messages are handled differently on IRC and on XMPP\. On IRC, you talk directly to one server\-user: toto on the channel #foo is the same user than toto on the channel #bar (as long as these two channels are on the same IRC server)\. Using biboumi, there is no way to receive a message from a room participant (from a jid like #test%irc\.example\.com/nickname)\. Instead, private messages are received from and sent to the user (using a jid like nickname%irc\.example\.com)\. For conveniance and compatibility with XMPP clients sending private messages to the MUC participants, a message sent toNickname%irc\.example\.com@irc\.example\.net, although this is not the prefered way to do it\. -. -.SS "Notices" -Notices are received exactly like private messages\. It is not possible to send a notice\. -. -.SS "Kicks and bans" -Kicks are transparently translated from one protocol to another\. However banning an XMPP participant has no effect\. To ban an user you need to set a mode +b on that user nick or host (see \fIMODES\fR) and then kick it\. -. -.SS "Encoding" -On XMPP, the encoding is always \fBUTF\-8\fR, whereas on IRC the encoding of each message can be anything\. -. -.P -This means that biboumi has to convert everything coming from IRC into UTF\-8 without knowing the encoding of the received messages\. To do so, it checks if each message is UTF\-8 valid, if not it tries to convert from \fBiso_8859\-1\fR (because this appears to be the most common case, at least on the channels I visit) to \fBUTF\-8\fR\. If that conversion fails at some point, a placeholder character \fB\'�\'\fR is inserted to indicate this decoding error\. -. -.P -Messages are always sent in UTF\-8 over IRC, no conversion is done in that direction\. -. -.SS "IRC modes" -One feature that doesn’t exist on XMPP but does on IRC is the \fBmodes\fR\. Although some of these modes have a correspondance in the XMPP world (for example the \fB+o\fR mode on an user corresponds to the \fBmoderator\fR role in XMPP), it is impossible to map all these modes to an XMPP feature\. To circumvent this problem, biboumi provides a raw notification when modes are changed, and lets the user change the modes directly\. -. -.P -To change modes, simply send a message starting with “\fB/mode\fR” followed by the modes and the arguments you want to send to the IRC server\. For example “/mode +aho louiz”\. Note that your XMPP client may inteprete messages begining with “/” like a command\. To actually send a message starting with a slash, you may need to start your message with “//mode” or “/say /mode”, depending on your client\. -. -.P -When a mode is changed, the user is notified by a message coming from the MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”\. In addition, if the mode change can be translated to an XMPP feature, the user will be notified of this XMPP event as well\. For example if a mode “+o toto” is received, then toto’s role will be changed to moderator\. The mapping between IRC modes and XMPP features is as follow: -. -.P -\fB+o\fR -. -.P -Sets the participant’s role to \fBmoderator\fR\. -. -.P -\fB+a\fR -. -.P -Sets the participant’s role to \fBadmin\fR\. -. -.P -\fB+v\fR -. -.P -Sets the participant’s affiliation to \fBmember\fR\. -. -.SH "SECURITY" -Biboumi does not provide any encryption mechanism: connection to the XMPP server MUST be made on localhost\. The XMPP server is not supposed to accept non\-local connection from components, thus encryption is useless\. IRC SSL/TLS is also not implemented although this could be useful for some users, this is however not a high priority feature\. -. -.P -Biboumi also does not check if JIDs are properly formatted using nodeprep\. This must be done by the XMPP server to which biboumi is directly connected\. -. -.SH "AUTHORS" -Written by Florent Le Coz diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md new file mode 100644 index 0000000..334e65e --- /dev/null +++ b/doc/biboumi.1.md @@ -0,0 +1,236 @@ +BIBOUMI 1 "2014-02-17" +====================== + +NAME +---- + +biboumi - XMPP gateway to IRC + +SYNOPSIS +-------- + +`biboumi` [`config_filename`] + +DESCRIPTION +----------- + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +OPTIONS +------- + +Available options: + +`config_filename` + + Specify the file to read for configuration. See *CONFIG* section for more + details on its content. + +CONFIG +------ + +The configuration file uses a simple format of the form +`"option=value"`. Here is a description of each possible option: + +`hostname` (mandatory) + + The hostname served by the XMPP gateway. This domain must be configured in + the XMPP server as an external component. See the manual for your XMPP + server for more information. + +`password` (mandatory) + + The password used to authenticate the XMPP component to your XMPP server. + This password must be configured in the XMPP server, associated with the + external component on *hostname*. + +`log_file` + + A filename into which logs are written. If none is provided, the logs are + written on standard output + +`log_level` + + Indicate what type of log messages to write in the logs. Values can be + from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The + default is 0, but a more practical value for production use is 1. + +USAGE +----- + +When started, biboumi connects, without encryption (see *SECURITY*), to the +local XMPP server on the port `5347` and provides the configured password to +authenticate. Biboumi then serves the configured `hostname`, this means +that all XMPP stanza with a `to` JID on that domain will be sent to biboumi, +and biboumi will send only send messages coming from this hostname. + +When an user joins an IRC channel on an IRC server (see *Join an IRC +channel*), biboumi connects to the remote IRC server, sets the user’s nick +as requested, and then tries to join the specified channel. If the same +user subsequently tries to connect to an other channel on the same server, +the same IRC connection is used. If, however, an other user wants to join +an IRC channel on that same IRC server, biboumi opens a new connection to +that server. Biboumi connects once to each IRC server, for each user on it. + +To cleanly shutdown the component, send the SIGINT or SIGTERM signals to it. +It will send messages to all connected IRC and XMPP servers to indicate a +reason why the users are being disconnected. Biboumi exits when all +connections are closed because the remote acknowledged the end of +communication. If the remote server does not respond, biboumi does not +exit, unless SIGINT or SIGTERM is received again, in which case biboumi +closes the TCP connections and exits immediately. + +### Addressing + +IRC entities are represented by XMPP JIDs. The domain part of the JID is +the domain served by biboumi, and the local part depends on the concerned +entity. + +IRC channels and IRC users JIDs have a localpart formed like this: +`name`, the `'%'` separator and the `irc_server`. + +For an IRC channel, the name starts with `'&'`, `'#'`, `'+'` +or `'!'`. Some other gateway implementations, as well as some IRC +clients, do not require them to be started by one of these characters, +adding an implicit `'#'` in that case. Biboumi does not do that because +this gets confusing when trying to understand the difference between +*foo*, *#foo*, and *##foo*. + +If the name starts with any other character, this represents an IRC user. +If compiled with Libidn, an IRC user has a bare JID representing the +“hostname” provided by the IRC server. + +### Join an IRC channel + +To join an IRC channel `#foo` on the IRC server `irc.example.com`, +join the XMPP MUC `#foo%irc.example.com@hostname`. + +### Channel messages + +On XMPP, unlike on IRC, the displayed order of the messages is the same for +all participants of a MUC. Biboumi can not however provide this feature, as +it cannot know whether the IRC server has received and forwarded the +messages to other users. This means that the order of the messages +displayed in your XMPP may not be the same than the order on other IRC +users’. + +### Nicknames + +On IRC, nicknames are server-wide. This means that one user only has one +single nickname at one given time on all the channels of a server. This is +different from XMPP where an user can have a different nick on each MUC, +even if these MUCs are on the same server. + +This means that the nick you choose when joining your first IRC channel on a +given IRC server will be your nickname in all other channels that you join +on that same IRC server. +If you explicitely change your nickname on one channel, your nickname will +be changed on all channels on the same server as well. + +### Private messages + +Private messages are handled differently on IRC and on XMPP. On IRC, you +talk directly to one server-user: toto on the channel #foo is the same user +as toto on the channel #bar (as long as these two channels are on the same +IRC server). Using biboumi, there is no way to receive a message from a +room participant (from a jid like #test%irc.example.com/nickname). Instead, +private messages are received from and sent to the user (using a jid like +nickname%irc.example.com). For conveniance and compatibility with XMPP +clients sending private messages to the MUC participants, a message sent to +#chan%irc.example.com@irc.example.net/Nickname will be redirected to +Nickname%irc.example.com@irc.example.net, although this is not the prefered +way to do it. + +### Notices + +Notices are received exactly like private messages. It is not possible to +send a notice. + +### Kicks and bans + +Kicks are transparently translated from one protocol to another. However +banning an XMPP participant has no effect. To ban an user you need to set a +mode +b on that user nick or host (see *MODES*) and then kick it. + +### Encoding + +On XMPP, the encoding is always `UTF-8`, whereas on IRC the encoding of +each message can be anything. + +This means that biboumi has to convert everything coming from IRC into UTF-8 +without knowing the encoding of the received messages. To do so, it checks +if each message is UTF-8 valid, if not it tries to convert from +`iso_8859-1` (because this appears to be the most common case, at least +on the channels I visit) to `UTF-8`. If that conversion fails at some +point, a placeholder character `'�'` is inserted to indicate this +decoding error. + +Messages are always sent in UTF-8 over IRC, no conversion is done in that +direction. + +### IRC modes + +One feature that doesn’t exist on XMPP but does on IRC is the `modes`. +Although some of these modes have a correspondance in the XMPP world (for +example the `+o` mode on an user corresponds to the `moderator` role +in XMPP), it is impossible to map all these modes to an XMPP feature. To +circumvent this problem, biboumi provides a raw notification when modes are +changed, and lets the user change the modes directly. + +To change modes, simply send a message starting with “`/mode`” followed by +the modes and the arguments you want to send to the IRC server. For example +“/mode +aho louiz”. Note that your XMPP client may interprete messages +begining with “/” like a command. To actually send a message starting with +a slash, you may need to start your message with “//mode” or “/say /mode”, +depending on your client. + +When a mode is changed, the user is notified by a message coming from the +MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if +the mode change can be translated to an XMPP feature, the user will be +notified of this XMPP event as well. For example if a mode “+o toto” is +received, then toto’s role will be changed to moderator. The mapping +between IRC modes and XMPP features is as follow: + +`+q` + + Sets the participant’s role to `moderator` and its affiliation to `owner`. + +`+a` + + Sets the participant’s role to `moderator` and its affiliation to `owner`. + +`+o` + + Sets the participant’s role to `moderator` and its affiliation to `admin`. + +`+h` + + Sets the participant’s role to `moderator` and its affiliation to `member`. + +`+v` + + Sets the participant’s role to `participant` and its affiliation to `member`. + +SECURITY +-------- + +Biboumi does not provide any encryption mechanism: connection to the XMPP +server MUST be made on localhost. The XMPP server is not supposed to accept +non-local connection from components, thus encryption is useless. IRC +SSL/TLS is also not yet implemented. + +Biboumi also does not check if received JIDs are properly formatted using +nodeprep. This must be done by the XMPP server to which biboumi is directly +connected. + +AUTHORS +------- + +This software and man page are both written by Florent Le Coz. + +LICENSE +------- + +Biboumi is released under the zlib license. diff --git a/doc/biboumi.md b/doc/biboumi.md deleted file mode 100644 index 334e65e..0000000 --- a/doc/biboumi.md +++ /dev/null @@ -1,236 +0,0 @@ -BIBOUMI 1 "2014-02-17" -====================== - -NAME ----- - -biboumi - XMPP gateway to IRC - -SYNOPSIS --------- - -`biboumi` [`config_filename`] - -DESCRIPTION ------------ - -Biboumi is an XMPP gateway that connects to IRC servers and translates -between the two protocols. It can be used to access IRC channels using any -XMPP client as if these channels were XMPP MUCs. - -OPTIONS -------- - -Available options: - -`config_filename` - - Specify the file to read for configuration. See *CONFIG* section for more - details on its content. - -CONFIG ------- - -The configuration file uses a simple format of the form -`"option=value"`. Here is a description of each possible option: - -`hostname` (mandatory) - - The hostname served by the XMPP gateway. This domain must be configured in - the XMPP server as an external component. See the manual for your XMPP - server for more information. - -`password` (mandatory) - - The password used to authenticate the XMPP component to your XMPP server. - This password must be configured in the XMPP server, associated with the - external component on *hostname*. - -`log_file` - - A filename into which logs are written. If none is provided, the logs are - written on standard output - -`log_level` - - Indicate what type of log messages to write in the logs. Values can be - from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The - default is 0, but a more practical value for production use is 1. - -USAGE ------ - -When started, biboumi connects, without encryption (see *SECURITY*), to the -local XMPP server on the port `5347` and provides the configured password to -authenticate. Biboumi then serves the configured `hostname`, this means -that all XMPP stanza with a `to` JID on that domain will be sent to biboumi, -and biboumi will send only send messages coming from this hostname. - -When an user joins an IRC channel on an IRC server (see *Join an IRC -channel*), biboumi connects to the remote IRC server, sets the user’s nick -as requested, and then tries to join the specified channel. If the same -user subsequently tries to connect to an other channel on the same server, -the same IRC connection is used. If, however, an other user wants to join -an IRC channel on that same IRC server, biboumi opens a new connection to -that server. Biboumi connects once to each IRC server, for each user on it. - -To cleanly shutdown the component, send the SIGINT or SIGTERM signals to it. -It will send messages to all connected IRC and XMPP servers to indicate a -reason why the users are being disconnected. Biboumi exits when all -connections are closed because the remote acknowledged the end of -communication. If the remote server does not respond, biboumi does not -exit, unless SIGINT or SIGTERM is received again, in which case biboumi -closes the TCP connections and exits immediately. - -### Addressing - -IRC entities are represented by XMPP JIDs. The domain part of the JID is -the domain served by biboumi, and the local part depends on the concerned -entity. - -IRC channels and IRC users JIDs have a localpart formed like this: -`name`, the `'%'` separator and the `irc_server`. - -For an IRC channel, the name starts with `'&'`, `'#'`, `'+'` -or `'!'`. Some other gateway implementations, as well as some IRC -clients, do not require them to be started by one of these characters, -adding an implicit `'#'` in that case. Biboumi does not do that because -this gets confusing when trying to understand the difference between -*foo*, *#foo*, and *##foo*. - -If the name starts with any other character, this represents an IRC user. -If compiled with Libidn, an IRC user has a bare JID representing the -“hostname” provided by the IRC server. - -### Join an IRC channel - -To join an IRC channel `#foo` on the IRC server `irc.example.com`, -join the XMPP MUC `#foo%irc.example.com@hostname`. - -### Channel messages - -On XMPP, unlike on IRC, the displayed order of the messages is the same for -all participants of a MUC. Biboumi can not however provide this feature, as -it cannot know whether the IRC server has received and forwarded the -messages to other users. This means that the order of the messages -displayed in your XMPP may not be the same than the order on other IRC -users’. - -### Nicknames - -On IRC, nicknames are server-wide. This means that one user only has one -single nickname at one given time on all the channels of a server. This is -different from XMPP where an user can have a different nick on each MUC, -even if these MUCs are on the same server. - -This means that the nick you choose when joining your first IRC channel on a -given IRC server will be your nickname in all other channels that you join -on that same IRC server. -If you explicitely change your nickname on one channel, your nickname will -be changed on all channels on the same server as well. - -### Private messages - -Private messages are handled differently on IRC and on XMPP. On IRC, you -talk directly to one server-user: toto on the channel #foo is the same user -as toto on the channel #bar (as long as these two channels are on the same -IRC server). Using biboumi, there is no way to receive a message from a -room participant (from a jid like #test%irc.example.com/nickname). Instead, -private messages are received from and sent to the user (using a jid like -nickname%irc.example.com). For conveniance and compatibility with XMPP -clients sending private messages to the MUC participants, a message sent to -#chan%irc.example.com@irc.example.net/Nickname will be redirected to -Nickname%irc.example.com@irc.example.net, although this is not the prefered -way to do it. - -### Notices - -Notices are received exactly like private messages. It is not possible to -send a notice. - -### Kicks and bans - -Kicks are transparently translated from one protocol to another. However -banning an XMPP participant has no effect. To ban an user you need to set a -mode +b on that user nick or host (see *MODES*) and then kick it. - -### Encoding - -On XMPP, the encoding is always `UTF-8`, whereas on IRC the encoding of -each message can be anything. - -This means that biboumi has to convert everything coming from IRC into UTF-8 -without knowing the encoding of the received messages. To do so, it checks -if each message is UTF-8 valid, if not it tries to convert from -`iso_8859-1` (because this appears to be the most common case, at least -on the channels I visit) to `UTF-8`. If that conversion fails at some -point, a placeholder character `'�'` is inserted to indicate this -decoding error. - -Messages are always sent in UTF-8 over IRC, no conversion is done in that -direction. - -### IRC modes - -One feature that doesn’t exist on XMPP but does on IRC is the `modes`. -Although some of these modes have a correspondance in the XMPP world (for -example the `+o` mode on an user corresponds to the `moderator` role -in XMPP), it is impossible to map all these modes to an XMPP feature. To -circumvent this problem, biboumi provides a raw notification when modes are -changed, and lets the user change the modes directly. - -To change modes, simply send a message starting with “`/mode`” followed by -the modes and the arguments you want to send to the IRC server. For example -“/mode +aho louiz”. Note that your XMPP client may interprete messages -begining with “/” like a command. To actually send a message starting with -a slash, you may need to start your message with “//mode” or “/say /mode”, -depending on your client. - -When a mode is changed, the user is notified by a message coming from the -MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if -the mode change can be translated to an XMPP feature, the user will be -notified of this XMPP event as well. For example if a mode “+o toto” is -received, then toto’s role will be changed to moderator. The mapping -between IRC modes and XMPP features is as follow: - -`+q` - - Sets the participant’s role to `moderator` and its affiliation to `owner`. - -`+a` - - Sets the participant’s role to `moderator` and its affiliation to `owner`. - -`+o` - - Sets the participant’s role to `moderator` and its affiliation to `admin`. - -`+h` - - Sets the participant’s role to `moderator` and its affiliation to `member`. - -`+v` - - Sets the participant’s role to `participant` and its affiliation to `member`. - -SECURITY --------- - -Biboumi does not provide any encryption mechanism: connection to the XMPP -server MUST be made on localhost. The XMPP server is not supposed to accept -non-local connection from components, thus encryption is useless. IRC -SSL/TLS is also not yet implemented. - -Biboumi also does not check if received JIDs are properly formatted using -nodeprep. This must be done by the XMPP server to which biboumi is directly -connected. - -AUTHORS -------- - -This software and man page are both written by Florent Le Coz. - -LICENSE -------- - -Biboumi is released under the zlib license. -- cgit v1.2.3 From 293cab0d3797dc244e9dc0844b4522d05ac844c5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 01:11:46 +0100 Subject: Fix FindIconv cmake module to correctly compile the c++ test code --- cmake/Modules/FindIconv.cmake | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake index ac81a2c..d4f8181 100644 --- a/cmake/Modules/FindIconv.cmake +++ b/cmake/Modules/FindIconv.cmake @@ -35,17 +35,22 @@ find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCL # char** outbuf, size_t* outbytesleft); if(ICONV_FOUND) include(CheckCXXSourceCompiles) - check_cxx_source_compiles(" - #include - int main(){ - iconv_t conv = 0; - const char* in = 0; - size_t ilen = 0; - char* out = 0; - size_t olen = 0; - iconv(conv, &in, &ilen, &out, &olen); - return 0;}" - ICONV_SECOND_ARGUMENT_IS_CONST) + + # Set the parameters needed to compile the following code. + set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) + + check_cxx_source_compiles(" + #include + int main(){ + iconv_t conv = 0; + const char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0;}" + ICONV_SECOND_ARGUMENT_IS_CONST) # Compatibility for all the ways of writing these variables set(ICONV_LIBRARY ${ICONV_LIBRARIES}) -- cgit v1.2.3 From 754dd898a7f93689aff22dcfbe71d6ca0095e019 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 01:48:55 +0100 Subject: Fix a typo in Find* comments --- cmake/Modules/FindIconv.cmake | 2 +- cmake/Modules/FindLibidn.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake index d4f8181..7ca173f 100644 --- a/cmake/Modules/FindIconv.cmake +++ b/cmake/Modules/FindIconv.cmake @@ -10,7 +10,7 @@ # # For conveniance, these variables are also set. They have the same values # than the variables above. The user can thus choose his/her prefered way -# to way to write them. +# to write them. # ICONV_LIBRARY # ICONV_INCLUDE_DIR # diff --git a/cmake/Modules/FindLibidn.cmake b/cmake/Modules/FindLibidn.cmake index 4c0b2c5..5434127 100644 --- a/cmake/Modules/FindLibidn.cmake +++ b/cmake/Modules/FindLibidn.cmake @@ -9,7 +9,7 @@ # # For conveniance, these variables are also set. They have the same values # than the variables above. The user can thus choose his/her prefered way -# to way to write them. +# to write them. # LIBIDN_INCLUDE_DIR # LIBIDN_LIBRARY # -- cgit v1.2.3 From 8fd2746696d32262b2e6900879a9a315e56f76f2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 02:24:00 +0100 Subject: Reload the conf on SIGUSR1/2 --- doc/biboumi.1.md | 4 ++++ src/config/config.cpp | 1 - src/main.cpp | 39 ++++++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 334e65e..18482fb 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -57,6 +57,10 @@ The configuration file uses a simple format of the form from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The default is 0, but a more practical value for production use is 1. +The configuration can be re-read at runtime (you can for example change the +log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 +(see kill(1)) to the process. + USAGE ----- diff --git a/src/config/config.cpp b/src/config/config.cpp index 5f937e6..82295d5 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -56,7 +56,6 @@ void Config::connect(t_config_changed_callback callback) void Config::close() { Config* self = Config::instance().get(); - self->save_to_file(); self->values.clear(); Config::instance().reset(); } diff --git a/src/main.cpp b/src/main.cpp index 28f5a76..b27fd8b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,9 @@ #include // A flag set by the SIGINT signal handler. -volatile std::atomic stop(false); +static volatile std::atomic stop(false); +// Flag set by the SIGUSR1/2 signal handler. +static volatile std::atomic reload(false); // A flag indicating that we are wanting to exit the process. i.e: if this // flag is set and all connections are closed, we can exit properly. static bool exiting = false; @@ -35,6 +37,11 @@ static void sigint_handler(int, siginfo_t*, void*) stop = true; } +static void sigusr_handler(int, siginfo_t*, void*) +{ + reload = true; +} + int main(int ac, char** av) { if (ac > 1) @@ -69,14 +76,21 @@ int main(int ac, char** av) // config sigset_t mask; sigemptyset(&mask); - struct sigaction on_sig; - on_sig.sa_sigaction = &sigint_handler; - on_sig.sa_mask = mask; + struct sigaction on_sigint; + on_sigint.sa_sigaction = &sigint_handler; + on_sigint.sa_mask = mask; // we want to catch that signal only once. // Sending SIGINT again will "force" an exit - on_sig.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &on_sig, nullptr); - sigaction(SIGTERM, &on_sig, nullptr); + on_sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &on_sigint, nullptr); + sigaction(SIGTERM, &on_sigint, nullptr); + + // Install a signal to reload the config on SIGUSR1/2 + struct sigaction on_sigusr; + on_sigusr.sa_sigaction = &sigusr_handler; + on_sigusr.sa_mask = mask; + sigaction(SIGUSR1, &on_sigusr, nullptr); + sigaction(SIGUSR2, &on_sigusr, nullptr); const std::chrono::milliseconds timeout(-1); while (p.poll(timeout) != -1 || !exiting) @@ -91,6 +105,17 @@ int main(int ac, char** av) stop = false; xmpp_component->shutdown(); } + if (reload) + { + // Closing the config will just force it to be reopened the next time + // a configuration option is needed + log_info("Signal received, reloading the config..."); + Config::close(); + // Destroy the logger instance, to be recreated the next time a log + // line needs to be written + Logger::instance().reset(); + reload = false; + } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. if (exiting && p.size() == 1 && xmpp_component->is_document_open()) -- cgit v1.2.3 From 112197f6ab8ec392aecfccd10af5547633fe7482 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 02:38:22 +0100 Subject: Fix the joined flag on channels, avoid sending the topic twice when joining --- src/irc/irc_client.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index bb2fc75..d65da87 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -270,10 +270,7 @@ void IrcClient::on_channel_join(const IrcMessage& message) IrcChannel* channel = this->get_channel(chan_name); const std::string nick = message.prefix; if (channel->joined == false) - { - channel->joined = true; - channel->set_self(nick); - } + channel->set_self(nick); else { const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); @@ -343,6 +340,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); + channel->joined = true; this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); -- cgit v1.2.3 From 6804f315464b592eeece8c8837b21c9cd1db074d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 02:38:58 +0100 Subject: Set sigaction.sa_flags to 0 --- src/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.cpp b/src/main.cpp index b27fd8b..43dfe4b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -89,6 +89,7 @@ int main(int ac, char** av) struct sigaction on_sigusr; on_sigusr.sa_sigaction = &sigusr_handler; on_sigusr.sa_mask = mask; + on_sigusr.sa_flags = 0; sigaction(SIGUSR1, &on_sigusr, nullptr); sigaction(SIGUSR2, &on_sigusr, nullptr); -- cgit v1.2.3 From aeae88dd32b5130c87929fb0d0bf0f97a17286c2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 02:49:22 +0100 Subject: =?UTF-8?q?Move=20CMake=E2=80=99s=20config.h(.cmake)=20files=20int?= =?UTF-8?q?o=20src/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 3 +-- config.h.cmake | 3 --- src/config.h.cmake | 3 +++ 3 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 config.h.cmake create mode 100644 src/config.h.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index edbc87e..21bfb78 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,6 @@ find_package(EXPAT REQUIRED) find_package(Libidn) # To be able to include the config.h file generated by cmake -include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories("src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) @@ -140,7 +139,7 @@ target_link_libraries(test config logger) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/config.h) # ## Install target diff --git a/config.h.cmake b/config.h.cmake deleted file mode 100644 index 8ee0fd3..0000000 --- a/config.h.cmake +++ /dev/null @@ -1,3 +0,0 @@ -#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST -#cmakedefine LIBIDN_FOUND -#cmakedefine POLLER ${POLLER} diff --git a/src/config.h.cmake b/src/config.h.cmake new file mode 100644 index 0000000..8ee0fd3 --- /dev/null +++ b/src/config.h.cmake @@ -0,0 +1,3 @@ +#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST +#cmakedefine LIBIDN_FOUND +#cmakedefine POLLER ${POLLER} -- cgit v1.2.3 From 16be9af3fcd23fa8590692cbe84af20cfe6a60b2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Feb 2014 23:59:43 +0100 Subject: Send unavailable presence to all muc when the IRC server closes the connection --- src/irc/irc_client.cpp | 2 ++ src/network/poller.cpp | 6 ++++-- src/network/socket_handler.cpp | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d65da87..e134bea 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -46,6 +46,8 @@ void IrcClient::on_connection_close() { static const std::string message = "Connection closed by remote server."; this->send_gateway_message(message); + const IrcMessage error{"ERROR", {message}}; + this->on_error(error); log_warning(message); } diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 75e7e6a..010dd58 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -155,13 +155,15 @@ int Poller::poll(const std::chrono::milliseconds& timeout) else if (this->fds[i].revents & POLLIN) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - socket_handler->on_recv(); + if (socket_handler->is_connected()) + socket_handler->on_recv(); nb_events--; } else if (this->fds[i].revents & POLLOUT) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - socket_handler->on_send(); + if (socket_handler->is_connected()) + socket_handler->on_send(); nb_events--; } } diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index ae9489d..580bba6 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -100,6 +100,7 @@ void SocketHandler::on_send() if (res == -1) { log_error("send failed: " << strerror(errno)); + this->on_connection_close(); this->close(); } else -- cgit v1.2.3 From e390b79a12a150b13570f7f0b19f50e1b0ead3a0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 20 Feb 2014 00:00:05 +0100 Subject: Do not receive SIGPIPE when send() is called on a closed socket The error is handled using the return value --- src/network/socket_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 580bba6..7faa9fd 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -96,7 +96,7 @@ void SocketHandler::on_recv(const size_t nb) void SocketHandler::on_send() { - const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), 0); + const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), MSG_NOSIGNAL); if (res == -1) { log_error("send failed: " << strerror(errno)); -- cgit v1.2.3 From 190c4ff1762e5e762e913f98033369ed75ed5291 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 20 Feb 2014 02:19:50 +0100 Subject: QUIT the irc server when the last channel is left --- src/bridge/bridge.cpp | 5 ++++- src/irc/irc_client.cpp | 11 ++++++++--- src/irc/irc_client.hpp | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6460e6b..5122a69 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -42,7 +42,7 @@ void Bridge::shutdown() { for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { - it->second->send_quit_command(); + it->second->send_quit_command("Gateway shutdown"); } } @@ -193,6 +193,9 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); + IrcClient* irc = this->get_irc_client(iid.server); + if (irc && irc->number_of_joined_channels() == 0) + irc->send_quit_command(""); } void Bridge::send_nick_change(Iid&& iid, diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e134bea..0d5c4e3 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -134,9 +134,9 @@ void IrcClient::send_topic_command(const std::string& chan_name, const std::stri this->send_message(IrcMessage("TOPIC", {chan_name, topic})); } -void IrcClient::send_quit_command() +void IrcClient::send_quit_command(const std::string& reason) { - this->send_message(IrcMessage("QUIT", {"gateway shutdown"})); + this->send_message(IrcMessage("QUIT", {reason})); } void IrcClient::send_join_command(const std::string& chan_name) @@ -403,7 +403,6 @@ void IrcClient::on_part(const IrcMessage& message) iid.chan = chan_name; iid.server = this->hostname; bool self = channel->get_self()->nick == nick; - this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); if (self) { channel->joined = false; @@ -411,6 +410,7 @@ void IrcClient::on_part(const IrcMessage& message) // channel pointer is now invalid channel = nullptr; } + this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); } } @@ -608,3 +608,8 @@ void IrcClient::on_user_mode(const IrcMessage& message) std::string("User mode for ") + message.arguments[0] + " is [" + message.arguments[1] + "]"); } + +size_t IrcClient::number_of_joined_channels() const +{ + return this->channels.size(); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7380a8d..5cd1403 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -101,7 +101,7 @@ public: /** * Send the QUIT irc command */ - void send_quit_command(); + void send_quit_command(const std::string& reason); /** * Send a message to the gateway user, not generated by the IRC server, * but that might be useful because we want to be verbose (for example we @@ -186,6 +186,10 @@ public: */ void on_channel_mode(const IrcMessage& message); void on_quit(const IrcMessage& message); + /** + * Return the number of joined channels + */ + size_t number_of_joined_channels() const; private: /** -- cgit v1.2.3 From 61ca40fa0e6c819aa72f3f2364667c7b990855d4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 20 Feb 2014 02:31:16 +0100 Subject: Delete empty bridges objects --- src/bridge/bridge.cpp | 5 +++++ src/bridge/bridge.hpp | 4 ++++ src/xmpp/xmpp_component.cpp | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 5122a69..bb3bfb0 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -249,6 +249,11 @@ std::string Bridge::get_own_nick(const Iid& iid) return ""; } +size_t Bridge::connected_clients() const +{ + return this->irc_clients.size(); +} + void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { this->xmpp->kick_user(iid.chan + "%" + iid.server, target, reason, author, this->user_jid); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b3a5d02..58ca24c 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -104,6 +104,10 @@ public: * Misc */ std::string get_own_nick(const Iid& iid); + /** + * Get the number of server to which this bridge is connected. + */ + size_t connected_clients() const; private: /** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 2fb8a23..e059764 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -99,9 +99,14 @@ void XmppComponent::shutdown() void XmppComponent::clean() { - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + auto it = this->bridges.begin(); + while (it != this->bridges.end()) { it->second->clean(); + if (it->second->connected_clients() == 0) + it = this->bridges.erase(it); + else + ++it; } } -- cgit v1.2.3 From 99aba5667d0d7ba6657f9c175a9342126bc4b0f2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 22 Feb 2014 21:42:24 +0100 Subject: Connection to servers does not block the process anymore --- src/bridge/bridge.cpp | 4 +-- src/bridge/bridge.hpp | 6 ++--- src/irc/irc_client.cpp | 11 +++++--- src/irc/irc_client.hpp | 4 +++ src/main.cpp | 11 ++++---- src/network/poller.cpp | 12 ++++++--- src/network/socket_handler.cpp | 57 ++++++++++++++++++++++++++++++++++++------ src/network/socket_handler.hpp | 23 ++++++++++++++++- src/xmpp/xmpp_component.cpp | 11 +++++--- src/xmpp/xmpp_component.hpp | 4 +-- 10 files changed, 111 insertions(+), 32 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index bb3bfb0..ed685f9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -52,7 +52,7 @@ void Bridge::clean() while (it != this->irc_clients.end()) { IrcClient* client = it->second.get(); - if (!client->is_connected()) + if (!client->is_connected() && !client->is_connecting()) it = this->irc_clients.erase(it); else ++it; @@ -249,7 +249,7 @@ std::string Bridge::get_own_nick(const Iid& iid) return ""; } -size_t Bridge::connected_clients() const +size_t Bridge::active_clients() const { return this->irc_clients.size(); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 58ca24c..e16ea39 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -105,9 +105,9 @@ public: */ std::string get_own_nick(const Iid& iid); /** - * Get the number of server to which this bridge is connected. + * Get the number of server to which this bridge is connected or connecting. */ - size_t connected_clients() const; + size_t active_clients() const; private: /** @@ -125,7 +125,7 @@ private: * The JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. */ - std::string user_jid; + const std::string user_jid; /** * One IrcClient for each IRC server we need to be connected to. * The pointer is shared by the bridge and the poller. diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0d5c4e3..d35437c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -29,10 +29,13 @@ void IrcClient::start() { this->bridge->send_xmpp_message(this->hostname, "", std::string("Connecting to ") + this->hostname + ":" + "6667"); - std::pair res = this->connect(this->hostname, "6667"); - if (!res.first) - this->bridge->send_xmpp_message(this->hostname, "", - std::string("Connection failed: ") + res.second); + this->connect(this->hostname, "6667"); +} + +void IrcClient::on_connection_failed(const std::string& reason) +{ + this->bridge->send_xmpp_message(this->hostname, "", + std::string("Connection failed: ") + reason); } void IrcClient::on_connected() diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 5cd1403..28a1424 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -29,6 +29,10 @@ public: * Connect to the IRC server */ void start(); + /** + * Called when the connection to the server cannot be established + */ + void on_connection_failed(const std::string& reason) override final; /** * Called when successfully connected to the server */ diff --git a/src/main.cpp b/src/main.cpp index 43dfe4b..9a9543d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -66,11 +66,6 @@ int main(int ac, char** av) Poller p; p.add_socket_handler(xmpp_component); - if (!xmpp_component->start()) - { - log_info("Exiting"); - return -1; - } // Install the signals used to exit the process cleanly, or reload the // config @@ -93,8 +88,10 @@ int main(int ac, char** av) sigaction(SIGUSR1, &on_sigusr, nullptr); sigaction(SIGUSR2, &on_sigusr, nullptr); + xmpp_component->start(); + const std::chrono::milliseconds timeout(-1); - while (p.poll(timeout) != -1 || !exiting) + while (p.poll(timeout) != -1) { // Check for empty irc_clients (not connected, or with no joined // channel) and remove them @@ -119,6 +116,8 @@ int main(int ac, char** av) } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. + if (exiting && xmpp_component->is_connecting()) + xmpp_component->close(); if (exiting && p.size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); } diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 010dd58..dbea856 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -155,8 +155,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) else if (this->fds[i].revents & POLLIN) { auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (socket_handler->is_connected()) - socket_handler->on_recv(); + socket_handler->on_recv(); nb_events--; } else if (this->fds[i].revents & POLLOUT) @@ -164,6 +163,8 @@ int Poller::poll(const std::chrono::milliseconds& timeout) auto socket_handler = this->socket_handlers.at(this->fds[i].fd); if (socket_handler->is_connected()) socket_handler->on_send(); + else + socket_handler->connect(); nb_events--; } } @@ -185,7 +186,12 @@ int Poller::poll(const std::chrono::milliseconds& timeout) if (revents[i].events & EPOLLIN) socket_handler->on_recv(); if (revents[i].events & EPOLLOUT) - socket_handler->on_send(); + { + if (socket_handler->is_connected()) + socket_handler->on_send(); + else + socket_handler->connect(); + } } return nb_events; #endif diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 7faa9fd..fc0a359 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -17,18 +18,38 @@ SocketHandler::SocketHandler(): poller(nullptr), - connected(false) + connected(false), + connecting(false) +{ + this->init_socket(); +} + +void SocketHandler::init_socket() { if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) throw std::runtime_error("Could not create socket"); int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); + // Set the socket on non-blocking mode. This is useful to receive a EAGAIN + // error when connect() would block, to not block the whole process if a + // remote is not responsive. + const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); + if ((existing_flags == -1) || + (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) + throw std::runtime_error(std::string("Could not initialize socket: ") + strerror(errno)); } -std::pair SocketHandler::connect(const std::string& address, const std::string& port) +void SocketHandler::connect(const std::string& address, const std::string& port) { - log_info("Trying to connect to " << address << ":" << port); + if (!this->connecting) + { + log_info("Trying to connect to " << address << ":" << port); + } + this->connecting = true; + this->address = address; + this->port = port; + struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = 0; @@ -43,7 +64,8 @@ std::pair SocketHandler::connect(const std::string& address, { log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); this->close(); - return std::make_pair(false, gai_strerror(res)); + this->on_connection_failed(gai_strerror(res)); + return ; } // Make sure the alloced structure is always freed at the end of the @@ -56,14 +78,28 @@ std::pair SocketHandler::connect(const std::string& address, { log_info("Connection success."); this->connected = true; + this->connecting = false; this->on_connected(); - return std::make_pair(true, ""); + return ; + } + else if (errno == EINPROGRESS || errno == EALREADY) + { // retry this process later, when the socket + // is ready to be written on. + log_debug("Need to retry connecting later..." << strerror(errno)); + this->poller->watch_send_events(this); + return ; } log_info("Connection failed:" << strerror(errno)); } log_error("All connection attempts failed."); this->close(); - return std::make_pair(false, ""); + this->on_connection_failed(strerror(errno)); + return ; +} + +void SocketHandler::connect() +{ + this->connect(this->address, this->port); } void SocketHandler::set_poller(Poller* poller) @@ -114,11 +150,11 @@ void SocketHandler::on_send() void SocketHandler::close() { this->connected = false; + this->connecting = false; this->poller->remove_socket_handler(this->get_socket()); ::close(this->socket); // recreate the socket for a potential future usage - if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) - throw std::runtime_error("Could not create socket"); + this->init_socket(); } socket_t SocketHandler::get_socket() const @@ -139,3 +175,8 @@ bool SocketHandler::is_connected() const { return this->connected; } + +bool SocketHandler::is_connecting() const +{ + return this->connecting; +} diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index c27d44c..cb09e4e 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -19,10 +19,15 @@ class SocketHandler public: explicit SocketHandler(); virtual ~SocketHandler() {} + /** + * (re-)Initialize the socket + */ + void init_socket(); /** * Connect to the remote server, and call on_connected() if this succeeds */ - std::pair connect(const std::string& address, const std::string& port); + void connect(const std::string& address, const std::string& port); + void connect(); /** * Set the pointer to the given Poller, to communicate with it. */ @@ -53,6 +58,11 @@ public: * Called when the connection is successful. */ virtual void on_connected() = 0; + /** + * Called when the connection fails. Not when it is closed later, just at + * the connect() call. + */ + virtual void on_connection_failed(const std::string& reason) = 0; /** * Called when we detect a disconnection from the remote host. */ @@ -63,6 +73,7 @@ public: */ virtual void parse_in_buffer() = 0; bool is_connected() const; + bool is_connecting() const; protected: socket_t socket; @@ -86,7 +97,17 @@ protected: * (actually it is sharing our ownership with a Bridge). */ Poller* poller; + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + bool connected; + bool connecting; private: SocketHandler(const SocketHandler&) = delete; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index e059764..c39585d 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -50,9 +50,9 @@ XmppComponent::~XmppComponent() { } -bool XmppComponent::start() +void XmppComponent::start() { - return this->connect("127.0.0.1", "5347").first; + this->connect("127.0.0.1", "5347"); } bool XmppComponent::is_document_open() const @@ -67,6 +67,11 @@ void XmppComponent::send_stanza(const Stanza& stanza) this->send_data(std::move(str)); } +void XmppComponent::on_connection_failed(const std::string& reason) +{ + log_error("Failed to connect to the XMPP server: " << reason); +} + void XmppComponent::on_connected() { log_info("connected to XMPP server"); @@ -103,7 +108,7 @@ void XmppComponent::clean() while (it != this->bridges.end()) { it->second->clean(); - if (it->second->connected_clients() == 0) + if (it->second->active_clients() == 0) it = this->bridges.erase(it); else ++it; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 27a735a..bc2b518 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -20,6 +20,7 @@ class XmppComponent: public SocketHandler public: explicit XmppComponent(const std::string& hostname, const std::string& secret); ~XmppComponent(); + void on_connection_failed(const std::string& reason) override final; void on_connected() override final; void on_connection_close() override final; void parse_in_buffer() override final; @@ -38,9 +39,8 @@ public: void clean(); /** * Connect to the XMPP server. - * Returns false if we failed to connect */ - bool start(); + void start(); /** * Serialize the stanza and add it to the out_buf to be sent to the * server. -- cgit v1.2.3 From 730cc6e1bd52ed2f9e785b08bfbe91e0d42605e5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 22 Feb 2014 23:35:14 +0100 Subject: Save the addrinfo values for reuse on subsequent connect() call --- src/network/socket_handler.cpp | 68 +++++++++++++++++++++++++++++------------- src/network/socket_handler.hpp | 11 +++++++ 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index fc0a359..ee21bca 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -42,35 +43,57 @@ void SocketHandler::init_socket() void SocketHandler::connect(const std::string& address, const std::string& port) { - if (!this->connecting) - { - log_info("Trying to connect to " << address << ":" << port); - } - this->connecting = true; this->address = address; this->port = port; - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; + utils::ScopeGuard sg; struct addrinfo* addr_res; - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - if (res != 0) + if (!this->connecting) { - log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); - this->close(); - this->on_connection_failed(gai_strerror(res)); - return ; + log_info("Trying to connect to " << address << ":" << port); + // Get the addrinfo from getaddrinfo, only if this is the first call + // of this function. + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); + + if (res != 0) + { + log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); + this->close(); + this->on_connection_failed(gai_strerror(res)); + return ; + } + // Make sure the alloced structure is always freed at the end of the + // function + sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); } - - // Make sure the alloced structure is always freed at the end of the - // function - utils::ScopeGuard sg([&addr_res](){ freeaddrinfo(addr_res); }); + else + { + // This function is called again, use the saved addrinfo structure, + // instead of re-doing the whole getaddrinfo process. We insert only + // the meaningful values in the structure, and indicate that these are + // the only possible values with ai_next = NULL. + addr_res = (struct addrinfo*)malloc(sizeof(struct addrinfo)); + if (!addr_res) + { + this->close(); + this->on_connection_failed("memory error"); + return ; + } + sg.add_callback([&addr_res](){ free(addr_res); }); + addr_res->ai_next = NULL; + addr_res->ai_addr = &this->ai_addr; + addr_res->ai_addrlen = this->ai_addrlen; + } + this->connecting = true; for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) { @@ -87,6 +110,9 @@ void SocketHandler::connect(const std::string& address, const std::string& port) // is ready to be written on. log_debug("Need to retry connecting later..." << strerror(errno)); this->poller->watch_send_events(this); + // Save the addrinfo structure, to use it on the next call + this->ai_addrlen = rp->ai_addrlen; + memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); return ; } log_info("Connection failed:" << strerror(errno)); diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index cb09e4e..04e215a 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,6 +1,10 @@ #ifndef SOCKET_HANDLER_INCLUDED # define SOCKET_HANDLER_INCLUDED +#include +#include +#include + #include #include @@ -105,6 +109,13 @@ protected: * Port we are connected/connecting to */ std::string port; + /** + * Keep the details of the addrinfo the triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct sockaddr ai_addr; + socklen_t ai_addrlen; bool connected; bool connecting; -- cgit v1.2.3 From 541af5236e69bcc86c6f993d09358173596483a7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 22 Feb 2014 23:36:03 +0100 Subject: Consider that the connect() succeded if errno EISCONN Apparently on some systems, subsquent connect() calls may fail with EISCONN error, to indicate that the connection succeded in the background, instead of returning 0. --- src/network/socket_handler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index ee21bca..ca9d9e2 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -97,7 +97,8 @@ void SocketHandler::connect(const std::string& address, const std::string& port) for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) { - if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0) + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 + || errno == EISCONN) { log_info("Connection success."); this->connected = true; -- cgit v1.2.3 From 5f82c93754037ecbbe8f3632dc3a5f88071415fc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Feb 2014 21:02:21 +0100 Subject: Use store() instead of operator=() for std::atomic objects --- src/main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 9a9543d..6b24662 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,12 +34,12 @@ int config_help(const std::string& missing_option) static void sigint_handler(int, siginfo_t*, void*) { - stop = true; + stop.store(true); } static void sigusr_handler(int, siginfo_t*, void*) { - reload = true; + reload.store(true); } int main(int ac, char** av) @@ -100,7 +100,7 @@ int main(int ac, char** av) { log_info("Signal received, exiting..."); exiting = true; - stop = false; + stop.store(false); xmpp_component->shutdown(); } if (reload) @@ -112,7 +112,7 @@ int main(int ac, char** av) // Destroy the logger instance, to be recreated the next time a log // line needs to be written Logger::instance().reset(); - reload = false; + reload.store(false); } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. -- cgit v1.2.3 From f6029d5bcf9f529d72e0846661f555e189669b26 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Feb 2014 21:03:51 +0100 Subject: Add missing stdexcept includes --- src/bridge/bridge.cpp | 1 + src/xmpp/xmpp_component.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ed685f9..9b7908a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index c39585d..9b73626 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -6,6 +6,7 @@ #include +#include #include #include -- cgit v1.2.3 From 76b131e13a842657b899d8852efb0f92b2d32145 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 25 Feb 2014 02:10:45 +0100 Subject: Do not try to connect to an irc server if we are connected or connecting --- src/irc/irc_client.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d35437c..232b87f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -27,6 +27,8 @@ IrcClient::~IrcClient() void IrcClient::start() { + if (this->connected || this->connecting) + return ; this->bridge->send_xmpp_message(this->hostname, "", std::string("Connecting to ") + this->hostname + ":" + "6667"); this->connect(this->hostname, "6667"); @@ -144,8 +146,7 @@ void IrcClient::send_quit_command(const std::string& reason) void IrcClient::send_join_command(const std::string& chan_name) { - if (!this->connected) - this->start(); + this->start(); if (this->welcomed == false) this->channels_to_join.push_back(chan_name); else -- cgit v1.2.3 From e2b9e80244abe0712b8d7e3a8f9e3a3474072a4b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 26 Feb 2014 01:15:44 +0100 Subject: Fix a wrong comment --- src/network/socket_handler.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 04e215a..fcc8734 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -82,8 +82,8 @@ public: protected: socket_t socket; /** - * Where data read from the socket is added, until we can parse a whole - * IRC message, the used data is then removed from that buffer. + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. * * TODO: something more efficient than a string. */ -- cgit v1.2.3 From df774d45a9754274a1701d801e4a42e71d38c97c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 27 Feb 2014 23:43:05 +0100 Subject: Use scatter/gather io with sendmsg to avoid concataning strings all the time --- src/network/socket_handler.cpp | 49 ++++++++++++++++++++++++++++++++++-------- src/network/socket_handler.hpp | 5 +++-- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index ca9d9e2..6f7cc3f 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -17,6 +17,10 @@ #include +#ifndef UIO_FASTIOV +# define UIO_FASTIOV 8 +#endif + SocketHandler::SocketHandler(): poller(nullptr), connected(false), @@ -159,16 +163,44 @@ void SocketHandler::on_recv(const size_t nb) void SocketHandler::on_send() { - const ssize_t res = ::send(this->socket, this->out_buf.data(), this->out_buf.size(), MSG_NOSIGNAL); - if (res == -1) + struct iovec msg_iov[UIO_FASTIOV] = {}; + struct msghdr msg{nullptr, 0, + msg_iov, + 0, nullptr, 0, 0}; + for (std::string& s: this->out_buf) + { + // unconsting the content of s is ok, sendmsg will never modify it + msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); + msg_iov[msg.msg_iovlen].iov_len = s.size(); + msg.msg_iovlen++; + } + ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); + if (res < 0) { - log_error("send failed: " << strerror(errno)); + log_error("sendmsg failed: " << strerror(errno)); this->on_connection_close(); this->close(); } else { - this->out_buf = this->out_buf.substr(res, std::string::npos); + // remove all the strings that were successfully sent. + for (auto it = this->out_buf.begin(); + it != this->out_buf.end();) + { + if (static_cast(res) >= (*it).size()) + { + res -= (*it).size(); + it = this->out_buf.erase(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); + break; + } + } if (this->out_buf.empty()) this->poller->stop_watching_send_events(this); } @@ -191,11 +223,10 @@ socket_t SocketHandler::get_socket() const void SocketHandler::send_data(std::string&& data) { - this->out_buf += std::move(data); - if (!this->out_buf.empty()) - { - this->poller->watch_send_events(this); - } + if (data.empty()) + return ; + this->out_buf.emplace_back(std::move(data)); + this->poller->watch_send_events(this); } bool SocketHandler::is_connected() const diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index fcc8734..8828596 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -5,8 +5,9 @@ #include #include -#include #include +#include +#include typedef int socket_t; @@ -91,7 +92,7 @@ protected: /** * Where data is added, when we want to send something to the client. */ - std::string out_buf; + std::list out_buf; /** * A pointer to the poller that manages us, because we need to communicate * with it, sometimes (for example to tell it that he now needs to watch -- cgit v1.2.3 From 86d4347af8532ef85472e47c01d645fa5ad1b3b1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Feb 2014 01:14:38 +0100 Subject: Avoid unnecessary copies by recv()ing data directly into the expat buffer --- src/irc/irc_client.cpp | 2 +- src/irc/irc_client.hpp | 2 +- src/network/socket_handler.cpp | 26 +++++++++++++++++++++----- src/network/socket_handler.hpp | 20 +++++++++++++++++--- src/xmpp/xmpp_component.cpp | 21 ++++++++++++++++++--- src/xmpp/xmpp_component.hpp | 8 ++++++-- src/xmpp/xmpp_parser.cpp | 22 ++++++++++++++++++++-- src/xmpp/xmpp_parser.hpp | 10 +++++++++- 8 files changed, 93 insertions(+), 18 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 232b87f..0c36372 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -80,7 +80,7 @@ std::string IrcClient::get_own_nick() const return this->current_nick; } -void IrcClient::parse_in_buffer() +void IrcClient::parse_in_buffer(const size_t) { while (true) { diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 28a1424..908db8e 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -45,7 +45,7 @@ public: * Parse the data we have received so far and try to get one or more * complete messages from it. */ - void parse_in_buffer() override final; + void parse_in_buffer(const size_t) override final; /** * Return the channel with this name, create it if it does not yet exist */ diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 6f7cc3f..eb427b8 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -138,11 +138,16 @@ void SocketHandler::set_poller(Poller* poller) this->poller = poller; } -void SocketHandler::on_recv(const size_t nb) +void SocketHandler::on_recv() { - char buf[4096]; + static constexpr size_t buf_size = 4096; + char buf[buf_size]; + void* recv_buf = this->get_receive_buffer(buf_size); - ssize_t size = ::recv(this->socket, buf, nb, 0); + if (recv_buf == nullptr) + recv_buf = buf; + + ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); if (0 == size) { this->on_connection_close(); @@ -156,8 +161,14 @@ void SocketHandler::on_recv(const size_t nb) } else { - this->in_buf += std::string(buf, size); - this->parse_in_buffer(); + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); } } @@ -238,3 +249,8 @@ bool SocketHandler::is_connecting() const { return this->connecting; } + +void* SocketHandler::get_receive_buffer(const size_t) const +{ + return nullptr; +} diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 8828596..35b0fdc 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -41,7 +41,7 @@ public: * Reads data in our in_buf and the call parse_in_buf, for the implementor * to handle the data received so far. */ - void on_recv(const size_t nb = 4096); + void on_recv(); /** * Write as much data from out_buf as possible, in the socket. */ @@ -73,14 +73,28 @@ public: */ virtual void on_connection_close() = 0; /** - * Handle/consume (some of) the data received so far. If some data is used, the in_buf + * Handle/consume (some of) the data received so far. The data to handle + * may be in the in_buf buffer, or somewhere else, depending on what + * get_receive_buffer() returned. If some data is used from in_buf, it * should be truncated, only the unused data should be left untouched. + * + * The size argument is the size of the last chunk of data that was added to the buffer. */ - virtual void parse_in_buffer() = 0; + virtual void parse_in_buffer(const size_t size) = 0; bool is_connected() const; bool is_connecting() const; protected: + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * can provided, nullptr is returned (the default implementation does + * that). + */ + virtual void* get_receive_buffer(const size_t size) const; + /** + * The handled socket. + */ socket_t socket; /** * Where data read from the socket is added until we can extract a full diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9b73626..cd424ed 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -89,10 +89,20 @@ void XmppComponent::on_connection_close() log_info("XMPP server closed connection"); } -void XmppComponent::parse_in_buffer() +void XmppComponent::parse_in_buffer(const size_t size) { - this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); - this->in_buf.clear(); + if (!this->in_buf.empty()) + { // This may happen if the parser could not allocate enough space for + // us. We try to feed it the data that was read into our in_buf + // instead. If this fails again we are in trouble. + this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); + this->in_buf.clear(); + } + else + { // Just tell the parser to parse the data that was placed into the + // buffer it provided to us with GetBuffer + this->parser.parse(size, false); + } } void XmppComponent::shutdown() @@ -308,6 +318,11 @@ Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) } } +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) { XmlNode node("message"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index bc2b518..0c040f4 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -23,7 +23,7 @@ public: void on_connection_failed(const std::string& reason) override final; void on_connected() override final; void on_connection_close() override final; - void parse_in_buffer() override final; + void parse_in_buffer(const size_t size) override final; /** * Send a "close" message to all our connected peers. That message * depends on the protocol used (this may be a QUIT irc message, or a @@ -142,7 +142,11 @@ private: * if none already exist. */ Bridge* get_user_bridge(const std::string& user_jid); - + /** + * Return a buffer provided by the XML parser, to read data directly into + * it, and avoiding some unnecessary copy. + */ + void* get_receive_buffer(const size_t size) const override final; XmppParser parser; std::string stream_id; std::string served_hostname; diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 6e4809d..9fcc16c 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -1,6 +1,8 @@ #include #include +#include + /** * Expat handlers. Called by the Expat library, never by ourself. * They just forward the call to the XmppParser corresponding methods. @@ -45,9 +47,25 @@ XmppParser::~XmppParser() XML_ParserFree(this->parser); } -void XmppParser::feed(const char* data, const int len, const bool is_final) +int XmppParser::feed(const char* data, const int len, const bool is_final) +{ + int res = XML_Parse(this->parser, data, len, is_final); + if (res == 0) + log_error("Xml_Parse encountered an error"); + return res; +} + +int XmppParser::parse(const int len, const bool is_final) +{ + int res = XML_ParseBuffer(this->parser, len, is_final); + if (res == 0) + log_error("Xml_Parsebuffer encountered an error"); + return res; +} + +void* XmppParser::get_buffer(const size_t size) const { - XML_Parse(this->parser, data, len, is_final); + return XML_GetBuffer(this->parser, static_cast(size)); } void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp index afdfdfa..df9cda7 100644 --- a/src/xmpp/xmpp_parser.hpp +++ b/src/xmpp/xmpp_parser.hpp @@ -37,7 +37,15 @@ public: /** * Feed the parser with some XML data */ - void feed(const char* data, const int len, const bool is_final); + int feed(const char* data, const int len, const bool is_final); + /** + * Parse the data placed in the parser buffer + */ + int parse(const int size, const bool is_final); + /** + * Get a buffer provided by the xml parser. + */ + void* get_buffer(const size_t size) const; /** * Add one callback for the various events that this parser can spawn. */ -- cgit v1.2.3 From db6ea52b44b2e6f2c03c818ffa94af93976f78c5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Feb 2014 01:18:21 +0100 Subject: Provide details about what error the XML parser encountered --- src/xmpp/xmpp_parser.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 9fcc16c..064453e 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -51,7 +51,8 @@ int XmppParser::feed(const char* data, const int len, const bool is_final) { int res = XML_Parse(this->parser, data, len, is_final); if (res == 0) - log_error("Xml_Parse encountered an error"); + log_error("Xml_Parse encountered an error: " << + XML_ErrorString(XML_GetErrorCode(this->parser))) return res; } @@ -59,7 +60,8 @@ int XmppParser::parse(const int len, const bool is_final) { int res = XML_ParseBuffer(this->parser, len, is_final); if (res == 0) - log_error("Xml_Parsebuffer encountered an error"); + log_error("Xml_Parsebuffer encountered an error: " << + XML_ErrorString(XML_GetErrorCode(this->parser))); return res; } -- cgit v1.2.3 From 601159e8e25028155a86de2e5fdf55c9402936d7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 3 Mar 2014 21:02:55 +0100 Subject: Fix a bus error by not going above an array boundary --- src/network/socket_handler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index eb427b8..d774927 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -183,7 +183,8 @@ void SocketHandler::on_send() // unconsting the content of s is ok, sendmsg will never modify it msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); msg_iov[msg.msg_iovlen].iov_len = s.size(); - msg.msg_iovlen++; + if (++msg.msg_iovlen == UIO_FASTIOV) + break; } ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); if (res < 0) -- cgit v1.2.3 From 930696e48a992222d1edf15b00f7b566512428a7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 10 Mar 2014 00:12:42 +0100 Subject: Remove useless cast --- src/network/socket_handler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index d774927..3f4c01d 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -153,7 +153,7 @@ void SocketHandler::on_recv() this->on_connection_close(); this->close(); } - else if (-1 == static_cast(size)) + else if (-1 == size) { log_warning("Error while reading from socket: " << strerror(errno)); this->on_connection_close(); -- cgit v1.2.3 From 3b1bf740a3299e3373916fd343492420550464e1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 14 Mar 2014 01:34:35 +0100 Subject: Fix the CMakeLists.txt regarding ronn --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21bfb78..21e7fa6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Og") +# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") # Define a __FILENAME__ macro to get the filename of each file, instead of # the full path as in __FILE__ @@ -47,11 +47,10 @@ find_program(RONN_EXECUTABLE NAMES ronn if(RONN_EXECUTABLE) set(WITH_DOC true) add_custom_target(doc - ${RONN_SOFTWARE} --roff ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1.md + ${RONN_EXECUTABLE} --roff ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1.md COMMENT "Generate the man page" VERBATIM) endif() - # ## utils # -- cgit v1.2.3 From e196d2f1f400f5c9634ed42fcbdf5c5fb63a13fb Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 18:37:31 +0100 Subject: Do not send data if we are connected, send it only once we actually are --- src/irc/irc_client.cpp | 1 + src/network/socket_handler.cpp | 9 ++++++++- src/network/socket_handler.hpp | 4 ++++ src/xmpp/xmpp_component.cpp | 4 ++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0c36372..3737b91 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -45,6 +45,7 @@ void IrcClient::on_connected() this->send_nick_command(this->username); this->send_user_command(this->username, this->username); this->send_gateway_message("Connected to IRC server."); + this->send_pending_data(); } void IrcClient::on_connection_close() diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 3f4c01d..f344786 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -238,7 +238,14 @@ void SocketHandler::send_data(std::string&& data) if (data.empty()) return ; this->out_buf.emplace_back(std::move(data)); - this->poller->watch_send_events(this); + if (this->connected) + this->poller->watch_send_events(this); +} + +void SocketHandler::send_pending_data() +{ + if (this->connected && !this->out_buf.empty()) + this->poller->watch_send_events(this); } bool SocketHandler::is_connected() const diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 35b0fdc..f554350 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -51,6 +51,10 @@ public: * notified when a send event is ready. */ void send_data(std::string&& data); + /** + * Watch the socket for send events, if our out buffer is not empty. + */ + void send_pending_data(); /** * Returns the socket that should be handled by the poller. */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index cd424ed..d30f778 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -82,6 +82,9 @@ void XmppComponent::on_connected() node["to"] = this->served_hostname; this->send_stanza(node); this->doc_open = true; + // We may have some pending data to send: this happens when we try to send + // some data before we are actually connected. We send that data right now, if any + this->send_pending_data(); } void XmppComponent::on_connection_close() @@ -161,6 +164,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) void XmppComponent::on_remote_stream_close(const XmlNode& node) { log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); + this->doc_open = false; } void XmppComponent::on_stanza(const Stanza& stanza) -- cgit v1.2.3 From ffc820e234ebba39a0f04607f9a0fb044fe31b73 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 20:31:02 +0100 Subject: Reconnected immediately to the XMPP whenever it closes the connection --- src/main.cpp | 11 +++++++++++ src/network/socket_handler.cpp | 3 +++ src/xmpp/xmpp_component.cpp | 5 +++++ src/xmpp/xmpp_component.hpp | 4 ++++ src/xmpp/xmpp_parser.cpp | 15 +++++++++++++++ src/xmpp/xmpp_parser.hpp | 8 ++++++++ 6 files changed, 46 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 6b24662..d40c457 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -114,6 +114,17 @@ int main(int ac, char** av) Logger::instance().reset(); reload.store(false); } + // Reconnect to the XMPP server if this was not intended. This may have + // happened because we sent something invalid to it and it decided to + // close the connection. This is a bug that should be fixed, but we + // still reconnect automatically instead of dropping everything + if (!exiting && !xmpp_component->is_connected() && + !xmpp_component->is_connecting()) + { + xmpp_component->reset(); + p.add_socket_handler(xmpp_component); + xmpp_component->start(); + } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. if (exiting && xmpp_component->is_connecting()) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index f344786..6f91e74 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -222,6 +222,9 @@ void SocketHandler::close() { this->connected = false; this->connecting = false; + this->in_buf.clear(); + this->out_buf.clear(); + this->port.clear(); this->poller->remove_socket_handler(this->get_socket()); ::close(this->socket); // recreate the socket for a potential future usage diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index d30f778..5400535 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -167,6 +167,11 @@ void XmppComponent::on_remote_stream_close(const XmlNode& node) this->doc_open = false; } +void XmppComponent::reset() +{ + this->parser.reset(); +} + void XmppComponent::on_stanza(const Stanza& stanza) { log_debug("XMPP RECEIVING: " << stanza.to_string()); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 0c040f4..3e401e5 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -41,6 +41,10 @@ public: * Connect to the XMPP server. */ void start(); + /** + * Reset the component so we can use the component on a new XMPP stream + */ + void reset(); /** * Serialize the stanza and add it to the out_buf to be sent to the * server. diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 064453e..867648b 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -30,6 +30,11 @@ static void character_data_handler(void *user_data, const XML_Char *s, int len) XmppParser::XmppParser(): level(0), current_node(nullptr) +{ + this->init_xml_parser(); +} + +void XmppParser::init_xml_parser() { // Create the expat parser this->parser = XML_ParserCreateNS("UTF-8", ':'); @@ -65,6 +70,16 @@ int XmppParser::parse(const int len, const bool is_final) return res; } +void XmppParser::reset() +{ + XML_ParserFree(this->parser); + this->init_xml_parser(); + if (this->current_node) + delete this->current_node; + this->current_node = nullptr; + this->level = 0; +} + void* XmppParser::get_buffer(const size_t size) const { return XML_GetBuffer(this->parser, static_cast(size)); diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp index df9cda7..b87ee6d 100644 --- a/src/xmpp/xmpp_parser.hpp +++ b/src/xmpp/xmpp_parser.hpp @@ -34,6 +34,10 @@ public: ~XmppParser(); public: + /** + * Init the XML parser and install the callbacks + */ + void init_xml_parser(); /** * Feed the parser with some XML data */ @@ -42,6 +46,10 @@ public: * Parse the data placed in the parser buffer */ int parse(const int size, const bool is_final); + /** + * Reset the parser, so it can be used from scratch afterward + */ + void reset(); /** * Get a buffer provided by the xml parser. */ -- cgit v1.2.3 From cdc3183d9eb234feb2e8ca3d3019b78cce73bcf6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 20:39:59 +0100 Subject: Introduce two new bool, to know if the xmpp component should try to reconnect If we never succeeded our connection+auth process, means we should probably not attempt any-more, and just give up. If we ever did, this means a reconnect is a good idea --- src/main.cpp | 3 ++- src/xmpp/xmpp_component.cpp | 5 +++++ src/xmpp/xmpp_component.hpp | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d40c457..6cba134 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,7 +118,8 @@ int main(int ac, char** av) // happened because we sent something invalid to it and it decided to // close the connection. This is a bug that should be fixed, but we // still reconnect automatically instead of dropping everything - if (!exiting && !xmpp_component->is_connected() && + if (!exiting && xmpp_component->ever_auth && + !xmpp_component->is_connected() && !xmpp_component->is_connecting()) { xmpp_component->reset(); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 5400535..d083190 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -24,6 +24,8 @@ #define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): + ever_auth(false), + last_auth(false), served_hostname(hostname), secret(secret), authenticated(false), @@ -144,6 +146,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) return ; } + this->last_auth = false; // Try to authenticate char digest[HASH_LENGTH * 2 + 1]; sha1nfo sha1; @@ -212,6 +215,8 @@ void XmppComponent::handle_handshake(const Stanza& stanza) { (void)stanza; this->authenticated = true; + this->ever_auth = true; + this->last_auth = true; log_info("Authenticated with the XMPP server"); } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 3e401e5..373104c 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -140,6 +140,15 @@ public: void handle_iq(const Stanza& stanza); void handle_error(const Stanza& stanza); + /** + * Whether or not we ever succeeded our authentication to the XMPP server + */ + bool ever_auth; + /** + * Whether or not the last connection+auth attempt was successful + */ + bool last_auth; + private: /** * Return the bridge associated with the given full JID. Create a new one -- cgit v1.2.3 From e47b164a43e0c4f411869fbbefe875fd70df8506 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 21:08:09 +0100 Subject: Send an error presence whenever a user tries to join an invalid room --- src/xmpp/xmpp_component.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- src/xmpp/xmpp_component.hpp | 6 ++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index d083190..a2c6c2b 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -231,7 +231,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) } catch (const AttributeNotFound&) {} - if (!iid.chan.empty() && !iid.chan.empty()) + if (!iid.chan.empty() && !iid.server.empty()) { // presence toward a MUC that corresponds to an irc channel if (type.empty()) { @@ -246,6 +246,12 @@ void XmppComponent::handle_presence(const Stanza& stanza) bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); } } + else + { + // An user wants to join an invalid IRC channel, return a presence error to him + if (type.empty()) + this->send_invalid_room_error(to.local, to.resource, stanza["from"]); + } } void XmppComponent::handle_message(const Stanza& stanza) @@ -392,6 +398,39 @@ void XmppComponent::send_user_join(const std::string& from, this->send_stanza(node); } +void XmppComponent::send_invalid_room_error(const std::string& muc_name, + const std::string& nick, + const std::string& to) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + presence["to"] = to; + presence["type"] = "error"; + XmlNode x("x"); + x["xmlns"] = MUC_NS; + x.close(); + presence.add_child(std::move(x)); + XmlNode error("error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = "wait"; + XmlNode service_unavailable("service-unavailable"); + service_unavailable["xmlns"] = STANZA_NS; + service_unavailable.close(); + error.add_child(std::move(service_unavailable)); + XmlNode text("text"); + text["xmlns"] = STANZA_NS; + text["xml:lang"] = "en"; + text.set_inner(muc_name + + " is not a valid IRC channel name. A correct room jid is of the form: #%@" + + this->served_hostname); + text.close(); + error.add_child(std::move(text)); + error.close(); + presence.add_child(std::move(error)); + presence.close(); + this->send_stanza(presence); +} + void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) { XmlNode message("message"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 373104c..d4853c8 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -86,6 +86,12 @@ public: const std::string& role, const std::string& to, const bool self); + /** + * Send an error to indicate that the user tried to join an invalid room + */ + void send_invalid_room_error(const std::string& muc_jid, + const std::string& nick, + const std::string& to); /** * Send the MUC topic to the user */ -- cgit v1.2.3 From 1208ba2524da3b4d3a592306838a55d446b3414a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 21:29:12 +0100 Subject: Use the correct type of error --- src/xmpp/xmpp_component.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index a2c6c2b..cd6e06b 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -413,10 +413,10 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, XmlNode error("error"); error["by"] = muc_name + "@" + this->served_hostname; error["type"] = "wait"; - XmlNode service_unavailable("service-unavailable"); - service_unavailable["xmlns"] = STANZA_NS; - service_unavailable.close(); - error.add_child(std::move(service_unavailable)); + XmlNode item_not_found("item-not-found"); + item_not_found["xmlns"] = STANZA_NS; + item_not_found.close(); + error.add_child(std::move(item_not_found)); XmlNode text("text"); text["xmlns"] = STANZA_NS; text["xml:lang"] = "en"; -- cgit v1.2.3 From 2ffbfb71ddeb791e6a5ab1955208067f2856aeea Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Mar 2014 21:29:46 +0100 Subject: Idem --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index cd6e06b..94ad634 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -412,7 +412,7 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, presence.add_child(std::move(x)); XmlNode error("error"); error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "wait"; + error["type"] = "cancel"; XmlNode item_not_found("item-not-found"); item_not_found["xmlns"] = STANZA_NS; item_not_found.close(); -- cgit v1.2.3 From 3c5f823621e170aaa5ae9f240335bea49d470da5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Mar 2014 01:16:05 +0100 Subject: Send a stanza error when receiving bad stanzas Whether this is a bad-request (missing XML attributes or elements) or an internal server error. --- src/xmpp/xmpp_component.cpp | 120 ++++++++++++++++++++++++++++++++++++++++---- src/xmpp/xmpp_component.hpp | 6 +++ 2 files changed, 116 insertions(+), 10 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 94ad634..1eea8ba 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -204,6 +205,38 @@ void XmppComponent::send_stream_error(const std::string& name, const std::string this->send_stanza(node); } +void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, + const std::string& id, const std::string& error_type, + const std::string& defined_condition, const std::string& text) +{ + Stanza node(kind); + if (!to.empty()) + node["to"] = to; + if (!from.empty()) + node["from"] = from; + if (!id.empty()) + node["id"] = id; + node["type"] = "error"; + XmlNode error("error"); + error["type"] = error_type; + XmlNode inner_error(defined_condition); + inner_error["xmlns"] = STANZA_NS; + inner_error.close(); + error.add_child(std::move(inner_error)); + if (!text.empty()) + { + XmlNode text_node("text"); + text_node["xmlns"] = STANZA_NS; + text_node.set_inner(text); + text_node.close(); + error.add_child(std::move(text_node)); + } + error.close(); + node.add_child(std::move(error)); + node.close(); + this->send_stanza(node); +} + void XmppComponent::close_document() { log_debug("XMPP SENDING: "); @@ -222,6 +255,14 @@ void XmppComponent::handle_handshake(const Stanza& stanza) void XmppComponent::handle_presence(const Stanza& stanza) { + std::string id; + try { + id = stanza["id"]; + } catch (const AttributeNotFound&) {} + utils::ScopeGuard malformed_stanza_error([this, &id](){ + this->send_stanza_error("presence", "", this->served_hostname, id, + "modify", "bad-request", ""); + }); Bridge* bridge = this->get_user_bridge(stanza["from"]); Jid to(stanza["to"]); Iid iid(to.local); @@ -230,6 +271,20 @@ void XmppComponent::handle_presence(const Stanza& stanza) type = stanza["type"]; } catch (const AttributeNotFound&) {} + malformed_stanza_error.disable(); + + // An error stanza is sent whenever we exit this function without + // disabling this scopeguard. If error_type and error_name are not + // changed, the error signaled is internal-server-error. Change their + // value to signal an other kind of error. For example + // feature-not-implemented, etc. Any non-error process should reach the + // stanza_error.disable() call at the end of the function. + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + this->send_stanza_error("presence", stanza["from"], stanza["to"], id, + error_type, error_name, ""); + }); if (!iid.chan.empty() && !iid.server.empty()) { // presence toward a MUC that corresponds to an irc channel @@ -252,15 +307,37 @@ void XmppComponent::handle_presence(const Stanza& stanza) if (type.empty()) this->send_invalid_room_error(to.local, to.resource, stanza["from"]); } + stanza_error.disable(); } void XmppComponent::handle_message(const Stanza& stanza) { + std::string id; + try { + id = stanza["id"]; + } catch (const AttributeNotFound&) {} + utils::ScopeGuard malformed_stanza_error([this, &id](){ + this->send_stanza_error("message", "", this->served_hostname, id, + "modify", "bad-request", ""); + }); Bridge* bridge = this->get_user_bridge(stanza["from"]); Jid to(stanza["to"]); Iid iid(to.local); + std::string type; + try { + type = stanza["type"]; + } + catch (const AttributeNotFound&) {} + malformed_stanza_error.disable(); + + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + this->send_stanza_error("message", stanza["from"], stanza["to"], id, + error_type, error_name, ""); + }); XmlNode* body = stanza.get_child(COMPONENT_NS":body"); - if (stanza["type"] == "groupchat") + if (type == "groupchat") { if (to.resource.empty()) if (body && !body->get_inner().empty()) @@ -274,25 +351,37 @@ void XmppComponent::handle_message(const Stanza& stanza) if (body && !body->get_inner().empty()) bridge->send_private_message(iid, body->get_inner()); } + stanza_error.disable(); } void XmppComponent::handle_iq(const Stanza& stanza) { + std::string id; + try { + id = stanza["id"]; + } catch (const AttributeNotFound&) {} + utils::ScopeGuard malformed_stanza_error([this, &id](){ + this->send_stanza_error("iq", "", this->served_hostname, id, + "modify", "bad-request", ""); + }); Bridge* bridge = this->get_user_bridge(stanza["from"]); Jid to(stanza["to"]); - std::string type; - try { - type = stanza["type"]; - } - catch (const AttributeNotFound&) - { return; } + std::string type = stanza["type"]; + malformed_stanza_error.disable(); + + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + this->send_stanza_error("iq", stanza["from"], stanza["to"], id, + error_type, error_name, ""); + }); if (type == "set") { XmlNode* query; if ((query = stanza.get_child(MUC_ADMIN_NS":query"))) { - XmlNode* child; - if ((child = query->get_child(MUC_ADMIN_NS":item"))) + const XmlNode* child = query->get_child(MUC_ADMIN_NS":item"); + if (child) { std::string nick; std::string role; @@ -301,7 +390,11 @@ void XmppComponent::handle_iq(const Stanza& stanza) role = (*child)["role"]; } catch (const AttributeNotFound&) - { return; } + { + error_type = "modify"; + error_name = "bad-request"; + return; + } if (!nick.empty() && role == "none") { std::string reason; @@ -314,6 +407,13 @@ void XmppComponent::handle_iq(const Stanza& stanza) } } } + else + { + error_type = "cancel"; + error_name = "feature-not-implemented"; + return; + } + stanza_error.disable(); } void XmppComponent::handle_error(const Stanza& stanza) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index d4853c8..5a5d3d8 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -68,6 +68,12 @@ public: * describing the error. */ void send_stream_error(const std::string& message, const std::string& explanation); + /** + * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error + */ + void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, + const std::string& id, const std::string& error_type, + const std::string& defined_condition, const std::string& text); /** * Send the closing signal for our document (not closing the connection though). */ -- cgit v1.2.3 From 44b72b743e68168e0ab55a74719d57971fe81aa3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Mar 2014 01:47:05 +0100 Subject: The absence of a from attribute is an unrecoverable error, just ignore it --- src/xmpp/xmpp_component.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1eea8ba..d849884 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -255,12 +255,16 @@ void XmppComponent::handle_handshake(const Stanza& stanza) void XmppComponent::handle_presence(const Stanza& stanza) { + std::string from; std::string id; try { id = stanza["id"]; + from = stanza["from"]; } catch (const AttributeNotFound&) {} - utils::ScopeGuard malformed_stanza_error([this, &id](){ - this->send_stanza_error("presence", "", this->served_hostname, id, + if (from.empty()) + return; + utils::ScopeGuard malformed_stanza_error([&](){ + this->send_stanza_error("presence", from, this->served_hostname, id, "modify", "bad-request", ""); }); Bridge* bridge = this->get_user_bridge(stanza["from"]); @@ -281,7 +285,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) // stanza_error.disable() call at the end of the function. std::string error_type("cancel"); std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + utils::ScopeGuard stanza_error([&](){ this->send_stanza_error("presence", stanza["from"], stanza["to"], id, error_type, error_name, ""); }); @@ -312,12 +316,16 @@ void XmppComponent::handle_presence(const Stanza& stanza) void XmppComponent::handle_message(const Stanza& stanza) { + std::string from; std::string id; try { id = stanza["id"]; + from = stanza["from"]; } catch (const AttributeNotFound&) {} - utils::ScopeGuard malformed_stanza_error([this, &id](){ - this->send_stanza_error("message", "", this->served_hostname, id, + if (from.empty()) + return; + utils::ScopeGuard malformed_stanza_error([&](){ + this->send_stanza_error("message", from, this->served_hostname, id, "modify", "bad-request", ""); }); Bridge* bridge = this->get_user_bridge(stanza["from"]); @@ -332,7 +340,7 @@ void XmppComponent::handle_message(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + utils::ScopeGuard stanza_error([&](){ this->send_stanza_error("message", stanza["from"], stanza["to"], id, error_type, error_name, ""); }); @@ -357,11 +365,15 @@ void XmppComponent::handle_message(const Stanza& stanza) void XmppComponent::handle_iq(const Stanza& stanza) { std::string id; + std::string from; try { id = stanza["id"]; + from = stanza["from"]; } catch (const AttributeNotFound&) {} - utils::ScopeGuard malformed_stanza_error([this, &id](){ - this->send_stanza_error("iq", "", this->served_hostname, id, + if (from.empty()) + return; + utils::ScopeGuard malformed_stanza_error([&](){ + this->send_stanza_error("iq", from, this->served_hostname, id, "modify", "bad-request", ""); }); Bridge* bridge = this->get_user_bridge(stanza["from"]); @@ -371,7 +383,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([this, &stanza, &error_type, &error_name, &id](){ + utils::ScopeGuard stanza_error([&](){ this->send_stanza_error("iq", stanza["from"], stanza["to"], id, error_type, error_name, ""); }); -- cgit v1.2.3 From e971f64f92c640d3ee634b01eeba7fbf056fdaac Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Mar 2014 01:47:48 +0100 Subject: Catch all exception produced by a received message (irc or xmpp) --- src/irc/irc_client.cpp | 8 +++++++- src/xmpp/xmpp_parser.cpp | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 3737b91..884f214 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -93,7 +93,13 @@ void IrcClient::parse_in_buffer(const size_t) this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); auto cb = irc_callbacks.find(message.command); if (cb != irc_callbacks.end()) - (this->*(cb->second))(message); + { + try { + (this->*(cb->second))(message); + } catch (const std::exception& e) { + log_error("Unhandled exception: " << e.what()); + } + } else log_info("No handler for command " << message.command); } diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 867648b..536d9da 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -131,7 +131,13 @@ void XmppParser::char_data(const XML_Char* data, int len) void XmppParser::stanza_event(const Stanza& stanza) const { for (const auto& callback: this->stanza_callbacks) - callback(stanza); + { + try { + callback(stanza); + } catch (const std::exception& e) { + log_debug("Unhandled exception: " << e.what()); + } + } } void XmppParser::stream_open_event(const XmlNode& node) const -- cgit v1.2.3 From 65601a96d20d4ce010723df3a059ee5a9713fae7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 28 Mar 2014 01:48:08 +0100 Subject: The default type for message stanza is "normal" --- src/xmpp/xmpp_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index d849884..2799a21 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -335,7 +335,9 @@ void XmppComponent::handle_message(const Stanza& stanza) try { type = stanza["type"]; } - catch (const AttributeNotFound&) {} + catch (const AttributeNotFound&) { + type = "normal"; + } malformed_stanza_error.disable(); std::string error_type("cancel"); -- cgit v1.2.3 From a20c60a0d84f2f22777e3831cac1315302b7a095 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 10 Apr 2014 20:11:17 +0200 Subject: Messages coming from the IRC server are of type "chat" --- src/bridge/bridge.cpp | 4 ++-- src/xmpp/xmpp_component.cpp | 4 +++- src/xmpp/xmpp_component.hpp | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9b7908a..277f696 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -188,7 +188,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st this->make_xmpp_body(body), this->user_jid); else this->xmpp->send_message(iid.chan + "%" + iid.server, - this->make_xmpp_body(body), this->user_jid); + this->make_xmpp_body(body), this->user_jid, "chat"); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) @@ -220,7 +220,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho body = std::string("[") + author + std::string("] ") + msg; else body = msg; - this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid); + this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid, "chat"); } void Bridge::send_user_join(const std::string& hostname, diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 2799a21..85a83de 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -457,11 +457,13 @@ 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) +void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type) { XmlNode node("message"); node["to"] = to; node["from"] = from + "@" + this->served_hostname; + if (!type.empty()) + node["type"] = type; XmlNode body_node("body"); body_node.set_inner(std::get<0>(body)); body_node.close(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 5a5d3d8..3e13086 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -81,7 +81,8 @@ public: /** * Send a message from from@served_hostname, with the given body */ - void send_message(const std::string& from, Xmpp::body&& body, const std::string& to); + void send_message(const std::string& from, Xmpp::body&& body, + const std::string& to, const std::string& type); /** * Send a join from a new participant */ -- cgit v1.2.3 From f00f5c3ffbc72652568c75de6e48e41b3275fb0a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 11 Apr 2014 23:06:13 +0200 Subject: Do not use exceptions for missing tags, improvement in code simplicity --- src/test.cpp | 2 +- src/xmpp/xmpp_component.cpp | 124 +++++++++++++++++++------------------------- src/xmpp/xmpp_stanza.cpp | 4 +- src/xmpp/xmpp_stanza.hpp | 11 +--- 4 files changed, 58 insertions(+), 83 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index b95b379..7a2051f 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -87,7 +87,7 @@ int main() { std::cout << stanza.to_string() << std::endl; assert(stanza.get_name() == "stream_ns:stanza"); - assert(stanza["b"] == "c"); + assert(stanza.get_tag("b") == "c"); assert(stanza.get_inner() == "inner"); assert(stanza.get_tail() == ""); assert(stanza.get_child("stream_ns:child1") != nullptr); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 85a83de..606be10 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -135,11 +135,8 @@ void XmppComponent::clean() void XmppComponent::on_remote_stream_open(const XmlNode& node) { log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); - try - { - this->stream_id = node["id"]; - } - catch (const AttributeNotFound& e) + this->stream_id = node.get_tag("id"); + if (this->stream_id.empty()) { log_error("Error: no attribute 'id' found"); this->send_stream_error("bad-format", "missing 'id' attribute"); @@ -255,27 +252,27 @@ void XmppComponent::handle_handshake(const Stanza& stanza) void XmppComponent::handle_presence(const Stanza& stanza) { - std::string from; - std::string id; - try { - id = stanza["id"]; - from = stanza["from"]; - } catch (const AttributeNotFound&) {} + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + // Check for mandatory tags if (from.empty()) - return; - utils::ScopeGuard malformed_stanza_error([&](){ + { + log_warning("Received an invalid presence stanza: tag 'from' is missing."); + return; + } + if (to_str.empty()) + { this->send_stanza_error("presence", from, this->served_hostname, id, - "modify", "bad-request", ""); - }); - Bridge* bridge = this->get_user_bridge(stanza["from"]); - Jid to(stanza["to"]); + "modify", "bad-request", "Missing 'to' tag"); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); Iid iid(to.local); - std::string type; - try { - type = stanza["type"]; - } - catch (const AttributeNotFound&) {} - malformed_stanza_error.disable(); // An error stanza is sent whenever we exit this function without // disabling this scopeguard. If error_type and error_name are not @@ -286,7 +283,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("presence", stanza["from"], stanza["to"], id, + this->send_stanza_error("presence", from, to_str, id, error_type, error_name, ""); }); @@ -309,41 +306,30 @@ void XmppComponent::handle_presence(const Stanza& stanza) { // An user wants to join an invalid IRC channel, return a presence error to him if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, stanza["from"]); + this->send_invalid_room_error(to.local, to.resource, from); } stanza_error.disable(); } void XmppComponent::handle_message(const Stanza& stanza) { - std::string from; - std::string id; - try { - id = stanza["id"]; - from = stanza["from"]; - } catch (const AttributeNotFound&) {} + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + if (from.empty()) return; - utils::ScopeGuard malformed_stanza_error([&](){ - this->send_stanza_error("message", from, this->served_hostname, id, - "modify", "bad-request", ""); - }); - Bridge* bridge = this->get_user_bridge(stanza["from"]); - Jid to(stanza["to"]); - Iid iid(to.local); - std::string type; - try { - type = stanza["type"]; - } - catch (const AttributeNotFound&) { + if (type.empty()) type = "normal"; - } - malformed_stanza_error.disable(); + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + Iid iid(to.local); std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("message", stanza["from"], stanza["to"], id, + this->send_stanza_error("message", from, to_str, id, error_type, error_name, ""); }); XmlNode* body = stanza.get_child(COMPONENT_NS":body"); @@ -366,27 +352,27 @@ void XmppComponent::handle_message(const Stanza& stanza) void XmppComponent::handle_iq(const Stanza& stanza) { - std::string id; - std::string from; - try { - id = stanza["id"]; - from = stanza["from"]; - } catch (const AttributeNotFound&) {} + std::string id = stanza.get_tag("id"); + std::string from = stanza.get_tag("from"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + if (from.empty()) return; - utils::ScopeGuard malformed_stanza_error([&](){ + if (id.empty() || to_str.empty() || type.empty()) + { this->send_stanza_error("iq", from, this->served_hostname, id, "modify", "bad-request", ""); - }); - Bridge* bridge = this->get_user_bridge(stanza["from"]); - Jid to(stanza["to"]); - std::string type = stanza["type"]; - malformed_stanza_error.disable(); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(from); std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("iq", stanza["from"], stanza["to"], id, + this->send_stanza_error("iq", from, to_str, id, error_type, error_name, ""); }); if (type == "set") @@ -397,18 +383,8 @@ void XmppComponent::handle_iq(const Stanza& stanza) const XmlNode* child = query->get_child(MUC_ADMIN_NS":item"); if (child) { - std::string nick; - std::string role; - try { - nick = (*child)["nick"]; - role = (*child)["role"]; - } - catch (const AttributeNotFound&) - { - error_type = "modify"; - error_name = "bad-request"; - return; - } + std::string nick = child->get_tag("nick"); + std::string role = child->get_tag("role"); if (!nick.empty() && role == "none") { std::string reason; @@ -418,6 +394,12 @@ void XmppComponent::handle_iq(const Stanza& stanza) Iid iid(to.local); bridge->send_irc_kick(iid, nick, reason); } + else + { + error_type = "cancel"; + error_name = "feature-not-implemented"; + return; + } } } } diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 23b2d25..948e5f5 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -216,7 +216,7 @@ bool XmlNode::has_children() const return !this->children.empty(); } -const std::string& XmlNode::operator[](const std::string& name) const +const std::string XmlNode::get_tag(const std::string& name) const { try { @@ -225,7 +225,7 @@ const std::string& XmlNode::operator[](const std::string& name) const } catch (const std::out_of_range& e) { - throw AttributeNotFound(); + return ""; } } diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index d9bf81d..1c63b86 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -8,13 +8,6 @@ std::string xml_escape(const std::string& data); std::string xml_unescape(const std::string& data); -/** - * Raised on operator[] when the attribute does not exist - */ -class AttributeNotFound: public std::exception -{ -}; - /** * Represent an XML node. It has * - A parent XML node (in the case of the first-level nodes, the parent is @@ -103,10 +96,10 @@ public: */ bool has_children() const; /** - * Gets the value for the given attribute, raises AttributeNotFound if the + * Gets the value for the given attribute, returns an empty string if the * node as no such attribute. */ - const std::string& operator[](const std::string& name) const; + const std::string get_tag(const std::string& name) const; /** * Use this to set an attribute's value, like node["id"] = "12"; */ -- cgit v1.2.3 From d2a7fd129a8d4bac6959425bdfab21095bed082b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 11 Apr 2014 23:34:42 +0200 Subject: Do not print an error message from expat if the error was "parsing finished" --- src/xmpp/xmpp_parser.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp index 536d9da..6bb0d28 100644 --- a/src/xmpp/xmpp_parser.cpp +++ b/src/xmpp/xmpp_parser.cpp @@ -55,7 +55,8 @@ XmppParser::~XmppParser() int XmppParser::feed(const char* data, const int len, const bool is_final) { int res = XML_Parse(this->parser, data, len, is_final); - if (res == 0) + if (res == XML_STATUS_ERROR && + (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) log_error("Xml_Parse encountered an error: " << XML_ErrorString(XML_GetErrorCode(this->parser))) return res; @@ -64,7 +65,7 @@ int XmppParser::feed(const char* data, const int len, const bool is_final) int XmppParser::parse(const int len, const bool is_final) { int res = XML_ParseBuffer(this->parser, len, is_final); - if (res == 0) + if (res == XML_STATUS_ERROR) log_error("Xml_Parsebuffer encountered an error: " << XML_ErrorString(XML_GetErrorCode(this->parser))); return res; -- cgit v1.2.3 From 020325dbb071f1735bceb80de9f982aefcd2de47 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 13 Apr 2014 15:37:56 +0200 Subject: [WIP] DummyIrcChannel --- src/bridge/bridge.cpp | 20 ++++++++++++++++++++ src/irc/irc_channel.cpp | 6 ++++++ src/irc/irc_channel.hpp | 33 ++++++++++++++++++++++++++++++++- src/irc/irc_client.cpp | 23 ++++++++++++++++++++++- src/irc/irc_client.hpp | 17 +++++++++++++++++ src/test.cpp | 1 + src/xmpp/xmpp_component.cpp | 5 +++-- 7 files changed, 101 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9b7908a..f4d4814 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -100,6 +101,25 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) { IrcClient* irc = this->get_irc_client(iid.server, username); + if (iid.chan.empty()) + { // Join the dummy channel + if (irc->is_welcomed()) + { + if (irc->get_dummy_channel().joined) + return false; + // Immediately simulate a message coming from the IRC server saying that we + // joined the channel + const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); + irc->on_channel_join(join_message); + const IrcMessage end_join_message(std::string(iid.server), "366", + {irc->get_nick(), + "", "End of NAMES list"}); + irc->on_channel_completely_joined(end_join_message); + } + else + irc->get_dummy_channel().joining = true; + return true; + } if (irc->is_channel_joined(iid.chan) == false) { irc->send_join_command(iid.chan); diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 99783fa..0604528 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -47,3 +47,9 @@ void IrcChannel::remove_user(const IrcUser* user) } } } + +DummyIrcChannel::DummyIrcChannel(): + IrcChannel(), + joining(false) +{ +} diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 0160469..ab04d60 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -25,13 +25,44 @@ public: IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); -private: +protected: std::unique_ptr self; std::vector> users; + +private: IrcChannel(const IrcChannel&) = delete; IrcChannel(IrcChannel&&) = delete; IrcChannel& operator=(const IrcChannel&) = delete; IrcChannel& operator=(IrcChannel&&) = delete; }; +/** + * A special channel that is not actually linked to any real irc + * channel. This is just a channel representing a connection to the + * server. If an user wants to maintain the connection to the server without + * having to be on any irc channel of that server, he can just join this + * dummy channel. + * It’s not actually dummy because it’s useful and it does things, but well. + */ +class DummyIrcChannel: public IrcChannel +{ +public: + explicit DummyIrcChannel(); + + /** + * This flag is at true whenever the user wants to join this channel, but + * he is not yet connected to the server. When the connection is made, we + * check that flag and if it’s true, we inform the user that he has just + * joined that channel. + * If the user is already connected to the server when he tries to join + * the channel, we don’t use that flag, we just join it immediately. + */ + bool joining; +private: + DummyIrcChannel(const DummyIrcChannel&) = delete; + DummyIrcChannel(DummyIrcChannel&&) = delete; + DummyIrcChannel& operator=(const DummyIrcChannel&) = delete; + DummyIrcChannel& operator=(DummyIrcChannel&&) = delete; +}; + #endif // IRC_CHANNEL_INCLUDED diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 884f214..e565658 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -280,7 +280,11 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) void IrcClient::on_channel_join(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[0]); - IrcChannel* channel = this->get_channel(chan_name); + IrcChannel* channel; + if (chan_name.empty()) + channel = &this->dummy_channel; + else + channel = this->get_channel(chan_name); const std::string nick = message.prefix; if (channel->joined == false) channel->set_self(nick); @@ -394,6 +398,18 @@ void IrcClient::on_welcome_message(const IrcMessage& message) for (const std::string& chan_name: this->channels_to_join) this->send_join_command(chan_name); this->channels_to_join.clear(); + // Indicate that the dummy channel is joined as well, if needed + if (this->dummy_channel.joining) + { + // Simulate a message coming from the IRC server saying that we joined + // the channel + const IrcMessage join_message(this->get_nick(), "JOIN", {""}); + this->on_channel_join(join_message); + const IrcMessage end_join_message(std::string(this->hostname), "366", + {this->get_nick(), + "", "End of NAMES list"}); + this->on_channel_completely_joined(end_join_message); + } } void IrcClient::on_part(const IrcMessage& message) @@ -624,3 +640,8 @@ size_t IrcClient::number_of_joined_channels() const { return this->channels.size(); } + +DummyIrcChannel& IrcClient::get_dummy_channel() +{ + return this->dummy_channel; +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 908db8e..960d36f 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -194,6 +194,14 @@ public: * Return the number of joined channels */ size_t number_of_joined_channels() const; + /** + * Get a reference to the unique dummy channel + */ + DummyIrcChannel& get_dummy_channel(); + + const std::string& get_hostname() const { return this->hostname; } + std::string get_nick() const { return this->current_nick; } + bool is_welcomed() const { return this->welcomed; } private: /** @@ -216,6 +224,11 @@ private: * The list of joined channels, indexed by name */ std::unordered_map> channels; + /** + * A single channel with a iid of the form "hostname" (normal channel have + * an iid of the form "chan%hostname". + */ + DummyIrcChannel dummy_channel; /** * A list of chan we want to join, but we need a response 001 from * the server before sending the actual JOIN commands. So we just keep the @@ -223,6 +236,10 @@ private: * whenever the WELCOME message is received. */ std::vector channels_to_join; + /** + * This flag indicates that the server is completely joined (connection + * has been established, we are authentified and we have a nick) + */ bool welcomed; /** * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.3 diff --git a/src/test.cpp b/src/test.cpp index b95b379..b421941 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 2799a21..e558e46 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -290,8 +290,9 @@ void XmppComponent::handle_presence(const Stanza& stanza) error_type, error_name, ""); }); - if (!iid.chan.empty() && !iid.server.empty()) - { // presence toward a MUC that corresponds to an irc channel + if (!iid.server.empty()) + { // presence toward a MUC that corresponds to an irc channel, or a + // dummy channel if iid.chan is empty if (type.empty()) { const std::string own_nick = bridge->get_own_nick(iid); -- cgit v1.2.3 From 8246849fdfdbfa32c921203dbe9c3b2c29d3e853 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 13 Apr 2014 19:16:18 +0200 Subject: Do not link with pthread --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 21e7fa6..acba720 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,7 +98,7 @@ file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp bridge network utils logger - ${EXPAT_LIBRARIES} pthread) + ${EXPAT_LIBRARIES}) if(LIBIDN_FOUND) target_link_libraries(xmpp ${LIBIDN_LIBRARIES}) endif() -- cgit v1.2.3 From c64bb0bde9dbf572bd4d3bbaf478ec812a2f12d6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 13 Apr 2014 20:28:42 +0200 Subject: Improved doc rule in the cmake thing --- CMakeLists.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index acba720..85c4138 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,6 @@ set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") -# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") # Define a __FILENAME__ macro to get the filename of each file, instead of # the full path as in __FILE__ @@ -42,13 +41,17 @@ endif() # ## Documentation # +set(MAN_PAGE ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1) +set(DOC_PAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/${PROJECT_NAME}.1.md) find_program(RONN_EXECUTABLE NAMES ronn DOC "The ronn software, to build the man page from the markdown documentation") if(RONN_EXECUTABLE) set(WITH_DOC true) - add_custom_target(doc - ${RONN_EXECUTABLE} --roff ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1.md - COMMENT "Generate the man page" VERBATIM) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/) + add_custom_command(OUTPUT ${MAN_PAGE} + COMMAND ${RONN_EXECUTABLE} --roff < ${DOC_PAGE} > ${MAN_PAGE} + DEPENDS ${DOC_PAGE}) + add_custom_target(doc DEPENDS ${MAN_PAGE}) endif() # @@ -146,5 +149,5 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BI install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) if(WITH_DOC) - install(FILES doc/${PROJECT_NAME}.1 DESTINATION man/man1) + install(FILES ${MAN_PAGE} DESTINATION man/man1) endif() \ No newline at end of file -- cgit v1.2.3 From 576fb3d132a11ca787f98da67889690b03c7ba8d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 04:44:09 +0200 Subject: Correctly use the dummy channel whenever we interract with an empty-string chan --- src/irc/irc_client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e565658..7be7cc8 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -59,6 +59,8 @@ void IrcClient::on_connection_close() IrcChannel* IrcClient::get_channel(const std::string& name) { + if (name.empty()) + return &this->dummy_channel; try { return this->channels.at(name).get(); -- cgit v1.2.3 From cfca16bbecac9f14812db6d06588aae60cc55649 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 04:44:51 +0200 Subject: Ability to leave the dummy channel --- src/irc/irc_client.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7be7cc8..78acce5 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -197,7 +197,12 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin { IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == true) - this->send_message(IrcMessage("PART", {chan_name, status_message})); + { + if (chan_name.empty()) + this->bridge->send_muc_leave(Iid(std::string("%") + this->hostname), std::string(this->current_nick), "", true); + else + this->send_message(IrcMessage("PART", {chan_name, status_message})); + } } void IrcClient::send_mode_command(const std::string& chan_name, const std::vector& arguments) -- cgit v1.2.3 From 804b686f1ce4e52174cfbaa1d3f5496c74a9d651 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 04:45:20 +0200 Subject: Fix the leave-muc presence stanza --- src/xmpp/xmpp_component.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 606be10..a27df32 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -576,13 +576,21 @@ void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, X presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; presence["type"] = "unavailable"; const std::string message_str = std::get<0>(message); - if (!message_str.empty() || self) + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + status.close(); + x.add_child(std::move(status)); + } + x.close(); + presence.add_child(std::move(x)); + if (!message_str.empty()) { XmlNode status("status"); - if (!message_str.empty()) - status.set_inner(message_str); - if (self) - status["code"] = "110"; + status.set_inner(message_str); status.close(); presence.add_child(std::move(status)); } -- cgit v1.2.3 From 5739d418e2b35dfc038fe1a12f8b5c7eeeed6868 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 04:56:50 +0200 Subject: Better way to leave the dummy room --- src/bridge/bridge.cpp | 4 +++- src/irc/irc_channel.cpp | 6 ++++++ src/irc/irc_channel.hpp | 1 + src/irc/irc_client.cpp | 12 +++++++++++- src/irc/irc_client.hpp | 5 +++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index da10e28..e874ccb 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -44,7 +44,9 @@ void Bridge::shutdown() { for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { - it->second->send_quit_command("Gateway shutdown"); + const std::string exit_message("Gateway shutdown"); + it->second->send_quit_command(exit_message); + it->second->leave_dummy_channel(exit_message); } } diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 0604528..7b0e766 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -48,6 +48,12 @@ void IrcChannel::remove_user(const IrcUser* user) } } +void IrcChannel::remove_all_users() +{ + this->users.clear(); + this->self.reset(); +} + DummyIrcChannel::DummyIrcChannel(): IrcChannel(), joining(false) diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index ab04d60..1c074b5 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -24,6 +24,7 @@ public: const std::map prefix_to_mode); IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); + void remove_all_users(); protected: std::unique_ptr self; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 78acce5..f44821e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -199,7 +199,7 @@ void IrcClient::send_part_command(const std::string& chan_name, const std::strin if (channel->joined == true) { if (chan_name.empty()) - this->bridge->send_muc_leave(Iid(std::string("%") + this->hostname), std::string(this->current_nick), "", true); + this->leave_dummy_channel(status_message); else this->send_message(IrcMessage("PART", {chan_name, status_message})); } @@ -652,3 +652,13 @@ DummyIrcChannel& IrcClient::get_dummy_channel() { return this->dummy_channel; } + +void IrcClient::leave_dummy_channel(const std::string& exit_message) +{ + if (!this->dummy_channel.joined) + return; + this->dummy_channel.joined = false; + this->dummy_channel.joining = false; + this->dummy_channel.remove_all_users(); + this->bridge->send_muc_leave(Iid(std::string("%") + this->hostname), std::string(this->current_nick), exit_message, true); +} diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 960d36f..811d416 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -198,6 +198,11 @@ public: * Get a reference to the unique dummy channel */ DummyIrcChannel& get_dummy_channel(); + /** + * Leave the dummy channel: forward a message to the user to indicate that + * he left it, and mark it as not joined. + */ + void leave_dummy_channel(const std::string& exit_message); const std::string& get_hostname() const { return this->hostname; } std::string get_nick() const { return this->current_nick; } -- cgit v1.2.3 From 28065d3d52cc48abd39a8556a0c282014b18f25a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 05:08:07 +0200 Subject: Do not disconnect from the IRC server if the dummy channel is joined --- src/irc/irc_client.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f44821e..55c442b 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -645,7 +645,10 @@ void IrcClient::on_user_mode(const IrcMessage& message) size_t IrcClient::number_of_joined_channels() const { - return this->channels.size(); + if (this->dummy_channel.joined) + return this->channels.size() + 1; + else + return this->channels.size(); } DummyIrcChannel& IrcClient::get_dummy_channel() -- cgit v1.2.3 From 14b6793d7fe78cfbab4f2686eb58e59d4f40338c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 05:11:36 +0200 Subject: Joining the dummy channel connects to the irc server --- src/bridge/bridge.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index e874ccb..eceb22e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -119,7 +119,10 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) irc->on_channel_completely_joined(end_join_message); } else + { irc->get_dummy_channel().joining = true; + irc->start(); + } return true; } if (irc->is_channel_joined(iid.chan) == false) -- cgit v1.2.3 From 3bc6f1834738a1ee5a97580678ce204c0c215b71 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 05:22:32 +0200 Subject: Add an explanatory topic on the dummy channel --- src/irc/irc_client.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 55c442b..537b738 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -19,6 +19,14 @@ IrcClient::IrcClient(const std::string& hostname, const std::string& username, B welcomed(false), chanmodes({"", "", "", ""}) { + this->dummy_channel.topic = "This is a virtual channel provided for " + "convenience by biboumi, it is not connected " + "to any actual IRC channel of the server '" + this->hostname + + "', and sending messages in it has no effect. " + "Its main goal is to keep the connection to the IRC server " + "alive without having to join a real channel of that server. " + "To disconnect from the IRC server, leave this room and all " + "other IRC channels of that server."; } IrcClient::~IrcClient() -- cgit v1.2.3 From fbcce3c1123c144a3b395ee6415465f61a8e0ac1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 05:41:17 +0200 Subject: Fix a wrong description of how addressing works in biboumi --- doc/biboumi.1.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 18482fb..bd6463d 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -95,14 +95,14 @@ entity. IRC channels and IRC users JIDs have a localpart formed like this: `name`, the `'%'` separator and the `irc_server`. -For an IRC channel, the name starts with `'&'`, `'#'`, `'+'` -or `'!'`. Some other gateway implementations, as well as some IRC -clients, do not require them to be started by one of these characters, -adding an implicit `'#'` in that case. Biboumi does not do that because -this gets confusing when trying to understand the difference between -*foo*, *#foo*, and *##foo*. - -If the name starts with any other character, this represents an IRC user. +If the IRC channel you want to adress starts with the `'#'` character (or +less frequently, but still valid, one of `'&'`, `'+'` or `'!'`), then you +must include it in the JID. Some other gateway implementations, as well as +some IRC clients, do not require them to be started by one of these +characters, adding an implicit `'#'` in that case. Biboumi does not do that +because this gets confusing when trying to understand the difference between +the channels *foo*, *#foo*, and *##foo*. + If compiled with Libidn, an IRC user has a bare JID representing the “hostname” provided by the IRC server. -- cgit v1.2.3 From 996a827a5231464eb4d31d78b56ed4054ab0f4a8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 05:42:12 +0200 Subject: Document the new dummy channel, and how biboumi creates and closes connections --- doc/biboumi.1.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index bd6463d..c985de7 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -111,6 +111,21 @@ If compiled with Libidn, an IRC user has a bare JID representing the To join an IRC channel `#foo` on the IRC server `irc.example.com`, join the XMPP MUC `#foo%irc.example.com@hostname`. +### Connect to an IRC server + +The connection to the IRC server is automatically made when the user tries +to join any channel on that IRC server. The connection is closed whenever +the last channel on that server is left by the user. To be able to stay +connected to an IRC server without having to be in a real IRC channel, +biboumi provides a virtual channel on the jid +`%irc_serve@biboumi.example.com`. For example if you want to join the +channel `#foo` on the server `irc.example.com`, but you need to authenticate +to a bot of the server before you can join it, you can first join the room +`%irc.example.com@biboumi.example.net` (this will effectively connect you to +the IRC server without joining any room), then send your authentication +message to the user `bot%irc.example.com@biboumi.example.net" and finally +join the room `#foo%irc.example.com@biboumi.example.net`. + ### Channel messages On XMPP, unlike on IRC, the displayed order of the messages is the same for -- cgit v1.2.3 From f7b75d7eb4a52ac58a213e98e300f66850e32e82 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 06:42:00 +0200 Subject: Remove a useless debug log line --- src/network/socket_handler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 6f91e74..74446d9 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -113,7 +113,6 @@ void SocketHandler::connect(const std::string& address, const std::string& port) else if (errno == EINPROGRESS || errno == EALREADY) { // retry this process later, when the socket // is ready to be written on. - log_debug("Need to retry connecting later..." << strerror(errno)); this->poller->watch_send_events(this); // Save the addrinfo structure, to use it on the next call this->ai_addrlen = rp->ai_addrlen; -- cgit v1.2.3 From 58a2c00d443db24e415582fcd267b848e8c7e313 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 06:47:38 +0200 Subject: Call on_connection_failed() when the connection fails to be established --- src/network/socket_handler.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 74446d9..546b1b4 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -155,7 +155,10 @@ void SocketHandler::on_recv() else if (-1 == size) { log_warning("Error while reading from socket: " << strerror(errno)); - this->on_connection_close(); + if (this->connecting) + this->on_connection_failed(strerror(errno)); + else + this->on_connection_close(); this->close(); } else -- cgit v1.2.3 From 9d8166f10d1f14acb936b58fd2907184b1eeadad Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 07:02:09 +0200 Subject: Add support for systemd-daemon --- CMakeLists.txt | 10 ++++++++++ cmake/Modules/FindSystemdDaemon.cmake | 34 ++++++++++++++++++++++++++++++++++ src/config.h.cmake | 1 + src/xmpp/xmpp_component.cpp | 17 +++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 cmake/Modules/FindSystemdDaemon.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 85c4138..84d9be9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ find_package(Iconv REQUIRED) include(FindEXPAT) find_package(EXPAT REQUIRED) find_package(Libidn) +find_package(SystemdDaemon) # To be able to include the config.h file generated by cmake include_directories("src/") @@ -31,6 +32,12 @@ else() message("Building without stringprep support.") endif() +if(SYSTEMDDAEMON_FOUND) + include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS}) +else() + message("Building without systemd daemon support.") +endif() + set(POLLER "POLL" CACHE STRING "Choose the poller between POLL and EPOLL (Linux-only)") if((NOT ${POLLER} MATCHES "POLL") AND @@ -124,6 +131,9 @@ target_link_libraries(${PROJECT_NAME} bridge utils config) +if(SYSTEMDDAEMON_FOUND) + target_link_libraries(xmpp ${SYSTEMDDAEMON_LIBRARIES}) +endif() if(WITH_DOC) add_dependencies(${PROJECT_NAME} doc) endif() diff --git a/cmake/Modules/FindSystemdDaemon.cmake b/cmake/Modules/FindSystemdDaemon.cmake new file mode 100644 index 0000000..b653889 --- /dev/null +++ b/cmake/Modules/FindSystemdDaemon.cmake @@ -0,0 +1,34 @@ +# - Find SystemdDaemon +# Find the systemd daemon library +# +# This module defines the following variables: +# SYSTEMDDAEMON_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# SYSTEMDDAEMON_INCLUDE_DIRS - The directory where to find the header file +# SYSTEMDDAEMON_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# SYSTEMDDAEMON_LIBRARY +# SYSTEMDDAEMON_INCLUDE_DIR +# +# This file is in the public domain + +find_path(SYSTEMDDAEMON_INCLUDE_DIRS NAMES systemd/sd-daemon.h + DOC "The Systemd Daemon include directory") + +find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd-daemon + DOC "The Systemd Daemon library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set SYSTEMDDAEMON_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SystemdDaemon REQUIRED_VARS SYSTEMDDAEMON_LIBRARIES SYSTEMDDAEMON_INCLUDE_DIRS) + +if(SYSTEMDDAEMON_FOUND) + set(SYSTEMDDAEMON_LIBRARY ${SYSTEMDDAEMON_LIBRARIES}) + set(SYSTEMDDAEMON_INCLUDE_DIR ${SYSTEMDDAEMON_INCLUDE_DIRS}) +endif() + +mark_as_advanced(SYSTEMDDAEMON_INCLUDE_DIRS SYSTEMDDAEMON_LIBRARIES) \ No newline at end of file diff --git a/src/config.h.cmake b/src/config.h.cmake index 8ee0fd3..62186cc 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -1,3 +1,4 @@ #cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST #cmakedefine LIBIDN_FOUND +#cmakedefine SYSTEMDDAEMON_FOUND #cmakedefine POLLER ${POLLER} diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index a5e9842..17afd63 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -12,6 +12,12 @@ #include +#include + +#ifdef SYSTEMDDAEMON_FOUND +# include +#endif + #define STREAM_NS "http://etherx.jabber.org/streams" #define COMPONENT_NS "jabber:component:accept" #define MUC_NS "http://jabber.org/protocol/muc" @@ -74,6 +80,9 @@ void XmppComponent::send_stanza(const Stanza& stanza) void XmppComponent::on_connection_failed(const std::string& reason) { log_error("Failed to connect to the XMPP server: " << reason); +#ifdef SYSTEMDDAEMON_FOUND + sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); +#endif } void XmppComponent::on_connected() @@ -248,6 +257,9 @@ void XmppComponent::handle_handshake(const Stanza& stanza) this->ever_auth = true; this->last_auth = true; log_info("Authenticated with the XMPP server"); +#ifdef SYSTEMDDAEMON_FOUND + sd_notify(0, "READY=1"); +#endif } void XmppComponent::handle_presence(const Stanza& stanza) @@ -420,6 +432,11 @@ void XmppComponent::handle_error(const Stanza& stanza) if (text) error_message = text->get_inner(); log_error("Stream error received from the XMPP server: " << error_message); +#ifdef SYSTEMDDAEMON_FOUND + if (!this->ever_auth) + sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); +#endif + } Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) -- cgit v1.2.3 From 3468924a626e666a059a9a252ccd1131b2955679 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 07:06:00 +0200 Subject: Document the systemd-daemon optional dependency --- INSTALL | 3 +++ 1 file changed, 3 insertions(+) diff --git a/INSTALL b/INSTALL index 986ee84..254d2fe 100644 --- a/INSTALL +++ b/INSTALL @@ -20,6 +20,9 @@ Libraries: not provided. http://www.gnu.org/software/libidn/ +- systemd-daemon (optionnal) + Provides the support for a systemd service of Type=notify. This is useful only + if you are packaging biboumi in a distribution with Systemd. Tools: - A C++11 compiler. -- cgit v1.2.3 From 9683f0a0b2789c97a2864e07c55f7ce1ab8c0e89 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Apr 2014 07:06:59 +0200 Subject: s/optionnal/optional/ --- INSTALL | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL b/INSTALL index 254d2fe..4de8ee2 100644 --- a/INSTALL +++ b/INSTALL @@ -15,12 +15,12 @@ Libraries: Encoding from anything into UTF-8 http://www.gnu.org/software/libiconv/ -- libidn (optionnal, but recommended) +- libidn (optional, but recommended) Provides the stringprep functionality. Without it, JIDs for IRC users are not provided. http://www.gnu.org/software/libidn/ -- systemd-daemon (optionnal) +- systemd-daemon (optional) Provides the support for a systemd service of Type=notify. This is useful only if you are packaging biboumi in a distribution with Systemd. -- cgit v1.2.3 From bd7936bdfe799d6b665c4b2bd30a5210592d9ae4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 17 Apr 2014 20:37:46 +0200 Subject: No more missing text when converting IRC colors to xhtml-im fix #2496 --- src/bridge/colors.cpp | 2 +- src/test.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 024121b..e42c5a3 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -71,7 +71,7 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) const std::string txt = s.substr(pos_start, pos_end-pos_start); cleaned += txt; if (current_node->has_children()) - current_node->get_last_child()->set_tail(txt); + current_node->get_last_child()->add_to_tail(txt); else current_node->set_inner(txt); diff --git a/src/test.cpp b/src/test.cpp index e66c4ad..553c3ce 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -144,6 +144,10 @@ int main() std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("[\x1D13dolphin-emu/dolphin\x1D] 03foo commented on #283 (Add support for the guide button to XInput): 02http://example.com"); + assert(xhtml->to_string() == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + assert(cleaned_up == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + /** * JID parsing */ -- cgit v1.2.3 From 114007f10dbc77fdc71c34689fc20ce3e3111492 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Apr 2014 10:45:28 +0200 Subject: Actually do the last commit, but completely this time --- src/bridge/colors.cpp | 6 +++--- src/test.cpp | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index e42c5a3..aff001e 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -73,7 +73,7 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) if (current_node->has_children()) current_node->get_last_child()->add_to_tail(txt); else - current_node->set_inner(txt); + current_node->add_to_inner(txt); if (s[pos_end] == IRC_FORMAT_BOLD_CHAR) styles.strong = !styles.strong; @@ -151,9 +151,9 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) const std::string txt = s.substr(pos_start, pos_end-pos_start); cleaned += txt; if (current_node->has_children()) - current_node->get_last_child()->set_tail(txt); + current_node->get_last_child()->add_to_tail(txt); else - current_node->set_inner(txt); + current_node->add_to_inner(txt); if (current_node != result.get()) { diff --git a/src/test.cpp b/src/test.cpp index 553c3ce..696438d 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -148,6 +148,10 @@ int main() assert(xhtml->to_string() == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); assert(cleaned_up == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("0e46ab by 03Pierre Dindon [090|091|040] 02http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + assert(cleaned_up == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + assert(xhtml->to_string() == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + /** * JID parsing */ -- cgit v1.2.3 From b37fc70fb4c69304281020c2c4a9b456641e59fd Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Apr 2014 17:56:06 +0200 Subject: Little updates to the README and INSTALL files --- INSTALL | 10 +++++----- README | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/INSTALL b/INSTALL index 4de8ee2..4d95754 100644 --- a/INSTALL +++ b/INSTALL @@ -34,16 +34,16 @@ Tools: Configure ============== -Configure the build system using cmake, there are many solutions to do - that, the simplest is to just run +Configure the build system using cmake, there are many solutions to do that, +the simplest is to just run % cmake . in the current directory -You can also configure many parameters of the build (like personnalize -CFLAGS, the install path, choose the compiler, or enabling some options -like the POLLER to use), using: +You can also configure many parameters of the build (like customize CFLAGS, +the install path, choose the compiler, or enabling some options like the +POLLER to use), using: % ccmake . diff --git a/README b/README index eec7480..d77bee5 100644 --- a/README +++ b/README @@ -37,7 +37,7 @@ Florent Le Coz (louiz’) ================= Contact/Support ================= -Jabber ChatRoom: biboumi@muc.poezio.eu +Jabber ChatRoom: biboumi@muc.poez.io Report a bug: http://dev.louiz.org/projects/biboumi/issues/new ================= -- cgit v1.2.3 From 848b50185f25927cffc250b1885e6d47a42b4326 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Apr 2014 22:46:45 +0200 Subject: Remove unused action_prefix_len variable --- src/bridge/bridge.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index eceb22e..a2f481c 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -11,7 +11,6 @@ #include static const char* action_prefix = "\01ACTION "; -static const size_t action_prefix_len = 8; Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): user_jid(user_jid), -- cgit v1.2.3 From 4b5d2d11c53628e9d65e907a179458a19192e45a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Apr 2014 22:56:22 +0200 Subject: test.cfg file is now created by the test suite --- src/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.cpp b/src/test.cpp index 696438d..d3d4f49 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -186,7 +186,7 @@ int main() std::cout << color << "Testing config…" << reset << std::endl; Config::filename = "test.cfg"; Config::file_must_exist = false; - Config::set("coucou", "bonjour"); + Config::set("coucou", "bonjour", true); Config::close(); bool error = false; -- cgit v1.2.3 From e847084b002b08f26533b13e0511eeaec36bdae8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Apr 2014 22:59:16 +0200 Subject: Improve the test on jidprep --- src/test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index d3d4f49..9cffc6d 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -171,9 +171,9 @@ int main() assert(jid2.resource == "coucou@coucou/coucou"); // Jidprep - const std::string& badjid("~zigougou@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt"); + const std::string& badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); const std::string correctjid = jidprep(badjid); - assert(correctjid == "~zigougou@epik-7d9d1fde.poez.io/Boujour/coucou/slt"); + assert(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); const std::string& badjid2("Zigougou@poez.io"); const std::string correctjid2 = jidprep(badjid2); -- cgit v1.2.3 From bd8a5a505edc07e207efb418542b384eddfa4504 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Apr 2014 23:03:58 +0200 Subject: The logging test is less ambiguous --- src/test.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 9cffc6d..0a1af49 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -203,13 +203,14 @@ int main() } assert(error == false); - Config::set("log_level", "3"); + Config::set("log_level", "2"); Config::set("log_file", ""); - log_debug("coucou"); - log_info("coucou"); - log_warning("coucou"); - log_error("coucou"); + std::cout << color << "Testing logging…" << reset << std::endl; + log_debug("If you see this, the test FAILED."); + log_info("If you see this, the test FAILED."); + log_warning("You wust see this message. And the next one too."); + log_error("It’s not an error, don’t worry, the test passed."); return 0; } -- cgit v1.2.3 From 0b4f761e314fe9055ed2bc353a699454a194a5d6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Apr 2014 19:28:02 +0200 Subject: Fix the CMakeLists.txt to correctly have -Ibuild/src/ --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 84d9be9..361237a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,8 @@ find_package(Libidn) find_package(SystemdDaemon) # To be able to include the config.h file generated by cmake -include_directories("src/") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) -- cgit v1.2.3 From 98ef49e78734b1d9cc3b0145ad256b8f14b769b6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Apr 2014 20:13:02 +0200 Subject: Fix the kick by correctly using the from and to attributes --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 17afd63..78149c4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -380,7 +380,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) } Bridge* bridge = this->get_user_bridge(from); - Jid to(from); + Jid to(to_str); std::string error_type("cancel"); std::string error_name("internal-server-error"); -- cgit v1.2.3 From 6ebb586b6ce097d5768e3863411042f4dc6c979e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 24 Apr 2014 19:36:14 +0200 Subject: Respond to a disco query the gateway jid itself --- src/xmpp/xmpp_component.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 4 ++++ 2 files changed, 44 insertions(+) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 78149c4..1eacf7c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -416,6 +416,17 @@ void XmppComponent::handle_iq(const Stanza& stanza) } } } + else if (type == "get") + { + XmlNode* query; + if ((query = stanza.get_child(DISCO_INFO_NS":query"))) + { // Disco info + if (to_str == this->served_hostname) + { // On the gateway itself + this->send_self_disco_info(id, from); + } + } + } else { error_type = "cancel"; @@ -735,3 +746,32 @@ void XmppComponent::send_affiliation_role_change(const std::string& muc_name, presence.close(); this->send_stanza(presence); } + +void XmppComponent::send_self_disco_info(const std::string& id, const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + XmlNode identity("identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "Biboumi XMPP-IRC gateway"; + identity.close(); + query.add_child(std::move(identity)); + for (const std::string& ns: {"http://jabber.org/protocol/disco#info", + "http://jabber.org/protocol/muc"}) + { + XmlNode feature("feature"); + feature["var"] = ns; + feature.close(); + query.add_child(std::move(feature)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 3e13086..aa19be3 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -144,6 +144,10 @@ public: const std::string& affiliation, const std::string& role, const std::string& jid_to); + /** + * Send a result IQ with the gateway disco informations. + */ + void send_self_disco_info(const std::string& id, const std::string& jid_to); /** * Handle the various stanza types */ -- cgit v1.2.3 From 77a84fd2d99bcffd562f09c8235e5bcd365accb1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 25 Apr 2014 00:35:57 +0200 Subject: NOTICE from channels are displayed in the channel, with a green "[notice]" --- src/irc/irc_client.cpp | 15 +++++++++++++++ src/irc/irc_client.hpp | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 537b738..f077e37 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -235,6 +235,21 @@ void IrcClient::forward_server_message(const IrcMessage& message) this->bridge->send_xmpp_message(this->hostname, from, body); } +void IrcClient::on_notice(const IrcMessage& message) +{ + std::string from = message.prefix; + const std::string to = message.arguments[0]; + const std::string body = message.arguments[1]; + + if (to == this->current_nick) + this->bridge->send_xmpp_message(this->hostname, from, body); + else + { + IrcMessage modified_message(std::move(from), "PRIVMSG", {to, std::string("\u000303[notice]\u0003 ") + body}); + this->on_channel_message(modified_message); + } +} + void IrcClient::on_isupport_message(const IrcMessage& message) { const size_t len = message.arguments.size(); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 811d416..849190a 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -147,6 +147,10 @@ public: * When a channel message is received */ void on_channel_message(const IrcMessage& message); + /** + * A notice is received + */ + void on_notice(const IrcMessage& message); /** * Save the topic in the IrcChannel */ @@ -282,7 +286,7 @@ private: typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); static const std::unordered_map irc_callbacks = { - {"NOTICE", &IrcClient::forward_server_message}, + {"NOTICE", &IrcClient::on_notice}, {"002", &IrcClient::forward_server_message}, {"003", &IrcClient::forward_server_message}, {"005", &IrcClient::on_isupport_message}, -- cgit v1.2.3 From 4e61b0d57e4e0a127b8b8db2dab6452695ff425c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 25 Apr 2014 00:53:08 +0200 Subject: The author name from messages from the server are now nicely formated --- src/bridge/bridge.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a2f481c..12a56d7 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -241,7 +241,12 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho { std::string body; if (!author.empty()) - body = std::string("[") + author + std::string("] ") + msg; + { + IrcUser user(author); + body = std::string("\u000303") + user.nick + (user.host.empty()? + std::string("\u0003: "): + (" (\u000310" + user.host + std::string("\u000303)\u0003: "))) + msg; + } else body = msg; this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid, "chat"); -- cgit v1.2.3 From ae41c5dee424e910e6e4d6a145493845a2c831c3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 25 Apr 2014 00:53:59 +0200 Subject: Include the xhtml-im element in private messages too --- src/xmpp/xmpp_component.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1eacf7c..9ad2c61 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -479,6 +479,15 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con body_node.set_inner(std::get<0>(body)); body_node.close(); node.add_child(std::move(body_node)); + if (std::get<1>(body)) + { + XmlNode html("html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::get<1>(body).release()); + html.close(); + node.add_child(std::move(html)); + } node.close(); this->send_stanza(node); } -- cgit v1.2.3 From c6059e5a215624e205cae401183f3a8bb1bf87d0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Apr 2014 18:46:25 +0200 Subject: Upgrade to C++14 --- CMakeLists.txt | 2 +- INSTALL | 2 +- README | 2 +- src/bridge/colors.cpp | 2 -- src/config/config.cpp | 1 - src/irc/irc_channel.cpp | 1 - src/irc/irc_client.cpp | 1 - src/logger/logger.cpp | 1 - src/utils/make_unique.hpp | 78 --------------------------------------------- src/xmpp/xmpp_component.cpp | 1 - 10 files changed, 3 insertions(+), 88 deletions(-) delete mode 100644 src/utils/make_unique.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 361237a..9c1cc5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 0) set(${PROJECT_NAME}_VERSION_MINOR 1) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pedantic -Wall -Wextra") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") # Define a __FILENAME__ macro to get the filename of each file, instead of # the full path as in __FILE__ diff --git a/INSTALL b/INSTALL index 4d95754..7974d99 100644 --- a/INSTALL +++ b/INSTALL @@ -25,7 +25,7 @@ Libraries: if you are packaging biboumi in a distribution with Systemd. Tools: -- A C++11 compiler. +- A C++14 compiler. - CMake - ronn (optional) to build the man page diff --git a/README b/README index d77bee5..cb75684 100644 --- a/README +++ b/README @@ -10,7 +10,7 @@ Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. -It is written in modern C++11 and makes great efforts to have as little +It is written in modern C++14 and makes great efforts to have as little dependencies and to be as simple as possible. The goal is to provide a way to access most of IRC features using any XMPP diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index aff001e..49f7a39 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -1,9 +1,7 @@ #include #include -#include #include - #include #include diff --git a/src/config/config.cpp b/src/config/config.cpp index 82295d5..e2e027b 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,4 +1,3 @@ -#include #include #include diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 7b0e766..2c0f8b1 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -1,5 +1,4 @@ #include -#include IrcChannel::IrcChannel(): joined(false), diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f077e37..e70dbe4 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp index 22d83bd..7336579 100644 --- a/src/logger/logger.cpp +++ b/src/logger/logger.cpp @@ -1,4 +1,3 @@ -#include #include #include diff --git a/src/utils/make_unique.hpp b/src/utils/make_unique.hpp deleted file mode 100644 index 67964c8..0000000 --- a/src/utils/make_unique.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2012 Nathan L. Binkert - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __MAKE_UNIQUE_HH__ -#define __MAKE_UNIQUE_HH__ - -#include - -namespace std { -namespace detail { - -// helper to construct a non-array unique_ptr -template -struct make_unique_helper { - typedef std::unique_ptr unique_ptr; - - template - static inline unique_ptr make(Args&&... args) { - return unique_ptr(new T(std::forward(args)...)); - } -}; - -// helper to construct an array unique_ptr -template -struct make_unique_helper { - typedef std::unique_ptr unique_ptr; - - template - static inline unique_ptr make(Args&&... args) { - return unique_ptr(new T[sizeof...(Args)]{std::forward(args)...}); -} -}; - -// helper to construct an array unique_ptr with specified extent -template -struct make_unique_helper { - typedef std::unique_ptr unique_ptr; - - template - static inline unique_ptr make(Args&&... args) { - static_assert(N >= sizeof...(Args), - "For make_unique N must be as largs as the number of arguments"); - return unique_ptr(new T[N]{std::forward(args)...}); - } - -#if __GNUC__ == 4 && __GNUC_MINOR__ <= 6 - // G++ 4.6 has an ICE when you have no arguments - static inline unique_ptr make() { - return unique_ptr(new T[N]); - } -#endif -}; - - -} // namespace detail - -template -inline typename detail::make_unique_helper::unique_ptr -make_unique(Args&&... args) { - return detail::make_unique_helper::make(std::forward(args)...); -} - -} // namespace std - -#endif // __MAKE_UNIQUE_HH__ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9ad2c61..60b12dd 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,4 +1,3 @@ -#include #include #include -- cgit v1.2.3 From 594bac1e841588aef66efc208a295e08273aec32 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Apr 2014 19:44:09 +0200 Subject: Remove binary.hpp and use the c++14 feature 0b --- src/utils/binary.hpp | 16 ---------------- src/utils/encoding.cpp | 45 ++++++++++++++++++++++----------------------- 2 files changed, 22 insertions(+), 39 deletions(-) delete mode 100644 src/utils/binary.hpp diff --git a/src/utils/binary.hpp b/src/utils/binary.hpp deleted file mode 100644 index 10807bc..0000000 --- a/src/utils/binary.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef BINARY_INCLUDED -# define BINARY_INCLUDED - -template struct binary -{ - static_assert(FIRST == '0' || FIRST == '1', "invalid binary digit" ); - enum { value = ((FIRST - '0') << sizeof...(REST)) + binary::value }; -}; - -template<> struct binary<'0'> { enum { value = 0 }; }; -template<> struct binary<'1'> { enum { value = 1 }; }; - -template inline -constexpr unsigned int operator "" _b() { return binary::value; } - -#endif // BINARY_INCLUDED diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index fa4958b..dc0101c 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -1,5 +1,4 @@ #include -#include #include @@ -35,34 +34,34 @@ namespace utils while (*str) { // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 11111000_b) == 11110000_b) + if ((str[0] & 0b11111000) == 0b11110000) { if (!str[1] || !str[2] || !str[3] - || ((str[1] & 11000000_b) != 10000000_b) - || ((str[2] & 11000000_b) != 10000000_b) - || ((str[3] & 11000000_b) != 10000000_b)) + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000) + || ((str[3] & 0b11000000) != 0b10000000)) return false; str += 4; } // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 11110000_b) == 11100000_b) + else if ((str[0] & 0b11110000) == 0b11100000) { if (!str[1] || !str[2] - || ((str[1] & 11000000_b) != 10000000_b) - || ((str[2] & 11000000_b) != 10000000_b)) + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000)) return false; str += 3; } // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 11100000_b) == 11000000_b) + else if (((str[0]) & 0b11100000) == 0b11000000) { if (!str[1] || - ((str[1] & 11000000_b) != 10000000_b)) + ((str[1] & 0b11000000) != 0b10000000)) return false; str += 2; } // 1 byte: 0xxxxxxx - else if ((str[0] & 10000000_b) != 0) + else if ((str[0] & 0b10000000) != 0) return false; else str++; @@ -85,12 +84,12 @@ namespace utils while (*str) { // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 11111000_b) == 11110000_b) + if ((str[0] & 0b11111000) == 0b11110000) { - codepoint = ((str[0] & 00000111_b) << 18); - codepoint |= ((str[1] & 00111111_b) << 12); - codepoint |= ((str[2] & 00111111_b) << 6 ); - codepoint |= ((str[3] & 00111111_b) << 0 ); + codepoint = ((str[0] & 0b00000111) << 18); + codepoint |= ((str[1] & 0b00111111) << 12); + codepoint |= ((str[2] & 0b00111111) << 6 ); + codepoint |= ((str[3] & 0b00111111) << 0 ); if (codepoint.to_ulong() <= 0x10FFFF) { ::memcpy(r, str, 4); @@ -99,11 +98,11 @@ namespace utils str += 4; } // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 11110000_b) == 11100000_b) + else if ((str[0] & 0b11110000) == 0b11100000) { - codepoint = ((str[0] & 00001111_b) << 12); - codepoint |= ((str[1] & 00111111_b) << 6); - codepoint |= ((str[2] & 00111111_b) << 0 ); + codepoint = ((str[0] & 0b00001111) << 12); + codepoint |= ((str[1] & 0b00111111) << 6); + codepoint |= ((str[2] & 0b00111111) << 0 ); if (codepoint.to_ulong() <= 0xD7FF || (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) { @@ -113,7 +112,7 @@ namespace utils str += 3; } // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 11100000_b) == 11000000_b) + else if (((str[0]) & 0b11100000) == 0b11000000) { // All 2 bytes char are valid, don't even bother calculating // the codepoint @@ -122,9 +121,9 @@ namespace utils str += 2; } // 1 byte: 0xxxxxxx - else if ((str[0] & 10000000_b) == 0) + else if ((str[0] & 0b10000000) == 0) { - codepoint = ((str[0] & 01111111_b)); + codepoint = ((str[0] & 0b01111111)); if (codepoint.to_ulong() == 0x09 || codepoint.to_ulong() == 0x0A || codepoint.to_ulong() == 0x0D || -- cgit v1.2.3 From f6e6b8905be010d7329316cea4546900ad8a2d19 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Apr 2014 21:50:13 +0200 Subject: Use C++14 string_literals --- src/bridge/bridge.cpp | 8 +++++--- src/bridge/colors.cpp | 6 ++++-- src/irc/irc_client.cpp | 19 +++++++++++-------- src/network/socket_handler.cpp | 4 ++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 12a56d7..4b2d895 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -10,6 +10,8 @@ #include #include +using namespace std::string_literals; + static const char* action_prefix = "\01ACTION "; Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): @@ -243,9 +245,9 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho if (!author.empty()) { IrcUser user(author); - body = std::string("\u000303") + user.nick + (user.host.empty()? - std::string("\u0003: "): - (" (\u000310" + user.host + std::string("\u000303)\u0003: "))) + msg; + body = "\u000303"s + user.nick + (user.host.empty()? + "\u0003: ": + (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg; } else body = msg; diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 49f7a39..6f6d7a9 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -6,6 +6,8 @@ #include +using namespace std::string_literals; + static const char IRC_NUM_COLORS = 16; static const char* irc_colors_to_css[IRC_NUM_COLORS] = { @@ -130,10 +132,10 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) if (styles.italic) styles_str += "font-style:italic;"; if (styles.fg != -1) - styles_str += std::string("color:") + + styles_str += "color:"s + irc_colors_to_css[styles.fg % IRC_NUM_COLORS] + ";"; if (styles.bg != -1) - styles_str += std::string("background-color:") + + styles_str += "background-color:"s + irc_colors_to_css[styles.bg % IRC_NUM_COLORS] + ";"; if (!styles_str.empty()) { diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e70dbe4..da5a947 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -10,6 +10,9 @@ #include #include +#include +using namespace std::string_literals; + IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge): hostname(hostname), username(username), @@ -36,7 +39,7 @@ void IrcClient::start() { if (this->connected || this->connecting) return ; - this->bridge->send_xmpp_message(this->hostname, "", std::string("Connecting to ") + + this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s + this->hostname + ":" + "6667"); this->connect(this->hostname, "6667"); } @@ -44,7 +47,7 @@ void IrcClient::start() void IrcClient::on_connection_failed(const std::string& reason) { this->bridge->send_xmpp_message(this->hostname, "", - std::string("Connection failed: ") + reason); + "Connection failed: "s + reason); } void IrcClient::on_connected() @@ -244,7 +247,7 @@ void IrcClient::on_notice(const IrcMessage& message) this->bridge->send_xmpp_message(this->hostname, from, body); else { - IrcMessage modified_message(std::move(from), "PRIVMSG", {to, std::string("\u000303[notice]\u0003 ") + body}); + IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 "s + body}); this->on_channel_message(modified_message); } } @@ -344,7 +347,7 @@ void IrcClient::on_channel_message(const IrcMessage& message) { if (body.substr(1, 6) == "ACTION") this->bridge->send_message(iid, nick, - std::string("/me") + body.substr(7, body.size() - 8), muc); + "/me"s + body.substr(7, body.size() - 8), muc); } else this->bridge->send_message(iid, nick, body, muc); @@ -486,7 +489,7 @@ void IrcClient::on_error(const IrcMessage& message) this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); } this->channels.clear(); - this->send_gateway_message(std::string("ERROR: ") + leave_message); + this->send_gateway_message("ERROR: "s + leave_message); } void IrcClient::on_quit(const IrcMessage& message) @@ -583,7 +586,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) mode_arguments += message.arguments[i]; } } - this->bridge->send_message(iid, "", std::string("Mode ") + iid.chan + + this->bridge->send_message(iid, "", "Mode "s + iid.chan + " [" + mode_arguments + "] by " + user.nick, true); const IrcChannel* channel = this->get_channel(iid.chan); @@ -661,7 +664,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) void IrcClient::on_user_mode(const IrcMessage& message) { this->bridge->send_xmpp_message(this->hostname, "", - std::string("User mode for ") + message.arguments[0] + + "User mode for "s + message.arguments[0] + " is [" + message.arguments[1] + "]"); } @@ -685,5 +688,5 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message) this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge->send_muc_leave(Iid(std::string("%") + this->hostname), std::string(this->current_nick), exit_message, true); + this->bridge->send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true); } diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 546b1b4..f1b70be 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -42,7 +42,7 @@ void SocketHandler::init_socket() const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); if ((existing_flags == -1) || (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) - throw std::runtime_error(std::string("Could not initialize socket: ") + strerror(errno)); + throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); } void SocketHandler::connect(const std::string& address, const std::string& port) @@ -70,7 +70,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port) if (res != 0) { - log_warning(std::string("getaddrinfo failed: ") + gai_strerror(res)); + log_warning("getaddrinfo failed: "s + gai_strerror(res)); this->close(); this->on_connection_failed(gai_strerror(res)); return ; -- cgit v1.2.3 From 65f219594d16bac119e50ac882139f5b1461b1e3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Apr 2014 21:56:05 +0200 Subject: Fix a little indentation --- src/bridge/bridge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4b2d895..aa88262 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -246,8 +246,8 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho { IrcUser user(author); body = "\u000303"s + user.nick + (user.host.empty()? - "\u0003: ": - (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg; + "\u0003: ": + (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg; } else body = msg; -- cgit v1.2.3 From 28d244631d10d10d813e068605b2aeda0e49e36e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Apr 2014 23:51:32 +0200 Subject: Add missing std::string_literals --- src/network/socket_handler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index f1b70be..2348faa 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -17,6 +17,8 @@ #include +using namespace std::string_literals; + #ifndef UIO_FASTIOV # define UIO_FASTIOV 8 #endif -- cgit v1.2.3 From dd552b1e3401fed2bbce61c02fe68f8776d95da3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 30 Apr 2014 02:03:24 +0200 Subject: Link with systemd instead of systemd-daemon --- cmake/Modules/FindSystemdDaemon.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/FindSystemdDaemon.cmake b/cmake/Modules/FindSystemdDaemon.cmake index b653889..441ac47 100644 --- a/cmake/Modules/FindSystemdDaemon.cmake +++ b/cmake/Modules/FindSystemdDaemon.cmake @@ -18,7 +18,7 @@ find_path(SYSTEMDDAEMON_INCLUDE_DIRS NAMES systemd/sd-daemon.h DOC "The Systemd Daemon include directory") -find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd-daemon +find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd DOC "The Systemd Daemon library") # Use some standard module to handle the QUIETLY and REQUIRED arguments, and -- cgit v1.2.3 From 18b27793de8863f58b284ee791a8207a79738b85 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 30 Apr 2014 02:30:19 +0200 Subject: Use epoll on linux by default, poll otherwise --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c1cc5f..943206e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,8 +39,12 @@ else() message("Building without systemd daemon support.") endif() -set(POLLER "POLL" CACHE STRING - "Choose the poller between POLL and EPOLL (Linux-only)") +set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) +else() + set(POLLER "POLL" CACHE STRING ${POLLER_DOCSTRING}) +endif() if((NOT ${POLLER} MATCHES "POLL") AND (NOT ${POLLER} MATCHES "EPOLL")) message(FATAL_ERROR "POLLER must be either POLL or EPOLL") -- cgit v1.2.3 From f35db5cebaf1b9e267a7f2341699bb50fbc54c65 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 6 May 2014 22:39:09 +0200 Subject: Implement next_id() to --- src/test.cpp | 8 ++++++++ src/xmpp/xmpp_component.cpp | 7 +++++++ src/xmpp/xmpp_component.hpp | 9 ++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/test.cpp b/src/test.cpp index 0a1af49..1056926 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -2,6 +2,7 @@ * Just a very simple test suite, by hand, using assert() */ +#include #include #include #include @@ -58,6 +59,13 @@ int main() assert(utils::remove_invalid_xml_chars(in) == in); assert(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); + /** + * Id generation + */ + assert(XmppComponent::next_id() == "0"); + assert(XmppComponent::next_id() == "1"); + assert(XmppComponent::next_id() == "2"); + /** * Utils */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 60b12dd..9f62514 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -29,6 +29,8 @@ #define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" #define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" +unsigned long XmppComponent::current_id = 0; + XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): ever_auth(false), last_auth(false), @@ -783,3 +785,8 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin iq.close(); this->send_stanza(iq); } + +std::string XmppComponent::next_id() +{ + return std::to_string(XmppComponent::current_id++); +} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index aa19be3..f54ebd7 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -20,10 +20,16 @@ class XmppComponent: public SocketHandler public: explicit XmppComponent(const std::string& hostname, const std::string& secret); ~XmppComponent(); + void on_connection_failed(const std::string& reason) override final; void on_connected() override final; void on_connection_close() override final; void parse_in_buffer(const size_t size) override final; + + /** + * Returns a unique id, to be used in the 'id' element of our iq stanzas. + */ + static std::string next_id(); /** * Send a "close" message to all our connected peers. That message * depends on the protocol used (this may be a QUIT irc message, or a @@ -195,6 +201,8 @@ private: */ std::unordered_map> bridges; + static unsigned long current_id; + XmppComponent(const XmppComponent&) = delete; XmppComponent(XmppComponent&&) = delete; XmppComponent& operator=(const XmppComponent&) = delete; @@ -202,4 +210,3 @@ private: }; #endif // XMPP_COMPONENT_INCLUDED - -- cgit v1.2.3 From 5ec05cb0edda6b01ff5c21a42edf9142b90399e5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 6 May 2014 22:50:37 +0200 Subject: Forward CTCP version request to XMPP --- src/bridge/bridge.cpp | 5 +++++ src/bridge/bridge.hpp | 4 ++++ src/irc/irc_client.cpp | 2 ++ src/xmpp/xmpp_component.cpp | 19 +++++++++++++++++++ src/xmpp/xmpp_component.hpp | 5 +++++ 5 files changed, 35 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index aa88262..6c85722 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -304,3 +304,8 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid); } + +void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) +{ + this->xmpp->send_iq_version_request(nick + "%" + hostname, this->user_jid); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index e16ea39..f2da8d7 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -99,6 +99,10 @@ public: * Send a role/affiliation change, matching the change of mode for that user */ void send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode); + /** + * Send an iq version request coming from nick%hostname@ + */ + void send_iq_version_request(const std::string& nick, const std::string& hostname); /** * Misc diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index da5a947..5f70efb 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -348,6 +348,8 @@ void IrcClient::on_channel_message(const IrcMessage& message) if (body.substr(1, 6) == "ACTION") this->bridge->send_message(iid, nick, "/me"s + body.substr(7, body.size() - 8), muc); + else if (body.substr(1, 8) == "VERSION\01") + this->bridge->send_iq_version_request(nick, this->hostname); } else this->bridge->send_message(iid, nick, body, muc); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9f62514..7e00346 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -28,6 +28,9 @@ #define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" #define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" #define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" +#define VERSION_NS "jabber:iq:version" + +using namespace std::string_literals; unsigned long XmppComponent::current_id = 0; @@ -786,6 +789,22 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin this->send_stanza(iq); } +void XmppComponent::send_iq_version_request(const std::string& from, + const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = "version_"s + XmppComponent::next_id(); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + std::string XmppComponent::next_id() { return std::to_string(XmppComponent::current_id++); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index f54ebd7..fdf068b 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -154,6 +154,11 @@ public: * Send a result IQ with the gateway disco informations. */ void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send an iq version request + */ + void send_iq_version_request(const std::string& from, + const std::string& jid_to); /** * Handle the various stanza types */ -- cgit v1.2.3 From 04fe15a1e78e2bb20e490b15d45853328545c97d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 6 May 2014 23:07:30 +0200 Subject: Correctly do the error handling when we receive an iq --- src/xmpp/xmpp_component.cpp | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 7e00346..eb23087 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -367,6 +367,18 @@ void XmppComponent::handle_message(const Stanza& stanza) stanza_error.disable(); } +// We MUST return an iq, whatever happens, except if the type is +// "result". +// To do this, we use a scopeguard. If an exception is raised somewhere, an +// iq of type error "internal-server-error" is sent. If we handle the +// request properly (by calling a function that registers an iq to be sent +// later, or that directly sends an iq), we disable the ScopeGuard. If we +// reach the end of the function without having disabled the scopeguard, we +// send a "feature-not-implemented" iq as a result. If an other kind of +// error is found (for example the feature is implemented in biboumi, but +// the request is missing some attribute) we can just change the values of +// error_type and error_name and return from the function (without disabling +// the scopeguard); an iq error will be sent void XmppComponent::handle_iq(const Stanza& stanza) { std::string id = stanza.get_tag("id"); @@ -386,6 +398,8 @@ void XmppComponent::handle_iq(const Stanza& stanza) Bridge* bridge = this->get_user_bridge(from); Jid to(to_str); + // These two values will be used in the error iq sent if we don't disable + // the scopeguard. std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ @@ -403,19 +417,14 @@ void XmppComponent::handle_iq(const Stanza& stanza) std::string nick = child->get_tag("nick"); std::string role = child->get_tag("role"); if (!nick.empty() && role == "none") - { + { // This is a kick std::string reason; XmlNode* reason_el = child->get_child(MUC_ADMIN_NS":reason"); if (reason_el) reason = reason_el->get_inner(); Iid iid(to.local); bridge->send_irc_kick(iid, nick, reason); - } - else - { - error_type = "cancel"; - error_name = "feature-not-implemented"; - return; + stanza_error.disable(); } } } @@ -428,16 +437,16 @@ void XmppComponent::handle_iq(const Stanza& stanza) if (to_str == this->served_hostname) { // On the gateway itself this->send_self_disco_info(id, from); + stanza_error.disable(); } } } - else + else if (type == "result") { - error_type = "cancel"; - error_name = "feature-not-implemented"; - return; + stanza_error.disable(); } - stanza_error.disable(); + error_type = "cancel"; + error_name = "feature-not-implemented"; } void XmppComponent::handle_error(const Stanza& stanza) -- cgit v1.2.3 From 579ca4bdb6b8806d821daa2ee47d60260b64f0f8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 7 May 2014 02:02:47 +0200 Subject: Forward iq version results to IRC --- src/bridge/bridge.cpp | 11 +++++++++-- src/bridge/bridge.hpp | 3 ++- src/irc/irc_client.cpp | 4 ++-- src/irc/irc_client.hpp | 2 +- src/xmpp/xmpp_component.cpp | 18 ++++++++++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6c85722..f9dc9a6 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -170,13 +170,13 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) this->make_xmpp_body(body), this->user_jid); } -void Bridge::send_private_message(const Iid& iid, const std::string& body) +void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) { if (iid.chan.empty() || iid.server.empty()) return ; IrcClient* irc = this->get_irc_client(iid.server); if (irc) - irc->send_private_message(iid.chan, body); + irc->send_private_message(iid.chan, body, type); } void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) @@ -207,6 +207,13 @@ void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) irc->send_topic_command(iid.chan, subject); } +void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os) +{ + std::string result(name + " " + version + " " + os); + + this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE"); +} + void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f2da8d7..d0fd5bd 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -45,11 +45,12 @@ public: */ bool join_irc_channel(const Iid& iid, const std::string& username); void send_channel_message(const Iid& iid, const std::string& body); - void send_private_message(const Iid& iid, const std::string& body); + void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void leave_irc_channel(Iid&& iid, std::string&& status_message); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason); void set_channel_topic(const Iid& iid, const std::string& subject); + void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os); /*** ** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 5f70efb..c149311 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -192,12 +192,12 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st return true; } -void IrcClient::send_private_message(const std::string& username, const std::string& body) +void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type) { std::string::size_type pos = 0; while (pos < body.size()) { - this->send_message(IrcMessage("PRIVMSG", {username, body.substr(pos, 400)})); + this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)})); pos += 400; } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 849190a..a2e2afe 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -88,7 +88,7 @@ public: /** * Send a PRIVMSG command for an user */ - void send_private_message(const std::string& username, const std::string& body); + void send_private_message(const std::string& username, const std::string& body, const std::string& type); /** * Send the PART irc command */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index eb23087..967614c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -444,6 +444,24 @@ void XmppComponent::handle_iq(const Stanza& stanza) else if (type == "result") { stanza_error.disable(); + XmlNode* query; + if ((query = stanza.get_child(VERSION_NS":query"))) + { + XmlNode* name_node = query->get_child(VERSION_NS":name"); + XmlNode* version_node = query->get_child(VERSION_NS":version"); + XmlNode* os_node = query->get_child(VERSION_NS":os"); + std::string name; + std::string version; + std::string os; + if (name_node) + name = name_node->get_inner() + " (through the biboumi gateway)"; + if (version_node) + version = version_node->get_inner(); + if (os_node) + os = os_node->get_inner(); + const Iid iid(to.local); + bridge->send_xmpp_version_to_irc(iid, name, version, os); + } } error_type = "cancel"; error_name = "feature-not-implemented"; -- cgit v1.2.3 From 23db31cff199e5b05facb80d9534ae51ce0d63ed Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 11 May 2014 03:36:42 +0200 Subject: [cmake] Do not print useless messages when an optional lib is not found --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 943206e..f29ceef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,14 +29,10 @@ include_directories(${ICONV_INCLUDE_DIRS}) if(LIBIDN_FOUND) include_directories(${LIBIDN_INCLUDE_DIRS}) -else() - message("Building without stringprep support.") endif() if(SYSTEMDDAEMON_FOUND) include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS}) -else() - message("Building without systemd daemon support.") endif() set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") -- cgit v1.2.3 From 634e7cf125a8870f49b3c99364d710b0da98decf Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 11 May 2014 03:37:07 +0200 Subject: mini reorder --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f29ceef..1589399 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,9 +15,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE ## Look for external libraries # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") -find_package(Iconv REQUIRED) include(FindEXPAT) find_package(EXPAT REQUIRED) +find_package(Iconv REQUIRED) find_package(Libidn) find_package(SystemdDaemon) -- cgit v1.2.3 From 712b7bdfdfe5d77001669dd1d11a860437dc3849 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 May 2014 21:48:38 +0200 Subject: Correctly handle the usage of ! as a IRC user mode indicator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since “!” is also the separator between the nickname and the user hostname, having “!” as the user mode (e.g. !nick!~some@host.bla) would cause the nick to be empty. Now we skip it if it is a valid user mode indicator. --- src/irc/irc_user.cpp | 6 +++--- src/test.cpp | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index 0f1b1ee..8785270 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -7,14 +7,14 @@ IrcUser::IrcUser(const std::string& name, { if (name.empty()) return ; - const std::string::size_type sep = name.find("!"); const std::map::const_iterator prefix = prefix_to_mode.find(name[0]); - const size_t name_begin = prefix == prefix_to_mode.end()? 0: 1; + const std::string::size_type name_begin = prefix == prefix_to_mode.end()? 0: 1; + const std::string::size_type sep = name.find("!", name_begin); if (sep == std::string::npos) this->nick = name.substr(name_begin); else { - this->nick = name.substr(name_begin, sep); + this->nick = name.substr(name_begin, sep-name_begin); this->host = name.substr(sep+1); } if (prefix != prefix_to_mode.end()) diff --git a/src/test.cpp b/src/test.cpp index 1056926..f624bc2 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,22 @@ int main() assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); assert(xml_unescape(xml_escape(unescaped)) == unescaped); + /** + * Irc user parsing + */ + const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; + + IrcUser user1("!nick!~some@host.bla", prefixes); + assert(user1.nick == "nick"); + assert(user1.host == "~some@host.bla"); + assert(user1.modes.size() == 1); + assert(user1.modes.find('a') != user1.modes.end()); + IrcUser user2("coucou!~other@host.bla", prefixes); + assert(user2.nick == "coucou"); + assert(user2.host == "~other@host.bla"); + assert(user2.modes.size() == 0); + assert(user2.modes.find('a') == user2.modes.end()); + /** * Colors conversion */ -- cgit v1.2.3 From 12c8b1ae0b6f4c2b80d7c787b892ebcaafae6b03 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 15 May 2014 00:21:08 +0200 Subject: Disconnect the user from all its IRC servers whenever he returns an error fix #2524 --- src/bridge/bridge.cpp | 3 +-- src/bridge/bridge.hpp | 3 ++- src/xmpp/xmpp_component.cpp | 32 +++++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index f9dc9a6..b2563f0 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -41,11 +41,10 @@ static std::tuple get_role_affiliation_from_irc_mode(c return std::make_tuple("participant", "none"); } -void Bridge::shutdown() +void Bridge::shutdown(const std::string& exit_message) { for (auto it = this->irc_clients.begin(); it != this->irc_clients.end(); ++it) { - const std::string exit_message("Gateway shutdown"); it->second->send_quit_command(exit_message); it->second->leave_dummy_channel(exit_message); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index d0fd5bd..f78bade 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -27,11 +27,12 @@ public: /** * QUIT all connected IRC servers. */ - void shutdown(); + void shutdown(const std::string& exit_message); /** * Remove all inactive IrcClients */ void clean(); + static Xmpp::body make_xmpp_body(const std::string& str); /*** ** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 967614c..aa466f5 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -34,6 +34,19 @@ using namespace std::string_literals; unsigned long XmppComponent::current_id = 0; +static std::set kickable_errors{ + "gone", + "internal-server-error", + "item-not-found", + "jid-malformed", + "recipient-unavailable", + "redirect", + "remote-server-not-found", + "remote-server-timeout", + "service-unavailable", + "malformed-error" + }; + XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): ever_auth(false), last_auth(false), @@ -128,7 +141,7 @@ void XmppComponent::shutdown() { for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) { - it->second->shutdown(); + it->second->shutdown("Gateway shutdown"); } } @@ -359,6 +372,23 @@ void XmppComponent::handle_message(const Stanza& stanza) if (subject) bridge->set_channel_topic(iid, subject->get_inner()); } + else if (type == "error") + { + const XmlNode* error = stanza.get_child(COMPONENT_NS":error"); + // Only a set of errors are considered “fatal”. If we encounter one of + // them, we purge (we disconnect the user from all the IRC servers). + // We consider this to be true, unless the error condition is + // specified and is not in the kickable_errors set + bool kickable_error = true; + if (error) + { + const XmlNode* condition = error->get_last_child(); + if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) + kickable_error = false; + } + if (kickable_error) + bridge->shutdown("Error from remote client"); + } else { if (body && !body->get_inner().empty()) -- cgit v1.2.3 From bafde1af9f284d8d1daf001d59f4b338e5d2f922 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 15 May 2014 00:27:58 +0200 Subject: Stanza:get_name() only returns the name of the tag, without the namespace --- src/xmpp/xmpp_component.cpp | 10 +++++----- src/xmpp/xmpp_stanza.cpp | 9 +++++++-- src/xmpp/xmpp_stanza.hpp | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index aa466f5..b87a023 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -61,15 +61,15 @@ XmppComponent::XmppComponent(const std::string& hostname, const std::string& sec std::placeholders::_1)); this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, std::placeholders::_1)); - this->stanza_handlers.emplace(COMPONENT_NS":handshake", + this->stanza_handlers.emplace("handshake", std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); - this->stanza_handlers.emplace(COMPONENT_NS":presence", + this->stanza_handlers.emplace("presence", std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); - this->stanza_handlers.emplace(COMPONENT_NS":message", + this->stanza_handlers.emplace("message", std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); - this->stanza_handlers.emplace(COMPONENT_NS":iq", + this->stanza_handlers.emplace("iq", std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1)); - this->stanza_handlers.emplace(STREAM_NS":error", + this->stanza_handlers.emplace("error", std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); } diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 948e5f5..400971b 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -183,9 +184,13 @@ XmlNode* XmlNode::get_parent() const return this->parent; } -const std::string& XmlNode::get_name() const +const std::string XmlNode::get_name() const { - return this->name; + const std::vector splited = utils::split(this->name, ':', false); + if (splited.empty()) + return ""; + const std::string res = splited.back(); + return res; } std::string XmlNode::to_string() const diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 1c63b86..9dee51f 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -85,7 +85,7 @@ public: */ void close(); XmlNode* get_parent() const; - const std::string& get_name() const; + const std::string get_name() const; /** * Serialize the stanza into a string */ -- cgit v1.2.3 From 7c7da2cedbd5701a849237f24407d5cb566db0b5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 15 May 2014 00:29:30 +0200 Subject: "Chat message" is not the same as "any kind of message" --- src/xmpp/xmpp_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index b87a023..f6e1ddb 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -389,7 +389,7 @@ void XmppComponent::handle_message(const Stanza& stanza) if (kickable_error) bridge->shutdown("Error from remote client"); } - else + else if (type == "chat") { if (body && !body->get_inner().empty()) bridge->send_private_message(iid, body->get_inner()); -- cgit v1.2.3 From df765faab92f5fcbf05cc9316ba27b509158ce1c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 15 May 2014 00:47:20 +0200 Subject: Only call get_last_child() if the node has children Explain that the behaviour is otherwise undefined, in the comment. --- src/xmpp/xmpp_component.cpp | 2 +- src/xmpp/xmpp_stanza.hpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index f6e1ddb..32f5d96 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -380,7 +380,7 @@ void XmppComponent::handle_message(const Stanza& stanza) // We consider this to be true, unless the error condition is // specified and is not in the kickable_errors set bool kickable_error = true; - if (error) + if (error && error->has_children()) { const XmlNode* condition = error->get_last_child(); if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 9dee51f..476596b 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -77,7 +77,9 @@ public: XmlNode* add_child(XmlNode* child); XmlNode* add_child(XmlNode&& child); /** - * Returns the last of the children + * Returns the last of the children. If the node doesn't have any child, + * the behaviour is undefined. The user should make sure this is the case + * by calling has_children() for example. */ XmlNode* get_last_child() const; /** -- cgit v1.2.3 From 01cd6eb5166c99a83facda804bfa3150e24e8b1e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 18 May 2014 20:23:08 +0200 Subject: Split the messages on \n when sending them back to the XMPP user --- src/bridge/bridge.cpp | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index b2563f0..7a60267 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -135,10 +135,6 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) void Bridge::send_channel_message(const Iid& iid, const std::string& body) { - std::vector lines = utils::split(body, '\n', true); - if (lines.empty()) - return ; - const std::string first_line = lines[0]; if (iid.chan.empty() || iid.server.empty()) { log_warning("Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]"); @@ -150,23 +146,33 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) log_warning("Cannot send message: no client exist for server " << iid.server); return; } - if (first_line.substr(0, 6) == "/mode ") + // Because an IRC message cannot contain \n, we need to convert each line + // of text into a separate IRC message. For conveniance, we also cut the + // message into submessages on the XMPP side, this way the user of the + // gateway sees what was actually sent over IRC. For example if an user + // sends “hello\n/me waves”, two messages will be generated: “hello” and + // “/me waves”. Note that the “command” handling (messages starting with + // /me, /mode, etc) is done for each message generated this way. In the + // above example, the /me will be interpreted. + std::vector lines = utils::split(body, '\n', true); + if (lines.empty()) + return ; + for (const std::string& line: lines) { - std::vector args = utils::split(first_line.substr(6), ' ', false); - irc->send_mode_command(iid.chan, args); - return; + if (line.substr(0, 6) == "/mode ") + { + std::vector args = utils::split(line.substr(6), ' ', false); + irc->send_mode_command(iid.chan, args); + continue; // We do not want to send that back to the + // XMPP user, that’s not a textual message. + } + else if (line.substr(0, 4) == "/me ") + irc->send_channel_message(iid.chan, action_prefix + line.substr(4) + "\01"); + else + irc->send_channel_message(iid.chan, line); + this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), + this->make_xmpp_body(line), this->user_jid); } - if (first_line.substr(0, 4) == "/me ") - irc->send_channel_message(iid.chan, action_prefix + first_line.substr(4) + "\01"); - else - irc->send_channel_message(iid.chan, first_line); - // Send each of the other lines of the message as a separate IRC message - for (std::vector::const_iterator it = lines.begin() + 1; it != lines.end(); ++it) - irc->send_channel_message(iid.chan, *it); - // We do not need to convert body to utf-8: it comes from our XMPP server, - // so it's ok to send it back - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), - this->make_xmpp_body(body), this->user_jid); } void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) -- cgit v1.2.3 From 7fb0b671bbe6150d60b9f1efd4d8abc885c23844 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 18 May 2014 20:48:42 +0200 Subject: Also do that cut of message on \n for private messages, and handle /me --- src/bridge/bridge.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 7a60267..d803e5e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -180,8 +180,21 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const if (iid.chan.empty() || iid.server.empty()) return ; IrcClient* irc = this->get_irc_client(iid.server); - if (irc) - irc->send_private_message(iid.chan, body, type); + if (!irc) + { + log_warning("Cannot send message: no client exist for server " << iid.server); + return; + } + std::vector lines = utils::split(body, '\n', true); + if (lines.empty()) + return ; + for (const std::string& line: lines) + { + if (line.substr(0, 4) == "/me ") + irc->send_private_message(iid.chan, action_prefix + line.substr(4) + "\01", type); + else + irc->send_private_message(iid.chan, line, type); + } } void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) -- cgit v1.2.3 From 6b0ffb5fc2eca537e2cfaf24acb8a4d2ca9b99f1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 May 2014 22:27:43 +0200 Subject: If both write and read events are available on a socket, only do the read Because the read handler may discover that the connection has been closed, and then remove the socket from the poller. It that case it is no longer valid to try to call the write handler (which may try to reconnect, but since that socket is no longer managed, this is not OK). --- src/network/poller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/poller.cpp b/src/network/poller.cpp index dbea856..708fe23 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -185,7 +185,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) auto socket_handler = static_cast(revents[i].data.ptr); if (revents[i].events & EPOLLIN) socket_handler->on_recv(); - if (revents[i].events & EPOLLOUT) + else if (revents[i].events & EPOLLOUT) { if (socket_handler->is_connected()) socket_handler->on_send(); -- cgit v1.2.3 From 5507adbe9473f4b41e52d16498f14850773e5e45 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 01:01:44 +0200 Subject: SocketHandlers own the poller and add themself into it only when the socket is created MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We want to call socket() with the parameters provided by getaddrinfo, so we can’t addd the fd into the poller immediately. We need to wait the connection attempt, and then the SocketHandler can call add_socket_handler itself, if the connection succeeds, or is in progress. --- src/bridge/bridge.cpp | 5 ++--- src/bridge/bridge.hpp | 5 ++--- src/irc/irc_client.cpp | 3 ++- src/irc/irc_client.hpp | 3 ++- src/main.cpp | 13 ++++++------- src/network/poller.cpp | 9 ++++----- src/network/poller.hpp | 4 ++-- src/network/socket_handler.cpp | 12 +++--------- src/network/socket_handler.hpp | 9 +++------ src/xmpp/xmpp_component.cpp | 3 ++- src/xmpp/xmpp_component.hpp | 2 +- 11 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index d803e5e..78cd2d2 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -14,7 +14,7 @@ using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; -Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller): +Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr poller): user_jid(user_jid), xmpp(xmpp), poller(poller) @@ -81,9 +81,8 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string } catch (const std::out_of_range& exception) { - this->irc_clients.emplace(hostname, std::make_shared(hostname, username, this)); + this->irc_clients.emplace(hostname, std::make_shared(this->poller, hostname, username, this)); std::shared_ptr irc = this->irc_clients.at(hostname); - this->poller->add_socket_handler(irc); return irc.get(); } } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f78bade..37f5b05 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -22,7 +22,7 @@ class Poller; class Bridge { public: - explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, Poller* poller); + explicit Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr poller); ~Bridge(); /** * QUIT all connected IRC servers. @@ -146,9 +146,8 @@ private: /** * Poller, to give it the IrcClients that we spawn, to make it manage * their sockets. - * We don't own it. */ - Poller* poller; + std::shared_ptr poller; Bridge(const Bridge&) = delete; Bridge(Bridge&& other) = delete; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index c149311..cea08f2 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -13,7 +13,8 @@ #include using namespace std::string_literals; -IrcClient::IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge): +IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge): + SocketHandler(poller), hostname(hostname), username(username), current_nick(username), diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index a2e2afe..e70ee33 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,7 +24,7 @@ class Bridge; class IrcClient: public SocketHandler { public: - explicit IrcClient(const std::string& hostname, const std::string& username, Bridge* bridge); + explicit IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge); ~IrcClient(); /** * Connect to the IRC server diff --git a/src/main.cpp b/src/main.cpp index 6cba134..40825c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,11 +61,11 @@ int main(int ac, char** av) return config_help("password"); if (hostname.empty()) return config_help("hostname"); - std::shared_ptr xmpp_component = - std::make_shared(hostname, password); - Poller p; - p.add_socket_handler(xmpp_component); + auto p = std::make_shared(); + auto xmpp_component = std::make_shared(p, + hostname, + password); // Install the signals used to exit the process cleanly, or reload the // config @@ -91,7 +91,7 @@ int main(int ac, char** av) xmpp_component->start(); const std::chrono::milliseconds timeout(-1); - while (p.poll(timeout) != -1) + while (p->poll(timeout) != -1) { // Check for empty irc_clients (not connected, or with no joined // channel) and remove them @@ -123,14 +123,13 @@ int main(int ac, char** av) !xmpp_component->is_connecting()) { xmpp_component->reset(); - p.add_socket_handler(xmpp_component); xmpp_component->start(); } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. if (exiting && xmpp_component->is_connecting()) xmpp_component->close(); - if (exiting && p.size() == 1 && xmpp_component->is_document_open()) + if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); } log_info("All connection cleanely closed, have a nice day."); diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 708fe23..d89e50f 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -27,15 +27,14 @@ Poller::~Poller() { } -void Poller::add_socket_handler(std::shared_ptr socket_handler) +void Poller::add_socket_handler(SocketHandler* socket_handler) { - // Raise an error if that socket is already in the list + // Don't do anything if the socket is already managed const auto it = this->socket_handlers.find(socket_handler->get_socket()); if (it != this->socket_handlers.end()) - throw std::runtime_error("Trying to insert SocketHandler already managed"); + return ; this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); - socket_handler->set_poller(this); // We always watch all sockets for receive events #if POLLER == POLL @@ -44,7 +43,7 @@ void Poller::add_socket_handler(std::shared_ptr socket_handler) this->nfds++; #endif #if POLLER == EPOLL - struct epoll_event event = {EPOLLIN, {socket_handler.get()}}; + struct epoll_event event = {EPOLLIN, {socket_handler}}; const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); if (res == -1) { diff --git a/src/network/poller.hpp b/src/network/poller.hpp index dc087a2..c3edcd7 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -42,7 +42,7 @@ public: * Add a SocketHandler to be monitored by this Poller. All receive events * are always automatically watched. */ - void add_socket_handler(std::shared_ptr socket_handler); + void add_socket_handler(SocketHandler* socket_handler); /** * Remove (and stop managing) a SocketHandler, designed by the given socket_t. */ @@ -77,7 +77,7 @@ private: * because that's what is returned by select/poll/etc when an event * occures. */ - std::unordered_map> socket_handlers; + std::unordered_map socket_handlers; #if POLLER == POLL struct pollfd fds[MAX_POLL_FD_NUMBER]; diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 2348faa..a9e0c5e 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -23,8 +23,8 @@ using namespace std::string_literals; # define UIO_FASTIOV 8 #endif -SocketHandler::SocketHandler(): - poller(nullptr), +SocketHandler::SocketHandler(std::shared_ptr poller): + poller(poller), connected(false), connecting(false) { @@ -107,6 +107,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port) || errno == EISCONN) { log_info("Connection success."); + this->poller->add_socket_handler(this); this->connected = true; this->connecting = false; this->on_connected(); @@ -134,11 +135,6 @@ void SocketHandler::connect() this->connect(this->address, this->port); } -void SocketHandler::set_poller(Poller* poller) -{ - this->poller = poller; -} - void SocketHandler::on_recv() { static constexpr size_t buf_size = 4096; @@ -231,8 +227,6 @@ void SocketHandler::close() this->port.clear(); this->poller->remove_socket_handler(this->get_socket()); ::close(this->socket); - // recreate the socket for a potential future usage - this->init_socket(); } socket_t SocketHandler::get_socket() const diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index f554350..1e31dcd 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -22,7 +23,7 @@ class Poller; class SocketHandler { public: - explicit SocketHandler(); + explicit SocketHandler(std::shared_ptr poller); virtual ~SocketHandler() {} /** * (re-)Initialize the socket @@ -33,10 +34,6 @@ public: */ void connect(const std::string& address, const std::string& port); void connect(); - /** - * Set the pointer to the given Poller, to communicate with it. - */ - void set_poller(Poller* poller); /** * Reads data in our in_buf and the call parse_in_buf, for the implementor * to handle the data received so far. @@ -119,7 +116,7 @@ protected: * And a raw pointer because we are not owning it, it is owning us * (actually it is sharing our ownership with a Bridge). */ - Poller* poller; + std::shared_ptr poller; /** * Hostname we are connected/connecting to */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 32f5d96..55f0ca4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -47,7 +47,8 @@ static std::set kickable_errors{ "malformed-error" }; -XmppComponent::XmppComponent(const std::string& hostname, const std::string& secret): +XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): + SocketHandler(poller), ever_auth(false), last_auth(false), served_hostname(hostname), diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index fdf068b..f081420 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -18,7 +18,7 @@ class XmppComponent: public SocketHandler { public: - explicit XmppComponent(const std::string& hostname, const std::string& secret); + explicit XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); ~XmppComponent(); void on_connection_failed(const std::string& reason) override final; -- cgit v1.2.3 From 23e8e32f84c8d7f8bb947a1ab484a81890b40371 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 01:05:51 +0200 Subject: Support IPv6 connections fix #2522 --- src/network/socket_handler.cpp | 20 +++++++++++++++----- src/network/socket_handler.hpp | 5 +++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index a9e0c5e..3d79290 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -28,12 +28,11 @@ SocketHandler::SocketHandler(std::shared_ptr poller): connected(false), connecting(false) { - this->init_socket(); } -void SocketHandler::init_socket() +void SocketHandler::init_socket(const struct addrinfo* rp) { - if ((this->socket = ::socket(AF_INET, SOCK_STREAM, 0)) == -1) + if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) throw std::runtime_error("Could not create socket"); int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) @@ -64,7 +63,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port) struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = 0; - hints.ai_family = AF_INET; + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; @@ -99,10 +98,19 @@ void SocketHandler::connect(const std::string& address, const std::string& port) addr_res->ai_addr = &this->ai_addr; addr_res->ai_addrlen = this->ai_addrlen; } - this->connecting = true; for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) { + if (!this->connecting) + { + try { + this->init_socket(rp); + } + catch (const std::runtime_error& error) { + log_error("Failed to init socket: " << error.what()); + break; + } + } if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 || errno == EISCONN) { @@ -116,6 +124,8 @@ void SocketHandler::connect(const std::string& address, const std::string& port) else if (errno == EINPROGRESS || errno == EALREADY) { // retry this process later, when the socket // is ready to be written on. + this->connecting = true; + this->poller->add_socket_handler(this); this->poller->watch_send_events(this); // Save the addrinfo structure, to use it on the next call this->ai_addrlen = rp->ai_addrlen; diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 1e31dcd..e6a36bf 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -26,9 +26,10 @@ public: explicit SocketHandler(std::shared_ptr poller); virtual ~SocketHandler() {} /** - * (re-)Initialize the socket + * Initialize the socket with the parameters contained in the given + * addrinfo structure. */ - void init_socket(); + void init_socket(const struct addrinfo* rp); /** * Connect to the remote server, and call on_connected() if this succeeds */ -- cgit v1.2.3 From 796af0531e0f5eb5fa2b800370338ede120a867d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 02:17:25 +0200 Subject: Add support for CHANTYPES isupport element, to know the prefixes of channels --- src/irc/irc_client.cpp | 15 ++++++++++++--- src/irc/irc_client.hpp | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index cea08f2..03402e7 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -20,7 +20,8 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname current_nick(username), bridge(bridge), welcomed(false), - chanmodes({"", "", "", ""}) + chanmodes({"", "", "", ""}), + chantypes({'#', '&'}) { this->dummy_channel.topic = "This is a virtual channel provided for " "convenience by biboumi, it is not connected " @@ -279,6 +280,15 @@ void IrcClient::on_isupport_message(const IrcMessage& message) this->prefix_to_mode[token[j++]] = token[i++]; } } + else if (token.substr(0, 10) == "CHANTYPES=") + { + // Remove the default types, they apply only if no other value is + // specified. + this->chantypes.clear(); + size_t i = 11; + while (i < token.size()) + this->chantypes.insert(token[i++]); + } } } @@ -564,8 +574,7 @@ void IrcClient::on_kick(const IrcMessage& message) void IrcClient::on_mode(const IrcMessage& message) { const std::string target = message.arguments[0]; - if (target[0] == '&' || target[0] == '#' || - target[0] == '!' || target[0] == '+') + if (this->chantypes.find(target[0]) != this->chantypes.end()) this->on_channel_mode(message); else this->on_user_mode(message); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index e70ee33..e6fb393 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -12,6 +12,7 @@ #include #include #include +#include class Bridge; @@ -257,6 +258,11 @@ private: * chanmodes[0] contains modes of type A, [1] of type B etc */ std::vector chanmodes; + /** + * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt + * section 3.5 + */ + std::set chantypes; /** * Each motd line received is appended to this string, which we send when * the motd is completely received -- cgit v1.2.3 From aea923bcb99f8a7dee83202b035bc377cf835c65 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 02:30:11 +0200 Subject: Use the CHANTYPES values to differentiate channel or user notices It also happens to fix #2517 because this used to create buggy channels named "auth" and stuf like that. --- src/irc/irc_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 03402e7..1411689 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -245,7 +245,7 @@ void IrcClient::on_notice(const IrcMessage& message) const std::string to = message.arguments[0]; const std::string body = message.arguments[1]; - if (to == this->current_nick) + if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end()) this->bridge->send_xmpp_message(this->hostname, from, body); else { -- cgit v1.2.3 From 34739728f930f461ae6763a5f144f709a9919e59 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 May 2014 18:34:56 +0200 Subject: Fix a by-one error in the CHANTYPES parsing --- src/irc/irc_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1411689..270e0ed 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -285,7 +285,7 @@ void IrcClient::on_isupport_message(const IrcMessage& message) // Remove the default types, they apply only if no other value is // specified. this->chantypes.clear(); - size_t i = 11; + size_t i = 10; while (i < token.size()) this->chantypes.insert(token[i++]); } -- cgit v1.2.3 From 7b31bea75b0c5b9fe127ea6d845e48a3f87d480f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 23:46:18 +0200 Subject: Only close/unmanage the socket if we are connected/connecting Since the socket is now only created and managed whenever the connection is being established, we only close the socket and if it was created (we use -1 to denote the fact that is not yet created, or has been closed) and we only unmanage the socket if it is effectively managed. fix #2529 --- src/network/socket_handler.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 3d79290..2baad3d 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -24,6 +24,7 @@ using namespace std::string_literals; #endif SocketHandler::SocketHandler(std::shared_ptr poller): + socket(-1), poller(poller), connected(false), connecting(false) @@ -33,7 +34,7 @@ SocketHandler::SocketHandler(std::shared_ptr poller): void SocketHandler::init_socket(const struct addrinfo* rp) { if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) - throw std::runtime_error("Could not create socket"); + throw std::runtime_error("Could not create socket: "s + strerror(errno)); int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); @@ -230,13 +231,18 @@ void SocketHandler::on_send() void SocketHandler::close() { + if (this->connected || this->connecting) + this->poller->remove_socket_handler(this->get_socket()); + if (this->socket != -1) + { + ::close(this->socket); + this->socket = -1; + } this->connected = false; this->connecting = false; this->in_buf.clear(); this->out_buf.clear(); this->port.clear(); - this->poller->remove_socket_handler(this->get_socket()); - ::close(this->socket); } socket_t SocketHandler::get_socket() const -- cgit v1.2.3 From 6c2d03da4ea0443624b6bf434b6a654c12e48438 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 May 2014 01:27:41 +0200 Subject: Send an error presence when the connection to the IRC server fails --- src/bridge/bridge.cpp | 5 +++++ src/bridge/bridge.hpp | 5 +++++ src/irc/irc_client.cpp | 9 ++++++++- src/xmpp/xmpp_component.cpp | 27 +++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 9 +++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 78cd2d2..70650a5 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -241,6 +241,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st this->make_xmpp_body(body), this->user_jid, "chat"); } +void Bridge::send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text) +{ + this->xmpp->send_presence_error(iid.chan + "%" + iid.server, nick, this->user_jid, type, condition, text); +} + void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 37f5b05..2618ca6 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -80,6 +80,11 @@ public: * Send a MUC message from some participant */ void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc); + /** + * Send a presence of type error, from a room. This is used to indicate + * why joining a room failed. + */ + void send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text); /** * Send an unavailable presence from this participant */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 270e0ed..f08e9d6 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -50,6 +50,13 @@ void IrcClient::on_connection_failed(const std::string& reason) { this->bridge->send_xmpp_message(this->hostname, "", "Connection failed: "s + reason); + // Send an error message for all room that the user wanted to join + for (const std::string& channel: this->channels_to_join) + { + Iid iid(channel + "%" + this->hostname); + this->bridge->send_join_failed(iid, this->current_nick, + "cancel", "item-not-found", reason); + } } void IrcClient::on_connected() @@ -167,11 +174,11 @@ void IrcClient::send_quit_command(const std::string& reason) void IrcClient::send_join_command(const std::string& chan_name) { - this->start(); if (this->welcomed == false) this->channels_to_join.push_back(chan_name); else this->send_message(IrcMessage("JOIN", {chan_name})); + this->start(); } bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 55f0ca4..3c2aa7a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -796,6 +796,33 @@ void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, this->send_stanza(presence); } +void XmppComponent::send_presence_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string&) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; + presence["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_NS; + x.close(); + presence.add_child(std::move(x)); + XmlNode error("error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = type; + XmlNode subnode(condition); + subnode["xmlns"] = STANZA_NS; + subnode.close(); + error.add_child(std::move(subnode)); + error.close(); + presence.add_child(std::move(error)); + presence.close(); + this->send_stanza(presence); +} + void XmppComponent::send_affiliation_role_change(const std::string& muc_name, const std::string& target, const std::string& affiliation, diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index f081420..9839158 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -141,6 +141,15 @@ public: void send_nickname_conflict_error(const std::string& muc_name, const std::string& nickname, const std::string& jid_to); + /** + * Send a generic presence error + */ + void send_presence_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string& text); /** * Send a presence from the MUC indicating a change in the role and/or * affiliation of a participant -- cgit v1.2.3 From e033b6a3ae2923b040eb08cfa5376efe0a3da898 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 May 2014 02:03:36 +0200 Subject: On connection in-progress, save the whole addrinfo struct, not just ai_addr --- src/network/socket_handler.cpp | 22 ++++++---------------- src/network/socket_handler.hpp | 1 + 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 2baad3d..d509513 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -82,22 +82,9 @@ void SocketHandler::connect(const std::string& address, const std::string& port) sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); } else - { - // This function is called again, use the saved addrinfo structure, - // instead of re-doing the whole getaddrinfo process. We insert only - // the meaningful values in the structure, and indicate that these are - // the only possible values with ai_next = NULL. - addr_res = (struct addrinfo*)malloc(sizeof(struct addrinfo)); - if (!addr_res) - { - this->close(); - this->on_connection_failed("memory error"); - return ; - } - sg.add_callback([&addr_res](){ free(addr_res); }); - addr_res->ai_next = NULL; - addr_res->ai_addr = &this->ai_addr; - addr_res->ai_addrlen = this->ai_addrlen; + { // This function is called again, use the saved addrinfo structure, + // instead of re-doing the whole getaddrinfo process. + addr_res = &this->addrinfo; } for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) @@ -131,6 +118,9 @@ void SocketHandler::connect(const std::string& address, const std::string& port) // Save the addrinfo structure, to use it on the next call this->ai_addrlen = rp->ai_addrlen; memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); + memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); + this->addrinfo.ai_addr = &this->ai_addr; + this->addrinfo.ai_next = nullptr; return ; } log_info("Connection failed:" << strerror(errno)); diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index e6a36bf..0b59757 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -131,6 +131,7 @@ protected: * connect()ing to it, to reuse it directly when connect() is called * again. */ + struct addrinfo addrinfo; struct sockaddr ai_addr; socklen_t ai_addrlen; -- cgit v1.2.3 From 5999e6e0c32e6897b88f59f0743b4bb1fc9c521c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 01:12:46 +0200 Subject: Introduce the timed events --- src/test.cpp | 24 ++++++++ src/utils/timed_events.cpp | 54 ++++++++++++++++++ src/utils/timed_events.hpp | 111 +++++++++++++++++++++++++++++++++++++ src/utils/timed_events_manager.cpp | 55 ++++++++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 src/utils/timed_events.cpp create mode 100644 src/utils/timed_events.hpp create mode 100644 src/utils/timed_events_manager.cpp diff --git a/src/test.cpp b/src/test.cpp index f624bc2..fe89b5a 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -3,6 +3,7 @@ */ #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include #include @@ -25,6 +27,28 @@ static const std::string reset(""); int main() { + /** + * Timed events + */ + std::cout << color << "Testing timed events…" << reset << std::endl; + TimedEventsManager te_manager; + // No event. + assert(te_manager.get_timeout() == utils::no_timeout); + assert(te_manager.execute_expired_events() == 0); + + // Add a single event + te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){ std::cout << "Timeout expired" << std::endl; })); + // The event should not yet be expired + assert(te_manager.get_timeout() > 0ms); + assert(te_manager.execute_expired_events() == 0); + std::chrono::milliseconds timoute = te_manager.get_timeout(); + std::cout << "Sleeping for " << timoute.count() << "ms" << std::endl; + std::this_thread::sleep_for(timoute); + + // Event is now expired + assert(te_manager.execute_expired_events() == 1); + assert(te_manager.get_timeout() == utils::no_timeout); + /** * Encoding */ diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp new file mode 100644 index 0000000..10ef4c1 --- /dev/null +++ b/src/utils/timed_events.cpp @@ -0,0 +1,54 @@ +#include + +TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback): + time_point(std::move(time_point)), + callback(callback), + repeat(false), + repeat_delay(0) +{ +} + +TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, + std::function callback): + time_point(std::chrono::steady_clock::now() + duration), + callback(callback), + repeat(true), + repeat_delay(std::move(duration)) +{ +} + +TimedEvent::TimedEvent(TimedEvent&& other): + time_point(std::move(other.time_point)), + callback(std::move(other.callback)), + repeat(other.repeat), + repeat_delay(std::move(other.repeat_delay)) +{ +} + +TimedEvent::~TimedEvent() +{ +} + +bool TimedEvent::is_after(const TimedEvent& other) const +{ + return this->is_after(other.time_point); +} + +bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const +{ + return this->time_point >= time_point; +} + +std::chrono::milliseconds TimedEvent::get_timeout() const +{ + auto now = std::chrono::steady_clock::now(); + if (now > this->time_point) + return std::chrono::milliseconds(0); + return std::chrono::duration_cast(this->time_point - now); +} + +void TimedEvent::execute() +{ + this->callback(); +} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp new file mode 100644 index 0000000..9be747d --- /dev/null +++ b/src/utils/timed_events.hpp @@ -0,0 +1,111 @@ +#ifndef TIMED_EVENTS_HPP +# define TIMED_EVENTS_HPP + +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace utils { +static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); +} + +class TimedEventsManager; + +/** + * A callback with an associated date. + */ + +class TimedEvent +{ + friend class TimedEventsManager; +public: + /** + * An event the occurs only once, at the given time_point + */ + explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback); + explicit TimedEvent(std::chrono::milliseconds&& duration, + std::function callback); + + explicit TimedEvent(TimedEvent&&); + ~TimedEvent(); + /** + * Whether or not this event happens after the other one. + */ + bool is_after(const TimedEvent& other) const; + bool is_after(const std::chrono::steady_clock::time_point& time_point) const; + /** + * Return the duration difference between now and the event time point. + * If the difference would be negative (i.e. the event is expired), the + * returned value is 0 instead. The value cannot then be negative. + */ + std::chrono::milliseconds get_timeout() const; + void execute(); + +private: + /** + * The next time point at which the event is executed. + */ + std::chrono::steady_clock::time_point time_point; + /** + * The function to execute. + */ + const std::function callback; + /** + * Whether or not this events repeats itself until it is destroyed. + */ + const bool repeat; + /** + * This value is added to the time_point each time the event is executed, + * if repeat is true. Otherwise it is ignored. + */ + const std::chrono::milliseconds repeat_delay; + + TimedEvent(const TimedEvent&) = delete; + TimedEvent& operator=(const TimedEvent&) = delete; + TimedEvent& operator=(TimedEvent&&) = delete; +}; + +/** + * A class managing a list of TimedEvents. + * They are sorted, new events can be added, removed, fetch, etc. + */ + +class TimedEventsManager +{ +public: + explicit TimedEventsManager(); + ~TimedEventsManager(); + /** + * Add an event to the list of managed events. The list is sorted after + * this call. + */ + void add_event(TimedEvent&& event); + /** + * Returns the duration, in milliseconds, between now and the next + * available event. If the event is already expired (the duration is + * negative), 0 is returned instead (as in “it's not too late, execute it + * now”) + * Returns a negative value if no event is available. + */ + std::chrono::milliseconds get_timeout() const; + /** + * Execute all the expired events (if their expiration time is exactly + * now, or before now). The event is then removed from the list. If the + * event does repeat, its expiration time is updated and it is reinserted + * in the list at the correct position. + * Returns the number of executed events. + */ + std::size_t execute_expired_events(); + +private: + std::list events; + TimedEventsManager(const TimedEventsManager&) = delete; + TimedEventsManager(TimedEventsManager&&) = delete; + TimedEventsManager& operator=(const TimedEventsManager&) = delete; + TimedEventsManager& operator=(TimedEventsManager&&) = delete; +}; + +#endif // TIMED_EVENTS_HPP diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp new file mode 100644 index 0000000..c3e260c --- /dev/null +++ b/src/utils/timed_events_manager.cpp @@ -0,0 +1,55 @@ +#include + +TimedEventsManager::TimedEventsManager() +{ +} + +TimedEventsManager::~TimedEventsManager() +{ +} + +void TimedEventsManager::add_event(TimedEvent&& event) +{ + for (auto it = this->events.begin(); it != this->events.end(); ++it) + { + if (it->is_after(event)) + { + this->events.emplace(it, std::move(event)); + return; + } + } + this->events.emplace_back(std::move(event)); +} + +std::chrono::milliseconds TimedEventsManager::get_timeout() const +{ + if (this->events.empty()) + return utils::no_timeout; + return this->events.front().get_timeout() + std::chrono::milliseconds(1); +} + +std::size_t TimedEventsManager::execute_expired_events() +{ + std::size_t count = 0; + const auto now = std::chrono::steady_clock::now(); + for (auto it = this->events.begin(); it != this->events.end();) + { + if (!it->is_after(now)) + { + TimedEvent copy(std::move(*it)); + it = this->events.erase(it); + ++count; + copy.execute(); + if (copy.repeat) + { + copy.time_point += copy.repeat_delay; + this->add_event(std::move(copy)); + } + continue; + } + else + break; + } + return count; +} + -- cgit v1.2.3 From d9d30dd782870b2ab6584fb54b7c19a2a9ae4c78 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 May 2014 01:30:04 +0200 Subject: =?UTF-8?q?Use=20a=20timed=20event=20to=20force=20the=20exit=202?= =?UTF-8?q?=E2=80=AFseconds=20after=20an=20exit=20signal=20is=20received?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #2469 --- src/main.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 40825c9..b5abad9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -17,6 +18,13 @@ static volatile std::atomic reload(false); // flag is set and all connections are closed, we can exit properly. static bool exiting = false; +// The global (and only) TimedEventsManager. It can be accessed by any +// class to add new TimedEvents, and it is used in the main loop to provide +// a timeout to the poller, this way the method execute_expired_events can +// be called on time, without having to do an active “poll” on it every n +// seconds. +TimedEventsManager timed_events; + /** * Provide an helpful message to help the user write a minimal working * configuration file. @@ -32,8 +40,11 @@ int config_help(const std::string& missing_option) return 1; } -static void sigint_handler(int, siginfo_t*, void*) +static void sigint_handler(int sig, siginfo_t*, void*) { + // In 2 seconds, repeat the same signal, to force the exit + timed_events.add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, + [sig]() { raise(sig); })); stop.store(true); } @@ -90,9 +101,10 @@ int main(int ac, char** av) xmpp_component->start(); - const std::chrono::milliseconds timeout(-1); + auto timeout = timed_events.get_timeout(); while (p->poll(timeout) != -1) { + timed_events.execute_expired_events(); // Check for empty irc_clients (not connected, or with no joined // channel) and remove them xmpp_component->clean(); @@ -131,6 +143,7 @@ int main(int ac, char** av) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); + timeout = timed_events.get_timeout(); } log_info("All connection cleanely closed, have a nice day."); return 0; -- cgit v1.2.3 From a350caf6e0fb95263e929fde7dd4f58931f0825c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 May 2014 03:59:42 +0200 Subject: Move the logging + config test on first position --- src/test.cpp | 66 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index fe89b5a..2b9aca2 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -27,6 +27,40 @@ static const std::string reset(""); int main() { + + /** + * Config + */ + std::cout << color << "Testing config…" << reset << std::endl; + Config::filename = "test.cfg"; + Config::file_must_exist = false; + Config::set("coucou", "bonjour", true); + Config::close(); + + bool error = false; + try + { + Config::file_must_exist = true; + assert(Config::get("coucou", "") == "bonjour"); + assert(Config::get("does not exist", "default") == "default"); + Config::close(); + } + catch (const std::ios::failure& e) + { + error = true; + } + assert(error == false); + + Config::set("log_level", "2"); + Config::set("log_file", ""); + + std::cout << color << "Testing logging…" << reset << std::endl; + log_debug("If you see this, the test FAILED."); + log_info("If you see this, the test FAILED."); + log_warning("You wust see this message. And the next one too."); + log_error("It’s not an error, don’t worry, the test passed."); + + /** * Timed events */ @@ -229,37 +263,5 @@ int main() std::cout << correctjid2 << std::endl; assert(correctjid2 == "zigougou@poez.io"); - /** - * Config - */ - std::cout << color << "Testing config…" << reset << std::endl; - Config::filename = "test.cfg"; - Config::file_must_exist = false; - Config::set("coucou", "bonjour", true); - Config::close(); - - bool error = false; - try - { - Config::file_must_exist = true; - assert(Config::get("coucou", "") == "bonjour"); - assert(Config::get("does not exist", "default") == "default"); - Config::close(); - } - catch (const std::ios::failure& e) - { - error = true; - } - assert(error == false); - - Config::set("log_level", "2"); - Config::set("log_file", ""); - - std::cout << color << "Testing logging…" << reset << std::endl; - log_debug("If you see this, the test FAILED."); - log_info("If you see this, the test FAILED."); - log_warning("You wust see this message. And the next one too."); - log_error("It’s not an error, don’t worry, the test passed."); - return 0; } -- cgit v1.2.3 From 1c93afc9a7ec33d90c81062c3f1077b5cf84c212 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 May 2014 04:22:40 +0200 Subject: Change the way the namespaces are handled in the XmlNode class --- src/test.cpp | 11 ++++++----- src/xmpp/xmpp_component.cpp | 46 +++++++++++++++++---------------------------- src/xmpp/xmpp_component.hpp | 14 ++++++++++++++ src/xmpp/xmpp_stanza.cpp | 27 +++++++++++++++++--------- src/xmpp/xmpp_stanza.hpp | 3 ++- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 2b9aca2..e71ea35 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -154,14 +154,15 @@ int main() xml.add_stanza_callback([](const Stanza& stanza) { std::cout << stanza.to_string() << std::endl; - assert(stanza.get_name() == "stream_ns:stanza"); + assert(stanza.get_name() == "stanza"); + assert(stanza.get_tag("xmlns") == "stream_ns"); assert(stanza.get_tag("b") == "c"); assert(stanza.get_inner() == "inner"); assert(stanza.get_tail() == ""); - assert(stanza.get_child("stream_ns:child1") != nullptr); - assert(stanza.get_child("stream_ns:child2") == nullptr); - assert(stanza.get_child("child2_ns:child2") != nullptr); - assert(stanza.get_child("child2_ns:child2")->get_tail() == "tail"); + assert(stanza.get_child("child1", "stream_ns") != nullptr); + assert(stanza.get_child("child2", "stream_ns") == nullptr); + assert(stanza.get_child("child2", "child2_ns") != nullptr); + assert(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); }); xml.feed(doc.data(), doc.size(), true); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 3c2aa7a..9ac05ac 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -17,19 +17,6 @@ # include #endif -#define STREAM_NS "http://etherx.jabber.org/streams" -#define COMPONENT_NS "jabber:component:accept" -#define MUC_NS "http://jabber.org/protocol/muc" -#define MUC_USER_NS MUC_NS"#user" -#define MUC_ADMIN_NS MUC_NS"#admin" -#define DISCO_NS "http://jabber.org/protocol/disco" -#define DISCO_ITEMS_NS DISCO_NS"#items" -#define DISCO_INFO_NS DISCO_NS"#info" -#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" -#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" -#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" -#define VERSION_NS "jabber:iq:version" - using namespace std::string_literals; unsigned long XmppComponent::current_id = 0; @@ -106,7 +93,8 @@ void XmppComponent::on_connection_failed(const std::string& reason) void XmppComponent::on_connected() { log_info("connected to XMPP server"); - XmlNode node("stream:stream", nullptr); + XmlNode node("", nullptr); + node.set_name("stream:stream"); node["xmlns"] = COMPONENT_NS; node["xmlns:stream"] = STREAM_NS; node["to"] = this->served_hostname; @@ -183,7 +171,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) sprintf(digest + (i*2), "%02x", result[i]); digest[HASH_LENGTH * 2] = '\0'; - Stanza handshake("handshake"); + Stanza handshake(COMPONENT_NS":handshake"); handshake.set_inner(digest); handshake.close(); this->send_stanza(handshake); @@ -329,7 +317,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) } else if (type == "unavailable") { - XmlNode* status = stanza.get_child(COMPONENT_NS":status"); + XmlNode* status = stanza.get_child("status", COMPONENT_NS); bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); } } @@ -363,19 +351,19 @@ void XmppComponent::handle_message(const Stanza& stanza) this->send_stanza_error("message", from, to_str, id, error_type, error_name, ""); }); - XmlNode* body = stanza.get_child(COMPONENT_NS":body"); + XmlNode* body = stanza.get_child("body", COMPONENT_NS); if (type == "groupchat") { if (to.resource.empty()) if (body && !body->get_inner().empty()) bridge->send_channel_message(iid, body->get_inner()); - XmlNode* subject = stanza.get_child(COMPONENT_NS":subject"); + XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); if (subject) bridge->set_channel_topic(iid, subject->get_inner()); } else if (type == "error") { - const XmlNode* error = stanza.get_child(COMPONENT_NS":error"); + const XmlNode* error = stanza.get_child("error", COMPONENT_NS); // Only a set of errors are considered “fatal”. If we encounter one of // them, we purge (we disconnect the user from all the IRC servers). // We consider this to be true, unless the error condition is @@ -436,13 +424,13 @@ void XmppComponent::handle_iq(const Stanza& stanza) utils::ScopeGuard stanza_error([&](){ this->send_stanza_error("iq", from, to_str, id, error_type, error_name, ""); - }); + }); if (type == "set") { XmlNode* query; - if ((query = stanza.get_child(MUC_ADMIN_NS":query"))) + if ((query = stanza.get_child("query", MUC_ADMIN_NS))) { - const XmlNode* child = query->get_child(MUC_ADMIN_NS":item"); + const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); if (child) { std::string nick = child->get_tag("nick"); @@ -450,7 +438,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) if (!nick.empty() && role == "none") { // This is a kick std::string reason; - XmlNode* reason_el = child->get_child(MUC_ADMIN_NS":reason"); + XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); if (reason_el) reason = reason_el->get_inner(); Iid iid(to.local); @@ -463,7 +451,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) else if (type == "get") { XmlNode* query; - if ((query = stanza.get_child(DISCO_INFO_NS":query"))) + if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info if (to_str == this->served_hostname) { // On the gateway itself @@ -476,11 +464,11 @@ void XmppComponent::handle_iq(const Stanza& stanza) { stanza_error.disable(); XmlNode* query; - if ((query = stanza.get_child(VERSION_NS":query"))) + if ((query = stanza.get_child("query", VERSION_NS))) { - XmlNode* name_node = query->get_child(VERSION_NS":name"); - XmlNode* version_node = query->get_child(VERSION_NS":version"); - XmlNode* os_node = query->get_child(VERSION_NS":os"); + XmlNode* name_node = query->get_child("name", VERSION_NS); + XmlNode* version_node = query->get_child("version", VERSION_NS); + XmlNode* os_node = query->get_child("os", VERSION_NS); std::string name; std::string version; std::string os; @@ -500,7 +488,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) void XmppComponent::handle_error(const Stanza& stanza) { - XmlNode* text = stanza.get_child(STREAMS_NS":text"); + XmlNode* text = stanza.get_child("text", STREAMS_NS); std::string error_message("Unspecified error"); if (text) error_message = text->get_inner(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 9839158..d934704 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -9,6 +9,20 @@ #include #include +#define STREAM_NS "http://etherx.jabber.org/streams" +#define COMPONENT_NS "jabber:component:accept" +#define MUC_NS "http://jabber.org/protocol/muc" +#define MUC_USER_NS MUC_NS"#user" +#define MUC_ADMIN_NS MUC_NS"#admin" +#define DISCO_NS "http://jabber.org/protocol/disco" +#define DISCO_ITEMS_NS DISCO_NS"#items" +#define DISCO_INFO_NS DISCO_NS"#info" +#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" +#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" +#define VERSION_NS "jabber:iq:version" +#define ADHOC_NS "http://jabber.org/protocol/commands" + /** * An XMPP component, communicating with an XMPP server using the protocole * described in XEP-0114: Jabber Component Protocol diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 400971b..be9f8ae 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -84,10 +84,18 @@ std::string xml_unescape(const std::string& data) } XmlNode::XmlNode(const std::string& name, XmlNode* parent): - name(name), parent(parent), closed(false) { + // split the namespace and the name + auto n = name.rfind(":"); + if (n == std::string::npos) + this->name = name; + else + { + this->name = name.substr(n+1); + this->attributes["xmlns"] = name.substr(0, n); + } } XmlNode::XmlNode(const std::string& name): @@ -144,11 +152,11 @@ std::string XmlNode::get_tail() const return xml_unescape(this->tail); } -XmlNode* XmlNode::get_child(const std::string& name) const +XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const { for (auto& child: this->children) { - if (child->name == name) + if (child->name == name && child->get_tag("xmlns") == xmlns) return child; } return nullptr; @@ -184,13 +192,14 @@ XmlNode* XmlNode::get_parent() const return this->parent; } +void XmlNode::set_name(const std::string& name) +{ + this->name = name; +} + const std::string XmlNode::get_name() const { - const std::vector splited = utils::split(this->name, ':', false); - if (splited.empty()) - return ""; - const std::string res = splited.back(); - return res; + return this->name; } std::string XmlNode::to_string() const @@ -209,7 +218,7 @@ std::string XmlNode::to_string() const res += child->to_string(); if (this->closed) { - res += "name + ">"; + res += "get_name() + ">"; } } res += utils::remove_invalid_xml_chars(this->tail); diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 476596b..cc8d53a 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -69,7 +69,7 @@ public: /** * Get a pointer to the first child element with that name */ - XmlNode* get_child(const std::string& name) const; + XmlNode* get_child(const std::string& name, const std::string& xmlns) const; /** * Add a node child to this node. Assign this node to the child’s parent. * Returns a pointer to the newly added child. @@ -87,6 +87,7 @@ public: */ void close(); XmlNode* get_parent() const; + void set_name(const std::string& name); const std::string get_name() const; /** * Serialize the stanza into a string -- cgit v1.2.3 From eb9a20187098185cc10ad192e91a90dbba12633a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 27 May 2014 01:01:38 +0200 Subject: Implement the support for adhoc commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have two basic example commands. But it’s not entirely finished because there are some error checks that we don’t do. ref #2521 --- src/xmpp/adhoc_command.cpp | 82 +++++++++++++++++++++++++++++ src/xmpp/adhoc_command.hpp | 40 +++++++++++++++ src/xmpp/adhoc_commands_handler.cpp | 100 ++++++++++++++++++++++++++++++++++++ src/xmpp/adhoc_commands_handler.hpp | 56 ++++++++++++++++++++ src/xmpp/xmpp_component.cpp | 62 ++++++++++++++++++++-- src/xmpp/xmpp_component.hpp | 7 +++ src/xmpp/xmpp_stanza.cpp | 7 +++ src/xmpp/xmpp_stanza.hpp | 21 +++++++- 8 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 src/xmpp/adhoc_command.cpp create mode 100644 src/xmpp/adhoc_command.hpp create mode 100644 src/xmpp/adhoc_commands_handler.cpp create mode 100644 src/xmpp/adhoc_commands_handler.hpp diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp new file mode 100644 index 0000000..b880533 --- /dev/null +++ b/src/xmpp/adhoc_command.cpp @@ -0,0 +1,82 @@ +#include + +using namespace std::string_literals; + +AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): + name(name), + callbacks(std::move(callbacks)), + admin_only(admin_only) +{ +} + +AdhocCommand::~AdhocCommand() +{ +} + +void PingStep1(AdhocSession&, XmlNode& command_node) +{ + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Pong"); + note.close(); + command_node.add_child(std::move(note)); +} + +void HelloStep1(AdhocSession&, XmlNode& command_node) +{ + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure your name."); + title.close(); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Please provide your name."); + instructions.close(); + x.add_child(std::move(instructions)); + XmlNode name_field("field"); + name_field["var"] = "name"; + name_field["type"] = "text-single"; + name_field["label"] = "Your name"; + XmlNode required("required"); + required.close(); + name_field.add_child(std::move(required)); + name_field.close(); + x.add_child(std::move(name_field)); + x.close(); + command_node.add_child(std::move(x)); +} + +void HelloStep2(AdhocSession& session, XmlNode& command_node) +{ + // Find out if the name was provided in the form. + XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + XmlNode* name_field = nullptr; + for (XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "name") + { + name_field = field; + break; + } + if (name_field) + { + XmlNode* value = name_field->get_child("value", "jabber:x:data"); + if (value) + { + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Hello "s + value->get_inner() + "!"s); + note.close(); + command_node.delete_all_children(); + command_node.add_child(std::move(note)); + return; + } + } + } + // TODO insert an error telling the name value is missing. Also it's + // useless to terminate it, since this step is the last of the command + // anyway. But this is for the example. + session.terminate(); +} diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp new file mode 100644 index 0000000..59a3cd6 --- /dev/null +++ b/src/xmpp/adhoc_command.hpp @@ -0,0 +1,40 @@ +#ifndef ADHOC_COMMAND_HPP +# define ADHOC_COMMAND_HPP + +/** + * Describe an ad-hoc command. + * + * Can only have zero or one step for now. When execution is requested, it + * can return a result immediately, or provide a form to be filled, and + * provide a result once the filled form is received. + */ + +#include + +#include +#include + +class AdhocCommand +{ + friend class AdhocSession; +public: + AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); + ~AdhocCommand(); + + const std::string name; + +private: + /** + * A command may have one or more steps. Each step is a different + * callback, inserting things into a XmlNode and calling + * methods of an AdhocSession. + */ + std::vector callbacks; + const bool admin_only; +}; + +void PingStep1(AdhocSession& session, XmlNode& command_node); +void HelloStep1(AdhocSession& session, XmlNode& command_node); +void HelloStep2(AdhocSession& session, XmlNode& command_node); + +#endif // ADHOC_COMMAND_HPP diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp new file mode 100644 index 0000000..a669ca1 --- /dev/null +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -0,0 +1,100 @@ +#include +#include + +#include + +#include + +AdhocCommandsHandler::AdhocCommandsHandler(): + commands{ + {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, + {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)} + } +{ +} + +AdhocCommandsHandler::~AdhocCommandsHandler() +{ +} + +const std::map& AdhocCommandsHandler::get_commands() const +{ + return this->commands; +} + +XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) +{ + // TODO check the type of action. Currently it assumes it is always + // 'execute'. + std::string action = command_node.get_tag("action"); + if (action.empty()) + action = "execute"; + command_node.del_tag("action"); + + const std::string node = command_node.get_tag("node"); + auto command_it = this->commands.find(node); + if (command_it == this->commands.end()) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "cancel"; + XmlNode condition(STANZA_NS":item-not-found"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + else + { + std::string sessionid = command_node.get_tag("sessionid"); + if (sessionid.empty()) + { // create a new session, with a new id + sessionid = XmppComponent::next_id(); + command_node["sessionid"] = sessionid; + this->sessions.emplace(std::piecewise_construct, + std::forward_as_tuple(sessionid, executor_jid), + std::forward_as_tuple(command_it->second, executor_jid)); + // TODO add a timed event to have an expiration date that deletes + // this session. We could have a nasty client starting commands + // but never finishing the last step, and that would fill the map + // with dummy sessions. + } + auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); + if (session_it == this->sessions.end()) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + else + { + // execute the step + AdhocSession& session = session_it->second; + const AdhocStep& step = session.get_next_step(); + step(session, command_node); + if (session.remaining_steps() == 0 || + session.is_terminated()) + { + this->sessions.erase(session_it); + command_node["status"] = "completed"; + } + else + { + command_node["status"] = "executing"; + XmlNode actions("actions"); + XmlNode next("next"); + next.close(); + actions.add_child(std::move(next)); + actions.close(); + command_node.add_child(std::move(actions)); + } + } + } + // TODO remove that once we make sure so session can stay there for ever, + // by mistake. + log_debug("Number of existing sessions: " << this->sessions.size()); + return std::move(command_node); +} diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp new file mode 100644 index 0000000..6e00188 --- /dev/null +++ b/src/xmpp/adhoc_commands_handler.hpp @@ -0,0 +1,56 @@ +#ifndef ADHOC_COMMANDS_HANDLER_HPP +# define ADHOC_COMMANDS_HANDLER_HPP + +/** + * Manage a list of available AdhocCommands and the list of ongoing + * AdhocCommandSessions. + */ + +#include +#include + +#include +#include +#include + +class AdhocCommandsHandler +{ +public: + explicit AdhocCommandsHandler(); + ~AdhocCommandsHandler(); + /** + * Returns the list of available commands. + */ + const std::map& get_commands() const; + /** + * 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 + * result). + * + * Returns a (moved) XmlNode that will be inserted in the iq response. It + * should be a node containing one or more useful children. If + * it contains an node, the iq response will have an error type. + * + * Takes a copy of the node so we can actually edit it and use + * it as our return value. + */ + XmlNode&& handle_request(const std::string& executor_jid, XmlNode command_node); +private: + /** + * The list of all available commands. + */ + const std::map commands; + /** + * The list of all currently on-going commands. + * + * Of the form: {{session_id, owner_jid}, session}. + */ + std::map, AdhocSession> sessions; + + AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; + AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; +}; + +#endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9ac05ac..4c62aa7 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -447,6 +447,22 @@ void XmppComponent::handle_iq(const Stanza& stanza) } } } + else if ((query = stanza.get_child("command", ADHOC_NS))) + { + Stanza response("iq"); + response["to"] = from; + response["from"] = this->served_hostname; + response["id"] = id; + XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); + if (inner_node.get_child("error", ADHOC_NS)) + response["type"] = "error"; + else + response["type"] = "result"; + response.add_child(std::move(inner_node)); + response.close(); + this->send_stanza(response); + stanza_error.disable(); + } } else if (type == "get") { @@ -454,8 +470,22 @@ void XmppComponent::handle_iq(const Stanza& stanza) if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info if (to_str == this->served_hostname) - { // On the gateway itself - this->send_self_disco_info(id, from); + { + const std::string node = query->get_tag("node"); + if (node.empty()) + { + // On the gateway itself + this->send_self_disco_info(id, from); + stanza_error.disable(); + } + } + } + else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) + { + const std::string node = query->get_tag("node"); + if (node == ADHOC_NS) + { + this->send_adhoc_commands_list(id, from); stanza_error.disable(); } } @@ -848,8 +878,7 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin identity["name"] = "Biboumi XMPP-IRC gateway"; identity.close(); query.add_child(std::move(identity)); - for (const std::string& ns: {"http://jabber.org/protocol/disco#info", - "http://jabber.org/protocol/muc"}) + for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) { XmlNode feature("feature"); feature["var"] = ns; @@ -862,6 +891,31 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin this->send_stanza(iq); } +void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = requester_jid; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + query["node"] = ADHOC_NS; + for (const auto& kv: this->adhoc_commands_handler.get_commands()) + { + XmlNode item("item"); + item["jid"] = this->served_hostname; + item["node"] = kv.first; + item["name"] = kv.second.name; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + void XmppComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index d934704..b234d76 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -1,6 +1,7 @@ #ifndef XMPP_COMPONENT_INCLUDED # define XMPP_COMPONENT_INCLUDED +#include #include #include #include @@ -177,6 +178,11 @@ public: * Send a result IQ with the gateway disco informations. */ void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send the list of all available ad-hoc commands to that JID. The list is + * different depending on what JID made the request. + */ + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); /** * Send an iq version request */ @@ -231,6 +237,7 @@ private: static unsigned long current_id; + AdhocCommandsHandler adhoc_commands_handler; XmppComponent(const XmppComponent&) = delete; XmppComponent(XmppComponent&&) = delete; XmppComponent& operator=(const XmppComponent&) = delete; diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index be9f8ae..c964c64 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -243,6 +243,13 @@ const std::string XmlNode::get_tag(const std::string& name) const } } +bool XmlNode::del_tag(const std::string& name) +{ + if (this->attributes.erase(name) != 0) + return true; + return false; +} + std::string& XmlNode::operator[](const std::string& name) { return this->attributes[name]; diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index cc8d53a..e55d555 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -34,6 +34,21 @@ public: { node.parent = nullptr; } + /** + * The copy constructor do not copy the children or parent attributes. The + * copied node is identical to the original except that it is not attached + * to any other node. + */ + XmlNode(const XmlNode& node): + name(node.name), + parent(nullptr), + closed(node.closed), + attributes(node.attributes), + children{}, + inner(node.inner), + tail(node.tail) + { + } ~XmlNode(); @@ -103,6 +118,11 @@ public: * node as no such attribute. */ const std::string get_tag(const std::string& name) const; + /** + * Remove the attribute of the node. Does nothing if that attribute is not + * present. Returns true if the tag was removed, false if it was absent. + */ + bool del_tag(const std::string& name); /** * Use this to set an attribute's value, like node["id"] = "12"; */ @@ -117,7 +137,6 @@ private: std::string inner; std::string tail; - XmlNode(const XmlNode&) = delete; XmlNode& operator=(const XmlNode&) = delete; XmlNode& operator=(XmlNode&&) = delete; }; -- cgit v1.2.3 From 6bde78b5743d6b323b9af14d434927fd175175c3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 03:51:54 +0200 Subject: =?UTF-8?q?XmlNode=E2=80=99s=20copy=20constructor=20now=20recursiv?= =?UTF-8?q?ely=20copies=20the=20children=20nodes=20as=20well?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test.cpp | 27 +++++++++++++++++---------- src/xmpp/xmpp_stanza.hpp | 10 +++++++--- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index e71ea35..8fe739c 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -151,18 +151,25 @@ int main() std::cout << color << "Testing XML parsing…" << reset << std::endl; XmppParser xml; const std::string doc = "innertail"; - xml.add_stanza_callback([](const Stanza& stanza) + auto check_stanza = [](const Stanza& stanza) + { + assert(stanza.get_name() == "stanza"); + assert(stanza.get_tag("xmlns") == "stream_ns"); + assert(stanza.get_tag("b") == "c"); + assert(stanza.get_inner() == "inner"); + assert(stanza.get_tail() == ""); + assert(stanza.get_child("child1", "stream_ns") != nullptr); + assert(stanza.get_child("child2", "stream_ns") == nullptr); + assert(stanza.get_child("child2", "child2_ns") != nullptr); + assert(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); + }; + xml.add_stanza_callback([check_stanza](const Stanza& stanza) { std::cout << stanza.to_string() << std::endl; - assert(stanza.get_name() == "stanza"); - assert(stanza.get_tag("xmlns") == "stream_ns"); - assert(stanza.get_tag("b") == "c"); - assert(stanza.get_inner() == "inner"); - assert(stanza.get_tail() == ""); - assert(stanza.get_child("child1", "stream_ns") != nullptr); - assert(stanza.get_child("child2", "stream_ns") == nullptr); - assert(stanza.get_child("child2", "child2_ns") != nullptr); - assert(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); + check_stanza(stanza); + // Do the same checks on a copy of that stanza. + Stanza copy(stanza); + check_stanza(copy); }); xml.feed(doc.data(), doc.size(), true); diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index e55d555..53e0139 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -35,9 +35,8 @@ public: node.parent = nullptr; } /** - * The copy constructor do not copy the children or parent attributes. The - * copied node is identical to the original except that it is not attached - * to any other node. + * The copy constructor do not copy the parent attribute. The children + * nodes are all copied recursively. */ XmlNode(const XmlNode& node): name(node.name), @@ -48,6 +47,11 @@ public: inner(node.inner), tail(node.tail) { + for (XmlNode* child: node.children) + { + XmlNode* child_copy = new XmlNode(*child); + this->add_child(child_copy); + } } ~XmlNode(); -- cgit v1.2.3 From d33a3b7d3587c22d54761e3ed04c85452d7ec550 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 03:52:22 +0200 Subject: XmlNode::get_children, to get a list of matching children instead of the first --- src/xmpp/xmpp_stanza.cpp | 11 +++++++++++ src/xmpp/xmpp_stanza.hpp | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index c964c64..4290fc7 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -162,6 +162,17 @@ XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) c return nullptr; } +std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const +{ + std::vector res; + for (auto& child: this->children) + { + if (child->name == name && child->get_tag("xmlns") == xmlns) + res.push_back(child); + } + return res; +} + XmlNode* XmlNode::add_child(XmlNode* child) { child->parent = this; diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 53e0139..a34ef50 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -86,9 +86,13 @@ public: */ std::string get_tail() const; /** - * Get a pointer to the first child element with that name + * Get a pointer to the first child element with that name and that xml namespace */ XmlNode* get_child(const std::string& name, const std::string& xmlns) const; + /** + * Get a vector of all the children that have that name and that xml namespace. + */ + std::vector get_children(const std::string& name, const std::string& xmlns) const; /** * Add a node child to this node. Assign this node to the child’s parent. * Returns a pointer to the newly added child. -- cgit v1.2.3 From 86c6f36faebadfc028ea5f9c64e5769fb0f77170 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 04:03:36 +0200 Subject: Add missing files (for adhoc command support) --- src/xmpp/adhoc_session.cpp | 37 +++++++++++++++++++++++++ src/xmpp/adhoc_session.hpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/xmpp/adhoc_session.cpp create mode 100644 src/xmpp/adhoc_session.hpp diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp new file mode 100644 index 0000000..fc60bb7 --- /dev/null +++ b/src/xmpp/adhoc_session.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include + +AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid): + command(command), + owner_jid(jid), + current_step(0), + terminated(false) +{ +} + +AdhocSession::~AdhocSession() +{ +} + +const AdhocStep& AdhocSession::get_next_step() +{ + assert(this->current_step < this->command.callbacks.size()); + return this->command.callbacks[this->current_step++]; +} + +size_t AdhocSession::remaining_steps() const +{ + return this->command.callbacks.size() - this->current_step; +} + +bool AdhocSession::is_terminated() const +{ + return this->terminated; +} + +void AdhocSession::terminate() +{ + this->terminated = true; +} diff --git a/src/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp new file mode 100644 index 0000000..111b43b --- /dev/null +++ b/src/xmpp/adhoc_session.hpp @@ -0,0 +1,68 @@ +#ifndef ADHOC_SESSION_HPP +# define ADHOC_SESSION_HPP + +#include + +#include +#include + +class AdhocCommand; +class AdhocSession; + +/** + * A function executed as an ad-hoc command step. It takes a + * XmlNode and modifies it accordingly (inserting for example an + * node, or a data form…). + * TODO fix this: + * It also must call one of step_passed(), cancel() etc on the AdhocSession object. + */ +typedef std::function AdhocStep; + +class AdhocSession +{ +public: + explicit AdhocSession(const AdhocCommand& command, const std::string& jid); + ~AdhocSession(); + /** + * Return the function to be executed, found in our AdhocCommand, for the + * current_step. And increment the current_step. + */ + const AdhocStep& get_next_step(); + /** + * Return the number of remaining steps. + */ + size_t remaining_steps() const; + /** + * This may be modified by an AdhocStep, to indicate that this session + * should no longer exist, because we encountered an error, and we can't + * execute any more step of it. + */ + void terminate(); + bool is_terminated() const; + +private: + /** + * A reference of the command concerned by this session. Used for example + * to get the next step of that command, things like that. + */ + const AdhocCommand& command; + /** + * The full JID of the XMPP user that created this session by executing + * the first step of a command. Only that JID must be allowed to access + * this session. + */ + const std::string& owner_jid; + /** + * The current step we are at. It starts at zero. It is used to index the + * associated AdhocCommand::callbacks vector. + */ + size_t current_step; + bool terminated; + + AdhocSession(const AdhocSession&) = delete; + AdhocSession(AdhocSession&&) = delete; + AdhocSession& operator=(const AdhocSession&) = delete; + AdhocSession& operator=(AdhocSession&&) = delete; +}; + +#endif // ADHOC_SESSION_HPP -- cgit v1.2.3 From 8eb9e535a3514a7d9254819f499dcb169c56b51e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 13:12:33 +0200 Subject: Use pkg-config first to find libraries --- cmake/Modules/FindLibidn.cmake | 31 ++++++++++++++++++------------- cmake/Modules/FindSystemdDaemon.cmake | 27 ++++++++++++++++----------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/cmake/Modules/FindLibidn.cmake b/cmake/Modules/FindLibidn.cmake index 5434127..611a6a8 100644 --- a/cmake/Modules/FindLibidn.cmake +++ b/cmake/Modules/FindLibidn.cmake @@ -15,22 +15,27 @@ # # This file is in the public domain -find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h - DOC "The libidn include directory") +include(FindPkgConfig) +pkg_check_modules(LIBIDN libidn) -# The library containing the stringprep module is libidn -find_library(LIBIDN_LIBRARIES NAMES idn - DOC "The libidn library") +if(NOT LIBIDN_FOUND) + find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h + DOC "The libidn include directory") -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set LIBIDN_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Libidn REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) + # The library containing the stringprep module is libidn + find_library(LIBIDN_LIBRARIES NAMES idn + DOC "The libidn library") -# Compatibility for all the ways of writing these variables -if(LIBIDN_FOUND) - set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) - set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBIDN_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBIDN_FOUND) + set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) + set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) + endif() endif() mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/cmake/Modules/FindSystemdDaemon.cmake b/cmake/Modules/FindSystemdDaemon.cmake index 441ac47..9492bf2 100644 --- a/cmake/Modules/FindSystemdDaemon.cmake +++ b/cmake/Modules/FindSystemdDaemon.cmake @@ -15,20 +15,25 @@ # # This file is in the public domain -find_path(SYSTEMDDAEMON_INCLUDE_DIRS NAMES systemd/sd-daemon.h - DOC "The Systemd Daemon include directory") +include(FindPkgConfig) +pkg_check_modules(SYSTEMDDAEMON libsystemd-daemon) -find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd - DOC "The Systemd Daemon library") +if(NOT SYSTEMDDAEMON_FOUND) + find_path(SYSTEMDDAEMON_INCLUDE_DIRS NAMES systemd/sd-daemon.h + DOC "The Systemd Daemon include directory") -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set SYSTEMDDAEMON_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SystemdDaemon REQUIRED_VARS SYSTEMDDAEMON_LIBRARIES SYSTEMDDAEMON_INCLUDE_DIRS) + find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd-daemon systemd + DOC "The Systemd Daemon library") -if(SYSTEMDDAEMON_FOUND) - set(SYSTEMDDAEMON_LIBRARY ${SYSTEMDDAEMON_LIBRARIES}) - set(SYSTEMDDAEMON_INCLUDE_DIR ${SYSTEMDDAEMON_INCLUDE_DIRS}) + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set SYSTEMDDAEMON_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SYSTEMDDAEMON REQUIRED_VARS SYSTEMDDAEMON_LIBRARIES SYSTEMDDAEMON_INCLUDE_DIRS) + + if(SYSTEMDDAEMON_FOUND) + set(SYSTEMDDAEMON_LIBRARY ${SYSTEMDDAEMON_LIBRARIES}) + set(SYSTEMDDAEMON_INCLUDE_DIR ${SYSTEMDDAEMON_INCLUDE_DIRS}) + endif() endif() mark_as_advanced(SYSTEMDDAEMON_INCLUDE_DIRS SYSTEMDDAEMON_LIBRARIES) \ No newline at end of file -- cgit v1.2.3 From a63faf6fa95017dbbfeaf0ff43fdb526c4ae7068 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 13:39:00 +0200 Subject: Use libuuid to generate unique IDs for iq and adhoc sessions --- CMakeLists.txt | 5 ++++- cmake/Modules/FindLibuuid.cmake | 41 +++++++++++++++++++++++++++++++++++++++++ src/test.cpp | 11 ++++++++--- src/xmpp/xmpp_component.cpp | 10 +++++++--- src/xmpp/xmpp_component.hpp | 2 -- 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 cmake/Modules/FindLibuuid.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1589399..90b5432 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") include(FindEXPAT) find_package(EXPAT REQUIRED) find_package(Iconv REQUIRED) +find_package(Libuuid REQUIRED) find_package(Libidn) find_package(SystemdDaemon) @@ -26,6 +27,7 @@ include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) +include_directories(${LIBUUID_INCLUDE_DIRS}) if(LIBIDN_FOUND) include_directories(${LIBIDN_INCLUDE_DIRS}) @@ -109,7 +111,8 @@ file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp bridge network utils logger - ${EXPAT_LIBRARIES}) + ${EXPAT_LIBRARIES} + ${LIBUUID_LIBRARIES}) if(LIBIDN_FOUND) target_link_libraries(xmpp ${LIBIDN_LIBRARIES}) endif() diff --git a/cmake/Modules/FindLibuuid.cmake b/cmake/Modules/FindLibuuid.cmake new file mode 100644 index 0000000..25b330b --- /dev/null +++ b/cmake/Modules/FindLibuuid.cmake @@ -0,0 +1,41 @@ +# - Find libuuid +# Find the libuuid library +# +# This module defines the following variables: +# LIBUUID_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBUUID_INCLUDE_DIRS - The directory where to find the header file +# LIBUUID_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LIBUUID_INCLUDE_DIR +# LIBUUID_LIBRARY +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(LIBUUID uuid) + +if(NOT LIBUUID_FOUND) + find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h + PATH uuid + DOC "The libuuid include directory") + + find_library(LIBUUID_LIBRARIES NAMES uuid + DOC "The libuuid library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBUUID_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBUUID_FOUND) + set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS}) + set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES}) + endif() +endif() + +mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES) diff --git a/src/test.cpp b/src/test.cpp index 8fe739c..ac14377 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -121,9 +121,14 @@ int main() /** * Id generation */ - assert(XmppComponent::next_id() == "0"); - assert(XmppComponent::next_id() == "1"); - assert(XmppComponent::next_id() == "2"); + std::cout << color << "Testing id generation…" << reset << std::endl; + const std::string first_uuid = XmppComponent::next_id(); + const std::string second_uuid = XmppComponent::next_id(); + std::cout << first_uuid << std::endl; + std::cout << second_uuid << std::endl; + assert(first_uuid.size() == 36); + assert(second_uuid.size() == 36); + assert(first_uuid != second_uuid); /** * Utils diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 4c62aa7..9547e3a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -13,14 +13,14 @@ #include +#include + #ifdef SYSTEMDDAEMON_FOUND # include #endif using namespace std::string_literals; -unsigned long XmppComponent::current_id = 0; - static std::set kickable_errors{ "gone", "internal-server-error", @@ -934,5 +934,9 @@ void XmppComponent::send_iq_version_request(const std::string& from, std::string XmppComponent::next_id() { - return std::to_string(XmppComponent::current_id++); + char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; } diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index b234d76..e3e24e0 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -235,8 +235,6 @@ private: */ std::unordered_map> bridges; - static unsigned long current_id; - AdhocCommandsHandler adhoc_commands_handler; XmppComponent(const XmppComponent&) = delete; XmppComponent(XmppComponent&&) = delete; -- cgit v1.2.3 From 5cca518c2d946144f4fee1b15dcfd3884850dcb1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 15:42:01 +0200 Subject: Timed events can have a name, and can be canceled based on their name --- src/test.cpp | 11 +++++++++++ src/utils/timed_events.cpp | 18 +++++++++++++----- src/utils/timed_events.hpp | 20 ++++++++++++++++++-- src/utils/timed_events_manager.cpp | 20 ++++++++++++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index ac14377..77e4ada 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -83,6 +83,17 @@ int main() assert(te_manager.execute_expired_events() == 1); assert(te_manager.get_timeout() == utils::no_timeout); + // Test canceling events + te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 100ms, [](){ }, "un")); + te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 200ms, [](){ }, "deux")); + te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 300ms, [](){ }, "deux")); + assert(te_manager.get_timeout() > 0ms); + assert(te_manager.size() == 3); + assert(te_manager.cancel("un") == 1); + assert(te_manager.size() == 2); + assert(te_manager.cancel("deux") == 2); + assert(te_manager.get_timeout() == utils::no_timeout); + /** * Encoding */ diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp index 10ef4c1..5010a3f 100644 --- a/src/utils/timed_events.cpp +++ b/src/utils/timed_events.cpp @@ -1,20 +1,22 @@ #include TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback): + std::function callback, const std::string& name): time_point(std::move(time_point)), callback(callback), repeat(false), - repeat_delay(0) + repeat_delay(0), + name(name) { } TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, - std::function callback): + std::function callback, const std::string& name): time_point(std::chrono::steady_clock::now() + duration), callback(callback), repeat(true), - repeat_delay(std::move(duration)) + repeat_delay(std::move(duration)), + name(name) { } @@ -22,7 +24,8 @@ TimedEvent::TimedEvent(TimedEvent&& other): time_point(std::move(other.time_point)), callback(std::move(other.callback)), repeat(other.repeat), - repeat_delay(std::move(other.repeat_delay)) + repeat_delay(std::move(other.repeat_delay)), + name(std::move(other.name)) { } @@ -52,3 +55,8 @@ void TimedEvent::execute() { this->callback(); } + +const std::string& TimedEvent::get_name() const +{ + return this->name; +} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp index 9be747d..f601cae 100644 --- a/src/utils/timed_events.hpp +++ b/src/utils/timed_events.hpp @@ -25,9 +25,9 @@ public: * An event the occurs only once, at the given time_point */ explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback); + std::function callback, const std::string& name=""); explicit TimedEvent(std::chrono::milliseconds&& duration, - std::function callback); + std::function callback, const std::string& name=""); explicit TimedEvent(TimedEvent&&); ~TimedEvent(); @@ -43,6 +43,7 @@ public: */ std::chrono::milliseconds get_timeout() const; void execute(); + const std::string& get_name() const; private: /** @@ -62,6 +63,12 @@ private: * if repeat is true. Otherwise it is ignored. */ const std::chrono::milliseconds repeat_delay; + /** + * A name that is used to identify that event. If you want to find your + * event (for example if you want to cancel it), the name should be + * unique. + */ + const std::string name; TimedEvent(const TimedEvent&) = delete; TimedEvent& operator=(const TimedEvent&) = delete; @@ -99,6 +106,15 @@ public: * Returns the number of executed events. */ std::size_t execute_expired_events(); + /** + * Remove (and thus cancel) all the timed events with the given name. + * Returns the number of canceled events. + */ + std::size_t cancel(const std::string& name); + /** + * Return the number of managed events. + */ + std::size_t size() const; private: std::list events; diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp index c3e260c..a03444e 100644 --- a/src/utils/timed_events_manager.cpp +++ b/src/utils/timed_events_manager.cpp @@ -53,3 +53,23 @@ std::size_t TimedEventsManager::execute_expired_events() return count; } +std::size_t TimedEventsManager::cancel(const std::string& name) +{ + std::size_t res = 0; + for (auto it = this->events.begin(); it != this->events.end();) + { + if (it->get_name() == name) + { + it = this->events.erase(it); + res++; + } + else + ++it; + } + return res; +} + +std::size_t TimedEventsManager::size() const +{ + return this->events.size(); +} -- cgit v1.2.3 From 5c9d2c23ba6a401bc9494a6023491bbf3ade8d34 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 15:51:43 +0200 Subject: TimedEventsManager is now a singleton --- src/main.cpp | 15 ++++----------- src/test.cpp | 35 +++++++++++++++++------------------ src/utils/timed_events.hpp | 6 +++++- src/utils/timed_events_manager.cpp | 6 ++++++ 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b5abad9..2e2f1b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,13 +18,6 @@ static volatile std::atomic reload(false); // flag is set and all connections are closed, we can exit properly. static bool exiting = false; -// The global (and only) TimedEventsManager. It can be accessed by any -// class to add new TimedEvents, and it is used in the main loop to provide -// a timeout to the poller, this way the method execute_expired_events can -// be called on time, without having to do an active “poll” on it every n -// seconds. -TimedEventsManager timed_events; - /** * Provide an helpful message to help the user write a minimal working * configuration file. @@ -43,7 +36,7 @@ int config_help(const std::string& missing_option) static void sigint_handler(int sig, siginfo_t*, void*) { // In 2 seconds, repeat the same signal, to force the exit - timed_events.add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, [sig]() { raise(sig); })); stop.store(true); } @@ -101,10 +94,10 @@ int main(int ac, char** av) xmpp_component->start(); - auto timeout = timed_events.get_timeout(); + auto timeout = TimedEventsManager::instance().get_timeout(); while (p->poll(timeout) != -1) { - timed_events.execute_expired_events(); + TimedEventsManager::instance().execute_expired_events(); // Check for empty irc_clients (not connected, or with no joined // channel) and remove them xmpp_component->clean(); @@ -143,7 +136,7 @@ int main(int ac, char** av) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); - timeout = timed_events.get_timeout(); + timeout = TimedEventsManager::instance().get_timeout(); } log_info("All connection cleanely closed, have a nice day."); return 0; diff --git a/src/test.cpp b/src/test.cpp index 77e4ada..9c77376 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -65,34 +65,33 @@ int main() * Timed events */ std::cout << color << "Testing timed events…" << reset << std::endl; - TimedEventsManager te_manager; // No event. - assert(te_manager.get_timeout() == utils::no_timeout); - assert(te_manager.execute_expired_events() == 0); + assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); + assert(TimedEventsManager::instance().execute_expired_events() == 0); // Add a single event - te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){ std::cout << "Timeout expired" << std::endl; })); + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){ std::cout << "Timeout expired" << std::endl; })); // The event should not yet be expired - assert(te_manager.get_timeout() > 0ms); - assert(te_manager.execute_expired_events() == 0); - std::chrono::milliseconds timoute = te_manager.get_timeout(); + assert(TimedEventsManager::instance().get_timeout() > 0ms); + assert(TimedEventsManager::instance().execute_expired_events() == 0); + std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); std::cout << "Sleeping for " << timoute.count() << "ms" << std::endl; std::this_thread::sleep_for(timoute); // Event is now expired - assert(te_manager.execute_expired_events() == 1); - assert(te_manager.get_timeout() == utils::no_timeout); + assert(TimedEventsManager::instance().execute_expired_events() == 1); + assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); // Test canceling events - te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 100ms, [](){ }, "un")); - te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 200ms, [](){ }, "deux")); - te_manager.add_event(TimedEvent(std::chrono::steady_clock::now() + 300ms, [](){ }, "deux")); - assert(te_manager.get_timeout() > 0ms); - assert(te_manager.size() == 3); - assert(te_manager.cancel("un") == 1); - assert(te_manager.size() == 2); - assert(te_manager.cancel("deux") == 2); - assert(te_manager.get_timeout() == utils::no_timeout); + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 100ms, [](){ }, "un")); + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 200ms, [](){ }, "deux")); + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 300ms, [](){ }, "deux")); + assert(TimedEventsManager::instance().get_timeout() > 0ms); + assert(TimedEventsManager::instance().size() == 3); + assert(TimedEventsManager::instance().cancel("un") == 1); + assert(TimedEventsManager::instance().size() == 2); + assert(TimedEventsManager::instance().cancel("deux") == 2); + assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); /** * Encoding diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp index f601cae..aafe532 100644 --- a/src/utils/timed_events.hpp +++ b/src/utils/timed_events.hpp @@ -83,8 +83,11 @@ private: class TimedEventsManager { public: - explicit TimedEventsManager(); ~TimedEventsManager(); + /** + * Return the unique instance of this class + */ + static TimedEventsManager& instance(); /** * Add an event to the list of managed events. The list is sorted after * this call. @@ -117,6 +120,7 @@ public: std::size_t size() const; private: + explicit TimedEventsManager(); std::list events; TimedEventsManager(const TimedEventsManager&) = delete; TimedEventsManager(TimedEventsManager&&) = delete; diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp index a03444e..2c75e48 100644 --- a/src/utils/timed_events_manager.cpp +++ b/src/utils/timed_events_manager.cpp @@ -1,5 +1,11 @@ #include +TimedEventsManager& TimedEventsManager::instance() +{ + static TimedEventsManager inst; + return inst; +} + TimedEventsManager::TimedEventsManager() { } -- cgit v1.2.3 From c2311b2893f3db755b67c43e5ad60cef66b10ab2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 16:08:23 +0200 Subject: Send (every 240s) a PING command to all connected irc servers fix #2452 --- src/bridge/bridge.cpp | 5 +++++ src/bridge/bridge.hpp | 4 ++++ src/irc/irc_client.cpp | 15 +++++++++++++++ src/irc/irc_client.hpp | 1 + 4 files changed, 25 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 70650a5..3377135 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -63,6 +63,11 @@ void Bridge::clean() } } +const std::string& Bridge::get_jid() const +{ + return this->user_jid; +} + Xmpp::body Bridge::make_xmpp_body(const std::string& str) { std::string res; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 2618ca6..9c06947 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -32,6 +32,10 @@ public: * Remove all inactive IrcClients */ void clean(); + /** + * Return the jid of the XMPP user using this bridge + */ + const std::string& get_jid() const; static Xmpp::body make_xmpp_body(const std::string& str); /*** diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f08e9d6..01bf569 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -10,8 +11,11 @@ #include #include +#include #include + using namespace std::string_literals; +using namespace std::chrono_literals; IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge): SocketHandler(poller), @@ -35,6 +39,9 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname IrcClient::~IrcClient() { + // This event may or may not exist (if we never got connected, it + // doesn't), but it's ok + TimedEventsManager::instance().cancel("PING"s + this->hostname + this->bridge->get_jid()); } void IrcClient::start() @@ -238,6 +245,11 @@ void IrcClient::send_pong_command(const IrcMessage& message) this->send_message(IrcMessage("PONG", {id})); } +void IrcClient::send_ping_command() +{ + this->send_message(IrcMessage("PING", {"biboumi"})); +} + void IrcClient::forward_server_message(const IrcMessage& message) { const std::string from = message.prefix; @@ -447,6 +459,9 @@ void IrcClient::on_welcome_message(const IrcMessage& message) { this->current_nick = message.arguments[0]; this->welcomed = true; + // Install a repeated events to regularly send a PING + TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), + "PING"s + this->hostname + this->bridge->get_jid())); for (const std::string& chan_name: this->channels_to_join) this->send_join_command(chan_name); this->channels_to_join.clear(); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index e6fb393..2f28c95 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -70,6 +70,7 @@ public: * Send the PONG irc command */ void send_pong_command(const IrcMessage& message); + void send_ping_command(); /** * Send the USER irc command */ -- cgit v1.2.3 From f5b61f0feba271770474c4d540d7bf48a6c2b180 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 May 2014 16:16:18 +0200 Subject: Add the libuuid dependency to the INSTALL file --- INSTALL | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/INSTALL b/INSTALL index 7974d99..b83faf4 100644 --- a/INSTALL +++ b/INSTALL @@ -15,6 +15,10 @@ Libraries: Encoding from anything into UTF-8 http://www.gnu.org/software/libiconv/ +- libuuid + Generate unique IDs + http://sourceforge.net/projects/libuuid/ + - libidn (optional, but recommended) Provides the stringprep functionality. Without it, JIDs for IRC users are not provided. -- cgit v1.2.3 From 4e27298b3a6389781893589b37f66260d6a34707 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 May 2014 17:06:36 +0200 Subject: Add an ad-hoc command to disconnect some users --- src/xmpp/adhoc_command.cpp | 112 +++++++++++++++++++++++++++++++++++- src/xmpp/adhoc_command.hpp | 10 +++- src/xmpp/adhoc_commands_handler.cpp | 23 +++++++- src/xmpp/adhoc_commands_handler.hpp | 9 ++- src/xmpp/adhoc_session.hpp | 4 +- src/xmpp/xmpp_component.cpp | 23 +++++++- src/xmpp/xmpp_component.hpp | 12 +++- 7 files changed, 180 insertions(+), 13 deletions(-) diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index b880533..a0d99a0 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -1,4 +1,7 @@ #include +#include + +#include using namespace std::string_literals; @@ -13,7 +16,12 @@ AdhocCommand::~AdhocCommand() { } -void PingStep1(AdhocSession&, XmlNode& command_node) +bool AdhocCommand::is_admin_only() const +{ + return this->admin_only; +} + +void PingStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { XmlNode note("note"); note["type"] = "info"; @@ -22,7 +30,7 @@ void PingStep1(AdhocSession&, XmlNode& command_node) command_node.add_child(std::move(note)); } -void HelloStep1(AdhocSession&, XmlNode& command_node) +void HelloStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -47,7 +55,7 @@ void HelloStep1(AdhocSession&, XmlNode& command_node) command_node.add_child(std::move(x)); } -void HelloStep2(AdhocSession& session, XmlNode& command_node) +void HelloStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) { // Find out if the name was provided in the form. XmlNode* x = command_node.get_child("x", "jabber:x:data"); @@ -80,3 +88,101 @@ void HelloStep2(AdhocSession& session, XmlNode& command_node) // anyway. But this is for the example. session.terminate(); } + +void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from the gateway"); + title.close(); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose a user JID and a quit message"); + instructions.close(); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "jids"; + jids_field["type"] = "list-multi"; + jids_field["label"] = "The JIDs to disconnect"; + XmlNode required("required"); + required.close(); + jids_field.add_child(std::move(required)); + for (Bridge* bridge: xmpp_component->get_bridges()) + { + XmlNode option("option"); + option["label"] = bridge->get_jid(); + XmlNode value("value"); + value.set_inner(bridge->get_jid()); + value.close(); + option.add_child(std::move(value)); + option.close(); + jids_field.add_child(std::move(option)); + } + jids_field.close(); + x.add_child(std::move(jids_field)); + + XmlNode message_field("field"); + message_field["var"] = "quit-message"; + message_field["type"] = "text-single"; + message_field["label"] = "Quit message"; + XmlNode message_value("value"); + message_value.set_inner("Disconnected by admin"); + message_value.close(); + message_field.add_child(std::move(message_value)); + message_field.close(); + x.add_child(std::move(message_field)); + x.close(); + command_node.add_child(std::move(x)); +} + +void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + // Find out if the jids, and the quit message are provided in the form. + std::string quit_message; + XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + XmlNode* message_field = nullptr; + XmlNode* jids_field = nullptr; + for (XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "jids") + jids_field = field; + else if (field->get_tag("var") == "quit-message") + message_field = field; + if (message_field) + { + XmlNode* value = message_field->get_child("value", "jabber:x:data"); + if (value) + quit_message = value->get_inner(); + } + if (jids_field) + { + std::size_t num = 0; + for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) + { + Bridge* bridge = xmpp_component->find_user_bridge(value->get_inner()); + if (bridge) + { + bridge->shutdown(quit_message); + num++; + } + } + command_node.delete_all_children(); + + XmlNode note("note"); + note["type"] = "info"; + if (num == 0) + note.set_inner("No user were disconnected."); + else if (num == 1) + note.set_inner("1 user has been disconnected."); + else + note.set_inner(std::to_string(num) + " users have been disconnected."); + note.close(); + command_node.add_child(std::move(note)); + } + } + // TODO insert an error telling the values are missing. + session.terminate(); +} + diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp index 59a3cd6..60f7d6c 100644 --- a/src/xmpp/adhoc_command.hpp +++ b/src/xmpp/adhoc_command.hpp @@ -23,6 +23,8 @@ public: const std::string name; + bool is_admin_only() const; + private: /** * A command may have one or more steps. Each step is a different @@ -33,8 +35,10 @@ private: const bool admin_only; }; -void PingStep1(AdhocSession& session, XmlNode& command_node); -void HelloStep1(AdhocSession& session, XmlNode& command_node); -void HelloStep2(AdhocSession& session, XmlNode& command_node); +void PingStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void HelloStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); #endif // ADHOC_COMMAND_HPP diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index a669ca1..8657944 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -2,13 +2,17 @@ #include #include +#include +#include #include -AdhocCommandsHandler::AdhocCommandsHandler(): +AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component): + xmpp_component(xmpp_component), commands{ {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, - {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)} + {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, + {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)} } { } @@ -31,6 +35,8 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, action = "execute"; command_node.del_tag("action"); + Jid jid(executor_jid); + const std::string node = command_node.get_tag("node"); auto command_it = this->commands.find(node); if (command_it == this->commands.end()) @@ -43,6 +49,17 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, error.close(); command_node.add_child(std::move(error)); } + else if (command_it->second.is_admin_only() && + Config::get("admin", "") != jid.local + "@" + jid.domain) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "cancel"; + XmlNode condition(STANZA_NS":forbidden"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } else { std::string sessionid = command_node.get_tag("sessionid"); @@ -74,7 +91,7 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, // execute the step AdhocSession& session = session_it->second; const AdhocStep& step = session.get_next_step(); - step(session, command_node); + step(this->xmpp_component, session, command_node); if (session.remaining_steps() == 0 || session.is_terminated()) { diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp index 6e00188..f443325 100644 --- a/src/xmpp/adhoc_commands_handler.hpp +++ b/src/xmpp/adhoc_commands_handler.hpp @@ -13,10 +13,12 @@ #include #include +class XmppComponent; + class AdhocCommandsHandler { public: - explicit AdhocCommandsHandler(); + explicit AdhocCommandsHandler(XmppComponent* xmpp_component); ~AdhocCommandsHandler(); /** * Returns the list of available commands. @@ -36,6 +38,11 @@ public: */ XmlNode&& handle_request(const std::string& executor_jid, XmlNode command_node); private: + /** + * A pointer to the XmppComponent, to access to basically anything in the + * gateway. + */ + XmppComponent* xmpp_component; /** * The list of all available commands. */ diff --git a/src/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp index 111b43b..ddfb2fe 100644 --- a/src/xmpp/adhoc_session.hpp +++ b/src/xmpp/adhoc_session.hpp @@ -6,6 +6,8 @@ #include #include +class XmppComponent; + class AdhocCommand; class AdhocSession; @@ -16,7 +18,7 @@ class AdhocSession; * TODO fix this: * It also must call one of step_passed(), cancel() etc on the AdhocSession object. */ -typedef std::function AdhocStep; +typedef std::function AdhocStep; class AdhocSession { diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 9547e3a..81a3b4f 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -41,7 +41,8 @@ XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& served_hostname(hostname), secret(secret), authenticated(false), - doc_open(false) + doc_open(false), + adhoc_commands_handler(this) { this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, std::placeholders::_1)); @@ -543,6 +544,26 @@ Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) } } +Bridge* XmppComponent::find_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + return nullptr; + } +} + +std::list XmppComponent::get_bridges() const +{ + std::list res; + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + res.push_back(it->second.get()); + return res; +} + void* XmppComponent::get_receive_buffer(const size_t size) const { return this->parser.get_buffer(size); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index e3e24e0..5de471c 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -41,6 +41,16 @@ public: void on_connection_close() override final; void parse_in_buffer(const size_t size) override final; + /** + * Returns the bridge for the given user. If it does not exist, return + * nullptr. + */ + Bridge* find_user_bridge(const std::string& user_jid); + /** + * Return a list of all the managed bridges. + */ + std::list get_bridges() const; + /** * Returns a unique id, to be used in the 'id' element of our iq stanzas. */ @@ -228,6 +238,7 @@ private: bool doc_open; std::unordered_map> stanza_handlers; + AdhocCommandsHandler adhoc_commands_handler; /** * One bridge for each user of the component. Indexed by the user's full @@ -235,7 +246,6 @@ private: */ std::unordered_map> bridges; - AdhocCommandsHandler adhoc_commands_handler; XmppComponent(const XmppComponent&) = delete; XmppComponent(XmppComponent&&) = delete; XmppComponent& operator=(const XmppComponent&) = delete; -- cgit v1.2.3 From a593d806dde970519b997c619e9e690e0aa1bcfc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 May 2014 17:08:49 +0200 Subject: Document the `admin` configuration option --- doc/biboumi.1.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index c985de7..5197d8f 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -46,6 +46,12 @@ The configuration file uses a simple format of the form This password must be configured in the XMPP server, associated with the external component on *hostname*. +`admin` + + The bare JID of the gateway admin. This JID will have more privileges than + other standard users, for example some administration ad-hoc commands will + only be available to that JID. + `log_file` A filename into which logs are written. If none is provided, the logs are -- cgit v1.2.3 From 4a9beff74d5ede4acb82c8f7deb3555a4f7bd724 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 May 2014 17:10:22 +0200 Subject: Remove some unused function parameters names --- src/xmpp/adhoc_command.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index a0d99a0..419c3ae 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -21,7 +21,7 @@ bool AdhocCommand::is_admin_only() const return this->admin_only; } -void PingStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) +void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) { XmlNode note("note"); note["type"] = "info"; @@ -30,7 +30,7 @@ void PingStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_no command_node.add_child(std::move(note)); } -void HelloStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) +void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) { XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -55,7 +55,7 @@ void HelloStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_n command_node.add_child(std::move(x)); } -void HelloStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) { // Find out if the name was provided in the form. XmlNode* x = command_node.get_child("x", "jabber:x:data"); @@ -89,7 +89,7 @@ void HelloStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& c session.terminate(); } -void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { XmlNode x("jabber:x:data:x"); x["type"] = "form"; -- cgit v1.2.3 From c530fb9156e3c8248e4a47316f5ac72e221d5811 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 May 2014 17:24:18 +0200 Subject: Do not send an item-not-found presence from an invalid JID --- src/xmpp/xmpp_component.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 81a3b4f..3ec043c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -640,7 +640,10 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, const std::string& to) { Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + if (!muc_name.empty()) + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else + presence["from"] = this->served_hostname; presence["to"] = to; presence["type"] = "error"; XmlNode x("x"); -- cgit v1.2.3 From 1706a92f0c67058591ac8ebb378cf46a6bdbad5a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 3 Jun 2014 02:11:29 +0200 Subject: Update the documentation --- doc/biboumi.1.md | 117 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 5197d8f..241e5c2 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -1,4 +1,4 @@ -BIBOUMI 1 "2014-02-17" +BIBOUMI 1 "2014-06-02" ====================== NAME @@ -48,18 +48,19 @@ The configuration file uses a simple format of the form `admin` - The bare JID of the gateway admin. This JID will have more privileges than - other standard users, for example some administration ad-hoc commands will - only be available to that JID. + The bare JID of the gateway administrator. This JID will have more + privileges than other standard users (the admin thus needs to check their + privileges), for example some administration ad-hoc commands will only be + available to that JID. `log_file` A filename into which logs are written. If none is provided, the logs are - written on standard output + written on standard output. `log_level` - Indicate what type of log messages to write in the logs. Values can be + Indicate what type of log messages to write in the logs. Value can be from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The default is 0, but a more practical value for production use is 1. @@ -71,12 +72,13 @@ USAGE ----- When started, biboumi connects, without encryption (see *SECURITY*), to the -local XMPP server on the port `5347` and provides the configured password to -authenticate. Biboumi then serves the configured `hostname`, this means -that all XMPP stanza with a `to` JID on that domain will be sent to biboumi, -and biboumi will send only send messages coming from this hostname. +local XMPP server on the port `5347` and authenticates with the provided +password. Biboumi then serves the configured `hostname`: this means that +all XMPP stanza with a `to` JID on that domain will be forwarded to biboumi +by the XMPP server, and biboumi will only send messages coming from that +hostname. -When an user joins an IRC channel on an IRC server (see *Join an IRC +When a user joins an IRC channel on an IRC server (see *Join an IRC channel*), biboumi connects to the remote IRC server, sets the user’s nick as requested, and then tries to join the specified channel. If the same user subsequently tries to connect to an other channel on the same server, @@ -84,13 +86,12 @@ the same IRC connection is used. If, however, an other user wants to join an IRC channel on that same IRC server, biboumi opens a new connection to that server. Biboumi connects once to each IRC server, for each user on it. -To cleanly shutdown the component, send the SIGINT or SIGTERM signals to it. +To cleanly shutdown the component, send a SIGINT or SIGTERM signal to it. It will send messages to all connected IRC and XMPP servers to indicate a -reason why the users are being disconnected. Biboumi exits when all -connections are closed because the remote acknowledged the end of -communication. If the remote server does not respond, biboumi does not -exit, unless SIGINT or SIGTERM is received again, in which case biboumi -closes the TCP connections and exits immediately. +reason why the users are being disconnected. Biboumi exits when the end of +communication is acknowledged by all IRC servers. If one or more IRC +servers do not respond, biboumi will only exit if it receives the same +signal again or if a 2 seconds delay has passed. ### Addressing @@ -109,8 +110,22 @@ characters, adding an implicit `'#'` in that case. Biboumi does not do that because this gets confusing when trying to understand the difference between the channels *foo*, *#foo*, and *##foo*. +On XMPP, the node part of the JID can only be lowercase. On the other hand, +IRC nicknames are case-insensitive, this means that the nicknames toto, +Toto, tOtO and TOTO all represent the same IRC user. This means you can +talk to the user toto, and this will work. + +Examples: + + `#foo%irc.example.com@biboumi.example.com` is the #foo IRC channel, on the + irc.example.com IRC server, and this is served by the biboumi instance on + biboumi.example.com + + `toto.example.com@biboumi.example.com` is the IRC user named toto, or TotO, etc. + If compiled with Libidn, an IRC user has a bare JID representing the -“hostname” provided by the IRC server. +“hostname” provided by the IRC server. This JID can only be used to set IRC +modes (for example to ban a user based on its IP), or to identify user. ### Join an IRC channel @@ -124,13 +139,13 @@ to join any channel on that IRC server. The connection is closed whenever the last channel on that server is left by the user. To be able to stay connected to an IRC server without having to be in a real IRC channel, biboumi provides a virtual channel on the jid -`%irc_serve@biboumi.example.com`. For example if you want to join the +`%irc.example.com@biboumi.example.com`. For example if you want to join the channel `#foo` on the server `irc.example.com`, but you need to authenticate -to a bot of the server before you can join it, you can first join the room -`%irc.example.com@biboumi.example.net` (this will effectively connect you to -the IRC server without joining any room), then send your authentication -message to the user `bot%irc.example.com@biboumi.example.net" and finally -join the room `#foo%irc.example.com@biboumi.example.net`. +to a bot of the server before you’re allowed to join it, you can first join +the room `%irc.example.com@biboumi.example.com` (this will effectively +connect you to the IRC server without joining any room), then send your +authentication message to the user `bot%irc.example.com@biboumi.example.com" +and finally join the room `#foo%irc.example.com@biboumi.example.com`. ### Channel messages @@ -138,14 +153,14 @@ On XMPP, unlike on IRC, the displayed order of the messages is the same for all participants of a MUC. Biboumi can not however provide this feature, as it cannot know whether the IRC server has received and forwarded the messages to other users. This means that the order of the messages -displayed in your XMPP may not be the same than the order on other IRC -users’. +displayed in your XMPP client may not be the same than the order on other +IRC users’. ### Nicknames On IRC, nicknames are server-wide. This means that one user only has one single nickname at one given time on all the channels of a server. This is -different from XMPP where an user can have a different nick on each MUC, +different from XMPP where a user can have a different nick on each MUC, even if these MUCs are on the same server. This means that the nick you choose when joining your first IRC channel on a @@ -160,13 +175,10 @@ Private messages are handled differently on IRC and on XMPP. On IRC, you talk directly to one server-user: toto on the channel #foo is the same user as toto on the channel #bar (as long as these two channels are on the same IRC server). Using biboumi, there is no way to receive a message from a -room participant (from a jid like #test%irc.example.com/nickname). Instead, -private messages are received from and sent to the user (using a jid like -nickname%irc.example.com). For conveniance and compatibility with XMPP -clients sending private messages to the MUC participants, a message sent to -#chan%irc.example.com@irc.example.net/Nickname will be redirected to -Nickname%irc.example.com@irc.example.net, although this is not the prefered -way to do it. +room participant (from a jid like +#test%irc.example.com@biboumi.example.com/nickname). Instead, private +messages are received from and sent to the user (using a jid like +nickname%irc.example.com@biboumi.example.com). ### Notices @@ -199,8 +211,8 @@ direction. One feature that doesn’t exist on XMPP but does on IRC is the `modes`. Although some of these modes have a correspondance in the XMPP world (for -example the `+o` mode on an user corresponds to the `moderator` role -in XMPP), it is impossible to map all these modes to an XMPP feature. To +example the `+o` mode on a user corresponds to the `moderator` role in +XMPP), it is impossible to map all these modes to an XMPP feature. To circumvent this problem, biboumi provides a raw notification when modes are changed, and lets the user change the modes directly. @@ -238,6 +250,23 @@ between IRC modes and XMPP features is as follow: Sets the participant’s role to `participant` and its affiliation to `member`. +### Ad-hoc commands + +Biboumi supports a few ad-hoc commands, as described in the XEP 0050. + + - ping: Just respond “pong” + + - hello: Provide a form, where the user enters their name, and biboumi + responds with a nice greeting. + + - disconnect-user: Available to the administrator only. The user provides + a list of JIDs, and a quit message. All these users are disconnected + from all the IRC servers to which they were connected, using the + provided quit message. Sending SIGINT to biboumi is equivalent to using + this command by selecting all the connected JIDs and using the “Gateway + shutdown” quit message, except that biboumi does not exit when using + this ad-hoc command. + SECURITY -------- @@ -246,9 +275,19 @@ server MUST be made on localhost. The XMPP server is not supposed to accept non-local connection from components, thus encryption is useless. IRC SSL/TLS is also not yet implemented. -Biboumi also does not check if received JIDs are properly formatted using -nodeprep. This must be done by the XMPP server to which biboumi is directly -connected. +Biboumi also does not check if the received JIDs are properly formatted +using nodeprep. This must be done by the XMPP server to which biboumi is +directly connected. + +Remember that the administrator of the gateway you use is able to view all +your IRC conversations, whether you’re using encryption or not. This is +exactly as if you were running your IRC client on someone else’s server. + +Biboumi does not yet provide a way to ban users from connecting to it, has +no protection against flood or any sort of abuse that your users may cause +on the IRC servers. Some XMPP server however offer the possibility to +restrict what JID can access a gateway. Use that feature if you wish to +grant access to your biboumi instance only to a list of users. AUTHORS ------- -- cgit v1.2.3 From be6265544ae4cfcb155cb9cdf7bfabc6c16d1ca4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Jun 2014 02:24:03 +0200 Subject: Make the destructor of the SocketHandler class protected non-virtual --- src/network/socket_handler.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 0b59757..1be3db1 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -22,9 +22,10 @@ class Poller; */ class SocketHandler { +protected: + ~SocketHandler() {} public: explicit SocketHandler(std::shared_ptr poller); - virtual ~SocketHandler() {} /** * Initialize the socket with the parameters contained in the given * addrinfo structure. -- cgit v1.2.3 From fa071309917252cd76d1334eedc6703057d1f29f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 6 Jun 2014 02:15:35 +0200 Subject: Little doc fix --- doc/biboumi.1.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 241e5c2..6b04802 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -102,13 +102,18 @@ entity. IRC channels and IRC users JIDs have a localpart formed like this: `name`, the `'%'` separator and the `irc_server`. -If the IRC channel you want to adress starts with the `'#'` character (or -less frequently, but still valid, one of `'&'`, `'+'` or `'!'`), then you -must include it in the JID. Some other gateway implementations, as well as -some IRC clients, do not require them to be started by one of these +If the IRC channel you want to adress starts with the `'#'` character (or an +other character, announced by the IRC server, like `'&'`, `'+'` or `'!'`), +then you must include it in the JID. Some other gateway implementations, as +well as some IRC clients, do not require them to be started by one of these characters, adding an implicit `'#'` in that case. Biboumi does not do that because this gets confusing when trying to understand the difference between -the channels *foo*, *#foo*, and *##foo*. +the channels *#foo*, and *##foo*. + +The name part can also be empty (for example `%irc.example.com`), in that +case this represents the virtual channel provided by biboumi. See *Connect +to an IRC server* for more details. + On XMPP, the node part of the JID can only be lowercase. On the other hand, IRC nicknames are case-insensitive, this means that the nicknames toto, @@ -121,7 +126,13 @@ Examples: irc.example.com IRC server, and this is served by the biboumi instance on biboumi.example.com - `toto.example.com@biboumi.example.com` is the IRC user named toto, or TotO, etc. + `toto%irc.example.com@biboumi.example.com` is the IRC user named toto, or + TotO, etc. + + `irc.example.com@biboumi.example.com` is the IRC server irc.example.com. + + `%irc.example.com@biboumi.example.com` is the virtual channel provided by + biboumi, for the IRC server irc.example.com. If compiled with Libidn, an IRC user has a bare JID representing the “hostname” provided by the IRC server. This JID can only be used to set IRC -- cgit v1.2.3 From 23f32ba39ebe5e9bbdfc4dd00d9914c0f0447ef4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 18 May 2014 20:23:08 +0200 Subject: Implement TLS support using Botan For now, it tries two TLS ports and then connects to the non-tls port. In the future we would like the user to be able to configure that. fix #2435 --- CMakeLists.txt | 8 ++ cmake/Modules/FindBotan.cmake | 34 ++++++++ src/config.h.cmake | 1 + src/irc/irc_client.cpp | 36 ++++++-- src/irc/irc_client.hpp | 9 +- src/network/socket_handler.cpp | 172 +++++++++++++++++++++++++++++++++---- src/network/socket_handler.hpp | 186 ++++++++++++++++++++++++++++++++++------- src/xmpp/xmpp_component.cpp | 2 +- 8 files changed, 388 insertions(+), 60 deletions(-) create mode 100644 cmake/Modules/FindBotan.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b5432..a71df57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ find_package(Iconv REQUIRED) find_package(Libuuid REQUIRED) find_package(Libidn) find_package(SystemdDaemon) +find_package(Botan) # To be able to include the config.h file generated by cmake include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") @@ -37,6 +38,10 @@ if(SYSTEMDDAEMON_FOUND) include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS}) endif() +if(BOTAN_FOUND) + include_directories(${BOTAN_INCLUDE_DIRS}) +endif() + set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) @@ -95,6 +100,9 @@ file(GLOB source_network src/network/*.[hc]pp) add_library(network STATIC ${source_network}) target_link_libraries(network logger) +if(BOTAN_FOUND) + target_link_libraries(network ${BOTAN_LIBRARIES}) +endif() # ## irclib diff --git a/cmake/Modules/FindBotan.cmake b/cmake/Modules/FindBotan.cmake new file mode 100644 index 0000000..f53680d --- /dev/null +++ b/cmake/Modules/FindBotan.cmake @@ -0,0 +1,34 @@ +# - Find botan +# Find the botan cryptographic library +# +# This module defines the following variables: +# BOTAN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# BOTAN_INCLUDE_DIRS - The directory where to find the header file +# BOTAN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# BOTAN_LIBRARY +# BOTAN_INCLUDE_DIR +# +# This file is in the public domain + +find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h + DOC "The botan include directory") + +find_library(BOTAN_LIBRARIES NAMES botan + DOC "The botan library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set BOTAN_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Botan REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) + +if(BOTAN_FOUND) + set(BOTAN_LIBRARY ${BOTAN_LIBRARIES}) + set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS}) +endif() + +mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) diff --git a/src/config.h.cmake b/src/config.h.cmake index 62186cc..66ea967 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -2,3 +2,4 @@ #cmakedefine LIBIDN_FOUND #cmakedefine SYSTEMDDAEMON_FOUND #cmakedefine POLLER ${POLLER} +#cmakedefine BOTAN_FOUND \ No newline at end of file diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 01bf569..c3765a6 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -14,6 +14,8 @@ #include #include +#include "config.h" + using namespace std::string_literals; using namespace std::chrono_literals; @@ -35,6 +37,13 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname "alive without having to join a real channel of that server. " "To disconnect from the IRC server, leave this room and all " "other IRC channels of that server."; + // TODO: get the values from the preferences of the user, and only use the + // list of default ports if the user didn't specify anything + this->ports_to_try.emplace("6667", false); // standard non-encrypted port +#ifdef BOTAN_FOUND + this->ports_to_try.emplace("6670", true); // non-standard but I want it for some servers + this->ports_to_try.emplace("6697", true); // standard encrypted port +#endif // BOTAN_FOUND } IrcClient::~IrcClient() @@ -48,29 +57,39 @@ void IrcClient::start() { if (this->connected || this->connecting) return ; + std::string port; + bool tls; + std::tie(port, tls) = this->ports_to_try.top(); + this->ports_to_try.pop(); this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s + - this->hostname + ":" + "6667"); - this->connect(this->hostname, "6667"); + this->hostname + ":" + port + " (" + + (tls ? "encrypted" : "not encrypted") + ")"); + this->connect(this->hostname, port, tls); } void IrcClient::on_connection_failed(const std::string& reason) { this->bridge->send_xmpp_message(this->hostname, "", "Connection failed: "s + reason); - // Send an error message for all room that the user wanted to join - for (const std::string& channel: this->channels_to_join) + if (this->ports_to_try.empty()) { - Iid iid(channel + "%" + this->hostname); - this->bridge->send_join_failed(iid, this->current_nick, - "cancel", "item-not-found", reason); + // Send an error message for all room that the user wanted to join + for (const std::string& channel: this->channels_to_join) + { + Iid iid(channel + "%" + this->hostname); + this->bridge->send_join_failed(iid, this->current_nick, + "cancel", "item-not-found", reason); + } } + else // try the next port + this->start(); } void IrcClient::on_connected() { this->send_nick_command(this->username); this->send_user_command(this->username, this->username); - this->send_gateway_message("Connected to IRC server."); + this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); this->send_pending_data(); } @@ -326,7 +345,6 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); if (user->nick != channel->get_self()->nick) { - log_debug("Adding user [" << nick << "] to chan " << chan_name); this->bridge->send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 2f28c95..7dff1db 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -19,8 +20,6 @@ class Bridge; /** * Represent one IRC client, i.e. an endpoint connected to a single IRC * server, through a TCP socket, receiving and sending commands to it. - * - * TODO: TLS support, maybe, but that's not high priority */ class IrcClient: public SocketHandler { @@ -280,6 +279,12 @@ private: * (for example 'ahov' is a common order). */ std::vector sorted_user_modes; + /** + * A list of ports to which we will try to connect, in reverse. Each port + * is associated with a boolean telling if we should use TLS or not if the + * connection succeeds on that port. + */ + std::stack> ports_to_try; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index d509513..d989623 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -10,26 +10,39 @@ #include #include #include +#include #include #include -#include #include #include -using namespace std::string_literals; +#ifdef BOTAN_FOUND +# include +#endif #ifndef UIO_FASTIOV # define UIO_FASTIOV 8 #endif +using namespace std::string_literals; + +namespace ph = std::placeholders; + SocketHandler::SocketHandler(std::shared_ptr poller): socket(-1), poller(poller), + use_tls(false), connected(false), connecting(false) -{ -} +#ifdef BOTAN_FOUND + , + rng(), + credential_manager(), + policy(), + session_manager(rng) +#endif +{} void SocketHandler::init_socket(const struct addrinfo* rp) { @@ -47,10 +60,11 @@ void SocketHandler::init_socket(const struct addrinfo* rp) throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); } -void SocketHandler::connect(const std::string& address, const std::string& port) +void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls) { this->address = address; this->port = port; + this->use_tls = tls; utils::ScopeGuard sg; @@ -106,6 +120,10 @@ void SocketHandler::connect(const std::string& address, const std::string& port) this->poller->add_socket_handler(this); this->connected = true; this->connecting = false; +#ifdef BOTAN_FOUND + if (this->use_tls) + this->start_tls(); +#endif this->on_connected(); return ; } @@ -133,10 +151,20 @@ void SocketHandler::connect(const std::string& address, const std::string& port) void SocketHandler::connect() { - this->connect(this->address, this->port); + this->connect(this->address, this->port, this->use_tls); } void SocketHandler::on_recv() +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_recv(); + else +#endif + this->plain_recv(); +} + +void SocketHandler::plain_recv() { static constexpr size_t buf_size = 4096; char buf[buf_size]; @@ -145,6 +173,23 @@ void SocketHandler::on_recv() if (recv_buf == nullptr) recv_buf = buf; + const ssize_t size = this->do_recv(recv_buf, buf_size); + + if (size > 0) + { + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); + } +} + +ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_size) +{ ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); if (0 == size) { @@ -155,22 +200,17 @@ void SocketHandler::on_recv() { log_warning("Error while reading from socket: " << strerror(errno)); if (this->connecting) - this->on_connection_failed(strerror(errno)); + { + this->close(); + this->on_connection_failed(strerror(errno)); + } else - this->on_connection_close(); - this->close(); - } - else - { - if (buf == recv_buf) { - // data needs to be placed in the in_buf string, because no buffer - // was provided to receive that data directly. The in_buf buffer - // will be handled in parse_in_buffer() - this->in_buf += std::string(buf, size); + this->close(); + this->on_connection_close(); } - this->parse_in_buffer(size); } + return size; } void SocketHandler::on_send() @@ -241,6 +281,16 @@ socket_t SocketHandler::get_socket() const } void SocketHandler::send_data(std::string&& data) +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_send(std::move(data)); + else +#endif + this->raw_send(std::move(data)); +} + +void SocketHandler::raw_send(std::string&& data) { if (data.empty()) return ; @@ -269,3 +319,89 @@ void* SocketHandler::get_receive_buffer(const size_t) const { return nullptr; } + +#ifdef BOTAN_FOUND +void SocketHandler::start_tls() +{ + Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); + this->tls = std::make_unique( + std::bind(&SocketHandler::tls_output_fn, this, ph::_1, ph::_2), + std::bind(&SocketHandler::tls_data_cb, this, ph::_1, ph::_2), + std::bind(&SocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), + std::bind(&SocketHandler::tls_handshake_cb, this, ph::_1), + session_manager, credential_manager, policy, + rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); +} + +void SocketHandler::tls_recv() +{ + static constexpr size_t buf_size = 4096; + char 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(); + this->tls->received_data(reinterpret_cast(recv_buf), + static_cast(size)); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } +} + +void SocketHandler::tls_send(std::string&& data) +{ + if (this->tls->is_active()) + { + const bool was_active = this->tls->is_active(); + if (!this->pre_buf.empty()) + { + this->tls->send(reinterpret_cast(this->pre_buf.data()), + this->pre_buf.size()); + this->pre_buf = ""; + } + if (!data.empty()) + this->tls->send(reinterpret_cast(data.data()), + data.size()); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } + else + this->pre_buf += data; +} + +void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size) +{ + this->in_buf += std::string(reinterpret_cast(data), + size); + if (!this->in_buf.empty()) + this->parse_in_buffer(size); +} + +void SocketHandler::tls_output_fn(const Botan::byte* data, size_t size) +{ + this->raw_send(std::string(reinterpret_cast(data), size)); +} + +void SocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) +{ + log_debug("tls_alert: " << alert.type_string()); +} + +bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) +{ + log_debug("Handshake with " << session.server_info().hostname() << " complete." + << " Version: " << session.version().to_string() + << " using " << session.ciphersuite().to_string()); + if (!session.session_id().empty()) + log_debug("Session ID " << Botan::hex_encode(session.session_id())); + if (!session.session_ticket().empty()) + log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); + return true; +} + +void SocketHandler::on_tls_activated() +{ + this->send_data(""); +} +#endif // BOTAN_FOUND diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 1be3db1..02311c4 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,6 +1,8 @@ #ifndef SOCKET_HANDLER_INCLUDED # define SOCKET_HANDLER_INCLUDED +#include + #include #include #include @@ -10,6 +12,26 @@ #include #include +#include "config.h" + +#ifdef BOTAN_FOUND +# include +# include + +/** + * A very simple credential manager that accepts any certificate. + */ +class Permissive_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) + { // TODO: Offer the admin to disallow connection on untrusted + // certificates + log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + } +}; +#endif // BOTAN_FOUND + typedef int socket_t; class Poller; @@ -24,21 +46,19 @@ class SocketHandler { protected: ~SocketHandler() {} + public: explicit SocketHandler(std::shared_ptr poller); /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * Connect to the remote server, and call on_connected() if this succeeds + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. */ - void connect(const std::string& address, const std::string& port); + void connect(const std::string& address, const std::string& port, const bool tls); void connect(); /** - * Reads data in our in_buf and the call parse_in_buf, for the implementor - * to handle the data received so far. + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() */ void on_recv(); /** @@ -48,6 +68,9 @@ public: /** * Add the given data to out_buf and tell our poller that we want to be * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. */ void send_data(std::string&& data); /** @@ -87,29 +110,94 @@ public: bool is_connected() const; bool is_connecting() const; -protected: +private: /** - * Provide a buffer in which data can be directly received. This can be - * used to avoid copying data into in_buf before using it. If no buffer - * can provided, nullptr is returned (the default implementation does - * that). + * Initialize the socket with the parameters contained in the given + * addrinfo structure. */ - virtual void* get_receive_buffer(const size_t size) const; + void init_socket(const struct addrinfo* rp); /** - * The handled socket. + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. */ - socket_t socket; + ssize_t do_recv(void* recv_buf, const size_t buf_size); /** - * Where data read from the socket is added until we can extract a full - * and meaningful “message” from it. - * - * TODO: something more efficient than a string. + * Reads data from the socket and calls parse_in_buffer with it. */ - std::string in_buf; + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(); + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_data_cb(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_output_fn(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_handshake_cb(const Botan::TLS::Session& session); + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * The handled socket. + */ + socket_t socket; /** * Where data is added, when we want to send something to the client. */ std::list out_buf; + /** + * Keep the details of the addrinfo the triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct addrinfo addrinfo; + struct sockaddr ai_addr; + socklen_t ai_addrlen; + +protected: /** * A pointer to the poller that manages us, because we need to communicate * with it, sometimes (for example to tell it that he now needs to watch @@ -119,6 +207,25 @@ protected: * (actually it is sharing our ownership with a Bridge). */ std::shared_ptr poller; + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; /** * Hostname we are connected/connecting to */ @@ -127,14 +234,6 @@ protected: * Port we are connected/connecting to */ std::string port; - /** - * Keep the details of the addrinfo the triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. - */ - struct addrinfo addrinfo; - struct sockaddr ai_addr; - socklen_t ai_addrlen; bool connected; bool connecting; @@ -144,6 +243,33 @@ private: SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; SocketHandler& operator=(SocketHandler&&) = delete; + +#ifdef BOTAN_FOUND + /** + * Botan stuff to manipulate a TLS session. + */ + Botan::AutoSeeded_RNG rng; + Permissive_Credentials_Manager credential_manager; + Botan::TLS::Policy policy; + Botan::TLS::Session_Manager_In_Memory session_manager; + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message as + * soon and calls the output_fn callback with it as soon as it is + * created. Therefore, we do not want to create it if do not intend to do + * send any TLS-encrypted message. We create the object only when needed + * (for example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::string pre_buf; +#endif // BOTAN_FOUND }; #endif // SOCKET_HANDLER_INCLUDED diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 3ec043c..06db085 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -68,7 +68,7 @@ XmppComponent::~XmppComponent() void XmppComponent::start() { - this->connect("127.0.0.1", "5347"); + this->connect("127.0.0.1", "5347", false); } bool XmppComponent::is_document_open() const -- cgit v1.2.3 From 198b0fa6da7c35edac6ae30180f3d3a4a3db6ce7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 05:14:55 +0200 Subject: Document the new optional libbotan dependency --- INSTALL | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/INSTALL b/INSTALL index b83faf4..6bdba6f 100644 --- a/INSTALL +++ b/INSTALL @@ -24,6 +24,12 @@ Libraries: not provided. http://www.gnu.org/software/libidn/ +- libbotan 1.11 (optional) + Provides TLS support. Without it, IRC connections are all made in + plain-text mode. + Other branches than the 1.11 are not supported. + http://botan.randombit.net/ + - systemd-daemon (optional) Provides the support for a systemd service of Type=notify. This is useful only if you are packaging biboumi in a distribution with Systemd. -- cgit v1.2.3 From 2d5e2fdcf69416bd93982e1432b4d2b744da203e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 05:15:17 +0200 Subject: Give two supported compilers, as examples --- INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index 6bdba6f..645b9be 100644 --- a/INSTALL +++ b/INSTALL @@ -35,7 +35,7 @@ Libraries: if you are packaging biboumi in a distribution with Systemd. Tools: -- A C++14 compiler. +- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) - CMake - ronn (optional) to build the man page -- cgit v1.2.3 From 3d9183557f3ad9b599c812c03a445453e7d4f703 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 8 Jun 2014 05:35:36 +0200 Subject: Convert \n to
in xhtml body fix #2539 --- src/bridge/colors.cpp | 7 +++++++ src/bridge/colors.hpp | 2 ++ src/test.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 6f6d7a9..3aed07c 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -77,6 +77,13 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) if (s[pos_end] == IRC_FORMAT_BOLD_CHAR) styles.strong = !styles.strong; + else if (s[pos_end] == IRC_FORMAT_NEWLINE_CHAR) + { + XmlNode* br_node = new XmlNode("br"); + br_node->close(); + current_node->add_child(br_node); + cleaned += '\n'; + } else if (s[pos_end] == IRC_FORMAT_UNDERLINE_CHAR) styles.underline = !styles.underline; else if (s[pos_end] == IRC_FORMAT_ITALIC_CHAR) diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index 82e6faf..b5e6e7b 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -28,6 +28,7 @@ namespace Xmpp #define IRC_FORMAT_REVERSE2_CHAR '\x16' // wat #define IRC_FORMAT_ITALIC_CHAR '\x1D' // done #define IRC_FORMAT_UNDERLINE_CHAR '\x1F' // done +#define IRC_FORMAT_NEWLINE_CHAR '\n' // done static const char irc_format_char[] = { IRC_FORMAT_BOLD_CHAR, @@ -38,6 +39,7 @@ static const char irc_format_char[] = { IRC_FORMAT_REVERSE2_CHAR, IRC_FORMAT_ITALIC_CHAR, IRC_FORMAT_UNDERLINE_CHAR, + IRC_FORMAT_NEWLINE_CHAR, '\x00' }; diff --git a/src/test.cpp b/src/test.cpp index 9c77376..216608a 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -258,6 +258,10 @@ int main() assert(cleaned_up == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); assert(xhtml->to_string() == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); + assert(cleaned_up == "test\ncoucou"); + assert(xhtml->to_string() == "test
coucou"); + /** * JID parsing */ -- cgit v1.2.3 From 800d7df9aaff31b30f2762f5ae6d0dc0378ecef0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 12:59:50 +0200 Subject: Properly search for libbotan even in a botan-1.11 subdirectory --- cmake/Modules/FindBotan.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/Modules/FindBotan.cmake b/cmake/Modules/FindBotan.cmake index f53680d..aa8f58c 100644 --- a/cmake/Modules/FindBotan.cmake +++ b/cmake/Modules/FindBotan.cmake @@ -16,9 +16,10 @@ # This file is in the public domain find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h + PATH_SUFFIXES botan-1.11 DOC "The botan include directory") -find_library(BOTAN_LIBRARIES NAMES botan +find_library(BOTAN_LIBRARIES NAMES botan botan-1.11 DOC "The botan library") # Use some standard module to handle the QUIETLY and REQUIRED arguments, and -- cgit v1.2.3 From 349dc06e91bd1b944bd2a1770dba7b7a3a67ce61 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 13:03:05 +0200 Subject: Silence warnings coming from Botan headers --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a71df57..2f21103 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ if(SYSTEMDDAEMON_FOUND) endif() if(BOTAN_FOUND) - include_directories(${BOTAN_INCLUDE_DIRS}) + include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) endif() set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") -- cgit v1.2.3 From 8b30e312bbc0728b527941d5b0a6d0f621025ed0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 15:13:08 +0200 Subject: Add a TimedEvent to cancel the connection to a server after 5 seconds --- src/network/socket_handler.cpp | 18 ++++++++++++++++++ src/network/socket_handler.hpp | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index d989623..a2c22e4 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -26,6 +27,7 @@ #endif using namespace std::string_literals; +using namespace std::chrono_literals; namespace ph = std::placeholders; @@ -117,6 +119,8 @@ void SocketHandler::connect(const std::string& address, const std::string& port, || errno == EISCONN) { log_info("Connection success."); + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); this->poller->add_socket_handler(this); this->connected = true; this->connecting = false; @@ -139,6 +143,12 @@ void SocketHandler::connect(const std::string& address, const std::string& port, memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); this->addrinfo.ai_addr = &this->ai_addr; this->addrinfo.ai_next = nullptr; + // If the connection has not succeeded or failed in 5s, we consider + // it to have failed + TimedEventsManager::instance().add_event( + TimedEvent(std::chrono::steady_clock::now() + 5s, + std::bind(&SocketHandler::on_connection_timeout, this), + "connection_timeout"s + std::to_string(this->socket))); return ; } log_info("Connection failed:" << strerror(errno)); @@ -149,6 +159,12 @@ void SocketHandler::connect(const std::string& address, const std::string& port, return ; } +void SocketHandler::on_connection_timeout() +{ + this->close(); + this->on_connection_failed("connection timed out"); +} + void SocketHandler::connect() { this->connect(this->address, this->port, this->use_tls); @@ -273,6 +289,8 @@ void SocketHandler::close() this->in_buf.clear(); this->out_buf.clear(); this->port.clear(); + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); } socket_t SocketHandler::get_socket() const diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 02311c4..95e38bd 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -85,6 +85,11 @@ public: * Close the connection, remove us from the poller */ void close(); + /** + * Called by a TimedEvent, when the connection did not succeed or fail + * after a given time. + */ + void on_connection_timeout(); /** * Called when the connection is successful. */ -- cgit v1.2.3 From 1604320a2398901aa7662ece857e9bd217e8b06a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 8 Jun 2014 23:30:17 +0200 Subject: Make the XMPP component port configurable fix #2541 --- doc/biboumi.1.md | 5 +++++ src/xmpp/xmpp_component.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 6b04802..605fb96 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -46,6 +46,11 @@ The configuration file uses a simple format of the form This password must be configured in the XMPP server, associated with the external component on *hostname*. +`port` + + The TCP port to use to connect to the local XMPP component. The default + value is 5347. + `admin` The bare JID of the gateway administrator. This JID will have more diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 06db085..4c9c8ff 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -2,8 +2,8 @@ #include #include +#include #include - #include #include @@ -68,7 +68,7 @@ XmppComponent::~XmppComponent() void XmppComponent::start() { - this->connect("127.0.0.1", "5347", false); + this->connect("127.0.0.1", Config::get("port", "5347"), false); } bool XmppComponent::is_document_open() const -- cgit v1.2.3 From 3ea029dd98e41b32609571512359277259e81057 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Jun 2014 00:28:26 +0200 Subject: Remove inactive ad-hoc sessions after a given time ref #2521 --- src/xmpp/adhoc_commands_handler.cpp | 22 ++++++++++++++++++---- src/xmpp/adhoc_commands_handler.hpp | 6 ++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index 8657944..d288b40 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -1,12 +1,15 @@ #include #include +#include #include #include #include #include +using namespace std::string_literals; + AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component): xmpp_component(xmpp_component), commands{ @@ -70,10 +73,9 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, this->sessions.emplace(std::piecewise_construct, std::forward_as_tuple(sessionid, executor_jid), std::forward_as_tuple(command_it->second, executor_jid)); - // TODO add a timed event to have an expiration date that deletes - // this session. We could have a nasty client starting commands - // but never finishing the last step, and that would fill the map - // with dummy sessions. + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, + std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), + "adhocsession"s + sessionid + executor_jid)); } auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); if (session_it == this->sessions.end()) @@ -97,6 +99,7 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, { this->sessions.erase(session_it); command_node["status"] = "completed"; + TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); } else { @@ -115,3 +118,14 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, log_debug("Number of existing sessions: " << this->sessions.size()); return std::move(command_node); } + +void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) +{ + auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid)); + if (session_it != this->sessions.end()) + { + this->sessions.erase(session_it); + return ; + } + log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found"); +} diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp index f443325..87d4d3d 100644 --- a/src/xmpp/adhoc_commands_handler.hpp +++ b/src/xmpp/adhoc_commands_handler.hpp @@ -37,6 +37,12 @@ public: * it as our return value. */ XmlNode&& handle_request(const std::string& executor_jid, XmlNode command_node); + /** + * Remove the session from the list. This is done to avoid filling the + * memory with waiting session (for example due to a client that starts + * multi-steps command but never finishes them). + */ + void remove_session(const std::string& session_id, const std::string& initiator_jid); private: /** * A pointer to the XmppComponent, to access to basically anything in the -- cgit v1.2.3 From 5d1a567ea5b6407deff81f1ea02422d7a49441ec Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Jun 2014 00:42:55 +0200 Subject: Handle the 'cancel' ad-hoc action, and return an error for unsupported actions ref #2521 --- src/xmpp/adhoc_commands_handler.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index d288b40..7e1738a 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -31,8 +31,6 @@ const std::map& AdhocCommandsHandler::get XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) { - // TODO check the type of action. Currently it assumes it is always - // 'execute'. std::string action = command_node.get_tag("action"); if (action.empty()) action = "execute"; @@ -88,7 +86,7 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, error.close(); command_node.add_child(std::move(error)); } - else + else if (action == "execute" || action == "next" || action == "complete") { // execute the step AdhocSession& session = session_it->second; @@ -112,10 +110,23 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, command_node.add_child(std::move(actions)); } } + else if (action == "cancel") + { + this->sessions.erase(session_it); + command_node["status"] = "canceled"; + TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + } + else // unsupported action + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } } - // TODO remove that once we make sure so session can stay there for ever, - // by mistake. - log_debug("Number of existing sessions: " << this->sessions.size()); return std::move(command_node); } -- cgit v1.2.3 From 6210a9d50b216f22a8b755c2c9daea66659514f0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Jun 2014 01:00:02 +0200 Subject: Add an element when the provided form is wrong, in two ad-hoc commands --- src/xmpp/adhoc_command.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index 419c3ae..67bc706 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -83,9 +83,14 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) } } } - // TODO insert an error telling the name value is missing. Also it's - // useless to terminate it, since this step is the last of the command - // anyway. But this is for the example. + command_node.delete_all_children(); + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); session.terminate(); } @@ -182,7 +187,13 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X command_node.add_child(std::move(note)); } } - // TODO insert an error telling the values are missing. + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); session.terminate(); } -- cgit v1.2.3 From 7c1a38999c2eebfbd0939c9f8f8f864f10d9bc9a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 11 Jun 2014 02:37:11 +0200 Subject: Rewrite the whole IID usage IRC users and channels are now distinguished by the separator used in the IID (% or !). ref #2468 --- doc/biboumi.1.md | 36 ++++++++++++++++------- src/bridge/bridge.cpp | 72 ++++++++++++++++++++++----------------------- src/irc/iid.cpp | 62 +++++++++++++++++++++++++++++++------- src/irc/iid.hpp | 63 +++++++++++++++++++++++++++++++-------- src/irc/irc_client.cpp | 61 ++++++++++++++++++++++---------------- src/test.cpp | 36 +++++++++++++++++++++++ src/xmpp/xmpp_component.cpp | 40 ++++++++++++++++++++++--- src/xmpp/xmpp_component.hpp | 8 ++++- 8 files changed, 279 insertions(+), 99 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 605fb96..38a4718 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -101,11 +101,12 @@ signal again or if a 2 seconds delay has passed. ### Addressing IRC entities are represented by XMPP JIDs. The domain part of the JID is -the domain served by biboumi, and the local part depends on the concerned -entity. +the domain served by biboumi (the part after the `@`, biboumi.example.com in +the examples), and the local part (the part before the `@`) depends on the +concerned entity. -IRC channels and IRC users JIDs have a localpart formed like this: -`name`, the `'%'` separator and the `irc_server`. +IRC channels have a local part formed like this: +`channel_name`%`irc_server`. If the IRC channel you want to adress starts with the `'#'` character (or an other character, announced by the IRC server, like `'&'`, `'+'` or `'!'`), @@ -113,12 +114,16 @@ then you must include it in the JID. Some other gateway implementations, as well as some IRC clients, do not require them to be started by one of these characters, adding an implicit `'#'` in that case. Biboumi does not do that because this gets confusing when trying to understand the difference between -the channels *#foo*, and *##foo*. +the channels *#foo*, and *##foo*. Note that biboumi does not use the +presence of these special characters to identify an IRC channel, only the +presence of the separator `%` is used for that. -The name part can also be empty (for example `%irc.example.com`), in that +The channel name can also be empty (for example `%irc.example.com`), in that case this represents the virtual channel provided by biboumi. See *Connect to an IRC server* for more details. +IRC users have a local part formed like this: +`user_name`!`irc_server`. On XMPP, the node part of the JID can only be lowercase. On the other hand, IRC nicknames are case-insensitive, this means that the nicknames toto, @@ -131,7 +136,7 @@ Examples: irc.example.com IRC server, and this is served by the biboumi instance on biboumi.example.com - `toto%irc.example.com@biboumi.example.com` is the IRC user named toto, or + `toto!irc.example.com@biboumi.example.com` is the IRC user named toto, or TotO, etc. `irc.example.com@biboumi.example.com` is the IRC server irc.example.com. @@ -139,9 +144,20 @@ Examples: `%irc.example.com@biboumi.example.com` is the virtual channel provided by biboumi, for the IRC server irc.example.com. -If compiled with Libidn, an IRC user has a bare JID representing the -“hostname” provided by the IRC server. This JID can only be used to set IRC -modes (for example to ban a user based on its IP), or to identify user. +Note: Some JIDs are valid but make no sense in the context of +biboumi: + + `!irc.example.com@biboumi.example.com` is the empty-string nick on the + irc.example.com server. It makes no sense to try to send messages to it. + + `#test%@biboumi.example.com`, or any other JID that does not contain an + IRC server is invalid. Any message to that kind of JID will trigger an + error, or will be ignored. + +If compiled with Libidn, an IRC channel participant has a bare JID +representing the “hostname” provided by the IRC server. This JID can only +be used to set IRC modes (for example to ban a user based on its IP), or to +identify user. It cannot be used to contact that user using biboumi. ### Join an IRC channel diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 3377135..6d864c0 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -106,8 +106,8 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) { - IrcClient* irc = this->get_irc_client(iid.server, username); - if (iid.chan.empty()) + IrcClient* irc = this->get_irc_client(iid.get_server(), username); + if (iid.get_local().empty()) { // Join the dummy channel if (irc->is_welcomed()) { @@ -117,7 +117,7 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) // joined the channel const IrcMessage join_message(irc->get_nick(), "JOIN", {""}); irc->on_channel_join(join_message); - const IrcMessage end_join_message(std::string(iid.server), "366", + const IrcMessage end_join_message(std::string(iid.get_server()), "366", {irc->get_nick(), "", "End of NAMES list"}); irc->on_channel_completely_joined(end_join_message); @@ -129,9 +129,9 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) } return true; } - if (irc->is_channel_joined(iid.chan) == false) + if (irc->is_channel_joined(iid.get_local()) == false) { - irc->send_join_command(iid.chan); + irc->send_join_command(iid.get_local()); return true; } return false; @@ -139,15 +139,15 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) void Bridge::send_channel_message(const Iid& iid, const std::string& body) { - if (iid.chan.empty() || iid.server.empty()) + if (iid.get_local().empty() || iid.get_server().empty()) { - log_warning("Cannot send message to channel: [" << iid.chan << "] on server [" << iid.server << "]"); + log_warning("Cannot send message to channel: [" << iid.get_local() << "] on server [" << iid.get_server() << "]"); return; } - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (!irc) { - log_warning("Cannot send message: no client exist for server " << iid.server); + log_warning("Cannot send message: no client exist for server " << iid.get_server()); return; } // Because an IRC message cannot contain \n, we need to convert each line @@ -166,27 +166,27 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) if (line.substr(0, 6) == "/mode ") { std::vector args = utils::split(line.substr(6), ' ', false); - irc->send_mode_command(iid.chan, args); + irc->send_mode_command(iid.get_local(), args); continue; // We do not want to send that back to the // XMPP user, that’s not a textual message. } else if (line.substr(0, 4) == "/me ") - irc->send_channel_message(iid.chan, action_prefix + line.substr(4) + "\01"); + irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else - irc->send_channel_message(iid.chan, line); - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, irc->get_own_nick(), + irc->send_channel_message(iid.get_local(), line); + this->xmpp->send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid); } } void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) { - if (iid.chan.empty() || iid.server.empty()) + if (iid.get_local().empty() || iid.get_server().empty()) return ; - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (!irc) { - log_warning("Cannot send message: no client exist for server " << iid.server); + log_warning("Cannot send message: no client exist for server " << iid.get_server()); return; } std::vector lines = utils::split(body, '\n', true); @@ -195,38 +195,38 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const for (const std::string& line: lines) { if (line.substr(0, 4) == "/me ") - irc->send_private_message(iid.chan, action_prefix + line.substr(4) + "\01", type); + irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type); else - irc->send_private_message(iid.chan, line, type); + irc->send_private_message(iid.get_local(), line, type); } } void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) { - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) - irc->send_part_command(iid.chan, status_message); + irc->send_part_command(iid.get_local(), status_message); } void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) { - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) irc->send_nick_command(new_nick); } void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason) { - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) - irc->send_kick_command(iid.chan, target, reason); + irc->send_kick_command(iid.get_local(), target, reason); } void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) { - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) - irc->send_topic_command(iid.chan, subject); + irc->send_topic_command(iid.get_local(), subject); } void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os) @@ -239,22 +239,22 @@ void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, c void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) - this->xmpp->send_muc_message(iid.chan + "%" + iid.server, nick, + this->xmpp->send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body), this->user_jid); else - this->xmpp->send_message(iid.chan + "%" + iid.server, + this->xmpp->send_message(std::to_string(iid), this->make_xmpp_body(body), this->user_jid, "chat"); } void Bridge::send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text) { - this->xmpp->send_presence_error(iid.chan + "%" + iid.server, nick, this->user_jid, type, condition, text); + this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, text); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { - this->xmpp->send_muc_leave(std::move(iid.chan) + "%" + std::move(iid.server), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); - IrcClient* irc = this->get_irc_client(iid.server); + this->xmpp->send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); } @@ -269,7 +269,7 @@ void Bridge::send_nick_change(Iid&& iid, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp->send_nick_change(std::move(iid.chan) + "%" + std::move(iid.server), + this->xmpp->send_nick_change(std::to_string(iid), old_nick, new_nick, affiliation, role, this->user_jid, self); } @@ -309,7 +309,7 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam std::string Bridge::get_own_nick(const Iid& iid) { - IrcClient* irc = this->get_irc_client(iid.server); + IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) return irc->get_own_nick(); return ""; @@ -322,12 +322,12 @@ size_t Bridge::active_clients() const void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { - this->xmpp->kick_user(iid.chan + "%" + iid.server, target, reason, author, this->user_jid); + this->xmpp->kick_user(std::to_string(iid), target, reason, author, this->user_jid); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp->send_nickname_conflict_error(iid.chan + "%" + iid.server, nickname, this->user_jid); + this->xmpp->send_nickname_conflict_error(std::to_string(iid), nickname, this->user_jid); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) @@ -336,10 +336,10 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::string affiliation; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); - this->xmpp->send_affiliation_role_change(iid.chan + "%" + iid.server, target, affiliation, role, this->user_jid); + this->xmpp->send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid); } void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { - this->xmpp->send_iq_version_request(nick + "%" + hostname, this->user_jid); + this->xmpp->send_iq_version_request(nick + "!" + hostname, this->user_jid); } diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 4694c0c..4893e9e 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,21 +1,63 @@ +#include + #include -Iid::Iid(const std::string& iid) +Iid::Iid(const std::string& iid): + is_channel(false), + is_user(false) { - std::string::size_type sep = iid.find("%"); + const std::string::size_type sep = iid.find_first_of("%!"); if (sep != std::string::npos) { - this->chan = iid.substr(0, sep); - sep++; + if (iid[sep] == '%') + this->is_channel = true; + else + this->is_user = true; + this->set_local(iid.substr(0, sep)); + this->set_server(iid.substr(sep + 1)); } else - { - this->chan = iid; - return; - } - this->server = iid.substr(sep); + this->set_server(iid); +} + +Iid::Iid(): + is_channel(false), + is_user(false) +{ +} + +void Iid::set_local(const std::string& loc) +{ + this->local = utils::tolower(loc); +} + +void Iid::set_server(const std::string& serv) +{ + this->server = utils::tolower(serv); +} + +const std::string& Iid::get_local() const +{ + return this->local; } -Iid::Iid() +const std::string& Iid::get_server() const { + return this->server; +} + +std::string Iid::get_sep() const +{ + if (this->is_channel) + return "%"; + else if (this->is_user) + return "!"; + return ""; +} + +namespace std { + const std::string to_string(const Iid& iid) + { + return iid.get_local() + iid.get_sep() + iid.get_server(); + } } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index a62ac71..2302a18 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -4,17 +4,40 @@ #include /** - * A name representing an IRC channel, on the same model than the XMPP JIDs (but much simpler). - * The separator between the server and the channel name is '%' - * #test%irc.freenode.org has : - * - chan: "#test" (the # is part of the name, it could very well be absent, or & instead - * - server: "irc.freenode.org" - * #test has: - * - chan: "#test" - * - server: "" - * %irc.freenode.org: - * - chan: "" - * - server: "irc.freenode.org" + * A name representing an IRC channel on an IRC server, or an IRC user on an + * IRC server, or just an IRC server. + * + * The separator for an user is '!', for a channel it's '%'. If no separator + * is present, it's just an irc server. + * It’s possible to have an empty-string server, but it makes no sense in + * the biboumi context. + * + * #test%irc.example.org has : + * - local: "#test" (the # is part of the name, it could very well be absent, or & (for example) instead) + * - server: "irc.example.org" + * - is_channel: true + * - is_user: false + * + * %irc.example.org: + * - local: "" + * - server: "irc.example.org" + * - is_channel: true + * - is_user: false + * Note: this is the special empty-string channel, used internal in biboumi + * but has no meaning on IRC. + * + * foo!irc.example.org + * - local: "foo" + * - server: "irc.example.org" + * - is_channel: false + * - is_user: true + * Note: the empty-string user (!irc.example.org) has no special meaning in biboumi + * + * irc.example.org: + * - local: "" + * - server: "irc.example.org" + * - is_channel: false + * - is_user: false */ class Iid { @@ -22,14 +45,28 @@ public: explicit Iid(const std::string& iid); explicit Iid(); - std::string chan; - std::string server; + void set_local(const std::string& loc); + void set_server(const std::string& serv); + const std::string& get_local() const; + const std::string& get_server() const; + + bool is_channel; + bool is_user; + + std::string get_sep() const; private: + std::string local; + std::string server; + Iid(const Iid&) = delete; Iid(Iid&&) = delete; Iid& operator=(const Iid&) = delete; Iid& operator=(Iid&&) = delete; }; +namespace std { + const std::string to_string(const Iid& iid); +} + #endif // IID_INCLUDED diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index c3765a6..6e22980 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -102,10 +102,11 @@ void IrcClient::on_connection_close() log_warning(message); } -IrcChannel* IrcClient::get_channel(const std::string& name) +IrcChannel* IrcClient::get_channel(const std::string& n) { - if (name.empty()) + if (n.empty()) return &this->dummy_channel; + const std::string name = utils::tolower(n); try { return this->channels.at(name).get(); @@ -382,15 +383,18 @@ void IrcClient::on_channel_message(const IrcMessage& message) const IrcUser user(message.prefix); const std::string nick = user.nick; Iid iid; - iid.chan = utils::tolower(message.arguments[0]); - iid.server = this->hostname; + iid.set_local(message.arguments[0]); + iid.set_server(this->hostname); const std::string body = message.arguments[1]; bool muc = true; - if (!this->get_channel(iid.chan)->joined) + if (!this->get_channel(iid.get_local())->joined) { - iid.chan = nick; + iid.is_user = true; + iid.set_local(nick); muc = false; } + else + iid.is_channel = true; if (!body.empty() && body[0] == '\01') { if (body.substr(1, 6) == "ACTION") @@ -460,8 +464,9 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) for (auto it = this->channels.begin(); it != this->channels.end(); ++it) { Iid iid; - iid.chan = it->first; - iid.server = this->hostname; + iid.set_local(it->first); + iid.set_server(this->hostname); + iid.is_channel = true; this->bridge->send_nickname_conflict_error(iid, nickname); } } @@ -499,7 +504,7 @@ void IrcClient::on_welcome_message(const IrcMessage& message) void IrcClient::on_part(const IrcMessage& message) { - const std::string chan_name = utils::tolower(message.arguments[0]); + const std::string chan_name = message.arguments[0]; IrcChannel* channel = this->get_channel(chan_name); if (!channel->joined) return ; @@ -512,13 +517,14 @@ void IrcClient::on_part(const IrcMessage& message) std::string nick = user->nick; channel->remove_user(user); Iid iid; - iid.chan = chan_name; - iid.server = this->hostname; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.is_channel = true; bool self = channel->get_self()->nick == nick; if (self) { channel->joined = false; - this->channels.erase(chan_name); + this->channels.erase(utils::tolower(chan_name)); // channel pointer is now invalid channel = nullptr; } @@ -533,8 +539,9 @@ void IrcClient::on_error(const IrcMessage& message) for (auto it = this->channels.begin(); it != this->channels.end(); ++it) { Iid iid; - iid.chan = it->first; - iid.server = this->hostname; + iid.set_local(it->first); + iid.set_server(this->hostname); + iid.is_channel = true; IrcChannel* channel = it->second.get(); if (!channel->joined) continue; @@ -560,8 +567,9 @@ void IrcClient::on_quit(const IrcMessage& message) std::string nick = user->nick; channel->remove_user(user); Iid iid; - iid.chan = chan_name; - iid.server = this->hostname; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.is_channel = true; this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false); } } @@ -579,8 +587,9 @@ void IrcClient::on_nick(const IrcMessage& message) { std::string old_nick = user->nick; Iid iid; - iid.chan = chan_name; - iid.server = this->hostname; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.is_channel = true; const bool self = channel->get_self()->nick == old_nick; const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); @@ -606,8 +615,9 @@ void IrcClient::on_kick(const IrcMessage& message) channel->joined = false; IrcUser author(message.prefix); Iid iid; - iid.chan = chan_name; - iid.server = this->hostname; + iid.set_local(chan_name); + iid.set_server(this->hostname); + iid.is_channel = true; this->bridge->kick_muc_user(std::move(iid), target, reason, author.nick); } @@ -625,8 +635,9 @@ void IrcClient::on_channel_mode(const IrcMessage& message) // For now, just transmit the modes so the user can know what happens // TODO, actually interprete the mode. Iid iid; - iid.chan = utils::tolower(message.arguments[0]); - iid.server = this->hostname; + iid.set_local(message.arguments[0]); + iid.set_server(this->hostname); + iid.is_channel = true; IrcUser user(message.prefix); std::string mode_arguments; for (size_t i = 1; i < message.arguments.size(); ++i) @@ -638,10 +649,10 @@ void IrcClient::on_channel_mode(const IrcMessage& message) mode_arguments += message.arguments[i]; } } - this->bridge->send_message(iid, "", "Mode "s + iid.chan + + this->bridge->send_message(iid, "", "Mode "s + iid.get_local() + " [" + mode_arguments + "] by " + user.nick, true); - const IrcChannel* channel = this->get_channel(iid.chan); + const IrcChannel* channel = this->get_channel(iid.get_local()); if (!channel) return; @@ -695,7 +706,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) if (!user) { log_warning("Trying to set mode for non-existing user '" << target - << "' in channel" << iid.chan); + << "' in channel" << iid.get_local()); return; } if (add) diff --git a/src/test.cpp b/src/test.cpp index 216608a..87ab49c 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -290,5 +290,41 @@ int main() std::cout << correctjid2 << std::endl; assert(correctjid2 == "zigougou@poez.io"); + /** + * IID parsing + */ + std::cout << color << "Testing IID parsing…" << reset << std::endl; + Iid iid1("foo!irc.example.org"); + std::cout << std::to_string(iid1) << std::endl; + assert(std::to_string(iid1) == "foo!irc.example.org"); + assert(iid1.get_local() == "foo"); + assert(iid1.get_server() == "irc.example.org"); + assert(!iid1.is_channel); + assert(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + std::cout << std::to_string(iid2) << std::endl; + assert(std::to_string(iid2) == "#test%irc.example.org"); + assert(iid2.get_local() == "#test"); + assert(iid2.get_server() == "irc.example.org"); + assert(iid2.is_channel); + assert(!iid2.is_user); + + Iid iid3("%irc.example.org"); + std::cout << std::to_string(iid3) << std::endl; + assert(std::to_string(iid3) == "%irc.example.org"); + assert(iid3.get_local() == ""); + assert(iid3.get_server() == "irc.example.org"); + assert(iid3.is_channel); + assert(!iid3.is_user); + + Iid iid4("irc.example.org"); + std::cout << std::to_string(iid4) << std::endl; + assert(std::to_string(iid4) == "irc.example.org"); + assert(iid4.get_local() == ""); + assert(iid4.get_server() == "irc.example.org"); + assert(!iid4.is_channel); + assert(!iid4.is_user); + return 0; } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 4c9c8ff..ec8517c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -306,7 +306,7 @@ void XmppComponent::handle_presence(const Stanza& stanza) error_type, error_name, ""); }); - if (!iid.server.empty()) + if (iid.is_channel && !iid.get_server().empty()) { // presence toward a MUC that corresponds to an irc channel, or a // dummy channel if iid.chan is empty if (type.empty()) @@ -353,7 +353,7 @@ void XmppComponent::handle_message(const Stanza& stanza) error_type, error_name, ""); }); XmlNode* body = stanza.get_child("body", COMPONENT_NS); - if (type == "groupchat") + if (type == "groupchat" && iid.is_channel) { if (to.resource.empty()) if (body && !body->get_inner().empty()) @@ -379,11 +379,13 @@ void XmppComponent::handle_message(const Stanza& stanza) if (kickable_error) bridge->shutdown("Error from remote client"); } - else if (type == "chat") + else if (type == "chat" && iid.is_user && !iid.get_local().empty()) { if (body && !body->get_inner().empty()) bridge->send_private_message(iid, body->get_inner()); } + else if (iid.is_user) + this->send_invalid_user_error(to.local, from); stanza_error.disable(); } @@ -671,6 +673,36 @@ 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; + x.close(); + 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; + item_not_found.close(); + 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: !@" + + this->served_hostname); + text.close(); + error.add_child(std::move(text)); + error.close(); + message.add_child(std::move(error)); + message.close(); + this->send_stanza(message); +} + void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) { XmlNode message("message"); @@ -711,7 +743,7 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str this->send_stanza(message); } -void XmppComponent::send_muc_leave(std::string&& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) +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"); presence["to"] = jid_to; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 5de471c..f1806de 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -130,6 +130,12 @@ public: void send_invalid_room_error(const std::string& muc_jid, 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 */ @@ -141,7 +147,7 @@ public: /** * Send an unavailable presence for this nick */ - void send_muc_leave(std::string&& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); + void send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); /** * Indicate that a participant changed his nick */ -- cgit v1.2.3 From 6cbaa2c669ab572009eb560c4be8ff2e47542d55 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 13 Jun 2014 01:46:12 +0200 Subject: Document the encryption, and a few documentation issues are fixed --- doc/biboumi.1.md | 79 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 38a4718..1de6f81 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -6,11 +6,6 @@ NAME biboumi - XMPP gateway to IRC -SYNOPSIS --------- - -`biboumi` [`config_filename`] - DESCRIPTION ----------- @@ -18,10 +13,15 @@ Biboumi is an XMPP gateway that connects to IRC servers and translates between the two protocols. It can be used to access IRC channels using any XMPP client as if these channels were XMPP MUCs. +SYNOPSIS +-------- + +`biboumi` [`config_filename`] + OPTIONS ------- -Available options: +Available command line options: `config_filename` @@ -176,7 +176,7 @@ channel `#foo` on the server `irc.example.com`, but you need to authenticate to a bot of the server before you’re allowed to join it, you can first join the room `%irc.example.com@biboumi.example.com` (this will effectively connect you to the IRC server without joining any room), then send your -authentication message to the user `bot%irc.example.com@biboumi.example.com" +authentication message to the user `bot!irc.example.com@biboumi.example.com` and finally join the room `#foo%irc.example.com@biboumi.example.com`. ### Channel messages @@ -207,10 +207,9 @@ Private messages are handled differently on IRC and on XMPP. On IRC, you talk directly to one server-user: toto on the channel #foo is the same user as toto on the channel #bar (as long as these two channels are on the same IRC server). Using biboumi, there is no way to receive a message from a -room participant (from a jid like -#test%irc.example.com@biboumi.example.com/nickname). Instead, private -messages are received from and sent to the user (using a jid like -nickname%irc.example.com@biboumi.example.com). +room participant (from a jid like #test%irc.example.com@biboumi.example.com/nickname). +Instead, private messages are received from and sent to the user (using a +jid like `nickname!irc.example.com@biboumi.example.com`). ### Notices @@ -291,35 +290,43 @@ Biboumi supports a few ad-hoc commands, as described in the XEP 0050. - hello: Provide a form, where the user enters their name, and biboumi responds with a nice greeting. - - disconnect-user: Available to the administrator only. The user provides - a list of JIDs, and a quit message. All these users are disconnected - from all the IRC servers to which they were connected, using the - provided quit message. Sending SIGINT to biboumi is equivalent to using - this command by selecting all the connected JIDs and using the “Gateway - shutdown” quit message, except that biboumi does not exit when using - this ad-hoc command. + - disconnect-user: Only available to the administrator. The user provides + a list of JIDs, and a quit message. All the selected users are + disconnected from all the IRC servers to which they were connected, + using the provided quit message. Sending SIGINT to biboumi is equivalent + to using this command by selecting all the connected JIDs and using the + “Gateway shutdown” quit message, except that biboumi does not exit when + using this ad-hoc command. SECURITY -------- -Biboumi does not provide any encryption mechanism: connection to the XMPP -server MUST be made on localhost. The XMPP server is not supposed to accept -non-local connection from components, thus encryption is useless. IRC -SSL/TLS is also not yet implemented. - -Biboumi also does not check if the received JIDs are properly formatted -using nodeprep. This must be done by the XMPP server to which biboumi is -directly connected. - -Remember that the administrator of the gateway you use is able to view all -your IRC conversations, whether you’re using encryption or not. This is -exactly as if you were running your IRC client on someone else’s server. - -Biboumi does not yet provide a way to ban users from connecting to it, has -no protection against flood or any sort of abuse that your users may cause -on the IRC servers. Some XMPP server however offer the possibility to -restrict what JID can access a gateway. Use that feature if you wish to -grant access to your biboumi instance only to a list of users. +The connection to the XMPP server can only be made on localhost. The +XMPP server is not supposed to accept non-local connections from components. +Thus, encryption is not used to connect to the local XMPP server because it +is useless. + +If compiled with the Botan library, biboumi can use TLS when communicating +with the IRC serveres. It will first try ports 6697 and 6670 and use TLS if +it succeeds, if connection fails on both these ports, the connection is +established on port 6667 without any encryption. + +Biboumi does not check if the received JIDs are properly formatted using +nodeprep. This must be done by the XMPP server to which biboumi is directly +connected. + +Note if you use a biboumi that you have no control on: remember that the +administrator of the gateway you use is able to view all your IRC +conversations, whether you’re using encryption or not. This is exactly as +if you were running your IRC client on someone else’s server. Only use +biboumi if you trust its administrator (or, better, if you are the +administrator) or if you don’t intend to have any private conversation. + +Biboumi does not provide a way to ban users from connecting to it, has no +protection against flood or any sort of abuse that your users may cause on +the IRC servers. Some XMPP server however offer the possibility to restrict +what JID can access a gateway. Use that feature if you wish to grant access +to your biboumi instance only to a list of trusted users. AUTHORS ------- -- cgit v1.2.3 From 9757c2c6956762263c247922ed3262d0d1b3c0aa Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 17 Jun 2014 21:38:56 +0200 Subject: includes from cmake BINARY_DIR should be included before considering SOURCE_DIR --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f21103..8a565ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,8 @@ find_package(SystemdDaemon) find_package(Botan) # To be able to include the config.h file generated by cmake -include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") include_directories(${EXPAT_INCLUDE_DIRS}) include_directories(${ICONV_INCLUDE_DIRS}) include_directories(${LIBUUID_INCLUDE_DIRS}) -- cgit v1.2.3 From 56eb5df28a1023db2039388fe6c8fc1346da1a3d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 18 Jun 2014 18:46:41 +0200 Subject: Mini comment fix --- src/bridge/bridge.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 9c06947..814222b 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -111,7 +111,7 @@ public: */ void send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode); /** - * Send an iq version request coming from nick%hostname@ + * Send an iq version request coming from nick!hostname@ */ void send_iq_version_request(const std::string& nick, const std::string& hostname); -- cgit v1.2.3 From 04de62a4e0af4843e84c933e16f249ba1df6874f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 18 Jun 2014 22:42:58 +0200 Subject: Messages to room participants are forwarded to the IRC user For example, both JID #chan%server@biboumi/Toto and toto!server@biboumi are equivalent, except that if you send a message to the first one, subsequent messages coming from the user toto will come from that same JID. This is done to be consistent for the XMPP user, and respond from the same JID than the 'to' of the first message. fix #2468 --- src/bridge/bridge.cpp | 30 ++++++++++++++++++++++++++++-- src/bridge/bridge.hpp | 16 ++++++++++++++++ src/xmpp/xmpp_component.cpp | 36 ++++++++++++++++++++++++++++-------- src/xmpp/xmpp_component.hpp | 6 +++++- 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6d864c0..a0964df 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -242,8 +242,18 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st this->xmpp->send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body), this->user_jid); else - this->xmpp->send_message(std::to_string(iid), - this->make_xmpp_body(body), this->user_jid, "chat"); + { + std::string target = std::to_string(iid); + bool fulljid = false; + auto it = this->preferred_user_from.find(iid.get_local()); + if (it != this->preferred_user_from.end()) + { + target = it->second; + fulljid = true; + } + this->xmpp->send_message(target, this->make_xmpp_body(body), + this->user_jid, "chat", fulljid); + } } void Bridge::send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text) @@ -343,3 +353,19 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string& { this->xmpp->send_iq_version_request(nick + "!" + hostname, this->user_jid); } + +void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) +{ + auto it = this->preferred_user_from.find(nick); + if (it == this->preferred_user_from.end()) + this->preferred_user_from.emplace(nick, full_jid); + else + this->preferred_user_from[nick] = full_jid; +} + +void Bridge::remove_preferred_from_jid(const std::string& nick) +{ + auto it = this->preferred_user_from.find(nick); + if (it != this->preferred_user_from.end()) + this->preferred_user_from.erase(it); +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 814222b..8711829 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -123,6 +123,14 @@ public: * Get the number of server to which this bridge is connected or connecting. */ size_t active_clients() const; + /** + * Add (or replace the existing) into the preferred_user_from map + */ + void set_preferred_from_jid(const std::string& nick, const std::string& full_jid); + /** + * Remove the preferred jid for the given IRC nick + */ + void remove_preferred_from_jid(const std::string& nick); private: /** @@ -157,6 +165,14 @@ private: * their sockets. */ std::shared_ptr poller; + /** + * A map of . For example if this map contains <"toto", + * "#somechan%server@biboumi/ToTo">, whenever a private message is + * received from the user "toto", instead of forwarding it to XMPP with + * from='toto!server@biboumi', we use instead + * from='#somechan%server@biboumi/ToTo' + */ + std::unordered_map preferred_user_from; Bridge(const Bridge&) = delete; Bridge(Bridge&& other) = delete; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index ec8517c..da9cded 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -351,13 +352,14 @@ void XmppComponent::handle_message(const Stanza& stanza) utils::ScopeGuard stanza_error([&](){ this->send_stanza_error("message", from, to_str, id, error_type, error_name, ""); - }); + }); XmlNode* body = stanza.get_child("body", COMPONENT_NS); if (type == "groupchat" && iid.is_channel) { - if (to.resource.empty()) - if (body && !body->get_inner().empty()) + if (body && !body->get_inner().empty()) + { bridge->send_channel_message(iid, body->get_inner()); + } XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); if (subject) bridge->set_channel_topic(iid, subject->get_inner()); @@ -374,15 +376,30 @@ void XmppComponent::handle_message(const Stanza& stanza) { const XmlNode* condition = error->get_last_child(); if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) - kickable_error = false; + kickable_error = false; } if (kickable_error) bridge->shutdown("Error from remote client"); } - else if (type == "chat" && iid.is_user && !iid.get_local().empty()) + else if (type == "chat") { if (body && !body->get_inner().empty()) - bridge->send_private_message(iid, body->get_inner()); + { + // a message for nick!server + if (iid.is_user && !iid.get_local().empty()) + { + bridge->send_private_message(iid, body->get_inner()); + bridge->remove_preferred_from_jid(iid.get_local()); + } + else if (!iid.is_user && !to.resource.empty()) + { // a message for chan%server@biboumi/Nick or + // server@biboumi/Nick + // Convert that into a message to nick!server + Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); + bridge->send_private_message(user_iid, body->get_inner()); + bridge->set_preferred_from_jid(user_iid.get_local(), to_str); + } + } } else if (iid.is_user) this->send_invalid_user_error(to.local, from); @@ -571,11 +588,14 @@ 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) +void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to, const std::string& type, const bool fulljid) { XmlNode node("message"); node["to"] = to; - node["from"] = from + "@" + this->served_hostname; + if (fulljid) + node["from"] = from; + else + node["from"] = from + "@" + this->served_hostname; if (!type.empty()) node["type"] = type; XmlNode body_node("body"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index f1806de..17462f4 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -111,9 +111,13 @@ public: void close_document(); /** * Send a message from from@served_hostname, with the given body + * + * 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 std::string& to, const std::string& type, + const bool fulljid=false); /** * Send a join from a new participant */ -- cgit v1.2.3 From 386ac4186a750feee12b7d9b89ca37c3d16ebff4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 18 Jun 2014 22:55:18 +0200 Subject: Document the new way to send private messages --- doc/biboumi.1.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 1de6f81..f3f1677 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -122,8 +122,14 @@ The channel name can also be empty (for example `%irc.example.com`), in that case this represents the virtual channel provided by biboumi. See *Connect to an IRC server* for more details. -IRC users have a local part formed like this: -`user_name`!`irc_server`. +There is two ways to address an IRC user, using a local part like this: +`nickname`!`irc_server` +or by using the in-room address of the participant, like this: +`channel_name`%`irc_server`@`biboumi.example.com`/`Nickname` + +The second JID is available only to be compatible with XMPP clients when the +user wants to send a private message to the participant `Nickname` in the +room `channel_name%irc_server@biboumi.example.com`. On XMPP, the node part of the JID can only be lowercase. On the other hand, IRC nicknames are case-insensitive, this means that the nicknames toto, @@ -206,10 +212,11 @@ be changed on all channels on the same server as well. Private messages are handled differently on IRC and on XMPP. On IRC, you talk directly to one server-user: toto on the channel #foo is the same user as toto on the channel #bar (as long as these two channels are on the same -IRC server). Using biboumi, there is no way to receive a message from a -room participant (from a jid like #test%irc.example.com@biboumi.example.com/nickname). -Instead, private messages are received from and sent to the user (using a -jid like `nickname!irc.example.com@biboumi.example.com`). +IRC server). By default you will receive private messages from the “global” +user (aka nickname!irc.example.com@biboumi.example.com), unless you +previously sent a message to an in-room participant (something like +#test%irc.example.com@biboumi.example.com/nickname), in which case future +messages from that same user will be received from that same “in-room” JID. ### Notices -- cgit v1.2.3 From 77fe8b89a3a1debe59051b21776c1c6cf67a6070 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 19 Jun 2014 01:04:40 +0200 Subject: Make the Botan rng, credential_manager etc be static This actually makes the session_manager be useful, and saves a few octets of memory for the other ones --- src/network/socket_handler.cpp | 13 ++++++------- src/network/socket_handler.hpp | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index a2c22e4..43a63f0 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -20,6 +20,12 @@ #ifdef BOTAN_FOUND # include + +Botan::AutoSeeded_RNG SocketHandler::rng; +Permissive_Credentials_Manager SocketHandler::credential_manager; +Botan::TLS::Policy SocketHandler::policy; +Botan::TLS::Session_Manager_In_Memory SocketHandler::session_manager(SocketHandler::rng); + #endif #ifndef UIO_FASTIOV @@ -37,13 +43,6 @@ SocketHandler::SocketHandler(std::shared_ptr poller): use_tls(false), connected(false), connecting(false) -#ifdef BOTAN_FOUND - , - rng(), - credential_manager(), - policy(), - session_manager(rng) -#endif {} void SocketHandler::init_socket(const struct addrinfo* rp) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 95e38bd..b4cb6f2 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -253,10 +253,10 @@ private: /** * Botan stuff to manipulate a TLS session. */ - Botan::AutoSeeded_RNG rng; - Permissive_Credentials_Manager credential_manager; - Botan::TLS::Policy policy; - Botan::TLS::Session_Manager_In_Memory session_manager; + static Botan::AutoSeeded_RNG rng; + static Permissive_Credentials_Manager credential_manager; + static Botan::TLS::Policy policy; + static Botan::TLS::Session_Manager_In_Memory session_manager; /** * We use a unique_ptr because we may not want to create the object at * all. The Botan::TLS::Client object generates a handshake message as -- cgit v1.2.3 From a705b9af7b1bbce6b6c94788398a4cff9cad9ec9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 19 Jun 2014 01:10:13 +0200 Subject: =?UTF-8?q?Remove=20a=20duplicate=20=E2=80=9Cconnection=20closed?= =?UTF-8?q?=E2=80=9D=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/irc/irc_client.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6e22980..cc54971 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -96,7 +96,6 @@ void IrcClient::on_connected() void IrcClient::on_connection_close() { static const std::string message = "Connection closed by remote server."; - this->send_gateway_message(message); const IrcMessage error{"ERROR", {message}}; this->on_error(error); log_warning(message); -- cgit v1.2.3 From 26ffc8fe121e03e1b663aa0015a71b0fc914f95e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 19 Jun 2014 22:21:49 +0200 Subject: Implement a way to add callbacks, waiting for an IRC event to return an iq --- src/bridge/bridge.cpp | 18 ++++++++++++++++++ src/bridge/bridge.hpp | 26 ++++++++++++++++++++++++++ src/irc/iid.cpp | 8 ++++++++ src/irc/iid.hpp | 2 +- src/irc/irc_client.cpp | 7 ++++++- src/xmpp/xmpp_component.cpp | 12 +++++++++--- 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a0964df..c2ac11f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -369,3 +370,20 @@ void Bridge::remove_preferred_from_jid(const std::string& nick) if (it != this->preferred_user_from.end()) this->preferred_user_from.erase(it); } + +void Bridge::add_waiting_iq(iq_responder_callback_t&& callback) +{ + this->waiting_iq.emplace_back(std::move(callback)); +} + +void Bridge::trigger_response_iq(const std::string& irc_hostname, const IrcMessage& message) +{ + auto it = this->waiting_iq.begin(); + while (it != this->waiting_iq.end()) + { + if ((*it)(irc_hostname, message) == true) + it = this->waiting_iq.erase(it); + else + ++it; + } +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 8711829..0983595 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -7,12 +7,21 @@ #include #include +#include #include #include class XmppComponent; class Poller; +/** + * A callback called for each IrcMessage we receive. If the message triggers + * a response, it must send an iq and return true (in that case it is + * removed from the list), otherwise it must do nothing and just return + * false. + */ +typedef std::function iq_responder_callback_t; + /** * One bridge is spawned for each XMPP user that uses the component. The * bridge spawns IrcClients when needed (when the user wants to join a @@ -131,6 +140,16 @@ public: * Remove the preferred jid for the given IRC nick */ void remove_preferred_from_jid(const std::string& nick); + /** + * Add a callback to the waiting iq list. + */ + void add_waiting_iq(iq_responder_callback_t&& callback); + /** + * Iter over all the waiting_iq, call the iq_responder_filter_t for each, + * whenever one of them returns true: call the corresponding + * iq_responder_callback_t and remove the callback from the list. + */ + void trigger_response_iq(const std::string& irc_hostname, const IrcMessage& message); private: /** @@ -173,6 +192,13 @@ private: * from='#somechan%server@biboumi/ToTo' */ std::unordered_map preferred_user_from; + /** + * A list of callbacks that are waiting for some IrcMessage to trigger a + * response. We add callbacks in this list whenever we received an IQ + * request and we need a response from IRC to be able to provide the + * response iq. + */ + std::list waiting_iq; Bridge(const Bridge&) = delete; Bridge(Bridge&& other) = delete; diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 4893e9e..0bb991f 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -20,6 +20,14 @@ Iid::Iid(const std::string& iid): this->set_server(iid); } +Iid::Iid(const Iid& other): + is_channel(other.is_channel), + is_user(other.is_user), + local(other.local), + server(other.server) +{ +} + Iid::Iid(): is_channel(false), is_user(false) diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 2302a18..c547dea 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -43,6 +43,7 @@ class Iid { public: explicit Iid(const std::string& iid); + explicit Iid(const Iid&); explicit Iid(); void set_local(const std::string& loc); @@ -59,7 +60,6 @@ private: std::string local; std::string server; - Iid(const Iid&) = delete; Iid(Iid&&) = delete; Iid& operator=(const Iid&) = delete; Iid& operator=(Iid&&) = delete; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index cc54971..d179aaa 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -136,8 +136,11 @@ void IrcClient::parse_in_buffer(const size_t) if (pos == std::string::npos) break ; IrcMessage message(this->in_buf.substr(0, pos)); - log_debug("IRC RECEIVING: " << message); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); + log_debug("IRC RECEIVING: " << message); + + // Call the standard callback (if any), associated with the command + // name that we just received. auto cb = irc_callbacks.find(message.command); if (cb != irc_callbacks.end()) { @@ -149,6 +152,8 @@ void IrcClient::parse_in_buffer(const size_t) } else log_info("No handler for command " << message.command); + // Try to find a waiting_iq, which response will be triggered by this IrcMessage + this->bridge->trigger_response_iq(this->hostname, message); } } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index da9cded..901e168 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -220,14 +220,20 @@ void XmppComponent::send_stream_error(const std::string& name, const std::string } void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text) + const std::string& id, const std::string& error_type, + const std::string& defined_condition, const std::string& text, + const bool fulljid) { Stanza node(kind); if (!to.empty()) node["to"] = to; if (!from.empty()) - node["from"] = from; + { + if (fulljid) + node["from"] = from; + else + node["from"] = from + "@" + this->served_hostname; + } if (!id.empty()) node["id"] = id; node["type"] = "error"; -- cgit v1.2.3 From 2117838cf9fb083f6f74386abbb56e3b28d4db46 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 19 Jun 2014 22:22:29 +0200 Subject: =?UTF-8?q?Return=20a=20proper=20iq=20when=20the=20IRC=E2=80=AFser?= =?UTF-8?q?ver=20responds=20to=20our=20kick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A result or an error, depending on the type of message --- src/bridge/bridge.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- src/bridge/bridge.hpp | 4 +++- src/xmpp/xmpp_component.cpp | 13 ++++++++++++- src/xmpp/xmpp_component.hpp | 7 ++++++- 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index c2ac11f..384131f 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -216,11 +216,49 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) irc->send_nick_command(new_nick); } -void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason) +void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, + const std::string& iq_id, const std::string& to_jid) { IrcClient* irc = this->get_irc_client(iid.get_server()); if (irc) - irc->send_kick_command(iid.get_local(), target, reason); + { + irc->send_kick_command(iid.get_local(), target, reason); + this->add_waiting_iq([this, target, iq_id, to_jid, iid](const std::string& irc_hostname, const IrcMessage& message){ + if (irc_hostname != iid.get_server()) + return false; + if (message.command == "KICK" && message.arguments.size() >= 2) + { + const std::string target_later = message.arguments[1]; + const std::string chan_name_later = utils::tolower(message.arguments[0]); + if (target_later != target || chan_name_later != iid.get_local()) + return false; + this->xmpp->send_iq_result(iq_id, to_jid, std::to_string(iid)); + } + else if (message.command == "401" && message.arguments.size() >= 2) + { + const std::string target_later = message.arguments[1]; + if (target_later != target) + return false; + std::string error_message = "No such nick"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found", + error_message, false); + } + else if (message.command == "482" && message.arguments.size() >= 2) + { + const std::string chan_name_later = utils::tolower(message.arguments[1]); + if (chan_name_later != iid.get_local()) + return false; + std::string error_message = "You're not channel operator"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed", + error_message, false); + } + return true; + }); + } } void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 0983595..9eb239d 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -1,6 +1,7 @@ #ifndef BRIDGE_INCLUDED # define BRIDGE_INCLUDED +#include #include #include #include @@ -62,7 +63,8 @@ public: void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void leave_irc_channel(Iid&& iid, std::string&& status_message); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); - void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason); + void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, + const std::string& iq_id, const std::string& to_jid); void set_channel_topic(const Iid& iid, const std::string& subject); void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 901e168..5ecb283 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -468,7 +468,7 @@ void XmppComponent::handle_iq(const Stanza& stanza) if (reason_el) reason = reason_el->get_inner(); Iid iid(to.local); - bridge->send_irc_kick(iid, nick, reason); + bridge->send_irc_kick(iid, nick, reason, id, from); stanza_error.disable(); } } @@ -1014,6 +1014,17 @@ void XmppComponent::send_iq_version_request(const std::string& from, this->send_stanza(iq); } +void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) +{ + Stanza iq("iq"); + iq["from"] = from_local_part + "@" + this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + iq.close(); + this->send_stanza(iq); +} + std::string XmppComponent::next_id() { char uuid_str[37]; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 17462f4..ac12e40 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -104,7 +104,8 @@ public: */ void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text); + const std::string& defined_condition, const std::string& text, + const bool fulljid=true); /** * Send the closing signal for our document (not closing the connection though). */ @@ -208,6 +209,10 @@ public: */ void send_iq_version_request(const std::string& from, const std::string& jid_to); + /** + * Send an empty iq of type result + */ + void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); /** * Handle the various stanza types */ -- cgit v1.2.3 From aaf71774131198de2095c07e560fb420833c544d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 17 Jun 2014 20:56:32 +0200 Subject: Write the software version, including the git hash, in config.h using cmake --- CMakeLists.txt | 25 +++++++++++++++++++++++-- src/config.h.cmake | 3 ++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a565ff..3d634b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,9 @@ cmake_minimum_required(VERSION 2.6) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 0) -set(${PROJECT_NAME}_VERSION_MINOR 1) +set(${PROJECT_NAME}_VERSION_MAJOR 1) +set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") @@ -23,6 +24,26 @@ find_package(Libidn) find_package(SystemdDaemon) find_package(Botan) +# +## Get the software version +# +if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "~dev") + # If we are on a dev version, append the hash of the current git HEAD to + # the version + include(FindGit) + if(GIT_FOUND) + execute_process(COMMAND git --git-dir=${CMAKE_SOURCE_DIR}/.git rev-parse --short HEAD + OUTPUT_VARIABLE GIT_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(GIT_REVISION) + set(${PROJECT_NAME}_VERSION_SUFFIX "${${PROJECT_NAME}_VERSION_SUFFIX} (${GIT_REVISION})") + endif() + endif() +endif() + +set(BIBOUMI_VERSION + ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX}) + # To be able to include the config.h file generated by cmake include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") diff --git a/src/config.h.cmake b/src/config.h.cmake index 66ea967..bb4a5b8 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -2,4 +2,5 @@ #cmakedefine LIBIDN_FOUND #cmakedefine SYSTEMDDAEMON_FOUND #cmakedefine POLLER ${POLLER} -#cmakedefine BOTAN_FOUND \ No newline at end of file +#cmakedefine BOTAN_FOUND +#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" \ No newline at end of file -- cgit v1.2.3 From b747f2825c43e31ade20267cecefe2c2a9c76241 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 17 Jun 2014 21:41:09 +0200 Subject: Respond to IQ version on the gateway, a server or a chan with biboumi version ref #2455 --- src/config.h.cmake | 3 ++- src/xmpp/xmpp_component.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 4 ++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/config.h.cmake b/src/config.h.cmake index bb4a5b8..d8833ad 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -1,6 +1,7 @@ +#define SYSTEM_NAME "${CMAKE_SYSTEM}" #cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST #cmakedefine LIBIDN_FOUND #cmakedefine SYSTEMDDAEMON_FOUND #cmakedefine POLLER ${POLLER} #cmakedefine BOTAN_FOUND -#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" \ No newline at end of file +#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 5ecb283..c06b46b 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -506,6 +506,16 @@ void XmppComponent::handle_iq(const Stanza& stanza) } } } + else if ((query = stanza.get_child("query", VERSION_NS))) + { + Iid iid(to.local); + if (!iid.is_user) + { + // On the gateway itself or on a channel + this->send_self_version(id, from, to_str); + } + stanza_error.disable(); + } else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) { const std::string node = query->get_tag("node"); @@ -973,6 +983,33 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin this->send_stanza(iq); } +void XmppComponent::send_self_version(const std::string& id, const std::string& jid_to, const std::string& jid_from) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + XmlNode name("name"); + name.set_inner("biboumi"); + name.close(); + query.add_child(std::move(name)); + XmlNode version("version"); + version.set_inner(BIBOUMI_VERSION); + version.close(); + query.add_child(std::move(version)); + XmlNode os("os"); + os.set_inner(SYSTEM_NAME); + os.close(); + query.add_child(std::move(os)); + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) { Stanza iq("iq"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index ac12e40..ab1d584 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -199,6 +199,10 @@ public: * Send a result IQ with the gateway disco informations. */ void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send a result IQ with the gateway version. + */ + void send_self_version(const std::string& id, const std::string& jid_to, const std::string& jid_from); /** * Send the list of all available ad-hoc commands to that JID. The list is * different depending on what JID made the request. -- cgit v1.2.3 From 545ab11ff3a334b242ba2d7fc87e2b6ba0185cb5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 18 Jun 2014 11:46:28 +0200 Subject: Support version request to IRC users --- src/bridge/bridge.cpp | 36 ++++++++++++++++++++++++++++++ src/bridge/bridge.hpp | 6 ++++- src/xmpp/xmpp_component.cpp | 53 ++++++++++++++++++++++++++++++++------------- src/xmpp/xmpp_component.hpp | 6 +++-- 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 384131f..9501d47 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -275,6 +275,42 @@ void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, c this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE"); } +void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid) +{ + Iid iid(target + "!" + irc_hostname); + this->send_private_message(iid, "\01VERSION\01"); + + // TODO, add a timer to remove that waiting iq if the server does not + // respond with a matching command before n seconds + this->add_waiting_iq([this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message){ + if (irc_hostname != hostname) + return false; + IrcUser user(message.prefix); + if (message.command == "NOTICE" && user.nick == target && + message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ") + { + // remove the "\01VERSION " and the "\01" parts from the string + const std::string version = message.arguments[1].substr(9, message.arguments[1].size() - 10); + this->xmpp->send_version(iq_id, to_jid, from_jid, version); + return true; + } + if (message.command == "401" && message.arguments.size() >= 2 + && message.arguments[1] == target) + { + std::string error_message = "No such nick"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", + error_message, true); + return true; + } + return false; + }); +} + + void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 9eb239d..a75b319 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -66,7 +66,11 @@ public: void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid); void set_channel_topic(const Iid& iid, const std::string& subject); - void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os); + void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, + const std::string& os); + void send_irc_version_request(const std::string& irc_hostname, const std::string& target, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid); /*** ** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index c06b46b..b855d67 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -509,10 +509,22 @@ void XmppComponent::handle_iq(const Stanza& stanza) else if ((query = stanza.get_child("query", VERSION_NS))) { Iid iid(to.local); - if (!iid.is_user) + if (iid.is_user || + (iid.is_channel && !to.resource.empty())) + { + // Get the IRC user version + std::string target; + if (iid.is_user) + target = iid.get_local(); + else + target = to.resource; + bridge->send_irc_version_request(iid.get_server(), target, id, + from, to_str); + } + else { // On the gateway itself or on a channel - this->send_self_version(id, from, to_str); + this->send_version(id, from, to_str); } stanza_error.disable(); } @@ -983,7 +995,8 @@ void XmppComponent::send_self_disco_info(const std::string& id, const std::strin this->send_stanza(iq); } -void XmppComponent::send_self_version(const std::string& id, const std::string& jid_to, const std::string& jid_from) +void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, + const std::string& version) { Stanza iq("iq"); iq["type"] = "result"; @@ -992,18 +1005,28 @@ void XmppComponent::send_self_version(const std::string& id, const std::string& iq["from"] = jid_from; XmlNode query("query"); query["xmlns"] = VERSION_NS; - XmlNode name("name"); - name.set_inner("biboumi"); - name.close(); - query.add_child(std::move(name)); - XmlNode version("version"); - version.set_inner(BIBOUMI_VERSION); - version.close(); - query.add_child(std::move(version)); - XmlNode os("os"); - os.set_inner(SYSTEM_NAME); - os.close(); - query.add_child(std::move(os)); + if (version.empty()) + { + XmlNode name("name"); + name.set_inner("biboumi"); + name.close(); + query.add_child(std::move(name)); + XmlNode version("version"); + version.set_inner(BIBOUMI_VERSION); + version.close(); + query.add_child(std::move(version)); + XmlNode os("os"); + os.set_inner(SYSTEM_NAME); + os.close(); + query.add_child(std::move(os)); + } + else + { + XmlNode name("name"); + name.set_inner(version); + name.close(); + query.add_child(std::move(name)); + } query.close(); iq.add_child(std::move(query)); iq.close(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index ab1d584..ce594ec 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -200,9 +200,11 @@ public: */ void send_self_disco_info(const std::string& id, const std::string& jid_to); /** - * Send a result IQ with the gateway version. + * Send a result IQ with the given version, or the gateway version if the + * passed string is empty. */ - void send_self_version(const std::string& id, const std::string& jid_to, const std::string& jid_from); + void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, + const std::string& version=""); /** * Send the list of all available ad-hoc commands to that JID. The list is * different depending on what JID made the request. -- cgit v1.2.3 From 7b785cf552b69163c8e3ccc2dd3e05176d88e18b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 22 Jun 2014 23:56:51 +0200 Subject: Delete the timeout event using the correct socket number, instead of -1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was causing the event to not be removed in case of connection failure, which lead to a dangling timed event named “timeout-1”, which was called later, by some other SocketHandler for which even the socket creation failed (so, its socket is -1), with a pointer to the previous SocketHandler which has disappeared for a long time: segmentation fault etc. --- src/network/socket_handler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 43a63f0..52297e4 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -276,6 +276,8 @@ void SocketHandler::on_send() void SocketHandler::close() { + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); if (this->connected || this->connecting) this->poller->remove_socket_handler(this->get_socket()); if (this->socket != -1) @@ -288,8 +290,6 @@ void SocketHandler::close() this->in_buf.clear(); this->out_buf.clear(); this->port.clear(); - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); } socket_t SocketHandler::get_socket() const -- cgit v1.2.3 From a2d55e4bead65d211b1c84e3b2a2a55b70f852d8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 23 Jun 2014 01:00:21 +0200 Subject: Rename cmake target test->test_suite for cmake 3.0 See CMP0037 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d634b1..947e8fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,8 +175,8 @@ endif() ## Tests # -add_executable(test src/test.cpp) -target_link_libraries(test +add_executable(test_suite src/test.cpp) +target_link_libraries(test_suite xmpp irc bridge -- cgit v1.2.3 From 39175e1ad6fe6507bbe2874c6ce413cbe9c3366e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 24 Jun 2014 20:47:07 +0200 Subject: Little documentation update --- INSTALL | 27 ++++++++++++++++++--------- README | 4 ++-- doc/biboumi.1.md | 6 ++++++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/INSTALL b/INSTALL index 645b9be..fbe6623 100644 --- a/INSTALL +++ b/INSTALL @@ -4,6 +4,11 @@ Build and runtime dependencies: +Tools: + +- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) +- CMake +- ronn (optional) to build the man page Libraries: @@ -34,11 +39,6 @@ Libraries: Provides the support for a systemd service of Type=notify. This is useful only if you are packaging biboumi in a distribution with Systemd. -Tools: -- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) -- CMake -- ronn (optional) to build the man page - ============== Configure @@ -67,10 +67,10 @@ or and respond to the questions when you are prompted to. -You can select the poller used by biboumi, at compile-time, using the POLLER -cmake option. Available values are: - POLL: use the standard poll(2). This is the default value because it works on all supported plateforms - EPOLL: use the Linux-specific epoll(7) +You can, for example, select the poller used by biboumi, at compile-time, +using the POLLER cmake option. Available values are: + EPOLL: use the Linux-specific epoll(7). This is the default on Linux. + POLL: use the standard poll(2). This is the default value on all non-Linux platforms. Example, configure the poller with cmake: % cmake . -DPOLLER=EPOLL @@ -92,3 +92,12 @@ Example, configure the poller with cmake: - Install the software system-wide # make install + + +============= + Run +============= + +Run the software using the `biboumi` binary. Read the documentation (the +man page biboumi(1) or the “biboumi.1.md” file) for more information on how +to use biboumi. diff --git a/README b/README index cb75684..5fcae29 100644 --- a/README +++ b/README @@ -17,11 +17,11 @@ The goal is to provide a way to access most of IRC features using any XMPP client. It doesn’t however try to provide a complete mapping of the features of both worlds simply because this is not useful and most probably impossible. For example all IRC modes are not all translatable into an XMPP -features. Some of theme are (like +m (mute) or +o (operator) modes), but +features. Some of them are (like +m (mute) or +o (operator) modes), but some others are IRC-specific. If IRC is the limiting factor (for example you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to work around this issue: it just enforces the rules of the IRC server by -telling the user that he/she must choose an ASCII nickname only. An +telling the user that he/she must choose an ASCII-only nickname. An important goal is to keep the software (and its code) light and simple. ================ diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index f3f1677..fb48d4b 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -39,6 +39,7 @@ The configuration file uses a simple format of the form The hostname served by the XMPP gateway. This domain must be configured in the XMPP server as an external component. See the manual for your XMPP server for more information. + For prosody, see http://prosody.im/doc/components#adding_an_external_component `password` (mandatory) @@ -76,6 +77,11 @@ log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 USAGE ----- +Biboumi acts as a server, it should be run as a daemon that lives in the +background for as long as it is needed. Note that biboumi does not +daemonize itself, this task should be done by your init system (SysVinit, +systemd, upstart). + When started, biboumi connects, without encryption (see *SECURITY*), to the local XMPP server on the port `5347` and authenticates with the provided password. Biboumi then serves the configured `hostname`: this means that -- cgit v1.2.3 From 2cf64e10b51579d0bb1d270f82c4fdebf92a6ce6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 24 Jun 2014 17:45:50 +0200 Subject: Provide a make dist target --- CMakeLists.txt | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 947e8fe..a33fffe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,16 +27,22 @@ find_package(Botan) # ## Get the software version # -if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "~dev") +set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) +if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES ".+") + set(ARCHIVE_NAME ${ARCHIVE_NAME}-${${PROJECT_NAME}_VERSION_SUFFIX}) +endif() + +if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") # If we are on a dev version, append the hash of the current git HEAD to # the version include(FindGit) - if(GIT_FOUND) + if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") execute_process(COMMAND git --git-dir=${CMAKE_SOURCE_DIR}/.git rev-parse --short HEAD OUTPUT_VARIABLE GIT_REVISION OUTPUT_STRIP_TRAILING_WHITESPACE) if(GIT_REVISION) set(${PROJECT_NAME}_VERSION_SUFFIX "${${PROJECT_NAME}_VERSION_SUFFIX} (${GIT_REVISION})") + set(ARCHIVE_NAME ${ARCHIVE_NAME}-${GIT_REVISION}) endif() endif() endif() @@ -193,4 +199,13 @@ install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) if(WITH_DOC) install(FILES ${MAN_PAGE} DESTINATION man/man1) -endif() \ No newline at end of file +endif() + +# +## Dist target +## Generate a release tarball from the git sources +# +add_custom_target(dist + COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD + | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) \ No newline at end of file -- cgit v1.2.3 From 524f7925c44d9445cc4d3024406c80523a4ff9d4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Jun 2014 01:02:03 +0200 Subject: Add some details in the INSTALL file --- INSTALL | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/INSTALL b/INSTALL index fbe6623..825f549 100644 --- a/INSTALL +++ b/INSTALL @@ -49,11 +49,17 @@ the simplest is to just run % cmake . -in the current directory +in the current directory. + +The default build type is "Debug", if you want to build a release version, +set the CMAKE_BUILD_TYPE variable to "release", by running this command +instead: + +% cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr You can also configure many parameters of the build (like customize CFLAGS, the install path, choose the compiler, or enabling some options like the -POLLER to use), using: +POLLER to use), using the ncurses interface of ccmake: % ccmake . @@ -61,7 +67,7 @@ In ccmake, first use 'c' to configure the build system, edit the values you need and finaly use 'g' to generate the Makefiles to build the system and quit ccmake. -or +or you can also use an interactive mode with: % cmake -i . @@ -73,6 +79,7 @@ using the POLLER cmake option. Available values are: POLL: use the standard poll(2). This is the default value on all non-Linux platforms. Example, configure the poller with cmake: + % cmake . -DPOLLER=EPOLL @@ -80,7 +87,7 @@ Example, configure the poller with cmake: Build ============== -- Build the project +- Once you’ve configured everything using cmake, build the project % make @@ -89,7 +96,7 @@ Example, configure the poller with cmake: Install ============= -- Install the software system-wide +- And then, optionaly, Install the software system-wide # make install -- cgit v1.2.3 From 6f181acc6549c2a963cfa65e4b5eabf089522369 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Jun 2014 22:34:33 +0200 Subject: =?UTF-8?q?Fix=20ad-hoc=20=E2=80=9Cdisconnect=20user=E2=80=9D=20co?= =?UTF-8?q?mmand.=20Do=20not=20add=20the=20=20on=20success?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/adhoc_command.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index 67bc706..e1bfc97 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -185,6 +185,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X note.set_inner(std::to_string(num) + " users have been disconnected."); note.close(); command_node.add_child(std::move(note)); + return; } } XmlNode error(ADHOC_NS":error"); -- cgit v1.2.3 From 48ed47207cabe98ad7720048f03b7b09f5cf24cb Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 28 Jun 2014 18:38:08 +0200 Subject: Add missing include in timed_events.hpp fix #2552 --- src/utils/timed_events.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp index aafe532..4e2800c 100644 --- a/src/utils/timed_events.hpp +++ b/src/utils/timed_events.hpp @@ -2,6 +2,7 @@ # define TIMED_EVENTS_HPP #include +#include #include #include -- cgit v1.2.3 From a165e5924012c82d2598495d962597ae896e9cf7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Jun 2014 13:53:06 +0200 Subject: Fix some cmake issues fix #2551 --- CMakeLists.txt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a33fffe..70ead76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,15 +173,13 @@ target_link_libraries(${PROJECT_NAME} if(SYSTEMDDAEMON_FOUND) target_link_libraries(xmpp ${SYSTEMDDAEMON_LIBRARIES}) endif() -if(WITH_DOC) - add_dependencies(${PROJECT_NAME} doc) -endif() + # ## Tests # - -add_executable(test_suite src/test.cpp) +add_executable(test_suite EXCLUDE_FROM_ALL + src/test.cpp) target_link_libraries(test_suite xmpp irc @@ -190,6 +188,7 @@ target_link_libraries(test_suite config logger) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/config.h) # @@ -198,7 +197,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BI install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) if(WITH_DOC) - install(FILES ${MAN_PAGE} DESTINATION man/man1) + install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL) endif() # @@ -208,4 +207,4 @@ endif() add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) \ No newline at end of file + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -- cgit v1.2.3 From 70ebbae15e7432d56bf23751e548836b388781f6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Jun 2014 22:00:56 +0200 Subject: Regularly send a notification for the systemd watchdog --- src/xmpp/xmpp_component.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index b855d67..1e8f9e0 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -273,6 +274,16 @@ void XmppComponent::handle_handshake(const Stanza& stanza) log_info("Authenticated with the XMPP server"); #ifdef SYSTEMDDAEMON_FOUND sd_notify(0, "READY=1"); + // Install an event that sends a keepalive to systemd. If biboumi crashes + // or hangs for too long, systemd will restart it. + uint64_t usec; + if (sd_watchdog_enabled(0, &usec) > 0) + { + std::chrono::microseconds delay(usec); + TimedEventsManager::instance().add_event(TimedEvent( + std::chrono::duration_cast(std::chrono::microseconds(usec / 2)), + []() { sd_notify(0, "WATCHDOG=1"); })); + } #endif } -- cgit v1.2.3 From 5a43ac9a0d161b12c8745e3374983482049798a6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Jun 2014 21:38:12 +0200 Subject: Provide an example configuration file --- conf/biboumi.cfg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 conf/biboumi.cfg diff --git a/conf/biboumi.cfg b/conf/biboumi.cfg new file mode 100644 index 0000000..205b936 --- /dev/null +++ b/conf/biboumi.cfg @@ -0,0 +1,6 @@ +hostname=biboumi.example.com +password=secret +log_file=/var/log/biboumi/biboumi.log +log_level=0 +admin= +port=5347 -- cgit v1.2.3 From e01e34db22eb770efd0cb752bcfe4aad28157c93 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Jun 2014 21:39:28 +0200 Subject: Provide an example unit file for systemd --- unit/biboumi.service | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 unit/biboumi.service diff --git a/unit/biboumi.service b/unit/biboumi.service new file mode 100644 index 0000000..579ac4e --- /dev/null +++ b/unit/biboumi.service @@ -0,0 +1,14 @@ +[Unit] +Description=Biboumi, XMPP to IRC gateway +After=network.target + +[Service] +Type=notify +ExecStart=/usr/bin/biboumi /etc/biboumi/biboumi.cfg +ExecReload=/bin/kill -s USR1 $MAINPID +ExecStop=/bin/kill -s INT $MAINPID +WatchdogSec=10 +Restart=always + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 01c1ea14f79517ca829268a341da2eb60b7628f9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 00:50:48 +0200 Subject: Use systemd-daemon logging prefix if available --- src/logger/logger.hpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index 182c517..b42681b 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -17,6 +17,17 @@ #define warning_lvl 2 #define error_lvl 3 +#include "config.h" + +#ifdef SYSTEMDDAEMON_FOUND +# include +#else +# define SD_DEBUG "[ERROR]: " +# define SD_INFO "[INFO]: " +# define SD_WARNING "[WARNING]: " +# define SD_ERR "[ERROR]: " +#endif + // Macro defined to get the filename instead of the full path. But if it is // not properly defined by the build system, we fallback to __FILE__ #ifndef __FILENAME__ @@ -27,16 +38,16 @@ __FILENAME__ << ":" << __LINE__ #define log_debug(text)\ - Logger::instance()->get_stream(debug_lvl) << "[DEBUG]:" << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl; #define log_info(text)\ - Logger::instance()->get_stream(info_lvl) << "[INFO]:" << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl; #define log_warning(text)\ - Logger::instance()->get_stream(warning_lvl) << "[WARNING]:" << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl; #define log_error(text)\ - Logger::instance()->get_stream(error_lvl) << "[ERROR]:" << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl; /** * Juste a structure representing a stream doing nothing with its input. -- cgit v1.2.3 From 7fcc77f52e0bc0797a6c750fe3a8afc1bc5ed1ee Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 01:11:11 +0200 Subject: Provide a spec file to build a Fedora RPM --- packaging/biboumi.spec | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 packaging/biboumi.spec diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec new file mode 100644 index 0000000..5c5491b --- /dev/null +++ b/packaging/biboumi.spec @@ -0,0 +1,77 @@ +Name: biboumi +Version: 1.0 +Release: 1%{?dist} +Summary: Lightweight XMPP to IRC gateway + +License: zlib +URL: http://biboumi.louiz.org +Source0: http://biboumi.louiz.org/biboumi-1.0.tar.xz + +BuildRequires: libidn-devel +BuildRequires: expat-devel +BuildRequires: libuuid-devel +BuildRequires: systemd-devel +BuildRequires: cmake +BuildRequires: systemd +BuildRequires: rubygem-ronn + +%global biboumi_user %{name} +%global biboumi_group %{biboumi_user} +%global biboumi_confdir %{_sysconfdir}/%{name} + + +%description +An XMPP gateway that connects to IRC servers and translates between the two +protocols. It can be used to access IRC channels using any XMPP client as if +these channels were XMPP MUCs. + + +%prep +%setup -q + + +%build +cmake . -DCMAKE_BUILD_TYPE=release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DPOLLER=EPOLL +# The documentation is in utf-8, ronn fails to build it if that locale is +# not specified +make %{?_smp_mflags} +LC_ALL=en_GB.utf-8 make doc + + +%install +make install DESTDIR=%{buildroot} + +# Default config file +install -D -p -m 644 conf/biboumi.cfg \ + %{buildroot}%{biboumi_confdir}/biboumi.cfg + +# Systemd unit file +install -D -p -m 644 unit/%{name}.service \ + %{buildroot}%{_unitdir}/%{name}.service + +# Create default log directory +install -p -d %{buildroot}%{biboumi_logdir} + + +%pre +getent group %{biboumi_group} > /dev/null || groupadd -r %{biboumi_group} +getent passwd %{biboumi_user} > /dev/null || \ + useradd -r -g %{biboumi_group} \ + -s /sbin/nologin -c "Biboumi XMPP to IRC gateway" %{biboumi_user} +exit 0 + + +%files +%{_bindir}/%{name} +%{_mandir}/man1/%{name}.1* +%doc README COPYING doc/biboumi.1.md +%{_unitdir}/%{name}.service +%dir %{biboumi_logdir} +%config(noreplace) %{biboumi_confdir}/biboumi.cfg + + +%changelog +* Wed Jun 25 2014 Le Coz Florent - 1.0-1 +- Spec file written from scratch -- cgit v1.2.3 From 735ae278f333a3057c6afbd1e1b6a36cae340c62 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 01:27:41 +0200 Subject: Enable assert() even in release mode, in the test_suite --- src/test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test.cpp b/src/test.cpp index 87ab49c..85f1bad 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -20,6 +20,7 @@ #include #include +#undef NDEBUG #include static const std::string color(""); -- cgit v1.2.3 From cec9fed1f409c193bebf50b97e0b68ce48d4df9a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 01:33:45 +0200 Subject: Fix a typo --- src/test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.cpp b/src/test.cpp index 85f1bad..1b7a873 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -58,7 +58,7 @@ int main() std::cout << color << "Testing logging…" << reset << std::endl; log_debug("If you see this, the test FAILED."); log_info("If you see this, the test FAILED."); - log_warning("You wust see this message. And the next one too."); + log_warning("You must see this message. And the next one too."); log_error("It’s not an error, don’t worry, the test passed."); -- cgit v1.2.3 From eb76171aa00f319d55002a039bd9a042b977dfc0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 01:40:26 +0200 Subject: Run the test_suite in the spec %check section --- packaging/biboumi.spec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 5c5491b..e91ce23 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -63,6 +63,12 @@ getent passwd %{biboumi_user} > /dev/null || \ exit 0 +%check +make test_suite/fast VERBOSE=1 + +./test_suite || exit 1 + + %files %{_bindir}/%{name} %{_mandir}/man1/%{name}.1* -- cgit v1.2.3 From d64626648e90d3cd5446184851456308e438eb49 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 01:40:47 +0200 Subject: Remove biboumi_logdir from the spec file --- packaging/biboumi.spec | 1 - 1 file changed, 1 deletion(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index e91ce23..77d275d 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -74,7 +74,6 @@ make test_suite/fast VERBOSE=1 %{_mandir}/man1/%{name}.1* %doc README COPYING doc/biboumi.1.md %{_unitdir}/%{name}.service -%dir %{biboumi_logdir} %config(noreplace) %{biboumi_confdir}/biboumi.cfg -- cgit v1.2.3 From b0ae8c66e34cd253b97da09ef0aaca9261e022a2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Jun 2014 02:45:15 +0200 Subject: Also remove that from the spec file --- packaging/biboumi.spec | 3 --- 1 file changed, 3 deletions(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 77d275d..33464c6 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -51,9 +51,6 @@ install -D -p -m 644 conf/biboumi.cfg \ install -D -p -m 644 unit/%{name}.service \ %{buildroot}%{_unitdir}/%{name}.service -# Create default log directory -install -p -d %{buildroot}%{biboumi_logdir} - %pre getent group %{biboumi_group} > /dev/null || groupadd -r %{biboumi_group} -- cgit v1.2.3 From 04d999168ac4629f5e49939f3659b32b2da2563d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 2 Jul 2014 03:01:09 +0200 Subject: Add a level of inheritance above SocketHandler SocketHandler has been renamed to TCPSocketHandler SocketHandler is now a simple interface with a few methods, used only by Poller. This way we can inherite from the new SocketHandler class, to handle other types of sockets, and still make them manageable by the poller without any change in the Poller class. --- src/irc/irc_client.cpp | 2 +- src/irc/irc_client.hpp | 4 +- src/main.cpp | 1 + src/network/socket_handler.cpp | 424 ------------------------------------- src/network/socket_handler.hpp | 277 +----------------------- src/network/tcp_socket_handler.cpp | 424 +++++++++++++++++++++++++++++++++++++ src/network/tcp_socket_handler.hpp | 280 ++++++++++++++++++++++++ src/xmpp/xmpp_component.cpp | 2 +- src/xmpp/xmpp_component.hpp | 4 +- 9 files changed, 721 insertions(+), 697 deletions(-) delete mode 100644 src/network/socket_handler.cpp create mode 100644 src/network/tcp_socket_handler.cpp create mode 100644 src/network/tcp_socket_handler.hpp diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d179aaa..6468094 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -20,7 +20,7 @@ using namespace std::string_literals; using namespace std::chrono_literals; IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge): - SocketHandler(poller), + TCPSocketHandler(poller), hostname(hostname), username(username), current_nick(username), diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7dff1db..afa6437 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include @@ -21,7 +21,7 @@ class Bridge; * Represent one IRC client, i.e. an endpoint connected to a single IRC * server, through a TCP socket, receiving and sending commands to it. */ -class IrcClient: public SocketHandler +class IrcClient: public TCPSocketHandler { public: explicit IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge); diff --git a/src/main.cpp b/src/main.cpp index 2e2f1b2..d9f6a9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp deleted file mode 100644 index 52297e4..0000000 --- a/src/network/socket_handler.cpp +++ /dev/null @@ -1,424 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifdef BOTAN_FOUND -# include - -Botan::AutoSeeded_RNG SocketHandler::rng; -Permissive_Credentials_Manager SocketHandler::credential_manager; -Botan::TLS::Policy SocketHandler::policy; -Botan::TLS::Session_Manager_In_Memory SocketHandler::session_manager(SocketHandler::rng); - -#endif - -#ifndef UIO_FASTIOV -# define UIO_FASTIOV 8 -#endif - -using namespace std::string_literals; -using namespace std::chrono_literals; - -namespace ph = std::placeholders; - -SocketHandler::SocketHandler(std::shared_ptr poller): - socket(-1), - poller(poller), - use_tls(false), - connected(false), - connecting(false) -{} - -void SocketHandler::init_socket(const struct addrinfo* rp) -{ - if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) - throw std::runtime_error("Could not create socket: "s + strerror(errno)); - int optval = 1; - if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) - log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); - // Set the socket on non-blocking mode. This is useful to receive a EAGAIN - // error when connect() would block, to not block the whole process if a - // remote is not responsive. - const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); - if ((existing_flags == -1) || - (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) - throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); -} - -void SocketHandler::connect(const std::string& address, const std::string& port, const bool tls) -{ - this->address = address; - this->port = port; - this->use_tls = tls; - - utils::ScopeGuard sg; - - struct addrinfo* addr_res; - - if (!this->connecting) - { - log_info("Trying to connect to " << address << ":" << port); - // Get the addrinfo from getaddrinfo, only if this is the first call - // of this function. - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - - if (res != 0) - { - log_warning("getaddrinfo failed: "s + gai_strerror(res)); - this->close(); - this->on_connection_failed(gai_strerror(res)); - return ; - } - // Make sure the alloced structure is always freed at the end of the - // function - sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); - } - else - { // This function is called again, use the saved addrinfo structure, - // instead of re-doing the whole getaddrinfo process. - addr_res = &this->addrinfo; - } - - for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) - { - if (!this->connecting) - { - try { - this->init_socket(rp); - } - catch (const std::runtime_error& error) { - log_error("Failed to init socket: " << error.what()); - break; - } - } - if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 - || errno == EISCONN) - { - log_info("Connection success."); - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - this->poller->add_socket_handler(this); - this->connected = true; - this->connecting = false; -#ifdef BOTAN_FOUND - if (this->use_tls) - this->start_tls(); -#endif - this->on_connected(); - return ; - } - else if (errno == EINPROGRESS || errno == EALREADY) - { // retry this process later, when the socket - // is ready to be written on. - this->connecting = true; - this->poller->add_socket_handler(this); - this->poller->watch_send_events(this); - // Save the addrinfo structure, to use it on the next call - this->ai_addrlen = rp->ai_addrlen; - memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); - memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); - this->addrinfo.ai_addr = &this->ai_addr; - this->addrinfo.ai_next = nullptr; - // If the connection has not succeeded or failed in 5s, we consider - // it to have failed - TimedEventsManager::instance().add_event( - TimedEvent(std::chrono::steady_clock::now() + 5s, - std::bind(&SocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); - return ; - } - log_info("Connection failed:" << strerror(errno)); - } - log_error("All connection attempts failed."); - this->close(); - this->on_connection_failed(strerror(errno)); - return ; -} - -void SocketHandler::on_connection_timeout() -{ - this->close(); - this->on_connection_failed("connection timed out"); -} - -void SocketHandler::connect() -{ - this->connect(this->address, this->port, this->use_tls); -} - -void SocketHandler::on_recv() -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_recv(); - else -#endif - this->plain_recv(); -} - -void SocketHandler::plain_recv() -{ - static constexpr size_t buf_size = 4096; - char buf[buf_size]; - void* recv_buf = this->get_receive_buffer(buf_size); - - if (recv_buf == nullptr) - recv_buf = buf; - - const ssize_t size = this->do_recv(recv_buf, buf_size); - - if (size > 0) - { - if (buf == recv_buf) - { - // data needs to be placed in the in_buf string, because no buffer - // was provided to receive that data directly. The in_buf buffer - // will be handled in parse_in_buffer() - this->in_buf += std::string(buf, size); - } - this->parse_in_buffer(size); - } -} - -ssize_t SocketHandler::do_recv(void* recv_buf, const size_t buf_size) -{ - ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); - if (0 == size) - { - this->on_connection_close(); - this->close(); - } - else if (-1 == size) - { - log_warning("Error while reading from socket: " << strerror(errno)); - if (this->connecting) - { - this->close(); - this->on_connection_failed(strerror(errno)); - } - else - { - this->close(); - this->on_connection_close(); - } - } - return size; -} - -void SocketHandler::on_send() -{ - struct iovec msg_iov[UIO_FASTIOV] = {}; - struct msghdr msg{nullptr, 0, - msg_iov, - 0, nullptr, 0, 0}; - for (std::string& s: this->out_buf) - { - // unconsting the content of s is ok, sendmsg will never modify it - msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); - msg_iov[msg.msg_iovlen].iov_len = s.size(); - if (++msg.msg_iovlen == UIO_FASTIOV) - break; - } - ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); - if (res < 0) - { - log_error("sendmsg failed: " << strerror(errno)); - this->on_connection_close(); - this->close(); - } - else - { - // remove all the strings that were successfully sent. - for (auto it = this->out_buf.begin(); - it != this->out_buf.end();) - { - if (static_cast(res) >= (*it).size()) - { - res -= (*it).size(); - it = this->out_buf.erase(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); - break; - } - } - if (this->out_buf.empty()) - this->poller->stop_watching_send_events(this); - } -} - -void SocketHandler::close() -{ - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - if (this->connected || this->connecting) - this->poller->remove_socket_handler(this->get_socket()); - if (this->socket != -1) - { - ::close(this->socket); - this->socket = -1; - } - this->connected = false; - this->connecting = false; - this->in_buf.clear(); - this->out_buf.clear(); - this->port.clear(); -} - -socket_t SocketHandler::get_socket() const -{ - return this->socket; -} - -void SocketHandler::send_data(std::string&& data) -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_send(std::move(data)); - else -#endif - this->raw_send(std::move(data)); -} - -void SocketHandler::raw_send(std::string&& data) -{ - if (data.empty()) - return ; - this->out_buf.emplace_back(std::move(data)); - if (this->connected) - this->poller->watch_send_events(this); -} - -void SocketHandler::send_pending_data() -{ - if (this->connected && !this->out_buf.empty()) - this->poller->watch_send_events(this); -} - -bool SocketHandler::is_connected() const -{ - return this->connected; -} - -bool SocketHandler::is_connecting() const -{ - return this->connecting; -} - -void* SocketHandler::get_receive_buffer(const size_t) const -{ - return nullptr; -} - -#ifdef BOTAN_FOUND -void SocketHandler::start_tls() -{ - Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); - this->tls = std::make_unique( - std::bind(&SocketHandler::tls_output_fn, this, ph::_1, ph::_2), - std::bind(&SocketHandler::tls_data_cb, this, ph::_1, ph::_2), - std::bind(&SocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), - std::bind(&SocketHandler::tls_handshake_cb, this, ph::_1), - session_manager, credential_manager, policy, - rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); -} - -void SocketHandler::tls_recv() -{ - static constexpr size_t buf_size = 4096; - char 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(); - this->tls->received_data(reinterpret_cast(recv_buf), - static_cast(size)); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } -} - -void SocketHandler::tls_send(std::string&& data) -{ - if (this->tls->is_active()) - { - const bool was_active = this->tls->is_active(); - if (!this->pre_buf.empty()) - { - this->tls->send(reinterpret_cast(this->pre_buf.data()), - this->pre_buf.size()); - this->pre_buf = ""; - } - if (!data.empty()) - this->tls->send(reinterpret_cast(data.data()), - data.size()); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } - else - this->pre_buf += data; -} - -void SocketHandler::tls_data_cb(const Botan::byte* data, size_t size) -{ - this->in_buf += std::string(reinterpret_cast(data), - size); - if (!this->in_buf.empty()) - this->parse_in_buffer(size); -} - -void SocketHandler::tls_output_fn(const Botan::byte* data, size_t size) -{ - this->raw_send(std::string(reinterpret_cast(data), size)); -} - -void SocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) -{ - log_debug("tls_alert: " << alert.type_string()); -} - -bool SocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) -{ - log_debug("Handshake with " << session.server_info().hostname() << " complete." - << " Version: " << session.version().to_string() - << " using " << session.ciphersuite().to_string()); - if (!session.session_id().empty()) - log_debug("Session ID " << Botan::hex_encode(session.session_id())); - if (!session.session_ticket().empty()) - log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); - return true; -} - -void SocketHandler::on_tls_activated() -{ - this->send_data(""); -} -#endif // BOTAN_FOUND diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index b4cb6f2..b3a08d3 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,280 +1,23 @@ -#ifndef SOCKET_HANDLER_INCLUDED -# define SOCKET_HANDLER_INCLUDED - -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include "config.h" - -#ifdef BOTAN_FOUND -# include -# include - -/** - * A very simple credential manager that accepts any certificate. - */ -class Permissive_Credentials_Manager: public Botan::Credentials_Manager -{ -public: - void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) - { // TODO: Offer the admin to disallow connection on untrusted - // certificates - log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); - } -}; -#endif // BOTAN_FOUND +#ifndef SOCKET_HANDLER_INTERFACE_HPP +# define SOCKET_HANDLER_INTERFACE_HPP typedef int socket_t; -class Poller; - -/** - * An interface, with a series of callbacks that should be implemented in - * subclasses that deal with a socket. These callbacks are called on various events - * (read/write/timeout, etc) when they are notified to a poller - * (select/poll/epoll etc) - */ class SocketHandler { -protected: - ~SocketHandler() {} - public: - explicit SocketHandler(std::shared_ptr poller); - /** - * Connect to the remote server, and call on_connected() if this - * succeeds. If tls is true, we set use_tls to true and will also call - * start_tls() when the connection succeeds. - */ - void connect(const std::string& address, const std::string& port, const bool tls); - void connect(); - /** - * Reads raw data from the socket. And pass it to parse_in_buffer() - * If we are using TLS on this connection, we call tls_recv() - */ - void on_recv(); - /** - * Write as much data from out_buf as possible, in the socket. - */ - void on_send(); - /** - * Add the given data to out_buf and tell our poller that we want to be - * notified when a send event is ready. - * - * This can be overriden if we want to modify the data before sending - * it. For example if we want to encrypt it. - */ - void send_data(std::string&& data); - /** - * Watch the socket for send events, if our out buffer is not empty. - */ - void send_pending_data(); - /** - * Returns the socket that should be handled by the poller. - */ - socket_t get_socket() const; - /** - * Close the connection, remove us from the poller - */ - void close(); - /** - * Called by a TimedEvent, when the connection did not succeed or fail - * after a given time. - */ - void on_connection_timeout(); - /** - * Called when the connection is successful. - */ - virtual void on_connected() = 0; - /** - * Called when the connection fails. Not when it is closed later, just at - * the connect() call. - */ - virtual void on_connection_failed(const std::string& reason) = 0; - /** - * Called when we detect a disconnection from the remote host. - */ - virtual void on_connection_close() = 0; - /** - * Handle/consume (some of) the data received so far. The data to handle - * may be in the in_buf buffer, or somewhere else, depending on what - * get_receive_buffer() returned. If some data is used from in_buf, it - * should be truncated, only the unused data should be left untouched. - * - * The size argument is the size of the last chunk of data that was added to the buffer. - */ - virtual void parse_in_buffer(const size_t size) = 0; - bool is_connected() const; - bool is_connecting() const; - -private: - /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * Reads from the socket into the provided buffer. If an error occurs - * (read returns <= 0), the handling of the error is done here (close the - * connection, log a message, etc). - * - * Returns the value returned by ::recv(), so the buffer should not be - * used if it’s not positive. - */ - ssize_t do_recv(void* recv_buf, const size_t buf_size); - /** - * Reads data from the socket and calls parse_in_buffer with it. - */ - void plain_recv(); - /** - * Mark the given data as ready to be sent, as-is, on the socket, as soon - * as we can. - */ - void raw_send(std::string&& data); - -#ifdef BOTAN_FOUND - /** - * Create the TLS::Client object, with all the callbacks etc. This must be - * called only when we know we are able to send TLS-encrypted data over - * the socket. - */ - void start_tls(); - /** - * An additional step to pass the data into our tls object to decrypt it - * before passing it to parse_in_buffer. - */ - void tls_recv(); - /** - * Pass the data to the tls object in order to encrypt it. The tls object - * will then call raw_send as a callback whenever data as been encrypted - * and can be sent on the socket. - */ - void tls_send(std::string&& data); - /** - * Called by the tls object that some data has been decrypt. We call - * parse_in_buffer() to handle that unencrypted data. - */ - void tls_data_cb(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that some data has been encrypted - * and is now ready to be sent on the socket as is. - */ - void tls_output_fn(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that a TLS alert has been - * received. We don’t use it, we just log some message, at the moment. - */ - void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); - /** - * Called by the tls object at the end of the TLS handshake. We don't do - * anything here appart from logging the TLS session information. - */ - bool tls_handshake_cb(const Botan::TLS::Session& session); - /** - * Called whenever the tls session goes from inactive to active. This - * means that the handshake has just been successfully done, and we can - * now proceed to send any available data into our tls object. - */ - void on_tls_activated(); -#endif // BOTAN_FOUND - /** - * The handled socket. - */ - socket_t socket; - /** - * Where data is added, when we want to send something to the client. - */ - std::list out_buf; - /** - * Keep the details of the addrinfo the triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. - */ - struct addrinfo addrinfo; - struct sockaddr ai_addr; - socklen_t ai_addrlen; - -protected: - /** - * A pointer to the poller that manages us, because we need to communicate - * with it, sometimes (for example to tell it that he now needs to watch - * write events for us). Do not ever try to delete it. - * - * And a raw pointer because we are not owning it, it is owning us - * (actually it is sharing our ownership with a Bridge). - */ - std::shared_ptr poller; - /** - * Where data read from the socket is added until we can extract a full - * and meaningful “message” from it. - * - * TODO: something more efficient than a string. - */ - std::string in_buf; - /** - * Whether we are using TLS on this connection or not. - */ - bool use_tls; - /** - * Provide a buffer in which data can be directly received. This can be - * used to avoid copying data into in_buf before using it. If no buffer - * needs to be provided, nullptr is returned (the default implementation - * does that), in that case our internal in_buf will be used to save the - * data until it can be used by parse_in_buffer(). - */ - virtual void* get_receive_buffer(const size_t size) const; - /** - * Hostname we are connected/connecting to - */ - std::string address; - /** - * Port we are connected/connecting to - */ - std::string port; - - bool connected; - bool connecting; - + SocketHandler() {} + virtual ~SocketHandler() {} + virtual socket_t get_socket() const = 0; + virtual void on_recv() = 0; + virtual void on_send() = 0; + virtual void connect() = 0; + virtual bool is_connected() const = 0; private: SocketHandler(const SocketHandler&) = delete; SocketHandler(SocketHandler&&) = delete; SocketHandler& operator=(const SocketHandler&) = delete; SocketHandler& operator=(SocketHandler&&) = delete; - -#ifdef BOTAN_FOUND - /** - * Botan stuff to manipulate a TLS session. - */ - static Botan::AutoSeeded_RNG rng; - static Permissive_Credentials_Manager credential_manager; - static Botan::TLS::Policy policy; - static Botan::TLS::Session_Manager_In_Memory session_manager; - /** - * We use a unique_ptr because we may not want to create the object at - * all. The Botan::TLS::Client object generates a handshake message as - * soon and calls the output_fn callback with it as soon as it is - * created. Therefore, we do not want to create it if do not intend to do - * send any TLS-encrypted message. We create the object only when needed - * (for example after we have negociated a TLS session using a STARTTLS - * message, or stuf like that). - * - * See start_tls for the method where this object is created. - */ - std::unique_ptr tls; - /** - * An additional buffer to keep data that the user wants to send, but - * cannot because the handshake is not done. - */ - std::string pre_buf; -#endif // BOTAN_FOUND }; -#endif // SOCKET_HANDLER_INCLUDED +#endif // SOCKET_HANDLER_INTERFACE_HPP diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp new file mode 100644 index 0000000..f6b37de --- /dev/null +++ b/src/network/tcp_socket_handler.cpp @@ -0,0 +1,424 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef BOTAN_FOUND +# include + +Botan::AutoSeeded_RNG TCPSocketHandler::rng; +Permissive_Credentials_Manager TCPSocketHandler::credential_manager; +Botan::TLS::Policy TCPSocketHandler::policy; +Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); + +#endif + +#ifndef UIO_FASTIOV +# define UIO_FASTIOV 8 +#endif + +using namespace std::string_literals; +using namespace std::chrono_literals; + +namespace ph = std::placeholders; + +TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): + socket(-1), + poller(poller), + use_tls(false), + connected(false), + connecting(false) +{} + +void TCPSocketHandler::init_socket(const struct addrinfo* rp) +{ + if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) + throw std::runtime_error("Could not create socket: "s + strerror(errno)); + int optval = 1; + if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) + log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); + // Set the socket on non-blocking mode. This is useful to receive a EAGAIN + // error when connect() would block, to not block the whole process if a + // remote is not responsive. + const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); + if ((existing_flags == -1) || + (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) + throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); +} + +void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) +{ + this->address = address; + this->port = port; + this->use_tls = tls; + + utils::ScopeGuard sg; + + struct addrinfo* addr_res; + + if (!this->connecting) + { + log_info("Trying to connect to " << address << ":" << port); + // Get the addrinfo from getaddrinfo, only if this is the first call + // of this function. + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); + + if (res != 0) + { + log_warning("getaddrinfo failed: "s + gai_strerror(res)); + this->close(); + this->on_connection_failed(gai_strerror(res)); + return ; + } + // Make sure the alloced structure is always freed at the end of the + // function + sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); + } + else + { // This function is called again, use the saved addrinfo structure, + // instead of re-doing the whole getaddrinfo process. + addr_res = &this->addrinfo; + } + + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) + { + if (!this->connecting) + { + try { + this->init_socket(rp); + } + catch (const std::runtime_error& error) { + log_error("Failed to init socket: " << error.what()); + break; + } + } + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 + || errno == EISCONN) + { + log_info("Connection success."); + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + this->poller->add_socket_handler(this); + this->connected = true; + this->connecting = false; +#ifdef BOTAN_FOUND + if (this->use_tls) + this->start_tls(); +#endif + this->on_connected(); + return ; + } + else if (errno == EINPROGRESS || errno == EALREADY) + { // retry this process later, when the socket + // is ready to be written on. + this->connecting = true; + this->poller->add_socket_handler(this); + this->poller->watch_send_events(this); + // Save the addrinfo structure, to use it on the next call + this->ai_addrlen = rp->ai_addrlen; + memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); + memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); + this->addrinfo.ai_addr = &this->ai_addr; + this->addrinfo.ai_next = nullptr; + // If the connection has not succeeded or failed in 5s, we consider + // it to have failed + TimedEventsManager::instance().add_event( + TimedEvent(std::chrono::steady_clock::now() + 5s, + std::bind(&TCPSocketHandler::on_connection_timeout, this), + "connection_timeout"s + std::to_string(this->socket))); + return ; + } + log_info("Connection failed:" << strerror(errno)); + } + log_error("All connection attempts failed."); + this->close(); + this->on_connection_failed(strerror(errno)); + return ; +} + +void TCPSocketHandler::on_connection_timeout() +{ + this->close(); + this->on_connection_failed("connection timed out"); +} + +void TCPSocketHandler::connect() +{ + this->connect(this->address, this->port, this->use_tls); +} + +void TCPSocketHandler::on_recv() +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_recv(); + else +#endif + this->plain_recv(); +} + +void TCPSocketHandler::plain_recv() +{ + static constexpr size_t buf_size = 4096; + char buf[buf_size]; + void* recv_buf = this->get_receive_buffer(buf_size); + + if (recv_buf == nullptr) + recv_buf = buf; + + const ssize_t size = this->do_recv(recv_buf, buf_size); + + if (size > 0) + { + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); + } +} + +ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) +{ + ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); + if (0 == size) + { + this->on_connection_close(); + this->close(); + } + else if (-1 == size) + { + log_warning("Error while reading from socket: " << strerror(errno)); + if (this->connecting) + { + this->close(); + this->on_connection_failed(strerror(errno)); + } + else + { + this->close(); + this->on_connection_close(); + } + } + return size; +} + +void TCPSocketHandler::on_send() +{ + struct iovec msg_iov[UIO_FASTIOV] = {}; + struct msghdr msg{nullptr, 0, + msg_iov, + 0, nullptr, 0, 0}; + for (std::string& s: this->out_buf) + { + // unconsting the content of s is ok, sendmsg will never modify it + msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); + msg_iov[msg.msg_iovlen].iov_len = s.size(); + if (++msg.msg_iovlen == UIO_FASTIOV) + break; + } + ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); + if (res < 0) + { + log_error("sendmsg failed: " << strerror(errno)); + this->on_connection_close(); + this->close(); + } + else + { + // remove all the strings that were successfully sent. + for (auto it = this->out_buf.begin(); + it != this->out_buf.end();) + { + if (static_cast(res) >= (*it).size()) + { + res -= (*it).size(); + it = this->out_buf.erase(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); + break; + } + } + if (this->out_buf.empty()) + this->poller->stop_watching_send_events(this); + } +} + +void TCPSocketHandler::close() +{ + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + if (this->connected || this->connecting) + this->poller->remove_socket_handler(this->get_socket()); + if (this->socket != -1) + { + ::close(this->socket); + this->socket = -1; + } + this->connected = false; + this->connecting = false; + this->in_buf.clear(); + this->out_buf.clear(); + this->port.clear(); +} + +socket_t TCPSocketHandler::get_socket() const +{ + return this->socket; +} + +void TCPSocketHandler::send_data(std::string&& data) +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_send(std::move(data)); + else +#endif + this->raw_send(std::move(data)); +} + +void TCPSocketHandler::raw_send(std::string&& data) +{ + if (data.empty()) + return ; + this->out_buf.emplace_back(std::move(data)); + if (this->connected) + this->poller->watch_send_events(this); +} + +void TCPSocketHandler::send_pending_data() +{ + if (this->connected && !this->out_buf.empty()) + this->poller->watch_send_events(this); +} + +bool TCPSocketHandler::is_connected() const +{ + return this->connected; +} + +bool TCPSocketHandler::is_connecting() const +{ + return this->connecting; +} + +void* TCPSocketHandler::get_receive_buffer(const size_t) const +{ + return nullptr; +} + +#ifdef BOTAN_FOUND +void TCPSocketHandler::start_tls() +{ + Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); + this->tls = std::make_unique( + std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2), + std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2), + std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), + std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1), + session_manager, credential_manager, policy, + rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); +} + +void TCPSocketHandler::tls_recv() +{ + static constexpr size_t buf_size = 4096; + char 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(); + this->tls->received_data(reinterpret_cast(recv_buf), + static_cast(size)); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } +} + +void TCPSocketHandler::tls_send(std::string&& data) +{ + if (this->tls->is_active()) + { + const bool was_active = this->tls->is_active(); + if (!this->pre_buf.empty()) + { + this->tls->send(reinterpret_cast(this->pre_buf.data()), + this->pre_buf.size()); + this->pre_buf = ""; + } + if (!data.empty()) + this->tls->send(reinterpret_cast(data.data()), + data.size()); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } + else + this->pre_buf += data; +} + +void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) +{ + this->in_buf += std::string(reinterpret_cast(data), + size); + if (!this->in_buf.empty()) + this->parse_in_buffer(size); +} + +void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size) +{ + this->raw_send(std::string(reinterpret_cast(data), size)); +} + +void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) +{ + log_debug("tls_alert: " << alert.type_string()); +} + +bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) +{ + log_debug("Handshake with " << session.server_info().hostname() << " complete." + << " Version: " << session.version().to_string() + << " using " << session.ciphersuite().to_string()); + if (!session.session_id().empty()) + log_debug("Session ID " << Botan::hex_encode(session.session_id())); + if (!session.session_ticket().empty()) + log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); + return true; +} + +void TCPSocketHandler::on_tls_activated() +{ + this->send_data(""); +} +#endif // BOTAN_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp new file mode 100644 index 0000000..8ed3b67 --- /dev/null +++ b/src/network/tcp_socket_handler.hpp @@ -0,0 +1,280 @@ +#ifndef SOCKET_HANDLER_INCLUDED +# define SOCKET_HANDLER_INCLUDED + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "config.h" + +#ifdef BOTAN_FOUND +# include +# include + +/** + * A very simple credential manager that accepts any certificate. + */ +class Permissive_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) + { // TODO: Offer the admin to disallow connection on untrusted + // certificates + log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + } +}; +#endif // BOTAN_FOUND + +class Poller; + +/** + * An interface, with a series of callbacks that should be implemented in + * subclasses that deal with a socket. These callbacks are called on various events + * (read/write/timeout, etc) when they are notified to a poller + * (select/poll/epoll etc) + */ +class TCPSocketHandler: SocketHandler +{ +protected: + ~TCPSocketHandler() {} + +public: + explicit TCPSocketHandler(std::shared_ptr poller); + /** + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. + */ + void connect(const std::string& address, const std::string& port, const bool tls); + void connect(); + /** + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() + */ + void on_recv(); + /** + * Write as much data from out_buf as possible, in the socket. + */ + void on_send(); + /** + * Add the given data to out_buf and tell our poller that we want to be + * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. + */ + void send_data(std::string&& data); + /** + * Watch the socket for send events, if our out buffer is not empty. + */ + void send_pending_data(); + /** + * Returns the socket that should be handled by the poller. + */ + socket_t get_socket() const; + /** + * Close the connection, remove us from the poller + */ + void close(); + /** + * Called by a TimedEvent, when the connection did not succeed or fail + * after a given time. + */ + void on_connection_timeout(); + /** + * Called when the connection is successful. + */ + virtual void on_connected() = 0; + /** + * Called when the connection fails. Not when it is closed later, just at + * the connect() call. + */ + virtual void on_connection_failed(const std::string& reason) = 0; + /** + * Called when we detect a disconnection from the remote host. + */ + virtual void on_connection_close() = 0; + /** + * Handle/consume (some of) the data received so far. The data to handle + * may be in the in_buf buffer, or somewhere else, depending on what + * get_receive_buffer() returned. If some data is used from in_buf, it + * should be truncated, only the unused data should be left untouched. + * + * The size argument is the size of the last chunk of data that was added to the buffer. + */ + virtual void parse_in_buffer(const size_t size) = 0; + bool is_connected() const; + bool is_connecting() const; + +private: + /** + * Initialize the socket with the parameters contained in the given + * addrinfo structure. + */ + void init_socket(const struct addrinfo* rp); + /** + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. + */ + ssize_t do_recv(void* recv_buf, const size_t buf_size); + /** + * Reads data from the socket and calls parse_in_buffer with it. + */ + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(); + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_data_cb(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_output_fn(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_handshake_cb(const Botan::TLS::Session& session); + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * The handled socket. + */ + socket_t socket; + /** + * Where data is added, when we want to send something to the client. + */ + std::list out_buf; + /** + * Keep the details of the addrinfo the triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct addrinfo addrinfo; + struct sockaddr ai_addr; + socklen_t ai_addrlen; + +protected: + /** + * A pointer to the poller that manages us, because we need to communicate + * with it, sometimes (for example to tell it that he now needs to watch + * write events for us). Do not ever try to delete it. + * + * And a raw pointer because we are not owning it, it is owning us + * (actually it is sharing our ownership with a Bridge). + */ + std::shared_ptr poller; + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + + bool connected; + bool connecting; + +private: + TCPSocketHandler(const TCPSocketHandler&) = delete; + TCPSocketHandler(TCPSocketHandler&&) = delete; + TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; + TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + +#ifdef BOTAN_FOUND + /** + * Botan stuff to manipulate a TLS session. + */ + static Botan::AutoSeeded_RNG rng; + static Permissive_Credentials_Manager credential_manager; + static Botan::TLS::Policy policy; + static Botan::TLS::Session_Manager_In_Memory session_manager; + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message as + * soon and calls the output_fn callback with it as soon as it is + * created. Therefore, we do not want to create it if do not intend to do + * send any TLS-encrypted message. We create the object only when needed + * (for example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::string pre_buf; +#endif // BOTAN_FOUND +}; + +#endif // SOCKET_HANDLER_INCLUDED diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1e8f9e0..51d65aa 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -37,7 +37,7 @@ static std::set kickable_errors{ }; XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): - SocketHandler(poller), + TCPSocketHandler(poller), ever_auth(false), last_auth(false), served_hostname(hostname), diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index ce594ec..42cdb8a 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -2,7 +2,7 @@ # define XMPP_COMPONENT_INCLUDED #include -#include +#include #include #include @@ -30,7 +30,7 @@ * * TODO: implement XEP-0225: Component Connections */ -class XmppComponent: public SocketHandler +class XmppComponent: public TCPSocketHandler { public: explicit XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); -- cgit v1.2.3 From 8c34576ea0d97a22760a68aec228c76ecf25ab60 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Jul 2014 02:58:27 +0200 Subject: Move some members of TCPSocketHandler into the SocketHandler class --- src/network/socket_handler.hpp | 30 +++++++++++++++++++++++++----- src/network/tcp_socket_handler.cpp | 8 +------- src/network/tcp_socket_handler.hpp | 21 +-------------------- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index b3a08d3..9a894a4 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,18 +1,38 @@ -#ifndef SOCKET_HANDLER_INTERFACE_HPP -# define SOCKET_HANDLER_INTERFACE_HPP +#ifndef SOCKET_HANDLER_HPP +# define SOCKET_HANDLER_HPP + +#include + +class Poller; typedef int socket_t; class SocketHandler { public: - SocketHandler() {} + explicit SocketHandler(std::shared_ptr poller, const socket_t socket): + poller(poller), + socket(socket) + {} virtual ~SocketHandler() {} - virtual socket_t get_socket() const = 0; virtual void on_recv() = 0; virtual void on_send() = 0; virtual void connect() = 0; virtual bool is_connected() const = 0; + socket_t get_socket() const + { return this->socket; } + +protected: + /** + * A pointer to the poller that manages us, because we need to communicate + * with it. + */ + std::shared_ptr poller; + /** + * The handled socket. + */ + socket_t socket; + private: SocketHandler(const SocketHandler&) = delete; SocketHandler(SocketHandler&&) = delete; @@ -20,4 +40,4 @@ private: SocketHandler& operator=(SocketHandler&&) = delete; }; -#endif // SOCKET_HANDLER_INTERFACE_HPP +#endif // SOCKET_HANDLER_HPP diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index f6b37de..d9432a6 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -38,8 +38,7 @@ using namespace std::chrono_literals; namespace ph = std::placeholders; TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): - socket(-1), - poller(poller), + SocketHandler(poller, -1), use_tls(false), connected(false), connecting(false) @@ -292,11 +291,6 @@ void TCPSocketHandler::close() this->port.clear(); } -socket_t TCPSocketHandler::get_socket() const -{ - return this->socket; -} - void TCPSocketHandler::send_data(std::string&& data) { #ifdef BOTAN_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index 8ed3b67..8416690 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -34,15 +34,13 @@ public: }; #endif // BOTAN_FOUND -class Poller; - /** * An interface, with a series of callbacks that should be implemented in * subclasses that deal with a socket. These callbacks are called on various events * (read/write/timeout, etc) when they are notified to a poller * (select/poll/epoll etc) */ -class TCPSocketHandler: SocketHandler +class TCPSocketHandler: public SocketHandler { protected: ~TCPSocketHandler() {} @@ -77,10 +75,6 @@ public: * Watch the socket for send events, if our out buffer is not empty. */ void send_pending_data(); - /** - * Returns the socket that should be handled by the poller. - */ - socket_t get_socket() const; /** * Close the connection, remove us from the poller */ @@ -185,10 +179,6 @@ private: */ void on_tls_activated(); #endif // BOTAN_FOUND - /** - * The handled socket. - */ - socket_t socket; /** * Where data is added, when we want to send something to the client. */ @@ -203,15 +193,6 @@ private: socklen_t ai_addrlen; protected: - /** - * A pointer to the poller that manages us, because we need to communicate - * with it, sometimes (for example to tell it that he now needs to watch - * write events for us). Do not ever try to delete it. - * - * And a raw pointer because we are not owning it, it is owning us - * (actually it is sharing our ownership with a Bridge). - */ - std::shared_ptr poller; /** * Where data read from the socket is added until we can extract a full * and meaningful “message” from it. -- cgit v1.2.3 From fa74493c4011793d453fb598d73e7e21d5760605 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 12 Jul 2014 14:17:02 +0200 Subject: Add a CHANGELOG file describing a general view of 1.0 features --- CHANGELOG | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..1e99ffc --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,14 @@ +Version 1.0 2014-12-07 + + - First stable release. + - Mostly complete MUC to IRC, and IRC to MUC support + - Complete handling of private messages + - Full IRC modes support: setting any IRC mode, and receiving notifications + for every mode change + - Verbose connection status notifications + - Conversion from IRC formatting to XHTML-im + - Ad-hoc commands support + - Basic TLS support: auto-accepts all certificates, no cipher + configuration, no way to force usage of TLS (it is used only if + available, clear connection is automatically used as a fallback) + - IPv6 support -- cgit v1.2.3 From b578aebc039a38b9c9234af98ae106d78b48c450 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 12 Jul 2014 14:17:23 +0200 Subject: Release version 1.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70ead76..48ce504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 1) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") -- cgit v1.2.3 From 4582f1079767f53bb6bd9b96c358ea3c641aaa96 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 12 Jul 2014 14:30:27 +0200 Subject: Bump master version to 2.0~dev --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48ce504..f68e7db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.6) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 1) +set(${PROJECT_NAME}_VERSION_MAJOR 2) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") -- cgit v1.2.3 From 11a31db2d5bcc158bb8902e74f192dbc82827f53 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Jul 2014 15:39:25 +0200 Subject: Send the reason of the connection close to the user --- src/irc/irc_client.cpp | 6 ++++-- src/irc/irc_client.hpp | 2 +- src/network/tcp_socket_handler.cpp | 15 +++++---------- src/network/tcp_socket_handler.hpp | 2 +- src/xmpp/xmpp_component.cpp | 11 +++++++++-- src/xmpp/xmpp_component.hpp | 2 +- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6468094..e518ffc 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -93,9 +93,11 @@ void IrcClient::on_connected() this->send_pending_data(); } -void IrcClient::on_connection_close() +void IrcClient::on_connection_close(const std::string& error_msg) { - static const std::string message = "Connection closed by remote server."; + std::string message = "Connection closed by remote server."; + if (!error_msg.empty()) + message += ": " + error_msg; const IrcMessage error{"ERROR", {message}}; this->on_error(error); log_warning(message); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index afa6437..88d2d72 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -41,7 +41,7 @@ public: /** * Close the connection, remove us from the poller */ - void on_connection_close() override final; + void on_connection_close(const std::string& error) override final; /** * Parse the data we have received so far and try to get one or more * complete messages from it. diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index d9432a6..01adf04 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -207,22 +207,17 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); if (0 == size) { - this->on_connection_close(); + this->on_connection_close(""); this->close(); } else if (-1 == size) { log_warning("Error while reading from socket: " << strerror(errno)); + this->close(); if (this->connecting) - { - this->close(); - this->on_connection_failed(strerror(errno)); - } + this->on_connection_failed(strerror(errno)); else - { - this->close(); - this->on_connection_close(); - } + this->on_connection_close(strerror(errno)); } return size; } @@ -245,7 +240,7 @@ void TCPSocketHandler::on_send() if (res < 0) { log_error("sendmsg failed: " << strerror(errno)); - this->on_connection_close(); + this->on_connection_close(strerror(errno)); this->close(); } else diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index 8416690..876cd57 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -96,7 +96,7 @@ public: /** * Called when we detect a disconnection from the remote host. */ - virtual void on_connection_close() = 0; + virtual void on_connection_close(const std::string& error) = 0; /** * Handle/consume (some of) the data received so far. The data to handle * may be in the in_buf buffer, or somewhere else, depending on what diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 51d65aa..6af67a4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -108,9 +108,16 @@ void XmppComponent::on_connected() this->send_pending_data(); } -void XmppComponent::on_connection_close() +void XmppComponent::on_connection_close(const std::string& error) { - log_info("XMPP server closed connection"); + if (error.empty()) + { + log_info("XMPP server closed connection"); + } + else + { + log_info("XMPP server closed connection: " << error); + } } void XmppComponent::parse_in_buffer(const size_t size) diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 42cdb8a..daadbec 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -38,7 +38,7 @@ public: void on_connection_failed(const std::string& reason) override final; void on_connected() override final; - void on_connection_close() override final; + void on_connection_close(const std::string& error) override final; void parse_in_buffer(const size_t size) override final; /** -- cgit v1.2.3 From b94247813bfdcb95b2b3cde9a7d27f90254f2a2f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Jul 2014 15:44:19 +0200 Subject: Provide WITHOUT_BOTAN and WITHOUT_SYSTEMD cmake flags Use them to build without linking to them, even if they are on your system --- CMakeLists.txt | 8 ++++++-- INSTALL | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f68e7db..14c002c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,8 +21,12 @@ find_package(EXPAT REQUIRED) find_package(Iconv REQUIRED) find_package(Libuuid REQUIRED) find_package(Libidn) -find_package(SystemdDaemon) -find_package(Botan) +if(NOT WITHOUT_SYSTEMD) + find_package(SystemdDaemon) +endif() +if(NOT WITHOUT_BOTAN) + find_package(Botan) +endif() # ## Get the software version diff --git a/INSTALL b/INSTALL index 825f549..d77ef59 100644 --- a/INSTALL +++ b/INSTALL @@ -78,10 +78,20 @@ using the POLLER cmake option. Available values are: EPOLL: use the Linux-specific epoll(7). This is the default on Linux. POLL: use the standard poll(2). This is the default value on all non-Linux platforms. -Example, configure the poller with cmake: +Examples, configure the poller with cmake: % cmake . -DPOLLER=EPOLL +You can also decide not to use two of the optional dependencies, even if they are present on your system, for example if Botan is available but you do not want to use it, you can set the value of WITHOUT_BOTAN to 1, like this: + +% cmake . -DWITHOUT_BOTAN=1 + +This way, the binary will not be linked with libotan at all (and all +connection will then be made in clear text). +You can also decide not to link with systemd, like this: + +% cmake . -DWITHOUT_SYSTEMD=1 + ============== Build -- cgit v1.2.3 From 6a4e17cb0a2d48695af2af488068f98515d714a1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 15 Jul 2014 15:50:35 +0200 Subject: Fix IPv6 support (sockaddr size) Since struct sockaddr is too small to contain an IPv6, we use struct sockaddr_in6 instead, and we cast it where needed --- src/network/socket_handler.cpp | 2 +- src/network/socket_handler.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/socket_handler.cpp b/src/network/socket_handler.cpp index 52297e4..73e1ad1 100644 --- a/src/network/socket_handler.cpp +++ b/src/network/socket_handler.cpp @@ -140,7 +140,7 @@ void SocketHandler::connect(const std::string& address, const std::string& port, this->ai_addrlen = rp->ai_addrlen; memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); - this->addrinfo.ai_addr = &this->ai_addr; + this->addrinfo.ai_addr = reinterpret_cast(&this->ai_addr); this->addrinfo.ai_next = nullptr; // If the connection has not succeeded or failed in 5s, we consider // it to have failed diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index b4cb6f2..061658e 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -199,7 +199,7 @@ private: * again. */ struct addrinfo addrinfo; - struct sockaddr ai_addr; + struct sockaddr_in6 ai_addr; socklen_t ai_addrlen; protected: -- cgit v1.2.3 From cf32446d0a80b9a9394b6e94af07c303a998cf83 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 16 Jul 2014 00:38:38 +0200 Subject: Release version 1.1 --- CHANGELOG | 4 ++++ CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1e99ffc..edbb2c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +Version 1.1 2014-16-07 + + - Fix a segmentation fault when connecting to an IRC server using IPv6 + Version 1.0 2014-12-07 - First stable release. diff --git a/CMakeLists.txt b/CMakeLists.txt index 48ce504..3af1a66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.6) project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 1) -set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_MINOR 1) set(${PROJECT_NAME}_VERSION_SUFFIX "") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") -- cgit v1.2.3 From b39ed80dbbaabed9647b727fa3348147c2fbae2b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Jul 2014 22:04:29 +0200 Subject: Add two missing type="error" in some presence errors --- src/xmpp/xmpp_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 6af67a4..bc54bcd 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -918,6 +918,7 @@ void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, Stanza presence("presence"); presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; presence["to"] = jid_to; + presence["type"] = "error"; XmlNode x("x"); x["xmlns"] = MUC_NS; x.close(); @@ -946,6 +947,7 @@ void XmppComponent::send_presence_error(const std::string& muc_name, Stanza presence("presence"); presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; presence["to"] = jid_to; + presence["type"] = "error"; XmlNode x("x"); x["xmlns"] = MUC_NS; x.close(); -- cgit v1.2.3 From 8030e5096e1a73e0623bf0d0fd65b25530a318e9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 31 Jul 2014 15:26:47 +0200 Subject: Do not create a useless biboumi unix user with the RPM package --- packaging/biboumi.spec | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 33464c6..dac4a6d 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -15,8 +15,6 @@ BuildRequires: cmake BuildRequires: systemd BuildRequires: rubygem-ronn -%global biboumi_user %{name} -%global biboumi_group %{biboumi_user} %global biboumi_confdir %{_sysconfdir}/%{name} @@ -52,14 +50,6 @@ install -D -p -m 644 unit/%{name}.service \ %{buildroot}%{_unitdir}/%{name}.service -%pre -getent group %{biboumi_group} > /dev/null || groupadd -r %{biboumi_group} -getent passwd %{biboumi_user} > /dev/null || \ - useradd -r -g %{biboumi_group} \ - -s /sbin/nologin -c "Biboumi XMPP to IRC gateway" %{biboumi_user} -exit 0 - - %check make test_suite/fast VERBOSE=1 -- cgit v1.2.3 From 7d99ba8dd9aa5845741bb50d598082bb55c0adac Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 31 Jul 2014 15:27:11 +0200 Subject: Run the service with the nobody user --- unit/biboumi.service | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unit/biboumi.service b/unit/biboumi.service index 579ac4e..1bb4f63 100644 --- a/unit/biboumi.service +++ b/unit/biboumi.service @@ -9,6 +9,8 @@ ExecReload=/bin/kill -s USR1 $MAINPID ExecStop=/bin/kill -s INT $MAINPID WatchdogSec=10 Restart=always +User=nobody +Group=nobody [Install] WantedBy=multi-user.target -- cgit v1.2.3 From e3ea0d62c79cf74e154f24d7f5a0fa8c7d26d73b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 8 Aug 2014 00:46:27 +0200 Subject: Use generic send_presence_error() instead of almost identical specializations --- src/bridge/bridge.cpp | 8 +++++--- src/bridge/bridge.hpp | 5 ++--- src/irc/irc_client.cpp | 5 +++-- src/xmpp/xmpp_component.cpp | 39 ++++++++------------------------------- src/xmpp/xmpp_component.hpp | 17 ++++++----------- 5 files changed, 24 insertions(+), 50 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 9501d47..ba4911a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -331,9 +331,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st } } -void Bridge::send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text) +void Bridge::send_presence_error(const Iid& iid, const std::string& nick, + const std::string& type, const std::string& condition, + const std::string& error_code, const std::string& text) { - this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, text); + this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) @@ -412,7 +414,7 @@ void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::stri void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp->send_nickname_conflict_error(std::to_string(iid), nickname, this->user_jid); + this->xmpp->send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index a75b319..b0e6a27 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -100,10 +100,9 @@ public: */ void send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc); /** - * Send a presence of type error, from a room. This is used to indicate - * why joining a room failed. + * Send a presence of type error, from a room. */ - void send_join_failed(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& text); + void send_presence_error(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& error_code, const std::string& text); /** * Send an unavailable presence from this participant */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e518ffc..678cac8 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -77,8 +77,9 @@ void IrcClient::on_connection_failed(const std::string& reason) for (const std::string& channel: this->channels_to_join) { Iid iid(channel + "%" + this->hostname); - this->bridge->send_join_failed(iid, this->current_nick, - "cancel", "item-not-found", reason); + this->bridge->send_presence_error(iid, this->current_nick, + "cancel", "item-not-found", + "", reason); } } else // try the next port diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index bc54bcd..1aa98b0 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -911,38 +911,13 @@ void XmppComponent::kick_user(const std::string& muc_name, this->send_stanza(presence); } -void XmppComponent::send_nickname_conflict_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; - presence["to"] = jid_to; - presence["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - presence.add_child(std::move(x)); - XmlNode error("error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "cancel"; - error["code"] = "409"; - XmlNode conflict("conflict"); - conflict["xmlns"] = STANZA_NS; - conflict.close(); - error.add_child(std::move(conflict)); - error.close(); - presence.add_child(std::move(error)); - presence.close(); - this->send_stanza(presence); -} - void XmppComponent::send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string&) + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string& error_code, + const std::string& /* text */) { Stanza presence("presence"); presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; @@ -955,6 +930,8 @@ void XmppComponent::send_presence_error(const std::string& muc_name, XmlNode error("error"); error["by"] = muc_name + "@" + this->served_hostname; error["type"] = type; + if (!error_code.empty()) + error["code"] = error_code; XmlNode subnode(condition); subnode["xmlns"] = STANZA_NS; subnode.close(); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index daadbec..9f1cec3 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -171,21 +171,16 @@ public: const std::string& reason, const std::string& author, const std::string& jid_to); - /** - * Send a presence type=error with a conflict element - */ - void send_nickname_conflict_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to); /** * Send a generic presence error */ void send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& text); + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string& error_code, + const std::string& text); /** * Send a presence from the MUC indicating a change in the role and/or * affiliation of a participant -- cgit v1.2.3 From 41e8a3ba9b57e67aec5d0d30112338664afbd6e4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 8 Aug 2014 00:47:05 +0200 Subject: Send a proper error on IRC message 438 (nickname change too fast) fix #2576 --- src/irc/irc_client.cpp | 19 +++++++++++++++++++ src/irc/irc_client.hpp | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 678cac8..f9ebfa0 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -478,6 +478,25 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) } } +void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) +{ + const std::string nickname = message.arguments[1]; + std::string txt; + if (message.arguments.size() >= 3) + txt = message.arguments[2]; + this->on_generic_error(message); + for (auto it = this->channels.begin(); it != this->channels.end(); ++it) + { + Iid iid; + iid.set_local(it->first); + iid.set_server(this->hostname); + iid.is_channel = true; + this->bridge->send_presence_error(iid, nickname, + "cancel", "not-acceptable", + "", txt); + } +} + void IrcClient::on_generic_error(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 88d2d72..9bef04a 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -171,6 +171,10 @@ public: * presence conflict from all channels, because the name is server-wide. */ void on_nickname_conflict(const IrcMessage& message); + /** + * Idem, but for when the user changes their nickname too quickly + */ + void on_nickname_change_too_fast(const IrcMessage& message); /** * Handles most errors from the server by just forwarding the message to the user. */ @@ -317,6 +321,7 @@ static const std::unordered_map irc_callbacks = { {"366", &IrcClient::on_channel_completely_joined}, {"432", &IrcClient::on_erroneous_nickname}, {"433", &IrcClient::on_nickname_conflict}, + {"438", &IrcClient::on_nickname_change_too_fast}, {"001", &IrcClient::on_welcome_message}, {"PART", &IrcClient::on_part}, {"ERROR", &IrcClient::on_error}, -- cgit v1.2.3 From f4c08242fb55809cca3e12d66cbc39e5c9b51fb0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 18 Aug 2014 02:07:25 +0200 Subject: Update spec file to 1.1 --- packaging/biboumi.spec | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 33464c6..b436647 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -1,5 +1,5 @@ Name: biboumi -Version: 1.0 +Version: 1.1 Release: 1%{?dist} Summary: Lightweight XMPP to IRC gateway @@ -75,5 +75,8 @@ make test_suite/fast VERBOSE=1 %changelog +* Wed Aug 18 2014 Le Coz Florent - 1.1-1 +- Update to 1.1 release + * Wed Jun 25 2014 Le Coz Florent - 1.0-1 - Spec file written from scratch -- cgit v1.2.3 From 940c037a171e73dfa5405d57d3ad8ed25525972f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 18 Aug 2014 02:17:32 +0200 Subject: Wrap a paragraph from INSTALL file to 79 chars --- INSTALL | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/INSTALL b/INSTALL index d77ef59..051e203 100644 --- a/INSTALL +++ b/INSTALL @@ -82,7 +82,10 @@ Examples, configure the poller with cmake: % cmake . -DPOLLER=EPOLL -You can also decide not to use two of the optional dependencies, even if they are present on your system, for example if Botan is available but you do not want to use it, you can set the value of WITHOUT_BOTAN to 1, like this: +You can also decide not to use two of the optional dependencies, even if +they are present on your system, for example if Botan is available but you +do not want to use it, you can set the value of WITHOUT_BOTAN to 1, like +this: % cmake . -DWITHOUT_BOTAN=1 -- cgit v1.2.3 From 546f3536ee1ea7ce8922fa32b6fdf600adc133cb Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 18 Aug 2014 02:23:15 +0200 Subject: Escape a # in biboumi.1.md --- doc/biboumi.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index fb48d4b..a35d4fc 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -221,7 +221,7 @@ as toto on the channel #bar (as long as these two channels are on the same IRC server). By default you will receive private messages from the “global” user (aka nickname!irc.example.com@biboumi.example.com), unless you previously sent a message to an in-room participant (something like -#test%irc.example.com@biboumi.example.com/nickname), in which case future +\#test%irc.example.com@biboumi.example.com/nickname), in which case future messages from that same user will be received from that same “in-room” JID. ### Notices -- cgit v1.2.3 From 3d254281afef96ee71ab6fd05c99059a390189c4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 29 Aug 2014 03:47:49 +0200 Subject: [spec] Update the URL, and do not hardcode the version in it --- packaging/biboumi.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index b436647..83b6eb8 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -5,7 +5,7 @@ Summary: Lightweight XMPP to IRC gateway License: zlib URL: http://biboumi.louiz.org -Source0: http://biboumi.louiz.org/biboumi-1.0.tar.xz +Source0: http://git.louiz.org/biboumi/snapshot/biboumi-%{version}.tar.xz BuildRequires: libidn-devel BuildRequires: expat-devel -- cgit v1.2.3 From e1d69806ed7c92bdfe1bf632064bf68b3d1d266b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 04:05:56 +0100 Subject: =?UTF-8?q?Rename=20iq=5Fresponder=5Fcallback=5Ft=20to=20irc=5F?= =?UTF-8?q?=E2=80=A6=20and=20add=20the=20equivalent=20to=20wait=20for=20iq?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bridge/bridge.cpp | 98 ++++++++++++++++++++++++--------------------- src/bridge/bridge.hpp | 14 +++---- src/irc/iid.hpp | 2 +- src/irc/irc_client.cpp | 2 +- src/xmpp/xmpp_component.cpp | 9 +++++ src/xmpp/xmpp_component.hpp | 14 +++++++ 6 files changed, 84 insertions(+), 55 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ba4911a..bc89073 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -220,45 +221,49 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: const std::string& iq_id, const std::string& to_jid) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (irc) + + if (!irc) + return; + + irc->send_kick_command(iid.get_local(), target, reason); + irc_responder_callback_t cb = [this, target, iq_id, to_jid, iid](const std::string& irc_hostname, + const IrcMessage& message) -> bool { - irc->send_kick_command(iid.get_local(), target, reason); - this->add_waiting_iq([this, target, iq_id, to_jid, iid](const std::string& irc_hostname, const IrcMessage& message){ - if (irc_hostname != iid.get_server()) + if (irc_hostname != iid.get_server()) + return false; + if (message.command == "KICK" && message.arguments.size() >= 2) + { + const std::string target_later = message.arguments[1]; + const std::string chan_name_later = utils::tolower(message.arguments[0]); + if (target_later != target || chan_name_later != iid.get_local()) return false; - if (message.command == "KICK" && message.arguments.size() >= 2) - { - const std::string target_later = message.arguments[1]; - const std::string chan_name_later = utils::tolower(message.arguments[0]); - if (target_later != target || chan_name_later != iid.get_local()) - return false; - this->xmpp->send_iq_result(iq_id, to_jid, std::to_string(iid)); - } - else if (message.command == "401" && message.arguments.size() >= 2) - { - const std::string target_later = message.arguments[1]; - if (target_later != target) - return false; - std::string error_message = "No such nick"; - if (message.arguments.size() >= 3) - error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found", - error_message, false); - } - else if (message.command == "482" && message.arguments.size() >= 2) - { - const std::string chan_name_later = utils::tolower(message.arguments[1]); - if (chan_name_later != iid.get_local()) - return false; - std::string error_message = "You're not channel operator"; - if (message.arguments.size() >= 3) - error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed", - error_message, false); - } - return true; - }); - } + this->xmpp->send_iq_result(iq_id, to_jid, std::to_string(iid)); + } + else if (message.command == "401" && message.arguments.size() >= 2) + { + const std::string target_later = message.arguments[1]; + if (target_later != target) + return false; + std::string error_message = "No such nick"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found", + error_message, false); + } + else if (message.command == "482" && message.arguments.size() >= 2) + { + const std::string chan_name_later = utils::tolower(message.arguments[1]); + if (chan_name_later != iid.get_local()) + return false; + std::string error_message = "You're not channel operator"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed", + error_message, false); + } + return true; + }; + this->add_waiting_irc(std::move(cb)); } void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) @@ -284,7 +289,8 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std // TODO, add a timer to remove that waiting iq if the server does not // respond with a matching command before n seconds - this->add_waiting_iq([this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message){ + irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + { if (irc_hostname != hostname) return false; IrcUser user(message.prefix); @@ -307,10 +313,10 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std return true; } return false; - }); + }; + this->add_waiting_irc(std::move(cb)); } - void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) @@ -447,18 +453,18 @@ void Bridge::remove_preferred_from_jid(const std::string& nick) this->preferred_user_from.erase(it); } -void Bridge::add_waiting_iq(iq_responder_callback_t&& callback) +void Bridge::add_waiting_irc(irc_responder_callback_t&& callback) { - this->waiting_iq.emplace_back(std::move(callback)); + this->waiting_irc.emplace_back(std::move(callback)); } -void Bridge::trigger_response_iq(const std::string& irc_hostname, const IrcMessage& message) +void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message) { - auto it = this->waiting_iq.begin(); - while (it != this->waiting_iq.end()) + auto it = this->waiting_irc.begin(); + while (it != this->waiting_irc.end()) { if ((*it)(irc_hostname, message) == true) - it = this->waiting_iq.erase(it); + it = this->waiting_irc.erase(it); else ++it; } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b0e6a27..1f45b27 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -17,11 +17,11 @@ class Poller; /** * A callback called for each IrcMessage we receive. If the message triggers - * a response, it must send an iq and return true (in that case it is - * removed from the list), otherwise it must do nothing and just return + * a response, it must send ore or more iq and return true (in that case it + * is removed from the list), otherwise it must do nothing and just return * false. */ -typedef std::function iq_responder_callback_t; +using irc_responder_callback_t = std::function; /** * One bridge is spawned for each XMPP user that uses the component. The @@ -146,15 +146,15 @@ public: */ void remove_preferred_from_jid(const std::string& nick); /** - * Add a callback to the waiting iq list. + * Add a callback to the waiting list of irc callbacks. */ - void add_waiting_iq(iq_responder_callback_t&& callback); + void add_waiting_irc(irc_responder_callback_t&& callback); /** * Iter over all the waiting_iq, call the iq_responder_filter_t for each, * whenever one of them returns true: call the corresponding * iq_responder_callback_t and remove the callback from the list. */ - void trigger_response_iq(const std::string& irc_hostname, const IrcMessage& message); + void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); private: /** @@ -203,7 +203,7 @@ private: * request and we need a response from IRC to be able to provide the * response iq. */ - std::list waiting_iq; + std::list waiting_irc; Bridge(const Bridge&) = delete; Bridge(Bridge&& other) = delete; diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index c547dea..d30cbaa 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -42,7 +42,7 @@ class Iid { public: - explicit Iid(const std::string& iid); + Iid(const std::string& iid); explicit Iid(const Iid&); explicit Iid(); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f9ebfa0..90f0644 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -156,7 +156,7 @@ void IrcClient::parse_in_buffer(const size_t) else log_info("No handler for command " << message.command); // Try to find a waiting_iq, which response will be triggered by this IrcMessage - this->bridge->trigger_response_iq(this->hostname, message); + this->bridge->trigger_on_irc_message(this->hostname, message); } } diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1aa98b0..948e680 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -577,6 +577,15 @@ void XmppComponent::handle_iq(const Stanza& stanza) const Iid iid(to.local); bridge->send_xmpp_version_to_irc(iid, name, version, os); } + else + { + const auto it = this->waiting_iq.find(id); + if (it != this->waiting_iq.end()) + { + it->second(bridge, stanza); + this->waiting_iq.erase(it); + } + } } error_type = "cancel"; error_name = "feature-not-implemented"; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 9f1cec3..6ccc753 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #define STREAM_NS "http://etherx.jabber.org/streams" #define COMPONENT_NS "jabber:component:accept" @@ -23,6 +24,11 @@ #define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" #define VERSION_NS "jabber:iq:version" #define ADHOC_NS "http://jabber.org/protocol/commands" +/** + * A callback called when the waited iq result is received (it is matched + * against the iq id) + */ +using iq_responder_callback_t = std::function; /** * An XMPP component, communicating with an XMPP server using the protocole @@ -256,6 +262,14 @@ private: std::unordered_map> stanza_handlers; AdhocCommandsHandler adhoc_commands_handler; + /** + * A map of id -> callback. When we want to wait for an iq result, we add + * the callback to this map, with the iq id as the key. When an iq result + * is received, we look for a corresponding callback in this map. If + * found, we call it and remove it. + */ + std::map waiting_iq; + /** * One bridge for each user of the component. Indexed by the user's full * jid -- cgit v1.2.3 From d8da7984b23bf8f30b5f8f53895cbae5be50347a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 06:16:10 +0100 Subject: Implement PING, user to user only (XMPP and IRC side, using CTCP PING) ref #2757 --- src/bridge/bridge.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ src/bridge/bridge.hpp | 13 ++++++++++- src/irc/irc_client.cpp | 3 +++ src/xmpp/xmpp_component.cpp | 40 +++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 8 +++++++ 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index bc89073..08fd569 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -280,6 +282,50 @@ void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, c this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE"); } +void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id) +{ + this->send_private_message(iid, "\01PING "s + utils::revstr(id), "NOTICE"); +} + +void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid) +{ + Iid iid(nick + "!" + irc_hostname); + this->send_private_message(iid, "\01PING " + iq_id + "\01"); + + irc_responder_callback_t cb = [this, nick, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + { + if (irc_hostname != hostname) + return false; + IrcUser user(message.prefix); + const std::string body = message.arguments[1]; + if (message.command == "NOTICE" && user.nick == nick && + message.arguments.size() >= 2 && body.substr(0, 6) == "\01PING ") + { + const std::string id = body.substr(6, body.size() - 6); + if (id != iq_id) + return false; + Jid jid(from_jid); + this->xmpp->send_iq_result(iq_id, to_jid, jid.local); + return true; + } + if (message.command == "401" && message.arguments.size() >= 2 + && message.arguments[1] == nick) + { + std::string error_message = "No such nick"; + if (message.arguments.size() >= 3) + error_message = message.arguments[2]; + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", + error_message, true); + return true; + } + + return false; + }; + this->add_waiting_irc(std::move(cb)); +} + void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) @@ -437,6 +483,15 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string& this->xmpp->send_iq_version_request(nick + "!" + hostname, this->user_jid); } +void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, + const std::string& id) +{ + // Use revstr because the forwarded ping to target XMPP user must not be + // the same that the request iq, but we also need to get it back easily + // (revstr again) + this->xmpp->send_ping_request(nick + "!" + hostname, this->user_jid, utils::revstr(id)); +} + void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) { auto it = this->preferred_user_from.find(nick); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 1f45b27..f7edfaa 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -68,9 +68,16 @@ public: void set_channel_topic(const Iid& iid, const std::string& subject); void send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os); + void send_irc_ping_result(const Iid& iid, const std::string& id); void send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + /** + * Directly send a CTCP PING request to the IRC user + */ + void send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid); /*** ** @@ -128,7 +135,11 @@ public: * Send an iq version request coming from nick!hostname@ */ void send_iq_version_request(const std::string& nick, const std::string& hostname); - + /** + * Send an iq ping request coming from nick!hostname@ + */ + void send_xmpp_ping_request(const std::string& nick, const std::string& hostname, + const std::string& id); /** * Misc */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 90f0644..707593f 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -409,6 +409,9 @@ void IrcClient::on_channel_message(const IrcMessage& message) "/me"s + body.substr(7, body.size() - 8), muc); else if (body.substr(1, 8) == "VERSION\01") this->bridge->send_iq_version_request(nick, this->hostname); + else if (body.substr(1, 5) == "PING ") + this->bridge->send_xmpp_ping_request(nick, this->hostname, + body.substr(6, body.size() - 7)); } else this->bridge->send_message(iid, nick, body, muc); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 948e680..5f76bd3 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -555,6 +555,16 @@ void XmppComponent::handle_iq(const Stanza& stanza) stanza_error.disable(); } } + else if ((query = stanza.get_child("ping", PING_NS))) + { + Iid iid(to.local); + if (iid.is_user) + { // Ping any user (no check on the nick done ourself) + bridge->send_irc_user_ping_request(iid.get_server(), + iid.get_local(), id, from, to_str); + } + stanza_error.disable(); + } } else if (type == "result") { @@ -1080,6 +1090,36 @@ void XmppComponent::send_iq_version_request(const std::string& from, this->send_stanza(iq); } +void XmppComponent::send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = id; + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode ping("ping"); + ping["xmlns"] = PING_NS; + ping.close(); + iq.add_child(std::move(ping)); + iq.close(); + this->send_stanza(iq); + + auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) + { + Jid to(stanza.get_tag("to")); + if (to.local != from) + { + log_error("Received a corresponding ping result, but the 'to' from " + "the response mismatches the 'from' of the request"); + } + else + bridge->send_irc_ping_result(from, id); + }; + this->waiting_iq[id] = result_cb; +} + void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) { Stanza iq("iq"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 6ccc753..d7f7f7a 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -24,6 +24,8 @@ #define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" #define VERSION_NS "jabber:iq:version" #define ADHOC_NS "http://jabber.org/protocol/commands" +#define PING_NS "urn:xmpp:ping" + /** * A callback called when the waited iq result is received (it is matched * against the iq id) @@ -216,6 +218,12 @@ public: */ void send_iq_version_request(const std::string& from, const std::string& jid_to); + /** + * Send a ping request + */ + void send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id); /** * Send an empty iq of type result */ -- cgit v1.2.3 From c20bdd68796c0fc31441ffe059a462a0d423cc77 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 07:52:07 +0100 Subject: Add utils::revstr --- src/test.cpp | 4 ++++ src/utils/revstr.cpp | 9 +++++++++ src/utils/revstr.hpp | 11 +++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/utils/revstr.cpp create mode 100644 src/utils/revstr.hpp diff --git a/src/test.cpp b/src/test.cpp index 1b7a873..59c0a1e 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +162,9 @@ int main() std::cout << lowercase << std::endl; assert(lowercase == "coucou les copains ♥"); + const std::string ltr = "coucou"; + assert(utils::revstr(ltr) == "uocuoc"); + /** * XML parsing */ diff --git a/src/utils/revstr.cpp b/src/utils/revstr.cpp new file mode 100644 index 0000000..87fd801 --- /dev/null +++ b/src/utils/revstr.cpp @@ -0,0 +1,9 @@ +#include + +namespace utils +{ + std::string revstr(const std::string& original) + { + return {original.rbegin(), original.rend()}; + } +} diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp new file mode 100644 index 0000000..0f00076 --- /dev/null +++ b/src/utils/revstr.hpp @@ -0,0 +1,11 @@ +#ifndef REVSTRP_INCLUDED +# define REVSTR_INCLUDED + +#include + +namespace utils +{ + std::string revstr(const std::string& original); +} + +#endif // REVSTR_INCLUDED -- cgit v1.2.3 From 4a8bcd3cbde0a41902999db7acc346de020cf564 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 07:51:10 +0100 Subject: Implement PING to in-room participant ref #2575 --- src/bridge/bridge.cpp | 30 +++++++++++++++++++++++++++++- src/bridge/bridge.hpp | 7 +++++++ src/xmpp/xmpp_component.cpp | 5 +++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 08fd569..ed61c6b 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -326,13 +326,41 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s this->add_waiting_irc(std::move(cb)); } +void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string& nick, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + if (!irc) + { + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", + "Not connected to IRC server"s + iid.get_server(), true); + return; + } + IrcChannel* chan = irc->get_channel(iid.get_local()); + if (!chan->joined) + { + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed", + "", true); + return; + } + if (chan->get_self()->nick != nick && !chan->find_user(nick)) + { + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", + "Recipient not in room", true); + return; + } + + // The user is in the room, send it a direct PING + this->send_irc_user_ping_request(iid.get_server(), nick, iq_id, to_jid, from_jid); +} + void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) { Iid iid(target + "!" + irc_hostname); this->send_private_message(iid, "\01VERSION\01"); - // TODO, add a timer to remove that waiting iq if the server does not // respond with a matching command before n seconds irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f7edfaa..6d09fff 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -78,6 +78,13 @@ public: void send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + /** + * First check if the participant is in the room, before sending a direct + * CTCP PING request to the IRC user + */ + void send_irc_participant_ping_request(const Iid& iid, const std::string& nick, + const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid); /*** ** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 5f76bd3..794b45b 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -563,6 +563,11 @@ void XmppComponent::handle_iq(const Stanza& stanza) bridge->send_irc_user_ping_request(iid.get_server(), iid.get_local(), id, from, to_str); } + else if (iid.is_channel && !to.resource.empty()) + { // Ping a room participant (we check if the nick is in the room) + bridge->send_irc_participant_ping_request(iid, + to.resource, id, from, to_str); + } stanza_error.disable(); } } -- cgit v1.2.3 From 80789c2e927e90b025f26951ed1bbbde25d5eb0f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 08:11:21 +0100 Subject: send_iq_result can have a "from" with just the domain name --- src/xmpp/xmpp_component.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 794b45b..516b932 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -1128,7 +1128,10 @@ void XmppComponent::send_ping_request(const std::string& from, void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) { Stanza iq("iq"); - iq["from"] = from_local_part + "@" + this->served_hostname; + if (!from_local_part.empty()) + iq["from"] = from_local_part + "@" + this->served_hostname; + else + iq["from"] = this->served_hostname; iq["to"] = to_jid; iq["id"] = id; iq["type"] = "result"; -- cgit v1.2.3 From 12eeb4d11eee5b8e6514f0ce8bf7508cc2d6d7a1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 12 Nov 2014 08:10:46 +0100 Subject: Implement the PING on a server or the gateway itself fix #2575 --- CHANGELOG | 3 +++ src/bridge/bridge.cpp | 11 +++++++++++ src/bridge/bridge.hpp | 6 ++++++ src/xmpp/xmpp_component.cpp | 5 +++++ 4 files changed, 25 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index edbb2c1..ee0888b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +Version 2.0 +- Support PING requests in all directions + Version 1.1 2014-16-07 - Fix a segmentation fault when connecting to an IRC server using IPv6 diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ed61c6b..a7d8fe6 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -355,6 +355,17 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string this->send_irc_user_ping_request(iid.get_server(), nick, iq_id, to_jid, from_jid); } +void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid) +{ + Jid jid(from_jid); + if (irc_hostname.empty() || this->get_irc_client(irc_hostname)) + this->xmpp->send_iq_result(iq_id, to_jid, jid.local); + else + this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", + "", true); +} + void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 6d09fff..cf39545 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -85,6 +85,12 @@ public: void send_irc_participant_ping_request(const Iid& iid, const std::string& nick, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + /** + * Directly send back a result if it's a gateway ping or if we are + * connected to the given IRC server, an error otherwise. + */ + void on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid, + const std::string& from_jid); /*** ** diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 516b932..5a5aed4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -568,6 +568,11 @@ void XmppComponent::handle_iq(const Stanza& stanza) bridge->send_irc_participant_ping_request(iid, to.resource, id, from, to_str); } + else + { // Ping a channel, a server or the gateway itself + bridge->on_gateway_ping(iid.get_server(), + id, from, to_str); + } stanza_error.disable(); } } -- cgit v1.2.3 From 2bb4a347cdfbee92334d5340ba640c8680a81d9e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 13 Nov 2014 06:22:17 +0100 Subject: Improve dependencies checks in the build process - Rename all Find*.cmake files to uppercase, to make things more consistent, and fix some issues with them (notably the REQUIRED flag) - Rename SYSTEMDDAEMON to SYSTEMD and only use the libsystemd instead of libsystemd-daemon because it's deprecated for a long time now - Provide a WITH_* and WITHOUT_* switch for all optional dependencies - Document things in the INSTALL file --- CMakeLists.txt | 25 +++++++++++---- INSTALL | 40 ++++++++++++----------- cmake/Modules/FindBOTAN.cmake | 35 ++++++++++++++++++++ cmake/Modules/FindBotan.cmake | 35 -------------------- cmake/Modules/FindICONV.cmake | 60 +++++++++++++++++++++++++++++++++++ cmake/Modules/FindIconv.cmake | 60 ----------------------------------- cmake/Modules/FindLIBIDN.cmake | 41 ++++++++++++++++++++++++ cmake/Modules/FindLIBUUID.cmake | 41 ++++++++++++++++++++++++ cmake/Modules/FindLibidn.cmake | 41 ------------------------ cmake/Modules/FindLibuuid.cmake | 41 ------------------------ cmake/Modules/FindSYSTEMD.cmake | 39 +++++++++++++++++++++++ cmake/Modules/FindSystemdDaemon.cmake | 39 ----------------------- src/config.h.cmake | 2 +- 13 files changed, 256 insertions(+), 243 deletions(-) create mode 100644 cmake/Modules/FindBOTAN.cmake delete mode 100644 cmake/Modules/FindBotan.cmake create mode 100644 cmake/Modules/FindICONV.cmake delete mode 100644 cmake/Modules/FindIconv.cmake create mode 100644 cmake/Modules/FindLIBIDN.cmake create mode 100644 cmake/Modules/FindLIBUUID.cmake delete mode 100644 cmake/Modules/FindLibidn.cmake delete mode 100644 cmake/Modules/FindLibuuid.cmake create mode 100644 cmake/Modules/FindSYSTEMD.cmake delete mode 100644 cmake/Modules/FindSystemdDaemon.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 14c002c..138bdeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,14 +18,25 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") include(FindEXPAT) find_package(EXPAT REQUIRED) -find_package(Iconv REQUIRED) -find_package(Libuuid REQUIRED) -find_package(Libidn) -if(NOT WITHOUT_SYSTEMD) - find_package(SystemdDaemon) +find_package(ICONV REQUIRED) +find_package(LIBUUID REQUIRED) + +if(WITH_LIBIDN) + find_package(LIBIDN REQUIRED) +elseif(NOT WITHOUT_LIBIDN) + find_package(LIBIDN) +endif() + +if(WITH_SYSTEMD) + find_package(SYSTEMD REQUIRED) +elseif(NOT WITHOUT_SYSTEMD) + find_package(SYSTEMD) endif() -if(NOT WITHOUT_BOTAN) - find_package(Botan) + +if(WITH_BOTAN) + find_package(BOTAN REQUIRED) +elseif(NOT WITHOUT_BOTAN) + find_package(BOTAN) endif() # diff --git a/INSTALL b/INSTALL index 051e203..69fa4bd 100644 --- a/INSTALL +++ b/INSTALL @@ -35,7 +35,7 @@ Libraries: Other branches than the 1.11 are not supported. http://botan.randombit.net/ -- systemd-daemon (optional) +- systemd (optional) Provides the support for a systemd service of Type=notify. This is useful only if you are packaging biboumi in a distribution with Systemd. @@ -67,33 +67,35 @@ In ccmake, first use 'c' to configure the build system, edit the values you need and finaly use 'g' to generate the Makefiles to build the system and quit ccmake. -or you can also use an interactive mode with: +You can also configure these options using a -D command line flag. -% cmake -i . +The list of available options: -and respond to the questions when you are prompted to. - -You can, for example, select the poller used by biboumi, at compile-time, -using the POLLER cmake option. Available values are: +- POLLER: lets you select the poller used by biboumi, at + compile-time. Possible values are: EPOLL: use the Linux-specific epoll(7). This is the default on Linux. - POLL: use the standard poll(2). This is the default value on all non-Linux platforms. + POLL: use the standard poll(2). This is the default value on all non-Linux + platforms. -Examples, configure the poller with cmake: +- WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, + if it is not found, the configuration process will fail. The second will + make the build process ignore the Botan library, it will not be used even + if it's available on the system. If none of these option is specified, the + library will be used if available and will be ignored otherwise. -% cmake . -DPOLLER=EPOLL +- WITH_LIBIDN and WITHOUT_LIBIDN: Just like the WITH(OUT)_BOTAN options, but + for the IDN library -You can also decide not to use two of the optional dependencies, even if -they are present on your system, for example if Botan is available but you -do not want to use it, you can set the value of WITHOUT_BOTAN to 1, like -this: +- WITH_SYSTEMD and WITHOUT_SYSTEMD: Just like the other WITH(OUT)_* options, + but for the Systemd library -% cmake . -DWITHOUT_BOTAN=1 +Example: -This way, the binary will not be linked with libotan at all (and all -connection will then be made in clear text). -You can also decide not to link with systemd, like this: +% cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX \ + -DWITH_BOTAN=1 -DWITHOUT_SYSTEMD=1 -% cmake . -DWITHOUT_SYSTEMD=1 +This command will configure the project to build a release, with TLS enabled +(using Botan) but without using Systemd (even if available on the system). ============== diff --git a/cmake/Modules/FindBOTAN.cmake b/cmake/Modules/FindBOTAN.cmake new file mode 100644 index 0000000..a12bd35 --- /dev/null +++ b/cmake/Modules/FindBOTAN.cmake @@ -0,0 +1,35 @@ +# - Find botan +# Find the botan cryptographic library +# +# This module defines the following variables: +# BOTAN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# BOTAN_INCLUDE_DIRS - The directory where to find the header file +# BOTAN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# BOTAN_LIBRARY +# BOTAN_INCLUDE_DIR +# +# This file is in the public domain + +find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h + PATH_SUFFIXES botan-1.11 + DOC "The botan include directory") + +find_library(BOTAN_LIBRARIES NAMES botan botan-1.11 + DOC "The botan library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set BOTAN_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BOTAN REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) + +if(BOTAN_FOUND) + set(BOTAN_LIBRARY ${BOTAN_LIBRARIES}) + set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS}) +endif() + +mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) diff --git a/cmake/Modules/FindBotan.cmake b/cmake/Modules/FindBotan.cmake deleted file mode 100644 index aa8f58c..0000000 --- a/cmake/Modules/FindBotan.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# - Find botan -# Find the botan cryptographic library -# -# This module defines the following variables: -# BOTAN_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# BOTAN_INCLUDE_DIRS - The directory where to find the header file -# BOTAN_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# BOTAN_LIBRARY -# BOTAN_INCLUDE_DIR -# -# This file is in the public domain - -find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h - PATH_SUFFIXES botan-1.11 - DOC "The botan include directory") - -find_library(BOTAN_LIBRARIES NAMES botan botan-1.11 - DOC "The botan library") - -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set BOTAN_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Botan REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) - -if(BOTAN_FOUND) - set(BOTAN_LIBRARY ${BOTAN_LIBRARIES}) - set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS}) -endif() - -mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) diff --git a/cmake/Modules/FindICONV.cmake b/cmake/Modules/FindICONV.cmake new file mode 100644 index 0000000..7ca173f --- /dev/null +++ b/cmake/Modules/FindICONV.cmake @@ -0,0 +1,60 @@ +# - Find iconv +# Find the iconv (character set conversion) library +# +# This module defines the following variables: +# ICONV_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# ICONV_INCLUDE_DIRS - The directory where to find the header file +# ICONV_LIBRARIES - Where to find the library file +# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# ICONV_LIBRARY +# ICONV_INCLUDE_DIR +# +# This file is in the public domain + +find_path(ICONV_INCLUDE_DIRS NAMES iconv.h + DOC "The iconv include directory") + +find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c + DOC "The iconv library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set ICONV_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS) + +# Check if the prototype is +# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +# or +# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +if(ICONV_FOUND) + include(CheckCXXSourceCompiles) + + # Set the parameters needed to compile the following code. + set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) + + check_cxx_source_compiles(" + #include + int main(){ + iconv_t conv = 0; + const char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0;}" + ICONV_SECOND_ARGUMENT_IS_CONST) + +# Compatibility for all the ways of writing these variables + set(ICONV_LIBRARY ${ICONV_LIBRARIES}) + set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS}) +endif() + +mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST) \ No newline at end of file diff --git a/cmake/Modules/FindIconv.cmake b/cmake/Modules/FindIconv.cmake deleted file mode 100644 index 7ca173f..0000000 --- a/cmake/Modules/FindIconv.cmake +++ /dev/null @@ -1,60 +0,0 @@ -# - Find iconv -# Find the iconv (character set conversion) library -# -# This module defines the following variables: -# ICONV_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# ICONV_INCLUDE_DIRS - The directory where to find the header file -# ICONV_LIBRARIES - Where to find the library file -# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# ICONV_LIBRARY -# ICONV_INCLUDE_DIR -# -# This file is in the public domain - -find_path(ICONV_INCLUDE_DIRS NAMES iconv.h - DOC "The iconv include directory") - -find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c - DOC "The iconv library") - -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set ICONV_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS) - -# Check if the prototype is -# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft, -# char** outbuf, size_t* outbytesleft); -# or -# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft, -# char** outbuf, size_t* outbytesleft); -if(ICONV_FOUND) - include(CheckCXXSourceCompiles) - - # Set the parameters needed to compile the following code. - set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) - - check_cxx_source_compiles(" - #include - int main(){ - iconv_t conv = 0; - const char* in = 0; - size_t ilen = 0; - char* out = 0; - size_t olen = 0; - iconv(conv, &in, &ilen, &out, &olen); - return 0;}" - ICONV_SECOND_ARGUMENT_IS_CONST) - -# Compatibility for all the ways of writing these variables - set(ICONV_LIBRARY ${ICONV_LIBRARIES}) - set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS}) -endif() - -mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST) \ No newline at end of file diff --git a/cmake/Modules/FindLIBIDN.cmake b/cmake/Modules/FindLIBIDN.cmake new file mode 100644 index 0000000..611a6a8 --- /dev/null +++ b/cmake/Modules/FindLIBIDN.cmake @@ -0,0 +1,41 @@ +# - Find libidn +# Find the libidn library, and more particularly the stringprep header. +# +# This module defines the following variables: +# LIBIDN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBIDN_INCLUDE_DIRS - The directory where to find the header file +# LIBIDN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LIBIDN_INCLUDE_DIR +# LIBIDN_LIBRARY +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(LIBIDN libidn) + +if(NOT LIBIDN_FOUND) + find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h + DOC "The libidn include directory") + + # The library containing the stringprep module is libidn + find_library(LIBIDN_LIBRARIES NAMES idn + DOC "The libidn library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBIDN_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBIDN_FOUND) + set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) + set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) + endif() +endif() + +mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/cmake/Modules/FindLIBUUID.cmake b/cmake/Modules/FindLIBUUID.cmake new file mode 100644 index 0000000..25b330b --- /dev/null +++ b/cmake/Modules/FindLIBUUID.cmake @@ -0,0 +1,41 @@ +# - Find libuuid +# Find the libuuid library +# +# This module defines the following variables: +# LIBUUID_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBUUID_INCLUDE_DIRS - The directory where to find the header file +# LIBUUID_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LIBUUID_INCLUDE_DIR +# LIBUUID_LIBRARY +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(LIBUUID uuid) + +if(NOT LIBUUID_FOUND) + find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h + PATH uuid + DOC "The libuuid include directory") + + find_library(LIBUUID_LIBRARIES NAMES uuid + DOC "The libuuid library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBUUID_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBUUID_FOUND) + set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS}) + set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES}) + endif() +endif() + +mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES) diff --git a/cmake/Modules/FindLibidn.cmake b/cmake/Modules/FindLibidn.cmake deleted file mode 100644 index 611a6a8..0000000 --- a/cmake/Modules/FindLibidn.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# - Find libidn -# Find the libidn library, and more particularly the stringprep header. -# -# This module defines the following variables: -# LIBIDN_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# LIBIDN_INCLUDE_DIRS - The directory where to find the header file -# LIBIDN_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# LIBIDN_INCLUDE_DIR -# LIBIDN_LIBRARY -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(LIBIDN libidn) - -if(NOT LIBIDN_FOUND) - find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h - DOC "The libidn include directory") - - # The library containing the stringprep module is libidn - find_library(LIBIDN_LIBRARIES NAMES idn - DOC "The libidn library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set LIBIDN_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(LIBIDN_FOUND) - set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) - set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) - endif() -endif() - -mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/cmake/Modules/FindLibuuid.cmake b/cmake/Modules/FindLibuuid.cmake deleted file mode 100644 index 25b330b..0000000 --- a/cmake/Modules/FindLibuuid.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# - Find libuuid -# Find the libuuid library -# -# This module defines the following variables: -# LIBUUID_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# LIBUUID_INCLUDE_DIRS - The directory where to find the header file -# LIBUUID_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# LIBUUID_INCLUDE_DIR -# LIBUUID_LIBRARY -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(LIBUUID uuid) - -if(NOT LIBUUID_FOUND) - find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h - PATH uuid - DOC "The libuuid include directory") - - find_library(LIBUUID_LIBRARIES NAMES uuid - DOC "The libuuid library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set LIBUUID_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(LIBUUID_FOUND) - set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS}) - set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES}) - endif() -endif() - -mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES) diff --git a/cmake/Modules/FindSYSTEMD.cmake b/cmake/Modules/FindSYSTEMD.cmake new file mode 100644 index 0000000..c7decde --- /dev/null +++ b/cmake/Modules/FindSYSTEMD.cmake @@ -0,0 +1,39 @@ +# - Find SystemdDaemon +# Find the systemd daemon library +# +# This module defines the following variables: +# SYSTEMD_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# SYSTEMD_INCLUDE_DIRS - The directory where to find the header file +# SYSTEMD_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# SYSTEMD_LIBRARY +# SYSTEMD_INCLUDE_DIR +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(SYSTEMD libsystemd) + +if(NOT SYSTEMD_FOUND) + find_path(SYSTEMD_INCLUDE_DIRS NAMES systemd/sd-daemon.h + DOC "The Systemd include directory") + + find_library(SYSTEMD_LIBRARIES NAMES systemd + DOC "The Systemd library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set SYSTEMD_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SYSTEMD REQUIRED_VARS SYSTEMD_LIBRARIES SYSTEMD_INCLUDE_DIRS) + + if(SYSTEMD_FOUND) + set(SYSTEMD_LIBRARY ${SYSTEMD_LIBRARIES}) + set(SYSTEMD_INCLUDE_DIR ${SYSTEMD_INCLUDE_DIRS}) + endif() +endif() + +mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) \ No newline at end of file diff --git a/cmake/Modules/FindSystemdDaemon.cmake b/cmake/Modules/FindSystemdDaemon.cmake deleted file mode 100644 index 9492bf2..0000000 --- a/cmake/Modules/FindSystemdDaemon.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# - Find SystemdDaemon -# Find the systemd daemon library -# -# This module defines the following variables: -# SYSTEMDDAEMON_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# SYSTEMDDAEMON_INCLUDE_DIRS - The directory where to find the header file -# SYSTEMDDAEMON_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# SYSTEMDDAEMON_LIBRARY -# SYSTEMDDAEMON_INCLUDE_DIR -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(SYSTEMDDAEMON libsystemd-daemon) - -if(NOT SYSTEMDDAEMON_FOUND) - find_path(SYSTEMDDAEMON_INCLUDE_DIRS NAMES systemd/sd-daemon.h - DOC "The Systemd Daemon include directory") - - find_library(SYSTEMDDAEMON_LIBRARIES NAMES systemd-daemon systemd - DOC "The Systemd Daemon library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set SYSTEMDDAEMON_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(SYSTEMDDAEMON REQUIRED_VARS SYSTEMDDAEMON_LIBRARIES SYSTEMDDAEMON_INCLUDE_DIRS) - - if(SYSTEMDDAEMON_FOUND) - set(SYSTEMDDAEMON_LIBRARY ${SYSTEMDDAEMON_LIBRARIES}) - set(SYSTEMDDAEMON_INCLUDE_DIR ${SYSTEMDDAEMON_INCLUDE_DIRS}) - endif() -endif() - -mark_as_advanced(SYSTEMDDAEMON_INCLUDE_DIRS SYSTEMDDAEMON_LIBRARIES) \ No newline at end of file diff --git a/src/config.h.cmake b/src/config.h.cmake index d8833ad..8eb2d1c 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -1,7 +1,7 @@ #define SYSTEM_NAME "${CMAKE_SYSTEM}" #cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST #cmakedefine LIBIDN_FOUND -#cmakedefine SYSTEMDDAEMON_FOUND +#cmakedefine SYSTEMD_FOUND #cmakedefine POLLER ${POLLER} #cmakedefine BOTAN_FOUND #cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" -- cgit v1.2.3 From 3736cc1f4c23ac4ae29bb505498f174ae6ea73e1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 13 Nov 2014 06:28:11 +0100 Subject: Use the new flags in the .spec file --- packaging/biboumi.spec | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index cffff09..490aa42 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -1,6 +1,6 @@ Name: biboumi Version: 1.1 -Release: 1%{?dist} +Release: 2%{?dist} Summary: Lightweight XMPP to IRC gateway License: zlib @@ -31,10 +31,15 @@ these channels were XMPP MUCs. %build cmake . -DCMAKE_BUILD_TYPE=release \ -DCMAKE_INSTALL_PREFIX=/usr \ - -DPOLLER=EPOLL + -DPOLLER=EPOLL \ + -DWITHOUT_BOTAN=1 \ + -DWITH_SYSTEMD=1 \ + -DWITH_LIBIDN=1 + +make %{?_smp_mflags} + # The documentation is in utf-8, ronn fails to build it if that locale is # not specified -make %{?_smp_mflags} LC_ALL=en_GB.utf-8 make doc @@ -65,6 +70,9 @@ make test_suite/fast VERBOSE=1 %changelog +* Wed Nov 13 2014 Le Coz Florent - 1.1-2 +- Use the -DWITH(OUT) cmake flags for all optional dependencies + * Wed Aug 18 2014 Le Coz Florent - 1.1-1 - Update to 1.1 release -- cgit v1.2.3 From 0de421fad825494a977eb50d561eac92244cd42b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 13 Nov 2014 06:43:39 +0100 Subject: Build with the fedora CFLAGS in the .spec --- packaging/biboumi.spec | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 490aa42..0cd1435 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -29,12 +29,13 @@ these channels were XMPP MUCs. %build -cmake . -DCMAKE_BUILD_TYPE=release \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DPOLLER=EPOLL \ - -DWITHOUT_BOTAN=1 \ - -DWITH_SYSTEMD=1 \ - -DWITH_LIBIDN=1 +cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ + -DCMAKE_BUILD_TYPE=release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DPOLLER=EPOLL \ + -DWITHOUT_BOTAN=1 \ + -DWITH_SYSTEMD=1 \ + -DWITH_LIBIDN=1 make %{?_smp_mflags} @@ -72,6 +73,7 @@ make test_suite/fast VERBOSE=1 %changelog * Wed Nov 13 2014 Le Coz Florent - 1.1-2 - Use the -DWITH(OUT) cmake flags for all optional dependencies +- Build with the correct optflags * Wed Aug 18 2014 Le Coz Florent - 1.1-1 - Update to 1.1 release -- cgit v1.2.3 From 7bfe695c2f8ff4b365ab5c2d74b4a317518576e7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 13 Nov 2014 06:52:00 +0100 Subject: Use hardened_build 1 in the spec file See http://fedoraproject.org/wiki/Packaging:Guidelines#PIE --- packaging/biboumi.spec | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 0cd1435..aba47c1 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -15,6 +15,8 @@ BuildRequires: cmake BuildRequires: systemd BuildRequires: rubygem-ronn +%global _hardened_build 1 + %global biboumi_confdir %{_sysconfdir}/%{name} @@ -74,6 +76,7 @@ make test_suite/fast VERBOSE=1 * Wed Nov 13 2014 Le Coz Florent - 1.1-2 - Use the -DWITH(OUT) cmake flags for all optional dependencies - Build with the correct optflags +- Use hardened_build * Wed Aug 18 2014 Le Coz Florent - 1.1-1 - Update to 1.1 release -- cgit v1.2.3 From 0ab5ecea133cef5a009bc34ba42c4eaacde6a7dd Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 24 Nov 2014 03:38:10 +0100 Subject: Cache the result of jidprep() Avoid doing repetitive calculations, if we call jidprep() on the same JID multiple times --- src/test.cpp | 5 +++++ src/xmpp/jid.cpp | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 59c0a1e..2f29e01 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -288,7 +288,12 @@ int main() // Jidprep const std::string& badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); const std::string correctjid = jidprep(badjid); + std::cout << correctjid << std::endl; assert(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + // Check that the cache do not break things when we prep the same string + // multiple times + assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); const std::string& badjid2("Zigougou@poez.io"); const std::string correctjid2 = jidprep(badjid2); diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index e3b0c8f..c51e011 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #ifdef LIBIDN_FOUND #include @@ -35,7 +36,14 @@ static constexpr size_t max_jid_part_len = 1023; std::string jidprep(const std::string& original) { #ifdef LIBIDN_FOUND - // TODO: cache the result + using CacheType = std::map; + static CacheType cache; + std::pair cached = cache.insert({original, {}}); + if (std::get<1>(cached) == false) + { // Insertion failed: the result is already in the cache, return it + return std::get<0>(cached)->second; + } + const std::string error_msg("Failed to convert " + original + " into a valid JID:"); Jid jid(original); @@ -61,7 +69,10 @@ std::string jidprep(const std::string& original) // If there is no resource, stop here if (jid.resource.empty()) - return std::string(local) + "@" + domain; + { + std::get<0>(cached)->second = std::string(local) + "@" + domain; + return std::get<0>(cached)->second; + } // Otherwise, also process the resource part char resource[max_jid_part_len] = {}; @@ -73,7 +84,8 @@ std::string jidprep(const std::string& original) log_error(error_msg + stringprep_strerror(rc)); return ""; } - return std::string(local) + "@" + domain + "/" + resource; + std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource; + return std::get<0>(cached)->second; #else (void)original; -- cgit v1.2.3 From 720e31a5113c25e48d7754bb812ab84c6c31d1d9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 17 Dec 2014 13:37:57 +0100 Subject: Fix a few issues reported by static analyzers --- src/bridge/bridge.cpp | 2 +- src/bridge/bridge.hpp | 2 +- src/bridge/colors.cpp | 2 -- src/config/config.cpp | 7 ------- src/config/config.hpp | 8 -------- src/irc/irc_channel.cpp | 2 +- src/irc/irc_channel.hpp | 2 +- src/network/poller.cpp | 2 +- src/test.cpp | 2 +- src/utils/encoding.cpp | 1 + src/xmpp/adhoc_commands_handler.cpp | 4 ++-- src/xmpp/adhoc_commands_handler.hpp | 2 +- src/xmpp/xmpp_component.cpp | 1 - src/xmpp/xmpp_stanza.hpp | 2 +- 14 files changed, 11 insertions(+), 28 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a7d8fe6..c925f9e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -480,7 +480,7 @@ void Bridge::send_user_join(const std::string& hostname, affiliation, role, this->user_jid, self); } -void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic) +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic) { this->xmpp->send_topic(chan_name + "%" + hostname, this->make_xmpp_body(topic), this->user_jid); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index cf39545..c20bba2 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -114,7 +114,7 @@ public: /** * Send the topic of the MUC to the user */ - void send_topic(const std::string& hostname, const std::string& chan_name, const std::string topic); + void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic); /** * Send a MUC message from some participant */ diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 3aed07c..3d40ac4 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -166,10 +166,8 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) { current_node->close(); result->add_child(current_node); - current_node = result.get(); } - result->close(); Xmpp::body body_res = std::make_tuple(cleaned, std::move(result)); return body_res; diff --git a/src/config/config.cpp b/src/config/config.cpp index e2e027b..b870339 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -28,13 +28,6 @@ int Config::get_int(const std::string& option, const int& def) return def; } -void Config::set_int(const std::string& option, const int& value, bool save) -{ - std::ostringstream os; - os << value; - Config::set(option, os.str(), save); -} - void Config::set(const std::string& option, const std::string& value, bool save) { Config* self = Config::instance().get(); diff --git a/src/config/config.hpp b/src/config/config.hpp index ea28388..e070816 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -52,14 +52,6 @@ public: * @param save if true, save the config file */ static void set(const std::string&, const std::string&, bool save = false); - /** - * Set a value for the given option. And write all the config - * in the file from which it was read if boolean is set. - * @param option The option to set - * @param value The value to use - * @param save if true, save the config file - */ - static void set_int(const std::string&, const int&, bool save = false); /** * Adds a function to a list. This function will be called whenever a * configuration change occurs. diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 2c0f8b1..b1b3983 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -12,7 +12,7 @@ void IrcChannel::set_self(const std::string& name) } IrcUser* IrcChannel::add_user(const std::string& name, - const std::map prefix_to_mode) + const std::map& prefix_to_mode) { this->users.emplace_back(std::make_unique(name, prefix_to_mode)); return this->users.back().get(); diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 1c074b5..568de0a 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -21,7 +21,7 @@ public: void set_self(const std::string& name); IrcUser* get_self() const; IrcUser* add_user(const std::string& name, - const std::map prefix_to_mode); + const std::map& prefix_to_mode); IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); void remove_all_users(); diff --git a/src/network/poller.cpp b/src/network/poller.cpp index d89e50f..29c4bce 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -133,7 +133,7 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) int Poller::poll(const std::chrono::milliseconds& timeout) { - if (this->socket_handlers.size() == 0) + if (this->socket_handlers.empty()) return -1; #if POLLER == POLL int nb_events = ::poll(this->fds, this->nfds, timeout.count()); diff --git a/src/test.cpp b/src/test.cpp index 2f29e01..a4371b2 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -222,7 +222,7 @@ int main() IrcUser user2("coucou!~other@host.bla", prefixes); assert(user2.nick == "coucou"); assert(user2.host == "~other@host.bla"); - assert(user2.modes.size() == 0); + assert(user2.modes.empty()); assert(user2.modes.find('a') == user2.modes.end()); /** diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp index dc0101c..3e3580c 100644 --- a/src/utils/encoding.cpp +++ b/src/utils/encoding.cpp @@ -197,6 +197,7 @@ namespace utils case E2BIG: // This should never happen done = true; + break; default: // This should happen even neverer done = true; diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index 7e1738a..a0defdd 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -29,7 +29,7 @@ const std::map& AdhocCommandsHandler::get return this->commands; } -XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) +XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) { std::string action = command_node.get_tag("action"); if (action.empty()) @@ -127,7 +127,7 @@ XmlNode&& AdhocCommandsHandler::handle_request(const std::string& executor_jid, command_node.add_child(std::move(error)); } } - return std::move(command_node); + return command_node; } void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp index 87d4d3d..7ddad47 100644 --- a/src/xmpp/adhoc_commands_handler.hpp +++ b/src/xmpp/adhoc_commands_handler.hpp @@ -36,7 +36,7 @@ public: * Takes a copy of the node so we can actually edit it and use * it as our return value. */ - XmlNode&& handle_request(const std::string& executor_jid, XmlNode command_node); + XmlNode handle_request(const std::string& executor_jid, XmlNode command_node); /** * Remove the session from the list. This is done to avoid filling the * memory with waiting session (for example due to a client that starts diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 5a5aed4..a1585d7 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -286,7 +286,6 @@ void XmppComponent::handle_handshake(const Stanza& stanza) uint64_t usec; if (sd_watchdog_enabled(0, &usec) > 0) { - std::chrono::microseconds delay(usec); TimedEventsManager::instance().add_event(TimedEvent( std::chrono::duration_cast(std::chrono::microseconds(usec / 2)), []() { sd_notify(0, "WATCHDOG=1"); })); diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index a34ef50..9229ae6 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -151,7 +151,7 @@ private: /** * An XMPP stanza is just an XML node of level 2 in the XMPP document (the - * level 1 ones are the , and the ones about 2 are just the + * level 1 ones are the , and the ones above 2 are just the * content of the stanzas) */ typedef XmlNode Stanza; -- cgit v1.2.3 From f4fb961df5d751dfcbd10b297bb4eaaf8096ce34 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 17 Dec 2014 13:55:41 +0100 Subject: And another trivial -Weffc++ warning --- src/xmpp/xmpp_parser.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp index b87ee6d..79c9f8f 100644 --- a/src/xmpp/xmpp_parser.hpp +++ b/src/xmpp/xmpp_parser.hpp @@ -118,6 +118,9 @@ private: std::vector> stanza_callbacks; std::vector> stream_open_callbacks; std::vector> stream_close_callbacks; + + XmppParser(const XmppParser&) = delete; + XmppParser& operator=(const XmppParser&) = delete; }; #endif // XMPP_PARSER_INCLUDED -- cgit v1.2.3 From 0ee47f628d7b34f285264cde06ff01a5b27e0ace Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 10 Jan 2015 04:19:33 +0100 Subject: Fix the cmake LIBUUID_INCLUDE_DIRS value when we are not using pkg-config --- cmake/Modules/FindLIBUUID.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/FindLIBUUID.cmake b/cmake/Modules/FindLIBUUID.cmake index 25b330b..17d3c42 100644 --- a/cmake/Modules/FindLIBUUID.cmake +++ b/cmake/Modules/FindLIBUUID.cmake @@ -20,7 +20,7 @@ pkg_check_modules(LIBUUID uuid) if(NOT LIBUUID_FOUND) find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h - PATH uuid + PATH_SUFFIXES uuid DOC "The libuuid include directory") find_library(LIBUUID_LIBRARIES NAMES uuid -- cgit v1.2.3 From b79dbefbe71824d8d42c5034a6900644a0850c4c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 12 Jan 2015 22:50:06 +0100 Subject: If we sent a message to a user, their notices are considered private messages fix #2882 --- CHANGELOG | 5 ++++- src/irc/irc_client.cpp | 27 +++++++++++++++++++++++++-- src/irc/irc_client.hpp | 4 ++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ee0888b..15cf354 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Version 2.0 -- Support PING requests in all directions + - Support PING requests in all directions + - Improve the way we forward received NOTICEs by remembering to + which users we previously sent a private message. This improves the + user experience when talking to NickServ. Version 1.1 2014-16-07 diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 707593f..228fc6e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -243,7 +243,9 @@ void IrcClient::send_private_message(const std::string& username, const std::str this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)})); pos += 400; } - + // We always try to insert and we don't care if the username was already + // in the set. + this->nicks_to_treat_as_private.insert(username); } void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message) @@ -292,9 +294,30 @@ void IrcClient::on_notice(const IrcMessage& message) const std::string body = message.arguments[1]; if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end()) - this->bridge->send_xmpp_message(this->hostname, from, body); + { + // The notice is for the us precisely. + + // Find out if we already sent a private message to this user. If yes + // we treat that message as a private message coming from + // it. Otherwise we treat it as a notice coming from the server. + IrcUser user(from); + std::string nick = utils::tolower(user.nick); + log_debug("received notice from nick: " << nick); + if (this->nicks_to_treat_as_private.find(nick) != + this->nicks_to_treat_as_private.end()) + { // We previously sent a message to that nick) + // this->bridge->send_message(iid, nick, body, muc); + this->bridge->send_message({nick + "!" + this->hostname}, nick, body, + false); + } + else + this->bridge->send_xmpp_message(this->hostname, from, body); + } else { + // The notice was directed at a channel we are in. Modify the message + // to indicate that it is a notice, and make it a MUC message coming + // from the MUC JID IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 "s + body}); this->on_channel_message(modified_message); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 9bef04a..70d7955 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -289,6 +289,10 @@ private: * connection succeeds on that port. */ std::stack> ports_to_try; + /** + * A set of (lowercase) nicknames to which we sent a private message. + */ + std::set nicks_to_treat_as_private; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; -- cgit v1.2.3 From e2e2f3089469e3e2acbdf1ac6902241d994057c6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 12 Jan 2015 22:57:08 +0100 Subject: Remove a dummy commented line --- src/irc/irc_client.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 228fc6e..b892684 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -306,7 +306,6 @@ void IrcClient::on_notice(const IrcMessage& message) if (this->nicks_to_treat_as_private.find(nick) != this->nicks_to_treat_as_private.end()) { // We previously sent a message to that nick) - // this->bridge->send_message(iid, nick, body, muc); this->bridge->send_message({nick + "!" + this->hostname}, nick, body, false); } -- cgit v1.2.3 From e4fcbd3030f033c24102db9f6b6abfb540332c9d Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 14 Jan 2015 12:38:46 +0100 Subject: Add support for password-protected IRC rooms. --- src/bridge/bridge.cpp | 4 ++-- src/bridge/bridge.hpp | 2 +- src/irc/irc_client.cpp | 4 ++-- src/irc/irc_client.hpp | 2 +- src/xmpp/xmpp_component.cpp | 4 +++- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index c925f9e..5fa96c8 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -108,7 +108,7 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname) } } -bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) +bool Bridge::join_irc_channel(const Iid& iid, const std::string& username, const std::string& password) { IrcClient* irc = this->get_irc_client(iid.get_server(), username); if (iid.get_local().empty()) @@ -135,7 +135,7 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username) } if (irc->is_channel_joined(iid.get_local()) == false) { - irc->send_join_command(iid.get_local()); + irc->send_join_command(iid.get_local(), password); return true; } return false; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c20bba2..698a017 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -58,7 +58,7 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& username); + bool join_irc_channel(const Iid& iid, const std::string& username, const std::string& password = ""); void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void leave_irc_channel(Iid&& iid, std::string&& status_message); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index b892684..a29fb0a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -206,12 +206,12 @@ void IrcClient::send_quit_command(const std::string& reason) this->send_message(IrcMessage("QUIT", {reason})); } -void IrcClient::send_join_command(const std::string& chan_name) +void IrcClient::send_join_command(const std::string& chan_name, const std::string& password) { if (this->welcomed == false) this->channels_to_join.push_back(chan_name); else - this->send_message(IrcMessage("JOIN", {chan_name})); + this->send_message(IrcMessage("JOIN", {chan_name, password})); this->start(); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 70d7955..29da868 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -81,7 +81,7 @@ public: /** * Send the JOIN irc command. */ - void send_join_command(const std::string& chan_name); + void send_join_command(const std::string& chan_name, const std::string& password = ""); /** * Send a PRIVMSG command for a channel * Return true if the message was actually sent diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index a1585d7..825193c 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -338,7 +338,9 @@ void XmppComponent::handle_presence(const Stanza& stanza) const std::string own_nick = bridge->get_own_nick(iid); if (!own_nick.empty() && own_nick != to.resource) bridge->send_irc_nick_change(iid, to.resource); - bridge->join_irc_channel(iid, to.resource); + XmlNode* x = stanza.get_child("x", MUC_NS); + XmlNode* password = x? x->get_child("password", MUC_NS): NULL; + bridge->join_irc_channel(iid, to.resource, password? password->get_inner(): ""); } else if (type == "unavailable") { -- cgit v1.2.3 From f9e259c266e5e9247562f899bafd5ddd2aa46099 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 Jan 2015 13:36:42 +0100 Subject: Fix a little bit of style stuf from previous commit --- src/bridge/bridge.hpp | 2 +- src/xmpp/xmpp_component.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 698a017..13cac23 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -58,7 +58,7 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& username, const std::string& password = ""); + bool join_irc_channel(const Iid& iid, const std::string& username, const std::string& password); void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void leave_irc_channel(Iid&& iid, std::string&& status_message); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 825193c..c63dc00 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -339,8 +339,9 @@ void XmppComponent::handle_presence(const Stanza& stanza) if (!own_nick.empty() && own_nick != to.resource) bridge->send_irc_nick_change(iid, to.resource); XmlNode* x = stanza.get_child("x", MUC_NS); - XmlNode* password = x? x->get_child("password", MUC_NS): NULL; - bridge->join_irc_channel(iid, to.resource, password? password->get_inner(): ""); + XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; + bridge->join_irc_channel(iid, to.resource, + password ? password->get_inner() : ""); } else if (type == "unavailable") { -- cgit v1.2.3 From 60569993b4532caffd8b1a6646292efbbd933585 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 Jan 2015 13:48:34 +0100 Subject: Make the password work when we join our first channel on that server Because we need to wait for the welcome message, when we connect to the server, before sending the JOIN command, we need to also save the value of the password to reuse it when we actually send the JOIN command --- src/irc/irc_client.cpp | 10 +++++----- src/irc/irc_client.hpp | 14 ++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index a29fb0a..12f39d6 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -74,9 +74,9 @@ void IrcClient::on_connection_failed(const std::string& reason) if (this->ports_to_try.empty()) { // Send an error message for all room that the user wanted to join - for (const std::string& channel: this->channels_to_join) + for (const auto& tuple: this->channels_to_join) { - Iid iid(channel + "%" + this->hostname); + Iid iid(std::get<0>(tuple) + "%" + this->hostname); this->bridge->send_presence_error(iid, this->current_nick, "cancel", "item-not-found", "", reason); @@ -209,7 +209,7 @@ void IrcClient::send_quit_command(const std::string& reason) void IrcClient::send_join_command(const std::string& chan_name, const std::string& password) { if (this->welcomed == false) - this->channels_to_join.push_back(chan_name); + this->channels_to_join.emplace_back(chan_name, password); else this->send_message(IrcMessage("JOIN", {chan_name, password})); this->start(); @@ -536,8 +536,8 @@ void IrcClient::on_welcome_message(const IrcMessage& message) // Install a repeated events to regularly send a PING TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), "PING"s + this->hostname + this->bridge->get_jid())); - for (const std::string& chan_name: this->channels_to_join) - this->send_join_command(chan_name); + for (const auto& tuple: this->channels_to_join) + this->send_join_command(std::get<0>(tuple), std::get<1>(tuple)); this->channels_to_join.clear(); // Indicate that the dummy channel is joined as well, if needed if (this->dummy_channel.joining) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 29da868..578fce2 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -81,7 +82,7 @@ public: /** * Send the JOIN irc command. */ - void send_join_command(const std::string& chan_name, const std::string& password = ""); + void send_join_command(const std::string& chan_name, const std::string& password); /** * Send a PRIVMSG command for a channel * Return true if the message was actually sent @@ -245,12 +246,13 @@ private: */ DummyIrcChannel dummy_channel; /** - * A list of chan we want to join, but we need a response 001 from - * the server before sending the actual JOIN commands. So we just keep the - * channel names in a list, and send the JOIN commands for each of them - * whenever the WELCOME message is received. + * A list of chan we want to join (tuples with the channel name and the + * password, if any), but we need a response 001 from the server before + * sending the actual JOIN commands. So we just keep the channel names in + * a list, and send the JOIN commands for each of them whenever the + * WELCOME message is received. */ - std::vector channels_to_join; + std::vector> channels_to_join; /** * This flag indicates that the server is completely joined (connection * has been established, we are authentified and we have a nick) -- cgit v1.2.3 From 4203384e2adcc57ddaf0ede6ca7745fb4a828c44 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 Jan 2015 13:53:25 +0100 Subject: Do not send an empty password at all, if the user didn't provide one --- src/irc/irc_client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 12f39d6..2549138 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -210,6 +210,8 @@ void IrcClient::send_join_command(const std::string& chan_name, const std::strin { if (this->welcomed == false) this->channels_to_join.emplace_back(chan_name, password); + else if (password.empty()) + this->send_message(IrcMessage("JOIN", {chan_name})); else this->send_message(IrcMessage("JOIN", {chan_name, password})); this->start(); -- cgit v1.2.3 From ddc989ce5dfe4df14296e3f669a1ce2af082a111 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 Jan 2015 13:54:21 +0100 Subject: Update changelog with the password feature --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 15cf354..a4fbf35 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Version 2.0 - Improve the way we forward received NOTICEs by remembering to which users we previously sent a private message. This improves the user experience when talking to NickServ. + - Support joining key-protected channels Version 1.1 2014-16-07 -- cgit v1.2.3 From a447214f10e894a7de827c3ff10c185280ed2538 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 14 Jan 2015 13:56:27 +0100 Subject: Remove a recent debug line that should not be there --- src/irc/irc_client.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 2549138..ee8d2d8 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -304,7 +304,6 @@ void IrcClient::on_notice(const IrcMessage& message) // it. Otherwise we treat it as a notice coming from the server. IrcUser user(from); std::string nick = utils::tolower(user.nick); - log_debug("received notice from nick: " << nick); if (this->nicks_to_treat_as_private.find(nick) != this->nicks_to_treat_as_private.end()) { // We previously sent a message to that nick) -- cgit v1.2.3 From 2c5d3d89b0eb1e6b8d888b4d37ceca6f23c2e314 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Jan 2015 16:19:13 +0100 Subject: Change IRC modes when receiving an affiliation/role change request fix #2946 --- src/bridge/bridge.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++++ src/bridge/bridge.hpp | 2 ++ src/xmpp/xmpp_component.cpp | 20 +++++++++----- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 5fa96c8..fc00c8c 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -183,6 +183,72 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) } } +void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& nick, + const std::string& affiliation, + const std::string& role) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + if (!irc) + return; + IrcChannel* chan = irc->get_channel(iid.get_local()); + if (!chan || !chan->joined) + return; + IrcUser* user = chan->find_user(nick); + if (!user) + return; + // For each affiliation or role, we have a “maximal” mode that we want to + // set. We must remove any superior mode at the same time. For example if + // the user already has +o mode, and we set its affiliation to member, we + // remove the +o mode, and add +v. For each “superior” mode (for example, + // for +v, the superior modes are 'h', 'a', 'o' and 'q') we check if that + // user has it, and if yes we remove that mode + + std::size_t nb = 1; // the number of times the nick must be + // repeated in the argument list + std::string modes; // The string of modes to + // add/remove. For example "+v-aoh" + std::vector modes_to_remove; // List of modes to check for removal + if (affiliation == "none") + { + modes = ""; + nb = 0; + modes_to_remove = {'v', 'h', 'o', 'a', 'q'}; + } + else if (affiliation == "member") + { + modes = "+v"; + modes_to_remove = {'h', 'o', 'a', 'q'}; + } + else if (role == "moderator") + { + modes = "+h"; + modes_to_remove = {'o', 'a', 'q'}; + } + else if (affiliation == "admin") + { + modes = "+o"; + modes_to_remove = {'a', 'q'}; + } + else if (affiliation == "owner") + { + modes = "+a"; + modes_to_remove = {'q'}; + } + else + return; + for (const char mode: modes_to_remove) + if (user->modes.find(mode) != user->modes.end()) + { + modes += "-"s + mode; + nb++; + } + if (modes.empty()) + return; + std::vector args(nb, nick); + args.insert(args.begin(), modes); + irc->send_mode_command(iid.get_local(), args); +} + void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) { if (iid.get_local().empty() || iid.get_server().empty()) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 13cac23..b1f79d5 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -72,6 +72,8 @@ public: void send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + void forward_affiliation_role_change(const Iid& iid, const std::string& nick, + const std::string& affiliation, const std::string& role); /** * Directly send a CTCP PING request to the IRC user */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index c63dc00..0328c78 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -481,14 +481,20 @@ void XmppComponent::handle_iq(const Stanza& stanza) { std::string nick = child->get_tag("nick"); std::string role = child->get_tag("role"); - if (!nick.empty() && role == "none") - { // This is a kick - std::string reason; - XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); - if (reason_el) - reason = reason_el->get_inner(); + std::string affiliation = child->get_tag("affiliation"); + if (!nick.empty()) + { Iid iid(to.local); - bridge->send_irc_kick(iid, nick, reason, id, from); + if (role == "none") + { // This is a kick + std::string reason; + XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); + if (reason_el) + reason = reason_el->get_inner(); + bridge->send_irc_kick(iid, nick, reason, id, from); + } + else + bridge->forward_affiliation_role_change(iid, nick, affiliation, role); stanza_error.disable(); } } -- cgit v1.2.3 From fd0a3225be09990e8d2e0e7f441440de9879f7eb Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Jan 2015 18:03:00 +0100 Subject: Document the new affiliation/role stuff, and update changelog --- CHANGELOG | 3 +++ doc/biboumi.1.md | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index a4fbf35..9e907b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ Version 2.0 which users we previously sent a private message. This improves the user experience when talking to NickServ. - Support joining key-protected channels + - Setting a participant's role/affiliation now results in a change of IRC + mode, instead of being ignored. Setting Toto's affiliation to admin is + now equivalent to “/mode +o Toto” Version 1.1 2014-16-07 diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index a35d4fc..18df220 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -294,6 +294,28 @@ between IRC modes and XMPP features is as follow: Sets the participant’s role to `participant` and its affiliation to `member`. +Similarly, when a biboumi user changes some participant's affiliation or role, biboumi translates that in an IRC mode change. + +Affiliation set to `none` + + Sets mode to -vhoaq + +Affiliation set to `member` + + Sets mode to +v-hoaq + +Role set to `moderator` + + Sets mode to +h-oaq + +Affiliation set to `admin` + + Sets mode to +o-aq + +Affiliation set to `owner` + + Sets mode to +a-q + ### Ad-hoc commands Biboumi supports a few ad-hoc commands, as described in the XEP 0050. -- cgit v1.2.3 From 51c28a2dbb1506cbd73c97cdb1e2ddb7fba017b1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Jan 2015 21:12:40 +0100 Subject: Fix the include guard of revstr.hpp --- src/utils/revstr.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp index 0f00076..27c9e3e 100644 --- a/src/utils/revstr.hpp +++ b/src/utils/revstr.hpp @@ -1,5 +1,5 @@ -#ifndef REVSTRP_INCLUDED -# define REVSTR_INCLUDED +#ifndef REVSTR_HPP_INCLUDED +# define REVSTR_HPP_INCLUDED #include @@ -8,4 +8,4 @@ namespace utils std::string revstr(const std::string& original); } -#endif // REVSTR_INCLUDED +#endif // REVSTR_HPP_INCLUDED -- cgit v1.2.3 From 4069c5e4acef86facec49ff50da588705a93a649 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Jan 2015 21:15:09 +0100 Subject: Add a missing #include for sockaddr_in6 --- src/network/tcp_socket_handler.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index c25ad83..6d4bbe4 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include -- cgit v1.2.3 From a37573513bd91452316beaab8807edf298c2bc73 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 Jan 2015 00:53:40 +0100 Subject: Add a tl;dr section in the INSTALL file --- INSTALL | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/INSTALL b/INSTALL index 69fa4bd..c5fb50f 100644 --- a/INSTALL +++ b/INSTALL @@ -1,3 +1,11 @@ +============== + tl;dr: +============== + +$ cmake . && make && ./biboumi + +If that didn’t work, read on. + ============== Dependencies ============== -- cgit v1.2.3 From ae469c21ff72966db344af22894d42cf1a1ba58e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Feb 2015 05:28:21 +0100 Subject: =?UTF-8?q?Fix=20=E2=80=9Ccleanely=E2=80=9D=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d9f6a9f..4ed935e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -139,6 +139,6 @@ int main(int ac, char** av) xmpp_component->close_document(); timeout = TimedEventsManager::instance().get_timeout(); } - log_info("All connection cleanely closed, have a nice day."); + log_info("All connection cleanly closed, have a nice day."); return 0; } -- cgit v1.2.3 From 03a8accc464038c6ff206189e2e2824912f2450d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Feb 2015 05:30:36 +0100 Subject: =?UTF-8?q?Fix=20=E2=80=9Cconnections=E2=80=9D=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 4ed935e..393bf05 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -139,6 +139,6 @@ int main(int ac, char** av) xmpp_component->close_document(); timeout = TimedEventsManager::instance().get_timeout(); } - log_info("All connection cleanly closed, have a nice day."); + log_info("All connections cleanly closed, have a nice day."); return 0; } -- cgit v1.2.3 From 0038e15b5a71369b52c95fdca95442b1b06f2943 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Feb 2015 05:43:20 +0100 Subject: Properly display [DEBUG] on debug lines, instead of [ERROR] --- src/logger/logger.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index b42681b..7560505 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -22,7 +22,7 @@ #ifdef SYSTEMDDAEMON_FOUND # include #else -# define SD_DEBUG "[ERROR]: " +# define SD_DEBUG "[DEBUG]: " # define SD_INFO "[INFO]: " # define SD_WARNING "[WARNING]: " # define SD_ERR "[ERROR]: " -- cgit v1.2.3 From 3032dc3580e2d6c3fab57b587945fbb213271557 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Feb 2015 05:49:57 +0100 Subject: Remove the XmppComponent::last_auth bool that was never used anywhere --- src/xmpp/xmpp_component.cpp | 3 --- src/xmpp/xmpp_component.hpp | 4 ---- 2 files changed, 7 deletions(-) diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 0328c78..1335326 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -39,7 +39,6 @@ static std::set kickable_errors{ XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): TCPSocketHandler(poller), ever_auth(false), - last_auth(false), served_hostname(hostname), secret(secret), authenticated(false), @@ -169,7 +168,6 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) return ; } - this->last_auth = false; // Try to authenticate char digest[HASH_LENGTH * 2 + 1]; sha1nfo sha1; @@ -277,7 +275,6 @@ void XmppComponent::handle_handshake(const Stanza& stanza) (void)stanza; this->authenticated = true; this->ever_auth = true; - this->last_auth = true; log_info("Authenticated with the XMPP server"); #ifdef SYSTEMDDAEMON_FOUND sd_notify(0, "READY=1"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index d7f7f7a..64b2ff7 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -241,10 +241,6 @@ public: * Whether or not we ever succeeded our authentication to the XMPP server */ bool ever_auth; - /** - * Whether or not the last connection+auth attempt was successful - */ - bool last_auth; private: /** -- cgit v1.2.3 From a50ca30e769a628f609f8cc0eedf5bc10b3f1b5a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 21 Feb 2015 06:39:20 +0100 Subject: Use a timer to try reconnecting to the XMPP server only each 2 seconds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the connection is lost, immediately try to reconnect, then try to reconnect every 2 seconds. This is much better than the previous “Try to re-connect as fast as possible”. --- CHANGELOG | 2 ++ src/main.cpp | 23 +++++++++++++++++++++-- src/network/poller.cpp | 3 ++- src/network/tcp_socket_handler.cpp | 5 ++++- src/xmpp/xmpp_component.cpp | 3 +++ src/xmpp/xmpp_component.hpp | 6 ++++++ 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e907b0..872eb4d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,8 @@ Version 2.0 - Setting a participant's role/affiliation now results in a change of IRC mode, instead of being ignored. Setting Toto's affiliation to admin is now equivalent to “/mode +o Toto” + - Fix the reconnection to the XMPP server to try every 2 seconds + instead of immediately. This avoid hogging resources for nothing Version 1.1 2014-16-07 diff --git a/src/main.cpp b/src/main.cpp index 393bf05..a67baf9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -108,6 +108,8 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); + // Cancel the timer for an potential reconnection + TimedEventsManager::instance().cancel("XMPP reconnection"); } if (reload) { @@ -127,17 +129,34 @@ int main(int ac, char** av) if (!exiting && xmpp_component->ever_auth && !xmpp_component->is_connected() && !xmpp_component->is_connecting()) - { + { + if (xmpp_component->first_connection_try == true) + { // immediately re-try to connect xmpp_component->reset(); xmpp_component->start(); } + else + { // Re-connecting failed, we now try only each few seconds + auto reconnect_later = [xmpp_component]() + { + xmpp_component->reset(); + xmpp_component->start(); + }; + TimedEvent event(std::chrono::steady_clock::now() + 2s, + reconnect_later, "XMPP reconnection"); + TimedEventsManager::instance().add_event(std::move(event)); + } + } // If the only existing connection is the one to the XMPP component: // close the XMPP stream. if (exiting && xmpp_component->is_connecting()) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); - timeout = TimedEventsManager::instance().get_timeout(); + if (exiting) // If we are exiting, do not wait for any timed event + timeout = utils::no_timeout; + else + timeout = TimedEventsManager::instance().get_timeout(); } log_info("All connections cleanly closed, have a nice day."); return 0; diff --git a/src/network/poller.cpp b/src/network/poller.cpp index 29c4bce..ffc4f2d 100644 --- a/src/network/poller.cpp +++ b/src/network/poller.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -133,7 +134,7 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) int Poller::poll(const std::chrono::milliseconds& timeout) { - if (this->socket_handlers.empty()) + if (this->socket_handlers.empty() && timeout == utils::no_timeout) return -1; #if POLLER == POLL int nb_events = ::poll(this->fds, this->nfds, timeout.count()); diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index c5d254e..1d1eaa7 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -213,8 +213,11 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) else if (-1 == size) { log_warning("Error while reading from socket: " << strerror(errno)); + // Remember if we were connecting, or already connected when this + // happened, because close() sets this->connecting to false + const auto were_connecting = this->connecting; this->close(); - if (this->connecting) + if (were_connecting) this->on_connection_failed(strerror(errno)); else this->on_connection_close(strerror(errno)); diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1335326..841ead4 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -39,6 +39,7 @@ static std::set kickable_errors{ XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): TCPSocketHandler(poller), ever_auth(false), + first_connection_try(true), served_hostname(hostname), secret(secret), authenticated(false), @@ -86,6 +87,7 @@ void XmppComponent::send_stanza(const Stanza& stanza) void XmppComponent::on_connection_failed(const std::string& reason) { + this->first_connection_try = false; log_error("Failed to connect to the XMPP server: " << reason); #ifdef SYSTEMDDAEMON_FOUND sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); @@ -95,6 +97,7 @@ void XmppComponent::on_connection_failed(const std::string& reason) void XmppComponent::on_connected() { log_info("connected to XMPP server"); + this->first_connection_try = true; XmlNode node("", nullptr); node.set_name("stream:stream"); node["xmlns"] = COMPONENT_NS; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 64b2ff7..951d5a3 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -241,6 +241,12 @@ public: * Whether or not we ever succeeded our authentication to the XMPP server */ bool ever_auth; + /** + * Whether or not this is the first consecutive try on connecting to the + * XMPP server. We use this to delay the connection attempt for a few + * seconds, if it is not the first try. + */ + bool first_connection_try; private: /** -- cgit v1.2.3 From a17135720e77c03e66679852198e46a070d56f4d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 22 Feb 2015 18:00:35 +0100 Subject: Fix typo --- src/network/poller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/poller.hpp b/src/network/poller.hpp index c3edcd7..c674e4c 100644 --- a/src/network/poller.hpp +++ b/src/network/poller.hpp @@ -44,7 +44,7 @@ public: */ void add_socket_handler(SocketHandler* socket_handler); /** - * Remove (and stop managing) a SocketHandler, designed by the given socket_t. + * Remove (and stop managing) a SocketHandler, designated by the given socket_t. */ void remove_socket_handler(const socket_t socket); /** -- cgit v1.2.3 From b86547dc1ef407ca3838444533bc7145e32a0d90 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 9 Jul 2014 13:02:37 +0200 Subject: Implement async DNS resolution using c-ares fix #2533 --- CMakeLists.txt | 13 +++ src/config.h.cmake | 1 + src/main.cpp | 16 +++- src/network/dns_handler.cpp | 112 +++++++++++++++++++++++++ src/network/dns_handler.hpp | 62 ++++++++++++++ src/network/dns_socket_handler.cpp | 45 ++++++++++ src/network/dns_socket_handler.hpp | 46 ++++++++++ src/network/socket_handler.hpp | 2 + src/network/tcp_socket_handler.cpp | 166 +++++++++++++++++++++++++++++++++++-- src/network/tcp_socket_handler.hpp | 47 +++++++++-- src/xmpp/xmpp_component.cpp | 2 +- 11 files changed, 499 insertions(+), 13 deletions(-) create mode 100644 src/network/dns_handler.cpp create mode 100644 src/network/dns_handler.hpp create mode 100644 src/network/dns_socket_handler.cpp create mode 100644 src/network/dns_socket_handler.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 138bdeb..6272811 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,12 @@ elseif(NOT WITHOUT_BOTAN) find_package(BOTAN) endif() +if(WITH_CARES) + find_package(CARES REQUIRED) +elseif(NOT WITHOUT_CARES) + find_package(CARES) +endif() + # ## Get the software version # @@ -84,6 +90,10 @@ if(BOTAN_FOUND) include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) endif() +if(CARES_FOUND) + include_directories(${CARES_INCLUDE_DIRS}) +endif() + set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) @@ -145,6 +155,9 @@ target_link_libraries(network logger) if(BOTAN_FOUND) target_link_libraries(network ${BOTAN_LIBRARIES}) endif() +if(CARES_FOUND) + target_link_libraries(network ${CARES_LIBRARIES}) +endif() # ## irclib diff --git a/src/config.h.cmake b/src/config.h.cmake index 8eb2d1c..18d546f 100644 --- a/src/config.h.cmake +++ b/src/config.h.cmake @@ -4,4 +4,5 @@ #cmakedefine SYSTEMD_FOUND #cmakedefine POLLER ${POLLER} #cmakedefine BOTAN_FOUND +#cmakedefine CARES_FOUND #cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" diff --git a/src/main.cpp b/src/main.cpp index a67baf9..94c3cb5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -11,6 +10,10 @@ #include +#ifdef CARES_FOUND +# include +#endif + // A flag set by the SIGINT signal handler. static volatile std::atomic stop(false); // Flag set by the SIGUSR1/2 signal handler. @@ -95,6 +98,10 @@ int main(int ac, char** av) xmpp_component->start(); + +#ifdef CARES_FOUND + DNSHandler::instance.watch_dns_sockets(p); +#endif auto timeout = TimedEventsManager::instance().get_timeout(); while (p->poll(timeout) != -1) { @@ -108,6 +115,9 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif // Cancel the timer for an potential reconnection TimedEventsManager::instance().cancel("XMPP reconnection"); } @@ -153,6 +163,10 @@ int main(int ac, char** av) xmpp_component->close(); if (exiting && p->size() == 1 && xmpp_component->is_document_open()) xmpp_component->close_document(); +#ifdef CARES_FOUND + if (!exiting) + DNSHandler::instance.watch_dns_sockets(p); +#endif if (exiting) // If we are exiting, do not wait for any timed event timeout = utils::no_timeout; else diff --git a/src/network/dns_handler.cpp b/src/network/dns_handler.cpp new file mode 100644 index 0000000..45bf626 --- /dev/null +++ b/src/network/dns_handler.cpp @@ -0,0 +1,112 @@ +#include +#ifdef CARES_FOUND + +#include +#include +#include +#include + +#include +#include + +DNSHandler DNSHandler::instance; + +using namespace std::string_literals; + +void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent) +{ + TCPSocketHandler* socket_handler = static_cast(arg); + socket_handler->on_hostname4_resolved(status, hostent); +} + +void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent) +{ + TCPSocketHandler* socket_handler = static_cast(arg); + socket_handler->on_hostname6_resolved(status, hostent); +} + +DNSHandler::DNSHandler() +{ + int ares_error; + if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) + throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); + if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS) + throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); +} + +ares_channel& DNSHandler::get_channel() +{ + return this->channel; +} + +void DNSHandler::destroy() +{ + this->socket_handlers.clear(); + ::ares_destroy(this->channel); + ::ares_library_cleanup(); +} + +void DNSHandler::gethostbyname(const std::string& name, + TCPSocketHandler* socket_handler, int family) +{ + socket_handler->free_cares_addrinfo(); + if (family == AF_INET) + ::ares_gethostbyname(this->channel, name.data(), family, + &::on_hostname4_resolved, socket_handler); + else + ::ares_gethostbyname(this->channel, name.data(), family, + &::on_hostname6_resolved, socket_handler); +} + +void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) +{ + fd_set readers; + fd_set writers; + + FD_ZERO(&readers); + FD_ZERO(&writers); + + int ndfs = ::ares_fds(this->channel, &readers, &writers); + // For each existing DNS socket, see if we are still supposed to watch it, + // if not then erase it + this->socket_handlers.erase( + std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), + [&readers](const auto& dns_socket) + { + return !FD_ISSET(dns_socket->get_socket(), &readers); + }), + this->socket_handlers.end()); + + for (auto i = 0; i < ndfs; ++i) + { + bool read = FD_ISSET(i, &readers); + bool write = FD_ISSET(i, &writers); + // Look for the DNSSocketHandler with this fd + auto it = std::find_if(this->socket_handlers.begin(), + this->socket_handlers.end(), + [i](const auto& socket_handler) + { + return i == socket_handler->get_socket(); + }); + if (!read && !write) // No need to read or write to it + { // If found, erase it and stop watching it because it is not + // needed anymore + if (it != this->socket_handlers.end()) + // The socket destructor removes it from the poller + this->socket_handlers.erase(it); + } + else // We need to write and/or read to it + { // If not found, create it because we need to watch it + if (it == this->socket_handlers.end()) + { + this->socket_handlers.emplace_front(std::make_unique(poller, i)); + it = this->socket_handlers.begin(); + } + poller->add_socket_handler(it->get()); + if (write) + poller->watch_send_events(it->get()); + } + } +} + +#endif /* CARES_FOUND */ diff --git a/src/network/dns_handler.hpp b/src/network/dns_handler.hpp new file mode 100644 index 0000000..ec5b2fa --- /dev/null +++ b/src/network/dns_handler.hpp @@ -0,0 +1,62 @@ +#ifndef DNS_HANDLER_HPP_INCLUDED +#define DNS_HANDLER_HPP_INCLUDED + +#include +#ifdef CARES_FOUND + +class TCPSocketHandler; +class Poller; +class DNSSocketHandler; + +# include +# include +# include +# include + +void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); +void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); + +/** + * Class managing DNS resolution. It should only be statically instanciated + * once in SocketHandler. It manages ares channel and calls various + * functions of that library. + */ + +class DNSHandler +{ +public: + DNSHandler(); + ~DNSHandler() = default; + void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler, + int family); + /** + * Call ares_fds to know what fd needs to be watched by the poller, create + * or destroy DNSSocketHandlers depending on the result. + */ + void watch_dns_sockets(std::shared_ptr& poller); + /** + * Destroy and stop watching all the DNS sockets. Then de-init the channel + * and library. + */ + void destroy(); + ares_channel& get_channel(); + + static DNSHandler instance; + +private: + /** + * The list of sockets that needs to be watched, according to the last + * call to ares_fds. DNSSocketHandlers are added to it or removed from it + * in the watch_dns_sockets() method + */ + std::list> socket_handlers; + ares_channel channel; + + DNSHandler(const DNSHandler&) = delete; + DNSHandler(DNSHandler&&) = delete; + DNSHandler& operator=(const DNSHandler&) = delete; + DNSHandler& operator=(DNSHandler&&) = delete; +}; + +#endif /* CARES_FOUND */ +#endif /* DNS_HANDLER_HPP_INCLUDED */ diff --git a/src/network/dns_socket_handler.cpp b/src/network/dns_socket_handler.cpp new file mode 100644 index 0000000..6563894 --- /dev/null +++ b/src/network/dns_socket_handler.cpp @@ -0,0 +1,45 @@ +#include +#ifdef CARES_FOUND + +#include +#include +#include + +#include + +DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, + const socket_t socket): + SocketHandler(poller, socket) +{ +} + +DNSSocketHandler::~DNSSocketHandler() +{ +} + +void DNSSocketHandler::connect() +{ +} + +void DNSSocketHandler::on_recv() +{ + // always stop watching send and read events. We will re-watch them if the + // next call to ares_fds tell us to + this->poller->remove_socket_handler(this->socket); + ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); +} + +void DNSSocketHandler::on_send() +{ + // always stop watching send and read events. We will re-watch them if the + // next call to ares_fds tell us to + this->poller->remove_socket_handler(this->socket); + ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); +} + +bool DNSSocketHandler::is_connected() const +{ + return true; +} + +#endif /* CARES_FOUND */ diff --git a/src/network/dns_socket_handler.hpp b/src/network/dns_socket_handler.hpp new file mode 100644 index 0000000..beb47d9 --- /dev/null +++ b/src/network/dns_socket_handler.hpp @@ -0,0 +1,46 @@ +#ifndef DNS_SOCKET_HANDLER_HPP +# define DNS_SOCKET_HANDLER_HPP + +#include +#ifdef CARES_FOUND + +#include +#include + +/** + * Manage a socket returned by ares_fds. We do not create, open or close the + * socket ourself: this is done by c-ares. We just call ares_process_fd() + * with the correct parameters, depending on what can be done on that socket + * (Poller reported it to be writable or readeable) + */ + +class DNSSocketHandler: public SocketHandler +{ +public: + explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); + ~DNSSocketHandler(); + /** + * Just call dns_process_fd, c-ares will do its work of send()ing or + * recv()ing the data it wants on that socket. + */ + void on_recv() override final; + void on_send() override final; + /** + * Do nothing, because we are always considered to be connected, since the + * connection is done by c-ares and not by us. + */ + void connect() override final; + /** + * Always true, see the comment for connect() + */ + bool is_connected() const override final; + +private: + DNSSocketHandler(const DNSSocketHandler&) = delete; + DNSSocketHandler(DNSSocketHandler&&) = delete; + DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; + DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; +}; + +#endif // CARES_FOUND +#endif // DNS_SOCKET_HANDLER_HPP diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp index 9a894a4..0858474 100644 --- a/src/network/socket_handler.hpp +++ b/src/network/socket_handler.hpp @@ -1,6 +1,7 @@ #ifndef SOCKET_HANDLER_HPP # define SOCKET_HANDLER_HPP +#include #include class Poller; @@ -19,6 +20,7 @@ public: virtual void on_send() = 0; virtual void connect() = 0; virtual bool is_connected() const = 0; + socket_t get_socket() const { return this->socket; } diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 1d1eaa7..e9984e3 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -42,8 +43,22 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): use_tls(false), connected(false), connecting(false) +#ifdef CARES_FOUND + ,resolved(false), + resolved4(false), + resolved6(false), + cares_addrinfo(nullptr), + cares_error() +#endif {} +TCPSocketHandler::~TCPSocketHandler() +{ +#ifdef CARES_FOUND + this->free_cares_addrinfo(); +#endif +} + void TCPSocketHandler::init_socket(const struct addrinfo* rp) { if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) @@ -72,9 +87,35 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po if (!this->connecting) { + // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if + // this is the first call of this function. +#ifdef CARES_FOUND + if (!this->resolved) + { + log_info("Trying to connect to " << address << ":" << port); + // Start the asynchronous process of resolving the hostname. Once + // the addresses have been found and `resolved` has been set to true + // (but connecting will still be false), TCPSocketHandler::connect() + // needs to be called, again. + DNSHandler::instance.gethostbyname(address, this, AF_INET6); + DNSHandler::instance.gethostbyname(address, this, AF_INET); + return; + } + else + { + // The c-ares resolved the hostname and the available addresses + // where saved in the cares_addrinfo linked list. Now, just use + // this list to try to connect. + addr_res = this->cares_addrinfo; + if (!addr_res) + { + this->close(); + this->on_connection_failed(this->cares_error); + return ; + } + } +#else log_info("Trying to connect to " << address << ":" << port); - // Get the addrinfo from getaddrinfo, only if this is the first call - // of this function. struct addrinfo hints; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = 0; @@ -94,6 +135,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po // Make sure the alloced structure is always freed at the end of the // function sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); +#endif } else { // This function is called again, use the saved addrinfo structure, @@ -144,9 +186,9 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po // If the connection has not succeeded or failed in 5s, we consider // it to have failed TimedEventsManager::instance().add_event( - TimedEvent(std::chrono::steady_clock::now() + 5s, - std::bind(&TCPSocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); + TimedEvent(std::chrono::steady_clock::now() + 5s, + std::bind(&TCPSocketHandler::on_connection_timeout, this), + "connection_timeout"s + std::to_string(this->socket))); return ; } log_info("Connection failed:" << strerror(errno)); @@ -321,7 +363,11 @@ bool TCPSocketHandler::is_connected() const bool TCPSocketHandler::is_connecting() const { +#ifdef CARES_FOUND + return this->connecting || !this->resolved; +#else return this->connecting; +#endif } void* TCPSocketHandler::get_receive_buffer(const size_t) const @@ -413,4 +459,114 @@ void TCPSocketHandler::on_tls_activated() { this->send_data(""); } + #endif // BOTAN_FOUND + +#ifdef CARES_FOUND + +void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent) +{ + this->resolved4 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo4(hostent); + else + this->cares_error = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + { + this->resolved = true; + this->connect(); + } +} + +void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent) +{ + this->resolved6 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo6(hostent); + else + this->cares_error = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + { + this->resolved = true; + this->connect(); + } +} + +void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent) +{ + struct addrinfo* prev = this->cares_addrinfo; + struct in_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent) +{ + struct addrinfo* prev = this->cares_addrinfo; + struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +void TCPSocketHandler::free_cares_addrinfo() +{ + while (this->cares_addrinfo) + { + delete this->cares_addrinfo->ai_addr; + auto next = this->cares_addrinfo->ai_next; + delete this->cares_addrinfo; + this->cares_addrinfo = next; + } +} + +#endif // CARES_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index 6d4bbe4..7f10cff 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -17,6 +17,10 @@ #include "config.h" +#ifdef CARES_FOUND +# include +#endif + #ifdef BOTAN_FOUND # include # include @@ -44,7 +48,7 @@ public: class TCPSocketHandler: public SocketHandler { protected: - ~TCPSocketHandler() {} + ~TCPSocketHandler(); public: explicit TCPSocketHandler(std::shared_ptr poller); @@ -54,16 +58,16 @@ public: * start_tls() when the connection succeeds. */ void connect(const std::string& address, const std::string& port, const bool tls); - void connect(); + void connect() override final; /** * Reads raw data from the socket. And pass it to parse_in_buffer() * If we are using TLS on this connection, we call tls_recv() */ - void on_recv(); + void on_recv() override final; /** * Write as much data from out_buf as possible, in the socket. */ - void on_send(); + void on_send() override final; /** * Add the given data to out_buf and tell our poller that we want to be * notified when a send event is ready. @@ -107,9 +111,19 @@ public: * The size argument is the size of the last chunk of data that was added to the buffer. */ virtual void parse_in_buffer(const size_t size) = 0; - bool is_connected() const; + bool is_connected() const override final; bool is_connecting() const; +#ifdef CARES_FOUND + void on_hostname4_resolved(int status, struct hostent* hostent); + void on_hostname6_resolved(int status, struct hostent* hostent); + + void free_cares_addrinfo(); + + void fill_ares_addrinfo4(const struct hostent* hostent); + void fill_ares_addrinfo6(const struct hostent* hostent); +#endif + private: /** * Initialize the socket with the parameters contained in the given @@ -185,7 +199,7 @@ private: */ std::list out_buf; /** - * Keep the details of the addrinfo the triggered a EINPROGRESS error when + * Keep the details of the addrinfo that triggered a EINPROGRESS error when * connect()ing to it, to reuse it directly when connect() is called * again. */ @@ -225,6 +239,27 @@ protected: bool connected; bool connecting; +#ifdef CARES_FOUND + /** + * Whether or not the DNS resolution was successfully done + */ + bool resolved; + bool resolved4; + bool resolved6; + /** + * When using c-ares to resolve the host asynchronously, we need the + * c-ares callback to fill a structure (a struct addrinfo, for + * compatibility with getaddrinfo and the rest of the code that works when + * c-ares is not used) with all returned values (for example an IPv6 and + * an IPv4). The next call of connect() will then try all these values + * (exactly like we do with the result of getaddrinfo) and save the one + * that worked (or returned EINPROGRESS) in the other struct addrinfo (see + * the members addrinfo, ai_addrlen, and ai_addr). + */ + struct addrinfo* cares_addrinfo; + std::string cares_error; +#endif // CARES_FOUND + private: TCPSocketHandler(const TCPSocketHandler&) = delete; TCPSocketHandler(TCPSocketHandler&&) = delete; diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 841ead4..1df1e5d 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -70,7 +70,7 @@ XmppComponent::~XmppComponent() void XmppComponent::start() { - this->connect("127.0.0.1", Config::get("port", "5347"), false); + this->connect("localhost", Config::get("port", "5347"), false); } bool XmppComponent::is_document_open() const -- cgit v1.2.3 From 7ec1f1242fbec9bff1616576732c43249b521e9f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 22 Feb 2015 20:03:47 +0100 Subject: Add the FindCARES cmake module --- cmake/Modules/FindCARES.cmake | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 cmake/Modules/FindCARES.cmake diff --git a/cmake/Modules/FindCARES.cmake b/cmake/Modules/FindCARES.cmake new file mode 100644 index 0000000..c4c757a --- /dev/null +++ b/cmake/Modules/FindCARES.cmake @@ -0,0 +1,37 @@ +# - Find c-ares +# Find the c-ares library, and more particularly the stringprep header. +# +# This module defines the following variables: +# CARES_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# CARES_INCLUDE_DIRS - The directory where to find the header file +# CARES_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# CARES_INCLUDE_DIR +# CARES_LIBRARY +# +# This file is in the public domain + +if(NOT CARES_FOUND) + find_path(CARES_INCLUDE_DIRS NAMES ares.h + DOC "The c-ares include directory") + + find_library(CARES_LIBRARIES NAMES cares + DOC "The c-ares library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set CARES_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CARES REQUIRED_VARS CARES_LIBRARIES CARES_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(CARES_FOUND) + set(CARES_INCLUDE_DIR ${CARES_INCLUDE_DIRS}) + set(CARES_LIBRARY ${CARES_LIBRARIES}) + endif() +endif() + +mark_as_advanced(CARES_INCLUDE_DIRS CARES_LIBRARIES) -- cgit v1.2.3 From 9837f611d97bccdb4bf46f042120c3cb8d3fe907 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 23 Feb 2015 14:03:00 +0100 Subject: Update the CHANGELOG and INSTALL files, mentioning c-ares --- CHANGELOG | 2 ++ INSTALL | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 872eb4d..468974c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,8 @@ Version 2.0 now equivalent to “/mode +o Toto” - Fix the reconnection to the XMPP server to try every 2 seconds instead of immediately. This avoid hogging resources for nothing + - Asynchronously resolve domain names by optionally using the DNS + library c-ares. Version 1.1 2014-16-07 diff --git a/INSTALL b/INSTALL index c5fb50f..09a1590 100644 --- a/INSTALL +++ b/INSTALL @@ -37,6 +37,12 @@ Libraries: not provided. http://www.gnu.org/software/libidn/ +- c-ares (optional, but recommended) + Asynchronously resolve domain names. This offers better reactivity and + performances when connecting to a big number of IRC servers at the same + time. + http://c-ares.haxx.se/ + - libbotan 1.11 (optional) Provides TLS support. Without it, IRC connections are all made in plain-text mode. -- cgit v1.2.3 From 7115aa3b7f22f95e5e614ffc74c467844c08d965 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 24 Feb 2015 16:28:30 +0100 Subject: Add a reload add-hoc command --- CHANGELOG | 1 + src/main.cpp | 8 ++------ src/utils/reload.cpp | 13 +++++++++++++ src/utils/reload.hpp | 10 ++++++++++ src/xmpp/adhoc_command.cpp | 12 ++++++++++++ src/xmpp/adhoc_command.hpp | 1 + src/xmpp/adhoc_commands_handler.cpp | 3 ++- 7 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 src/utils/reload.cpp create mode 100644 src/utils/reload.hpp diff --git a/CHANGELOG b/CHANGELOG index 468974c..e3ba7bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ Version 2.0 instead of immediately. This avoid hogging resources for nothing - Asynchronously resolve domain names by optionally using the DNS library c-ares. + - Add a reload add-hoc command, to reload biboumi's configuration Version 1.1 2014-16-07 diff --git a/src/main.cpp b/src/main.cpp index 94c3cb5..148412e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -123,13 +124,8 @@ int main(int ac, char** av) } if (reload) { - // Closing the config will just force it to be reopened the next time - // a configuration option is needed log_info("Signal received, reloading the config..."); - Config::close(); - // Destroy the logger instance, to be recreated the next time a log - // line needs to be written - Logger::instance().reset(); + ::reload_process(); reload.store(false); } // Reconnect to the XMPP server if this was not intended. This may have diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp new file mode 100644 index 0000000..6600c75 --- /dev/null +++ b/src/utils/reload.cpp @@ -0,0 +1,13 @@ +#include +#include + +void reload_process() +{ + // Closing the config will just force it to be reopened the next time + // a configuration option is needed + Config::close(); + // Destroy the logger instance, to be recreated the next time a log + // line needs to be written + Logger::instance().reset(); + log_debug("Configuration and logger reloaded."); +} diff --git a/src/utils/reload.hpp b/src/utils/reload.hpp new file mode 100644 index 0000000..16d64f7 --- /dev/null +++ b/src/utils/reload.hpp @@ -0,0 +1,10 @@ +#ifndef RELOAD_HPP_INCLUDED +#define RELOAD_HPP_INCLUDED + +/** + * Reload the server's configuration, and close the logger (so that it + * closes its files etc, to take into account the new configuration) + */ +void reload_process(); + +#endif /* RELOAD_HPP_INCLUDED */ diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index e1bfc97..c20976a 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -3,6 +3,8 @@ #include +#include + using namespace std::string_literals; AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): @@ -198,3 +200,13 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X session.terminate(); } +void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node) +{ + ::reload_process(); + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration reloaded."); + note.close(); + command_node.add_child(std::move(note)); +} diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp index 60f7d6c..622d6b9 100644 --- a/src/xmpp/adhoc_command.hpp +++ b/src/xmpp/adhoc_command.hpp @@ -40,5 +40,6 @@ void HelloStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node); #endif // ADHOC_COMMAND_HPP diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index a0defdd..def1dcb 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -15,7 +15,8 @@ AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component): commands{ {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)} + {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, + {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} } { } -- cgit v1.2.3 From 89af57bfcde9713719f0147bb1df237f69c33d37 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Feb 2015 16:27:23 +0100 Subject: Forge link uses https --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 5fcae29..32636aa 100644 --- a/README +++ b/README @@ -38,7 +38,7 @@ Florent Le Coz (louiz’) Contact/Support ================= Jabber ChatRoom: biboumi@muc.poez.io -Report a bug: http://dev.louiz.org/projects/biboumi/issues/new +Report a bug: https://dev.louiz.org/projects/biboumi/issues/new ================= Licence -- cgit v1.2.3 From 1c43c3af4cbefcd482f4145ee3d7553631a7485d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Feb 2015 16:37:31 +0100 Subject: Include the IRC hostname in the IRC RECEIVING and SENDING debug messages fix #2715 --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ee8d2d8..f83b48c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -140,7 +140,7 @@ void IrcClient::parse_in_buffer(const size_t) break ; IrcMessage message(this->in_buf.substr(0, pos)); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); - log_debug("IRC RECEIVING: " << message); + log_debug("IRC RECEIVING: (" << this->get_hostname() << ") " << message); // Call the standard callback (if any), associated with the command // name that we just received. @@ -162,7 +162,7 @@ void IrcClient::parse_in_buffer(const size_t) void IrcClient::send_message(IrcMessage&& message) { - log_debug("IRC SENDING: " << message); + log_debug("IRC SENDING: (" << this->get_hostname() << ") " << message); std::string res; if (!message.prefix.empty()) res += ":" + std::move(message.prefix) + " "; -- cgit v1.2.3 From 2df0ebf2dfed1dcbf80c92bff8361e2a04581bec Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Feb 2015 18:35:30 +0100 Subject: Add support for a fixed_irc_server configuration This option lets the administrator choose a specific IRC server, and only that server can be used with this biboumi instance. In this mode, JIDs to use are changed like this: - #chan%irc.example.com@biboumi.example.com -> #chan@biboumi.example.com - user!irc.example.com@biboumi.example.com -> user!@biboumi.example.com - #chan%irc.example.com@biboumi.example.com/Nick -> #chan@biboumi.example.com/Nick - %irc.example.com@biboumi.example.com -> no equivalent - irc.example.com@biboumi.example.com -> no equivalent --- doc/biboumi.1.md | 14 ++++ src/bridge/bridge.cpp | 9 +-- src/irc/iid.cpp | 44 ++++++++++++- src/irc/iid.hpp | 4 ++ src/test.cpp | 126 +++++++++++++++++++++++++++--------- src/utils/empty_if_fixed_server.cpp | 8 +++ src/utils/empty_if_fixed_server.hpp | 26 ++++++++ 7 files changed, 194 insertions(+), 37 deletions(-) create mode 100644 src/utils/empty_if_fixed_server.cpp create mode 100644 src/utils/empty_if_fixed_server.hpp diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 18df220..9e95139 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -59,6 +59,20 @@ The configuration file uses a simple format of the form privileges), for example some administration ad-hoc commands will only be available to that JID. +`fixed_irc_server` + + If this option contains the hostname of an IRC server (for example + irc.example.org), then biboumi will enforce the connexion to that IRC + server only. This means that a JID like "#chan@irc.biboumi.org" must be + used instead of "#chan%irc.example.org@irc.biboumi.org". In that mode, + the virtual channel (see *Connect to an IRC server*) is not available and + you still need to use the ! separator to send message to an IRC user (for + example "foo!@biboumi.example.com" to send a message to foo), although the + in-room JID still work as expected ("#channel@biboumi.example.com/Nick"). + This option can for example be used by an administrator that just wants to + let their users join their own IRC server using an XMPP client, but + without letting them join any other IRC servers on the internet. + `log_file` A filename into which logs are written. If none is provided, the logs are diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index fc00c8c..e312345 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -542,13 +543,13 @@ void Bridge::send_user_join(const std::string& hostname, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp->send_user_join(chan_name + "%" + hostname, user->nick, user->host, + this->xmpp->send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, affiliation, role, this->user_jid, self); } void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic) { - this->xmpp->send_topic(chan_name + "%" + hostname, this->make_xmpp_body(topic), this->user_jid); + this->xmpp->send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic), this->user_jid); } std::string Bridge::get_own_nick(const Iid& iid) @@ -585,7 +586,7 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { - this->xmpp->send_iq_version_request(nick + "!" + hostname, this->user_jid); + this->xmpp->send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, @@ -594,7 +595,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Use revstr because the forwarded ping to target XMPP user must not be // the same that the request iq, but we also need to get it back easily // (revstr again) - this->xmpp->send_ping_request(nick + "!" + hostname, this->user_jid, utils::revstr(id)); + this->xmpp->send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id)); } void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 0bb991f..d4dc8ce 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,10 +1,21 @@ #include +#include #include Iid::Iid(const std::string& iid): is_channel(false), is_user(false) +{ + const std::string fixed_irc_server = Config::get("fixed_irc_server", ""); + if (fixed_irc_server.empty()) + this->init(iid); + else + this->init_with_fixed_server(iid, fixed_irc_server); +} + + +void Iid::init(const std::string& iid) { const std::string::size_type sep = iid.find_first_of("%!"); if (sep != std::string::npos) @@ -20,6 +31,29 @@ Iid::Iid(const std::string& iid): this->set_server(iid); } +void Iid::init_with_fixed_server(const std::string& iid, const std::string& hostname) +{ + this->set_server(hostname); + + const std::string::size_type sep = iid.find_first_of("%!"); + + // Without any separator, we consider that it's a channel + if (sep == std::string::npos) + { + this->is_channel = true; + this->set_local(iid); + } + else // A separator can be present to differenciate a channel from a user, + // but the part behind it (the hostname) is ignored + { + this->set_local(iid.substr(0, sep)); + if (iid[sep] == '%') + this->is_channel = true; + else + this->is_user = true; + } +} + Iid::Iid(const Iid& other): is_channel(other.is_channel), is_user(other.is_user), @@ -66,6 +100,14 @@ std::string Iid::get_sep() const namespace std { const std::string to_string(const Iid& iid) { - return iid.get_local() + iid.get_sep() + iid.get_server(); + if (Config::get("fixed_irc_server", "").empty()) + return iid.get_local() + iid.get_sep() + iid.get_server(); + else + { + if (iid.get_sep() == "!") + return iid.get_local() + iid.get_sep(); + else + return iid.get_local(); + } } } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index d30cbaa..91779b2 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -57,6 +57,10 @@ public: std::string get_sep() const; private: + + void init(const std::string& iid); + void init_with_fixed_server(const std::string& iid, const std::string& hostname); + std::string local; std::string server; diff --git a/src/test.cpp b/src/test.cpp index a4371b2..9fa2c99 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -303,38 +303,100 @@ int main() /** * IID parsing */ - std::cout << color << "Testing IID parsing…" << reset << std::endl; - Iid iid1("foo!irc.example.org"); - std::cout << std::to_string(iid1) << std::endl; - assert(std::to_string(iid1) == "foo!irc.example.org"); - assert(iid1.get_local() == "foo"); - assert(iid1.get_server() == "irc.example.org"); - assert(!iid1.is_channel); - assert(iid1.is_user); - - Iid iid2("#test%irc.example.org"); - std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test%irc.example.org"); - assert(iid2.get_local() == "#test"); - assert(iid2.get_server() == "irc.example.org"); - assert(iid2.is_channel); - assert(!iid2.is_user); - - Iid iid3("%irc.example.org"); - std::cout << std::to_string(iid3) << std::endl; - assert(std::to_string(iid3) == "%irc.example.org"); - assert(iid3.get_local() == ""); - assert(iid3.get_server() == "irc.example.org"); - assert(iid3.is_channel); - assert(!iid3.is_user); - - Iid iid4("irc.example.org"); - std::cout << std::to_string(iid4) << std::endl; - assert(std::to_string(iid4) == "irc.example.org"); - assert(iid4.get_local() == ""); - assert(iid4.get_server() == "irc.example.org"); - assert(!iid4.is_channel); - assert(!iid4.is_user); + { + std::cout << color << "Testing IID parsing…" << reset << std::endl; + Iid iid1("foo!irc.example.org"); + std::cout << std::to_string(iid1) << std::endl; + assert(std::to_string(iid1) == "foo!irc.example.org"); + assert(iid1.get_local() == "foo"); + assert(iid1.get_server() == "irc.example.org"); + assert(!iid1.is_channel); + assert(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + std::cout << std::to_string(iid2) << std::endl; + assert(std::to_string(iid2) == "#test%irc.example.org"); + assert(iid2.get_local() == "#test"); + assert(iid2.get_server() == "irc.example.org"); + assert(iid2.is_channel); + assert(!iid2.is_user); + + Iid iid3("%irc.example.org"); + std::cout << std::to_string(iid3) << std::endl; + assert(std::to_string(iid3) == "%irc.example.org"); + assert(iid3.get_local() == ""); + assert(iid3.get_server() == "irc.example.org"); + assert(iid3.is_channel); + assert(!iid3.is_user); + + Iid iid4("irc.example.org"); + std::cout << std::to_string(iid4) << std::endl; + assert(std::to_string(iid4) == "irc.example.org"); + assert(iid4.get_local() == ""); + assert(iid4.get_server() == "irc.example.org"); + assert(!iid4.is_channel); + assert(!iid4.is_user); + + Iid iid5("nick!"); + std::cout << std::to_string(iid5) << std::endl; + assert(std::to_string(iid5) == "nick!"); + assert(iid5.get_local() == "nick"); + assert(iid5.get_server() == ""); + assert(!iid5.is_channel); + assert(iid5.is_user); + + Iid iid6("##channel%"); + std::cout << std::to_string(iid6) << std::endl; + assert(std::to_string(iid6) == "##channel%"); + assert(iid6.get_local() == "##channel"); + assert(iid6.get_server() == ""); + assert(iid6.is_channel); + assert(!iid6.is_user); + } + + { + std::cout << color << "Testing IID parsing with a fixed server configured…" << reset << std::endl; + // Now do the same tests, but with a configured fixed_irc_server + Config::set("fixed_irc_server", "fixed.example.com", false); + + Iid iid1("foo!irc.example.org"); + std::cout << std::to_string(iid1) << std::endl; + assert(std::to_string(iid1) == "foo!"); + assert(iid1.get_local() == "foo"); + assert(iid1.get_server() == "fixed.example.com"); + assert(!iid1.is_channel); + assert(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + std::cout << std::to_string(iid2) << std::endl; + assert(std::to_string(iid2) == "#test"); + assert(iid2.get_local() == "#test"); + assert(iid2.get_server() == "fixed.example.com"); + assert(iid2.is_channel); + assert(!iid2.is_user); + + // Note that it is impossible to adress the XMPP server directly, or to + // use the virtual channel, in that mode + + // Iid iid3("%irc.example.org"); + // Iid iid4("irc.example.org"); + + Iid iid5("nick!"); + std::cout << std::to_string(iid5) << std::endl; + assert(std::to_string(iid5) == "nick!"); + assert(iid5.get_local() == "nick"); + assert(iid5.get_server() == "fixed.example.com"); + assert(!iid5.is_channel); + assert(iid5.is_user); + + Iid iid6("##channel%"); + std::cout << std::to_string(iid6) << std::endl; + assert(std::to_string(iid6) == "##channel"); + assert(iid6.get_local() == "##channel"); + assert(iid6.get_server() == "fixed.example.com"); + assert(iid6.is_channel); + assert(!iid6.is_user); + } return 0; } diff --git a/src/utils/empty_if_fixed_server.cpp b/src/utils/empty_if_fixed_server.cpp new file mode 100644 index 0000000..85fd86d --- /dev/null +++ b/src/utils/empty_if_fixed_server.cpp @@ -0,0 +1,8 @@ +// #include + +// #include + +// namespace utils +// { +// inline std::string empty_if_fixed_server(std::string&& str) +// } diff --git a/src/utils/empty_if_fixed_server.hpp b/src/utils/empty_if_fixed_server.hpp new file mode 100644 index 0000000..8739fd9 --- /dev/null +++ b/src/utils/empty_if_fixed_server.hpp @@ -0,0 +1,26 @@ +#ifndef EMPTY_IF_FIXED_SERVER_HPP_INCLUDED +#define EMPTY_IF_FIXED_SERVER_HPP_INCLUDED + +#include + +#include + +namespace utils +{ + inline std::string empty_if_fixed_server(std::string&& str) + { + if (!Config::get("fixed_irc_server", "").empty()) + return {}; + return str; + } + + inline std::string empty_if_fixed_server(const std::string& str) + { + if (!Config::get("fixed_irc_server", "").empty()) + return {}; + return str; + } + +} + +#endif /* EMPTY_IF_FIXED_SERVER_HPP_INCLUDED */ -- cgit v1.2.3 From c307df85c8e7d9bcd4570269bf13c3e92c3f5954 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 25 Feb 2015 19:05:04 +0100 Subject: Do not handle the "%" char in a special way, in the fixed_server mode Also fix some doc --- doc/biboumi.1.md | 22 +++++++++++++--------- src/irc/iid.cpp | 5 +---- src/test.cpp | 10 +++++----- src/utils/empty_if_fixed_server.cpp | 8 -------- 4 files changed, 19 insertions(+), 26 deletions(-) delete mode 100644 src/utils/empty_if_fixed_server.cpp diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 9e95139..3fd5a25 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -63,15 +63,19 @@ The configuration file uses a simple format of the form If this option contains the hostname of an IRC server (for example irc.example.org), then biboumi will enforce the connexion to that IRC - server only. This means that a JID like "#chan@irc.biboumi.org" must be - used instead of "#chan%irc.example.org@irc.biboumi.org". In that mode, - the virtual channel (see *Connect to an IRC server*) is not available and - you still need to use the ! separator to send message to an IRC user (for - example "foo!@biboumi.example.com" to send a message to foo), although the - in-room JID still work as expected ("#channel@biboumi.example.com/Nick"). - This option can for example be used by an administrator that just wants to - let their users join their own IRC server using an XMPP client, but - without letting them join any other IRC servers on the internet. + server only. This means that a JID like "#chan@biboumi.example.com" must + be used instead of "#chan%irc.example.org@biboumi.example.com". In that + mode, the virtual channel (see *Connect to an IRC server*) is not + available and you still need to use the ! separator to send message to an + IRC user (for example "foo!@biboumi.example.com" to send a message to + foo), although the in-room JID still work as expected + ("#channel@biboumi.example.com/Nick"). On the other hand, the '%' lose + any meaning. It can appear in the JID but will not be interpreted as a + separator (thus the JID "#channel%hello@biboumi.example.com" points to the + channel named "#channel%hello" on the configured IRC server) This option + can for example be used by an administrator that just wants to let their + users join their own IRC server using an XMPP client, while forbidding + access to any other IRC server. `log_file` diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index d4dc8ce..9d39129 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -35,7 +35,7 @@ void Iid::init_with_fixed_server(const std::string& iid, const std::string& host { this->set_server(hostname); - const std::string::size_type sep = iid.find_first_of("%!"); + const std::string::size_type sep = iid.find("!"); // Without any separator, we consider that it's a channel if (sep == std::string::npos) @@ -47,9 +47,6 @@ void Iid::init_with_fixed_server(const std::string& iid, const std::string& host // but the part behind it (the hostname) is ignored { this->set_local(iid.substr(0, sep)); - if (iid[sep] == '%') - this->is_channel = true; - else this->is_user = true; } } diff --git a/src/test.cpp b/src/test.cpp index 9fa2c99..553140f 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -369,13 +369,13 @@ int main() Iid iid2("#test%irc.example.org"); std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test"); - assert(iid2.get_local() == "#test"); + assert(std::to_string(iid2) == "#test%irc.example.org"); + assert(iid2.get_local() == "#test%irc.example.org"); assert(iid2.get_server() == "fixed.example.com"); assert(iid2.is_channel); assert(!iid2.is_user); - // Note that it is impossible to adress the XMPP server directly, or to + // Note that it is impossible to adress the IRC server directly, or to // use the virtual channel, in that mode // Iid iid3("%irc.example.org"); @@ -391,8 +391,8 @@ int main() Iid iid6("##channel%"); std::cout << std::to_string(iid6) << std::endl; - assert(std::to_string(iid6) == "##channel"); - assert(iid6.get_local() == "##channel"); + assert(std::to_string(iid6) == "##channel%"); + assert(iid6.get_local() == "##channel%"); assert(iid6.get_server() == "fixed.example.com"); assert(iid6.is_channel); assert(!iid6.is_user); diff --git a/src/utils/empty_if_fixed_server.cpp b/src/utils/empty_if_fixed_server.cpp deleted file mode 100644 index 85fd86d..0000000 --- a/src/utils/empty_if_fixed_server.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// #include - -// #include - -// namespace utils -// { -// inline std::string empty_if_fixed_server(std::string&& str) -// } -- cgit v1.2.3 From 5db063781032fb4ae9c823737636c1240b67d111 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Feb 2015 02:30:23 +0100 Subject: Remove an unused parameter --- src/xmpp/adhoc_command.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index c20976a..c4e8a44 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -200,7 +200,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X session.terminate(); } -void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node) +void Reload(XmppComponent*, AdhocSession&, XmlNode& command_node) { ::reload_process(); command_node.delete_all_children(); -- cgit v1.2.3 From 53e6b1da69199f54303e4cb2b00db3205f62ce6e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Feb 2015 02:56:46 +0100 Subject: Fix the systemd-conditional code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By using SYSTEMD_FOUND instead of SYSTEMDDAEMON_FOUND, where I forgot to rename it… --- CMakeLists.txt | 8 ++++---- src/logger/logger.hpp | 2 +- src/xmpp/xmpp_component.cpp | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6272811..df634b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,8 +82,8 @@ if(LIBIDN_FOUND) include_directories(${LIBIDN_INCLUDE_DIRS}) endif() -if(SYSTEMDDAEMON_FOUND) - include_directories(${SYSTEMDDAEMON_INCLUDE_DIRS}) +if(SYSTEMD_FOUND) + include_directories(${SYSTEMD_INCLUDE_DIRS}) endif() if(BOTAN_FOUND) @@ -198,8 +198,8 @@ target_link_libraries(${PROJECT_NAME} bridge utils config) -if(SYSTEMDDAEMON_FOUND) - target_link_libraries(xmpp ${SYSTEMDDAEMON_LIBRARIES}) +if(SYSTEMD_FOUND) + target_link_libraries(xmpp ${SYSTEMD_LIBRARIES}) endif() diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp index 7560505..b1ae20d 100644 --- a/src/logger/logger.hpp +++ b/src/logger/logger.hpp @@ -19,7 +19,7 @@ #include "config.h" -#ifdef SYSTEMDDAEMON_FOUND +#ifdef SYSTEMD_FOUND # include #else # define SD_DEBUG "[DEBUG]: " diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index 1df1e5d..d1f250a 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -17,7 +17,7 @@ #include -#ifdef SYSTEMDDAEMON_FOUND +#ifdef SYSTEMD_FOUND # include #endif @@ -89,7 +89,7 @@ void XmppComponent::on_connection_failed(const std::string& reason) { this->first_connection_try = false; log_error("Failed to connect to the XMPP server: " << reason); -#ifdef SYSTEMDDAEMON_FOUND +#ifdef SYSTEMD_FOUND sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); #endif } @@ -279,7 +279,7 @@ void XmppComponent::handle_handshake(const Stanza& stanza) this->authenticated = true; this->ever_auth = true; log_info("Authenticated with the XMPP server"); -#ifdef SYSTEMDDAEMON_FOUND +#ifdef SYSTEMD_FOUND sd_notify(0, "READY=1"); // Install an event that sends a keepalive to systemd. If biboumi crashes // or hangs for too long, systemd will restart it. @@ -626,7 +626,7 @@ void XmppComponent::handle_error(const Stanza& stanza) if (text) error_message = text->get_inner(); log_error("Stream error received from the XMPP server: " << error_message); -#ifdef SYSTEMDDAEMON_FOUND +#ifdef SYSTEMD_FOUND if (!this->ever_auth) sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); #endif -- cgit v1.2.3 From 6a2240f5935a4608e651a33c39219e912c9ea9ba Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Feb 2015 04:58:07 +0100 Subject: Properly sanitize everything in the XML we send to the XMPP server in this order: - Make sure it is utf-8 encoded - Remove all chars that are invalid in XML - Escape all XML special chars (&'"<>) --- src/xmpp/xmpp_stanza.cpp | 15 +++++++++++---- src/xmpp/xmpp_stanza.hpp | 1 + 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp index 4290fc7..df19105 100644 --- a/src/xmpp/xmpp_stanza.cpp +++ b/src/xmpp/xmpp_stanza.cpp @@ -218,13 +218,12 @@ std::string XmlNode::to_string() const std::string res("<"); res += this->name; for (const auto& it: this->attributes) - res += " " + utils::remove_invalid_xml_chars(it.first) + "='" + - utils::remove_invalid_xml_chars(it.second) + "'"; + res += " " + it.first + "='" + sanitize(it.second) + "'"; if (this->closed && !this->has_children() && this->inner.empty()) res += "/>"; else { - res += ">" + utils::remove_invalid_xml_chars(this->inner); + res += ">" + sanitize(this->inner); for (const auto& child: this->children) res += child->to_string(); if (this->closed) @@ -232,7 +231,7 @@ std::string XmlNode::to_string() const res += "get_name() + ">"; } } - res += utils::remove_invalid_xml_chars(this->tail); + res += sanitize(this->tail); return res; } @@ -265,3 +264,11 @@ std::string& XmlNode::operator[](const std::string& name) { return this->attributes[name]; } + +std::string sanitize(const std::string& data) +{ + if (utils::is_valid_utf8(data.data())) + return xml_escape(utils::remove_invalid_xml_chars(data)); + else + return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); +} diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp index 9229ae6..f1a6a0f 100644 --- a/src/xmpp/xmpp_stanza.hpp +++ b/src/xmpp/xmpp_stanza.hpp @@ -7,6 +7,7 @@ std::string xml_escape(const std::string& data); std::string xml_unescape(const std::string& data); +std::string sanitize(const std::string& data); /** * Represent an XML node. It has -- cgit v1.2.3 From c01befb054075ab414fd602859e5999a138aa5bf Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Feb 2015 05:02:08 +0100 Subject: Implement room discovery using the LIST irc command ref #2472 --- src/bridge/bridge.cpp | 38 ++++++++++++++++++++++++++++++++++++++ src/bridge/bridge.hpp | 2 ++ src/bridge/list_element.hpp | 19 +++++++++++++++++++ src/irc/irc_client.cpp | 5 +++++ src/irc/irc_client.hpp | 4 ++++ src/xmpp/xmpp_component.cpp | 32 ++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 8 ++++++++ 7 files changed, 108 insertions(+) create mode 100644 src/bridge/list_element.hpp diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index fc00c8c..2e3520d 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -285,6 +286,43 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) irc->send_nick_command(new_nick); } +void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, + const std::string& to_jid) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + + if (!irc) + return; + + irc->send_list_command(); + irc_responder_callback_t cb = [this, iid, iq_id, to_jid](const std::string& irc_hostname, + const IrcMessage& message) -> bool + { + static std::vector list; + + if (irc_hostname != iid.get_server()) + return false; + if (message.command == "263" || message.command == "RPL_TRYAGAIN") + { // TODO send an error iq + return true; + } + else if (message.command == "322" || message.command == "RPL_LIST") + { // Add element to list + if (message.arguments.size() == 4) + list.emplace_back(message.arguments[1], message.arguments[2], + message.arguments[3]); + return false; + } + else if (message.command == "323" || message.command == "RPL_LISTEND") + { // Send the iq response with the content of the list + this->xmpp->send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list); + return true; + } + return false; + }; + this->add_waiting_irc(std::move(cb)); +} + void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid) { diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b1f79d5..8f71846 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -72,6 +72,8 @@ public: void send_irc_version_request(const std::string& irc_hostname, const std::string& target, const std::string& iq_id, const std::string& to_jid, const std::string& from_jid); + void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, + const std::string& to_jid); void forward_affiliation_role_change(const Iid& iid, const std::string& nick, const std::string& affiliation, const std::string& role); /** diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp new file mode 100644 index 0000000..bd28185 --- /dev/null +++ b/src/bridge/list_element.hpp @@ -0,0 +1,19 @@ +#ifndef LIST_ELEMENT_HPP_INCLUDED +#define LIST_ELEMENT_HPP_INCLUDED + +#include + +struct ListElement +{ + ListElement(const std::string& channel, const std::string& nb_users, + const std::string& topic): + channel(channel), + nb_users(nb_users), + topic(topic){} + + std::string channel; + std::string nb_users; + std::string topic; +}; + +#endif /* LIST_ELEMENT_HPP_INCLUDED */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f83b48c..dedb5de 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -196,6 +196,11 @@ void IrcClient::send_kick_command(const std::string& chan_name, const std::strin this->send_message(IrcMessage("KICK", {chan_name, target, reason})); } +void IrcClient::send_list_command() +{ + this->send_message(IrcMessage("LIST", {})); +} + void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic) { this->send_message(IrcMessage("TOPIC", {chan_name, topic})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 578fce2..86edbab 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -104,6 +104,10 @@ public: * Send the KICK irc command */ void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason); + /** + * Send the LIST irc command + */ + void send_list_command(); void send_topic_command(const std::string& chan_name, const std::string& topic); /** * Send the QUIT irc command diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index d1f250a..0e2531d 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -556,12 +557,18 @@ void XmppComponent::handle_iq(const Stanza& stanza) } else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) { + Iid iid(to.local); const std::string node = query->get_tag("node"); if (node == ADHOC_NS) { this->send_adhoc_commands_list(id, from); stanza_error.disable(); } + else if (node.empty() && !iid.is_user && !iid.is_channel) + { // Disco on an IRC server: get the list of channels + bridge->send_irc_channel_list_request(iid, id, from); + stanza_error.disable(); + } } else if ((query = stanza.get_child("ping", PING_NS))) { @@ -1152,6 +1159,31 @@ void XmppComponent::send_iq_result(const std::string& id, const std::string& to_ this->send_stanza(iq); } +void XmppComponent::send_iq_room_list_result(const std::string& id, + const std::string to_jid, + const std::string& from, + const std::vector& rooms_list) +{ + Stanza iq("iq"); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + for (const auto& room: rooms_list) + { + XmlNode item("item"); + item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + std::string XmppComponent::next_id() { char uuid_str[37]; diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index 951d5a3..a0b06a6 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -26,6 +26,8 @@ #define ADHOC_NS "http://jabber.org/protocol/commands" #define PING_NS "urn:xmpp:ping" +class ListElement; + /** * A callback called when the waited iq result is received (it is matched * against the iq id) @@ -228,6 +230,12 @@ public: * Send an empty iq of type result */ void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); + /** + * Send the channels list in one big stanza + */ + void send_iq_room_list_result(const std::string& id, const std::string to_jid, + const std::string& from, + const std::vector& rooms_list); /** * Handle the various stanza types */ -- cgit v1.2.3 From 1028d4d549b517c5b42bb0c30a410d1ab43c4cf3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 26 Feb 2015 17:46:51 +0100 Subject: Add a changelog entry for the fixed_irc_server option --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e3ba7bb..9b1acfe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,8 @@ Version 2.0 - Asynchronously resolve domain names by optionally using the DNS library c-ares. - Add a reload add-hoc command, to reload biboumi's configuration + - Add a fixed_irc_server option. With this option enabled, + biboumi can only connect to the one single IRC server configured Version 1.1 2014-16-07 -- cgit v1.2.3 From d600a2843f1dbe3b1ba2dead9a020cc73d7d10ae Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 12:16:09 +0100 Subject: Remove all the libs that are now in louloulibs --- CMakeLists.txt | 123 +--- cmake/Modules/FindBOTAN.cmake | 35 - cmake/Modules/FindCARES.cmake | 37 -- cmake/Modules/FindICONV.cmake | 60 -- cmake/Modules/FindLIBIDN.cmake | 41 -- cmake/Modules/FindLIBUUID.cmake | 41 -- cmake/Modules/FindSYSTEMD.cmake | 39 -- src/bridge/bridge.cpp | 4 +- src/bridge/bridge.hpp | 6 +- src/config.h.cmake | 8 - src/config/config.cpp | 122 ---- src/config/config.hpp | 103 --- src/irc/irc_client.cpp | 2 +- src/logger/logger.cpp | 38 -- src/logger/logger.hpp | 81 --- src/main.cpp | 4 +- src/network/dns_handler.cpp | 112 ---- src/network/dns_handler.hpp | 62 -- src/network/dns_socket_handler.cpp | 45 -- src/network/dns_socket_handler.hpp | 46 -- src/network/poller.cpp | 203 ------ src/network/poller.hpp | 95 --- src/network/socket_handler.hpp | 45 -- src/network/tcp_socket_handler.cpp | 572 ----------------- src/network/tcp_socket_handler.hpp | 297 --------- src/utils/encoding.cpp | 221 ------- src/utils/encoding.hpp | 29 - src/utils/reload.cpp | 13 - src/utils/reload.hpp | 10 - src/utils/revstr.cpp | 9 - src/utils/revstr.hpp | 11 - src/utils/scopeguard.hpp | 89 --- src/utils/sha1.cpp | 154 ----- src/utils/sha1.hpp | 35 - src/utils/split.cpp | 18 - src/utils/split.hpp | 13 - src/utils/timed_events.cpp | 62 -- src/utils/timed_events.hpp | 132 ---- src/utils/timed_events_manager.cpp | 81 --- src/utils/tolower.cpp | 13 - src/utils/tolower.hpp | 11 - src/xmpp/adhoc_command.cpp | 10 +- src/xmpp/adhoc_commands_handler.cpp | 143 ----- src/xmpp/adhoc_commands_handler.hpp | 69 -- src/xmpp/adhoc_session.cpp | 37 -- src/xmpp/adhoc_session.hpp | 70 -- src/xmpp/biboumi_component.cpp | 568 +++++++++++++++++ src/xmpp/biboumi_component.hpp | 111 ++++ src/xmpp/jid.cpp | 2 +- src/xmpp/xmpp_component.cpp | 1194 ----------------------------------- src/xmpp/xmpp_component.hpp | 303 --------- 51 files changed, 713 insertions(+), 4916 deletions(-) delete mode 100644 cmake/Modules/FindBOTAN.cmake delete mode 100644 cmake/Modules/FindCARES.cmake delete mode 100644 cmake/Modules/FindICONV.cmake delete mode 100644 cmake/Modules/FindLIBIDN.cmake delete mode 100644 cmake/Modules/FindLIBUUID.cmake delete mode 100644 cmake/Modules/FindSYSTEMD.cmake delete mode 100644 src/config.h.cmake delete mode 100644 src/config/config.cpp delete mode 100644 src/config/config.hpp delete mode 100644 src/logger/logger.cpp delete mode 100644 src/logger/logger.hpp delete mode 100644 src/network/dns_handler.cpp delete mode 100644 src/network/dns_handler.hpp delete mode 100644 src/network/dns_socket_handler.cpp delete mode 100644 src/network/dns_socket_handler.hpp delete mode 100644 src/network/poller.cpp delete mode 100644 src/network/poller.hpp delete mode 100644 src/network/socket_handler.hpp delete mode 100644 src/network/tcp_socket_handler.cpp delete mode 100644 src/network/tcp_socket_handler.hpp delete mode 100644 src/utils/encoding.cpp delete mode 100644 src/utils/encoding.hpp delete mode 100644 src/utils/reload.cpp delete mode 100644 src/utils/reload.hpp delete mode 100644 src/utils/revstr.cpp delete mode 100644 src/utils/revstr.hpp delete mode 100644 src/utils/scopeguard.hpp delete mode 100644 src/utils/sha1.cpp delete mode 100644 src/utils/sha1.hpp delete mode 100644 src/utils/split.cpp delete mode 100644 src/utils/split.hpp delete mode 100644 src/utils/timed_events.cpp delete mode 100644 src/utils/timed_events.hpp delete mode 100644 src/utils/timed_events_manager.cpp delete mode 100644 src/utils/tolower.cpp delete mode 100644 src/utils/tolower.hpp delete mode 100644 src/xmpp/adhoc_commands_handler.cpp delete mode 100644 src/xmpp/adhoc_commands_handler.hpp delete mode 100644 src/xmpp/adhoc_session.cpp delete mode 100644 src/xmpp/adhoc_session.hpp create mode 100644 src/xmpp/biboumi_component.cpp create mode 100644 src/xmpp/biboumi_component.hpp delete mode 100644 src/xmpp/xmpp_component.cpp delete mode 100644 src/xmpp/xmpp_component.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index df634b5..808d9e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,35 +15,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE # ## Look for external libraries # -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") -include(FindEXPAT) -find_package(EXPAT REQUIRED) -find_package(ICONV REQUIRED) -find_package(LIBUUID REQUIRED) - -if(WITH_LIBIDN) - find_package(LIBIDN REQUIRED) -elseif(NOT WITHOUT_LIBIDN) - find_package(LIBIDN) -endif() - -if(WITH_SYSTEMD) - find_package(SYSTEMD REQUIRED) -elseif(NOT WITHOUT_SYSTEMD) - find_package(SYSTEMD) -endif() - -if(WITH_BOTAN) - find_package(BOTAN REQUIRED) -elseif(NOT WITHOUT_BOTAN) - find_package(BOTAN) -endif() - -if(WITH_CARES) - find_package(CARES REQUIRED) -elseif(NOT WITHOUT_CARES) - find_package(CARES) -endif() +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") # ## Get the software version @@ -68,42 +40,12 @@ if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") endif() endif() -set(BIBOUMI_VERSION +set(SOFTWARE_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX}) # To be able to include the config.h file generated by cmake include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") -include_directories(${EXPAT_INCLUDE_DIRS}) -include_directories(${ICONV_INCLUDE_DIRS}) -include_directories(${LIBUUID_INCLUDE_DIRS}) - -if(LIBIDN_FOUND) - include_directories(${LIBIDN_INCLUDE_DIRS}) -endif() - -if(SYSTEMD_FOUND) - include_directories(${SYSTEMD_INCLUDE_DIRS}) -endif() - -if(BOTAN_FOUND) - include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) -endif() - -if(CARES_FOUND) - include_directories(${CARES_INCLUDE_DIRS}) -endif() - -set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") -if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") - set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) -else() - set(POLLER "POLL" CACHE STRING ${POLLER_DOCSTRING}) -endif() -if((NOT ${POLLER} MATCHES "POLL") AND - (NOT ${POLLER} MATCHES "EPOLL")) - message(FATAL_ERROR "POLLER must be either POLL or EPOLL") -endif() # ## Documentation @@ -121,43 +63,21 @@ if(RONN_EXECUTABLE) add_custom_target(doc DEPENDS ${MAN_PAGE}) endif() -# -## utils -# -file(GLOB source_utils - src/utils/*.[hc]pp) -add_library(utils STATIC ${source_utils}) -target_link_libraries(utils ${ICONV_LIBRARIES}) - -# -## config -# -file(GLOB source_config - src/config/*.[hc]pp) -add_library(config STATIC ${source_config}) -target_link_libraries(config utils) +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs") + message(FATAL_ERROR "louloulibs/ directory not found. Make sure you fetched the submodules with 'git submodule init && git submodule update'") +endif() +add_subdirectory("louloulibs") +include_directories("louloulibs") -# -## logger -# -file(GLOB source_logger - src/logger/*.[hc]pp) -add_library(logger STATIC ${source_logger}) -target_link_libraries(logger config) +include_directories(${EXPAT_INCLUDE_DIRS}) +include_directories(${ICONV_INCLUDE_DIRS}) +include_directories(${LIBUUID_INCLUDE_DIRS}) -# -## network -# -file(GLOB source_network - src/network/*.[hc]pp) -add_library(network STATIC ${source_network}) -target_link_libraries(network logger) -if(BOTAN_FOUND) - target_link_libraries(network ${BOTAN_LIBRARIES}) -endif() -if(CARES_FOUND) - target_link_libraries(network ${CARES_LIBRARIES}) -endif() +# If they are found in louloulibs CMakeLists.txt, we inherite these values +include_directories(${LIBIDN_INCLUDE_DIRS}) +include_directories(${SYSTEMD_INCLUDE_DIRS}) +include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) +include_directories(${CARES_INCLUDE_DIRS}) # ## irclib @@ -168,17 +88,12 @@ add_library(irc STATIC ${source_irc}) target_link_libraries(irc network utils logger) # -## xmpplib +## xmpp # file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp bridge network utils logger - ${EXPAT_LIBRARIES} - ${LIBUUID_LIBRARIES}) -if(LIBIDN_FOUND) - target_link_libraries(xmpp ${LIBIDN_LIBRARIES}) -endif() +target_link_libraries(xmpp xmpplib bridge network utils logger) # ## bridge @@ -209,6 +124,7 @@ endif() add_executable(test_suite EXCLUDE_FROM_ALL src/test.cpp) target_link_libraries(test_suite + xmpplib xmpp irc bridge @@ -216,9 +132,6 @@ target_link_libraries(test_suite config logger) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/src/config.h) - # ## Install target # diff --git a/cmake/Modules/FindBOTAN.cmake b/cmake/Modules/FindBOTAN.cmake deleted file mode 100644 index a12bd35..0000000 --- a/cmake/Modules/FindBOTAN.cmake +++ /dev/null @@ -1,35 +0,0 @@ -# - Find botan -# Find the botan cryptographic library -# -# This module defines the following variables: -# BOTAN_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# BOTAN_INCLUDE_DIRS - The directory where to find the header file -# BOTAN_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# BOTAN_LIBRARY -# BOTAN_INCLUDE_DIR -# -# This file is in the public domain - -find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h - PATH_SUFFIXES botan-1.11 - DOC "The botan include directory") - -find_library(BOTAN_LIBRARIES NAMES botan botan-1.11 - DOC "The botan library") - -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set BOTAN_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(BOTAN REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) - -if(BOTAN_FOUND) - set(BOTAN_LIBRARY ${BOTAN_LIBRARIES}) - set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS}) -endif() - -mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) diff --git a/cmake/Modules/FindCARES.cmake b/cmake/Modules/FindCARES.cmake deleted file mode 100644 index c4c757a..0000000 --- a/cmake/Modules/FindCARES.cmake +++ /dev/null @@ -1,37 +0,0 @@ -# - Find c-ares -# Find the c-ares library, and more particularly the stringprep header. -# -# This module defines the following variables: -# CARES_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# CARES_INCLUDE_DIRS - The directory where to find the header file -# CARES_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# CARES_INCLUDE_DIR -# CARES_LIBRARY -# -# This file is in the public domain - -if(NOT CARES_FOUND) - find_path(CARES_INCLUDE_DIRS NAMES ares.h - DOC "The c-ares include directory") - - find_library(CARES_LIBRARIES NAMES cares - DOC "The c-ares library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set CARES_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(CARES REQUIRED_VARS CARES_LIBRARIES CARES_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(CARES_FOUND) - set(CARES_INCLUDE_DIR ${CARES_INCLUDE_DIRS}) - set(CARES_LIBRARY ${CARES_LIBRARIES}) - endif() -endif() - -mark_as_advanced(CARES_INCLUDE_DIRS CARES_LIBRARIES) diff --git a/cmake/Modules/FindICONV.cmake b/cmake/Modules/FindICONV.cmake deleted file mode 100644 index 7ca173f..0000000 --- a/cmake/Modules/FindICONV.cmake +++ /dev/null @@ -1,60 +0,0 @@ -# - Find iconv -# Find the iconv (character set conversion) library -# -# This module defines the following variables: -# ICONV_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# ICONV_INCLUDE_DIRS - The directory where to find the header file -# ICONV_LIBRARIES - Where to find the library file -# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# ICONV_LIBRARY -# ICONV_INCLUDE_DIR -# -# This file is in the public domain - -find_path(ICONV_INCLUDE_DIRS NAMES iconv.h - DOC "The iconv include directory") - -find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c - DOC "The iconv library") - -# Use some standard module to handle the QUIETLY and REQUIRED arguments, and -# set ICONV_FOUND to TRUE if these two variables are set. -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS) - -# Check if the prototype is -# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft, -# char** outbuf, size_t* outbytesleft); -# or -# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft, -# char** outbuf, size_t* outbytesleft); -if(ICONV_FOUND) - include(CheckCXXSourceCompiles) - - # Set the parameters needed to compile the following code. - set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS}) - set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) - - check_cxx_source_compiles(" - #include - int main(){ - iconv_t conv = 0; - const char* in = 0; - size_t ilen = 0; - char* out = 0; - size_t olen = 0; - iconv(conv, &in, &ilen, &out, &olen); - return 0;}" - ICONV_SECOND_ARGUMENT_IS_CONST) - -# Compatibility for all the ways of writing these variables - set(ICONV_LIBRARY ${ICONV_LIBRARIES}) - set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS}) -endif() - -mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST) \ No newline at end of file diff --git a/cmake/Modules/FindLIBIDN.cmake b/cmake/Modules/FindLIBIDN.cmake deleted file mode 100644 index 611a6a8..0000000 --- a/cmake/Modules/FindLIBIDN.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# - Find libidn -# Find the libidn library, and more particularly the stringprep header. -# -# This module defines the following variables: -# LIBIDN_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# LIBIDN_INCLUDE_DIRS - The directory where to find the header file -# LIBIDN_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# LIBIDN_INCLUDE_DIR -# LIBIDN_LIBRARY -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(LIBIDN libidn) - -if(NOT LIBIDN_FOUND) - find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h - DOC "The libidn include directory") - - # The library containing the stringprep module is libidn - find_library(LIBIDN_LIBRARIES NAMES idn - DOC "The libidn library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set LIBIDN_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(LIBIDN_FOUND) - set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) - set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) - endif() -endif() - -mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/cmake/Modules/FindLIBUUID.cmake b/cmake/Modules/FindLIBUUID.cmake deleted file mode 100644 index 17d3c42..0000000 --- a/cmake/Modules/FindLIBUUID.cmake +++ /dev/null @@ -1,41 +0,0 @@ -# - Find libuuid -# Find the libuuid library -# -# This module defines the following variables: -# LIBUUID_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# LIBUUID_INCLUDE_DIRS - The directory where to find the header file -# LIBUUID_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# LIBUUID_INCLUDE_DIR -# LIBUUID_LIBRARY -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(LIBUUID uuid) - -if(NOT LIBUUID_FOUND) - find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h - PATH_SUFFIXES uuid - DOC "The libuuid include directory") - - find_library(LIBUUID_LIBRARIES NAMES uuid - DOC "The libuuid library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set LIBUUID_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS) - - # Compatibility for all the ways of writing these variables - if(LIBUUID_FOUND) - set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS}) - set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES}) - endif() -endif() - -mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES) diff --git a/cmake/Modules/FindSYSTEMD.cmake b/cmake/Modules/FindSYSTEMD.cmake deleted file mode 100644 index c7decde..0000000 --- a/cmake/Modules/FindSYSTEMD.cmake +++ /dev/null @@ -1,39 +0,0 @@ -# - Find SystemdDaemon -# Find the systemd daemon library -# -# This module defines the following variables: -# SYSTEMD_FOUND - True if library and include directory are found -# If set to TRUE, the following are also defined: -# SYSTEMD_INCLUDE_DIRS - The directory where to find the header file -# SYSTEMD_LIBRARIES - Where to find the library file -# -# For conveniance, these variables are also set. They have the same values -# than the variables above. The user can thus choose his/her prefered way -# to write them. -# SYSTEMD_LIBRARY -# SYSTEMD_INCLUDE_DIR -# -# This file is in the public domain - -include(FindPkgConfig) -pkg_check_modules(SYSTEMD libsystemd) - -if(NOT SYSTEMD_FOUND) - find_path(SYSTEMD_INCLUDE_DIRS NAMES systemd/sd-daemon.h - DOC "The Systemd include directory") - - find_library(SYSTEMD_LIBRARIES NAMES systemd - DOC "The Systemd library") - - # Use some standard module to handle the QUIETLY and REQUIRED arguments, and - # set SYSTEMD_FOUND to TRUE if these two variables are set. - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(SYSTEMD REQUIRED_VARS SYSTEMD_LIBRARIES SYSTEMD_INCLUDE_DIRS) - - if(SYSTEMD_FOUND) - set(SYSTEMD_LIBRARY ${SYSTEMD_LIBRARIES}) - set(SYSTEMD_INCLUDE_DIR ${SYSTEMD_INCLUDE_DIRS}) - endif() -endif() - -mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) \ No newline at end of file diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1a205bd..85049b9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include #include @@ -20,7 +20,7 @@ using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; -Bridge::Bridge(const std::string& user_jid, XmppComponent* xmpp, std::shared_ptr poller): +Bridge::Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr poller): user_jid(user_jid), xmpp(xmpp), poller(poller) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 8f71846..c50b7ab 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -12,7 +12,7 @@ #include #include -class XmppComponent; +class BiboumiComponent; class Poller; /** @@ -32,7 +32,7 @@ using irc_responder_callback_t = std::function poller); + explicit Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr poller); ~Bridge(); /** * QUIT all connected IRC servers. @@ -211,7 +211,7 @@ private: * but we still need to communicate with it, when sending messages from * IRC to XMPP. */ - XmppComponent* xmpp; + BiboumiComponent* xmpp; /** * Poller, to give it the IrcClients that we spawn, to make it manage * their sockets. diff --git a/src/config.h.cmake b/src/config.h.cmake deleted file mode 100644 index 18d546f..0000000 --- a/src/config.h.cmake +++ /dev/null @@ -1,8 +0,0 @@ -#define SYSTEM_NAME "${CMAKE_SYSTEM}" -#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST -#cmakedefine LIBIDN_FOUND -#cmakedefine SYSTEMD_FOUND -#cmakedefine POLLER ${POLLER} -#cmakedefine BOTAN_FOUND -#cmakedefine CARES_FOUND -#cmakedefine BIBOUMI_VERSION "${BIBOUMI_VERSION}" diff --git a/src/config/config.cpp b/src/config/config.cpp deleted file mode 100644 index b870339..0000000 --- a/src/config/config.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include - -#include -#include - -#include - -std::string Config::filename = "./biboumi.cfg"; -bool Config::file_must_exist = false; - -std::string Config::get(const std::string& option, const std::string& def) -{ - Config* self = Config::instance().get(); - auto it = self->values.find(option); - - if (it == self->values.end()) - return def; - return it->second; -} - -int Config::get_int(const std::string& option, const int& def) -{ - Config* self = Config::instance().get(); - std::string res = self->get(option, ""); - if (!res.empty()) - return atoi(res.c_str()); - else - return def; -} - -void Config::set(const std::string& option, const std::string& value, bool save) -{ - Config* self = Config::instance().get(); - self->values[option] = value; - if (save) - { - self->save_to_file(); - self->trigger_configuration_change(); - } -} - -void Config::connect(t_config_changed_callback callback) -{ - Config* self = Config::instance().get(); - self->callbacks.push_back(callback); -} - -void Config::close() -{ - Config* self = Config::instance().get(); - self->values.clear(); - Config::instance().reset(); -} - -/** - * Private methods - */ - -void Config::trigger_configuration_change() -{ - std::vector::iterator it; - for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it) - (*it)(); -} - -std::unique_ptr& Config::instance() -{ - static std::unique_ptr instance; - - if (!instance) - { - instance = std::make_unique(); - instance->read_conf(); - } - return instance; -} - -bool Config::read_conf() -{ - std::ifstream file; - file.open(filename.data()); - if (!file.is_open()) - { - if (Config::file_must_exist) - { - perror(("Error while opening file " + filename + " for reading.").c_str()); - file.exceptions(std::ifstream::failbit); - } - return false; - } - - std::string line; - size_t pos; - std::string option; - std::string value; - while (file.good()) - { - std::getline(file, line); - if (line == "" || line[0] == '#') - continue ; - pos = line.find('='); - if (pos == std::string::npos) - continue ; - option = line.substr(0, pos); - value = line.substr(pos+1); - this->values[option] = value; - } - return true; -} - -void Config::save_to_file() const -{ - std::ofstream file(this->filename.data()); - if (file.fail()) - { - std::cerr << "Could not save config file." << std::endl; - return ; - } - for (auto& it: this->values) - file << it.first << "=" << it.second << std::endl; - file.close(); -} diff --git a/src/config/config.hpp b/src/config/config.hpp deleted file mode 100644 index e070816..0000000 --- a/src/config/config.hpp +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Read the config file and save all the values in a map. - * Also, a singleton. - * - * Use Config::filename = "bla" to set the filename you want to use. - * - * If you want to exit if the file does not exist when it is open for - * reading, set Config::file_must_exist = true. - * - * Config::get() can the be used to access the values in the conf. - * - * Use Config::close() when you're done getting/setting value. This will - * save the config into the file. - */ - -#ifndef CONFIG_INCLUDED -# define CONFIG_INCLUDED - -#include -#include -#include -#include -#include -#include - -typedef std::function t_config_changed_callback; - -class Config -{ -public: - Config(){}; - ~Config(){}; - /** - * returns a value from the config. If it doesn’t exist, use - * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist - */ - static std::string get(const std::string&, const std::string&); - /** - * returns a value from the config. If it doesn’t exist, use - * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist - */ - static int get_int(const std::string&, const int&); - /** - * Set a value for the given option. And write all the config - * in the file from which it was read if boolean is set. - * @param option The option to set - * @param value The value to use - * @param save if true, save the config file - */ - static void set(const std::string&, const std::string&, bool save = false); - /** - * Adds a function to a list. This function will be called whenever a - * configuration change occurs. - */ - static void connect(t_config_changed_callback); - /** - * Close the config file, saving it to the file is save == true. - */ - static void close(); - - /** - * Set the value of the filename to use, before calling any method. - */ - static std::string filename; - /** - * Set to true if you want an exception to be raised if the file does not - * exist when reading it. - */ - static bool file_must_exist; - -private: - /** - * Get the singleton instance - */ - static std::unique_ptr& instance(); - /** - * Read the configuration file at the given path. - */ - bool read_conf(); - /** - * Write all the config values into the configuration file - */ - void save_to_file() const; - /** - * Call all the callbacks previously registered using connect(). - * This is used to notify any class that a configuration change occured. - */ - void trigger_configuration_change(); - - std::map values; - std::vector callbacks; - - Config(const Config&) = delete; - Config& operator=(const Config&) = delete; - Config(Config&&) = delete; - Config& operator=(Config&&) = delete; -}; - -#endif // CONFIG_INCLUDED diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index dedb5de..cbbd4ea 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -14,7 +14,7 @@ #include #include -#include "config.h" +#include "louloulibs.h" using namespace std::string_literals; using namespace std::chrono_literals; diff --git a/src/logger/logger.cpp b/src/logger/logger.cpp deleted file mode 100644 index 7336579..0000000 --- a/src/logger/logger.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -Logger::Logger(const int log_level): - log_level(log_level), - stream(std::cout.rdbuf()) -{ -} - -Logger::Logger(const int log_level, const std::string& log_file): - log_level(log_level), - ofstream(log_file.data(), std::ios_base::app), - stream(ofstream.rdbuf()) -{ -} - -std::unique_ptr& Logger::instance() -{ - static std::unique_ptr instance; - - if (!instance) - { - const std::string log_file = Config::get("log_file", ""); - const int log_level = Config::get_int("log_level", 0); - if (log_file.empty()) - instance = std::make_unique(log_level); - else - instance = std::make_unique(log_level, log_file); - } - return instance; -} - -std::ostream& Logger::get_stream(const int lvl) -{ - if (lvl >= this->log_level) - return this->stream; - return this->null_stream; -} diff --git a/src/logger/logger.hpp b/src/logger/logger.hpp deleted file mode 100644 index b1ae20d..0000000 --- a/src/logger/logger.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef LOGGER_INCLUDED -# define LOGGER_INCLUDED - -/** - * Singleton used in logger macros to write into files or stdout, with - * various levels of severity. - * Only the macros should be used. - * @class Logger - */ - -#include -#include -#include - -#define debug_lvl 0 -#define info_lvl 1 -#define warning_lvl 2 -#define error_lvl 3 - -#include "config.h" - -#ifdef SYSTEMD_FOUND -# include -#else -# define SD_DEBUG "[DEBUG]: " -# define SD_INFO "[INFO]: " -# define SD_WARNING "[WARNING]: " -# define SD_ERR "[ERROR]: " -#endif - -// Macro defined to get the filename instead of the full path. But if it is -// not properly defined by the build system, we fallback to __FILE__ -#ifndef __FILENAME__ -# define __FILENAME__ __FILE__ -#endif - -#define WHERE\ - __FILENAME__ << ":" << __LINE__ - -#define log_debug(text)\ - Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl; - -#define log_info(text)\ - Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl; - -#define log_warning(text)\ - Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl; - -#define log_error(text)\ - Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl; - -/** - * Juste a structure representing a stream doing nothing with its input. - */ -class nullstream: public std::ostream -{ -public: - nullstream(): - std::ostream(0) - { } -}; - -class Logger -{ -public: - static std::unique_ptr& instance(); - std::ostream& get_stream(const int); - Logger(const int log_level, const std::string& log_file); - Logger(const int log_level); - -private: - Logger(const Logger&); - Logger& operator=(const Logger&); - - const int log_level; - std::ofstream ofstream; - nullstream null_stream; - std::ostream stream; -}; - -#endif // LOGGER_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 148412e..9042a57 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -72,7 +72,7 @@ int main(int ac, char** av) return config_help("hostname"); auto p = std::make_shared(); - auto xmpp_component = std::make_shared(p, + auto xmpp_component = std::make_shared(p, hostname, password); diff --git a/src/network/dns_handler.cpp b/src/network/dns_handler.cpp deleted file mode 100644 index 45bf626..0000000 --- a/src/network/dns_handler.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include -#ifdef CARES_FOUND - -#include -#include -#include -#include - -#include -#include - -DNSHandler DNSHandler::instance; - -using namespace std::string_literals; - -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast(arg); - socket_handler->on_hostname4_resolved(status, hostent); -} - -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast(arg); - socket_handler->on_hostname6_resolved(status, hostent); -} - -DNSHandler::DNSHandler() -{ - int ares_error; - if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) - throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); - if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS) - throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); -} - -ares_channel& DNSHandler::get_channel() -{ - return this->channel; -} - -void DNSHandler::destroy() -{ - this->socket_handlers.clear(); - ::ares_destroy(this->channel); - ::ares_library_cleanup(); -} - -void DNSHandler::gethostbyname(const std::string& name, - TCPSocketHandler* socket_handler, int family) -{ - socket_handler->free_cares_addrinfo(); - if (family == AF_INET) - ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname4_resolved, socket_handler); - else - ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname6_resolved, socket_handler); -} - -void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) -{ - fd_set readers; - fd_set writers; - - FD_ZERO(&readers); - FD_ZERO(&writers); - - int ndfs = ::ares_fds(this->channel, &readers, &writers); - // For each existing DNS socket, see if we are still supposed to watch it, - // if not then erase it - this->socket_handlers.erase( - std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), - [&readers](const auto& dns_socket) - { - return !FD_ISSET(dns_socket->get_socket(), &readers); - }), - this->socket_handlers.end()); - - for (auto i = 0; i < ndfs; ++i) - { - bool read = FD_ISSET(i, &readers); - bool write = FD_ISSET(i, &writers); - // Look for the DNSSocketHandler with this fd - auto it = std::find_if(this->socket_handlers.begin(), - this->socket_handlers.end(), - [i](const auto& socket_handler) - { - return i == socket_handler->get_socket(); - }); - if (!read && !write) // No need to read or write to it - { // If found, erase it and stop watching it because it is not - // needed anymore - if (it != this->socket_handlers.end()) - // The socket destructor removes it from the poller - this->socket_handlers.erase(it); - } - else // We need to write and/or read to it - { // If not found, create it because we need to watch it - if (it == this->socket_handlers.end()) - { - this->socket_handlers.emplace_front(std::make_unique(poller, i)); - it = this->socket_handlers.begin(); - } - poller->add_socket_handler(it->get()); - if (write) - poller->watch_send_events(it->get()); - } - } -} - -#endif /* CARES_FOUND */ diff --git a/src/network/dns_handler.hpp b/src/network/dns_handler.hpp deleted file mode 100644 index ec5b2fa..0000000 --- a/src/network/dns_handler.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef DNS_HANDLER_HPP_INCLUDED -#define DNS_HANDLER_HPP_INCLUDED - -#include -#ifdef CARES_FOUND - -class TCPSocketHandler; -class Poller; -class DNSSocketHandler; - -# include -# include -# include -# include - -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); - -/** - * Class managing DNS resolution. It should only be statically instanciated - * once in SocketHandler. It manages ares channel and calls various - * functions of that library. - */ - -class DNSHandler -{ -public: - DNSHandler(); - ~DNSHandler() = default; - void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler, - int family); - /** - * Call ares_fds to know what fd needs to be watched by the poller, create - * or destroy DNSSocketHandlers depending on the result. - */ - void watch_dns_sockets(std::shared_ptr& poller); - /** - * Destroy and stop watching all the DNS sockets. Then de-init the channel - * and library. - */ - void destroy(); - ares_channel& get_channel(); - - static DNSHandler instance; - -private: - /** - * The list of sockets that needs to be watched, according to the last - * call to ares_fds. DNSSocketHandlers are added to it or removed from it - * in the watch_dns_sockets() method - */ - std::list> socket_handlers; - ares_channel channel; - - DNSHandler(const DNSHandler&) = delete; - DNSHandler(DNSHandler&&) = delete; - DNSHandler& operator=(const DNSHandler&) = delete; - DNSHandler& operator=(DNSHandler&&) = delete; -}; - -#endif /* CARES_FOUND */ -#endif /* DNS_HANDLER_HPP_INCLUDED */ diff --git a/src/network/dns_socket_handler.cpp b/src/network/dns_socket_handler.cpp deleted file mode 100644 index 6563894..0000000 --- a/src/network/dns_socket_handler.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include -#ifdef CARES_FOUND - -#include -#include -#include - -#include - -DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, - const socket_t socket): - SocketHandler(poller, socket) -{ -} - -DNSSocketHandler::~DNSSocketHandler() -{ -} - -void DNSSocketHandler::connect() -{ -} - -void DNSSocketHandler::on_recv() -{ - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); - ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); -} - -void DNSSocketHandler::on_send() -{ - // always stop watching send and read events. We will re-watch them if the - // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); - ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); -} - -bool DNSSocketHandler::is_connected() const -{ - return true; -} - -#endif /* CARES_FOUND */ diff --git a/src/network/dns_socket_handler.hpp b/src/network/dns_socket_handler.hpp deleted file mode 100644 index beb47d9..0000000 --- a/src/network/dns_socket_handler.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef DNS_SOCKET_HANDLER_HPP -# define DNS_SOCKET_HANDLER_HPP - -#include -#ifdef CARES_FOUND - -#include -#include - -/** - * Manage a socket returned by ares_fds. We do not create, open or close the - * socket ourself: this is done by c-ares. We just call ares_process_fd() - * with the correct parameters, depending on what can be done on that socket - * (Poller reported it to be writable or readeable) - */ - -class DNSSocketHandler: public SocketHandler -{ -public: - explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); - ~DNSSocketHandler(); - /** - * Just call dns_process_fd, c-ares will do its work of send()ing or - * recv()ing the data it wants on that socket. - */ - void on_recv() override final; - void on_send() override final; - /** - * Do nothing, because we are always considered to be connected, since the - * connection is done by c-ares and not by us. - */ - void connect() override final; - /** - * Always true, see the comment for connect() - */ - bool is_connected() const override final; - -private: - DNSSocketHandler(const DNSSocketHandler&) = delete; - DNSSocketHandler(DNSSocketHandler&&) = delete; - DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; - DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; -}; - -#endif // CARES_FOUND -#endif // DNS_SOCKET_HANDLER_HPP diff --git a/src/network/poller.cpp b/src/network/poller.cpp deleted file mode 100644 index ffc4f2d..0000000 --- a/src/network/poller.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -Poller::Poller() -{ -#if POLLER == POLL - this->nfds = 0; -#elif POLLER == EPOLL - this->epfd = ::epoll_create1(0); - if (this->epfd == -1) - { - log_error("epoll failed: " << strerror(errno)); - throw std::runtime_error("Could not create epoll instance"); - } -#endif -} - -Poller::~Poller() -{ -} - -void Poller::add_socket_handler(SocketHandler* socket_handler) -{ - // Don't do anything if the socket is already managed - const auto it = this->socket_handlers.find(socket_handler->get_socket()); - if (it != this->socket_handlers.end()) - return ; - - this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); - - // We always watch all sockets for receive events -#if POLLER == POLL - this->fds[this->nfds].fd = socket_handler->get_socket(); - this->fds[this->nfds].events = POLLIN; - this->nfds++; -#endif -#if POLLER == EPOLL - struct epoll_event event = {EPOLLIN, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not add socket to epoll"); - } -#endif -} - -void Poller::remove_socket_handler(const socket_t socket) -{ - const auto it = this->socket_handlers.find(socket); - if (it == this->socket_handlers.end()) - throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); - this->socket_handlers.erase(it); - -#if POLLER == POLL - for (size_t i = 0; i < this->nfds; i++) - { - if (this->fds[i].fd == socket) - { - // Move all subsequent pollfd by one on the left, erasing the - // value of the one we remove - for (size_t j = i; j < this->nfds - 1; ++j) - { - this->fds[j].fd = this->fds[j+1].fd; - this->fds[j].events= this->fds[j+1].events; - } - this->nfds--; - } - } -#elif POLLER == EPOLL - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not remove socket from epoll"); - } -#endif -} - -void Poller::watch_send_events(SocketHandler* socket_handler) -{ -#if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) - { - if (this->fds[i].fd == socket_handler->get_socket()) - { - this->fds[i].events = POLLIN|POLLOUT; - return; - } - } - throw std::runtime_error("Cannot watch a non-registered socket for send events"); -#elif POLLER == EPOLL - struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not modify socket flags in epoll"); - } -#endif -} - -void Poller::stop_watching_send_events(SocketHandler* socket_handler) -{ -#if POLLER == POLL - for (size_t i = 0; i <= this->nfds; ++i) - { - if (this->fds[i].fd == socket_handler->get_socket()) - { - this->fds[i].events = POLLIN; - return; - } - } - throw std::runtime_error("Cannot watch a non-registered socket for send events"); -#elif POLLER == EPOLL - struct epoll_event event = {EPOLLIN, {socket_handler}}; - const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); - if (res == -1) - { - log_error("epoll_ctl failed: " << strerror(errno)); - throw std::runtime_error("Could not modify socket flags in epoll"); - } -#endif -} - -int Poller::poll(const std::chrono::milliseconds& timeout) -{ - if (this->socket_handlers.empty() && timeout == utils::no_timeout) - return -1; -#if POLLER == POLL - int nb_events = ::poll(this->fds, this->nfds, timeout.count()); - if (nb_events < 0) - { - if (errno == EINTR) - return true; - log_error("poll failed: " << strerror(errno)); - throw std::runtime_error("Poll failed"); - } - // We cannot possibly have more ready events than the number of fds we are - // watching - assert(static_cast(nb_events) <= this->nfds); - for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) - { - if (this->fds[i].revents == 0) - continue; - else if (this->fds[i].revents & POLLIN) - { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - socket_handler->on_recv(); - nb_events--; - } - else if (this->fds[i].revents & POLLOUT) - { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (socket_handler->is_connected()) - socket_handler->on_send(); - else - socket_handler->connect(); - nb_events--; - } - } - return 1; -#elif POLLER == EPOLL - static const size_t max_events = 12; - struct epoll_event revents[max_events]; - const int nb_events = ::epoll_wait(this->epfd, revents, max_events, timeout.count()); - if (nb_events == -1) - { - if (errno == EINTR) - return 0; - log_error("epoll wait: " << strerror(errno)); - throw std::runtime_error("Epoll_wait failed"); - } - for (int i = 0; i < nb_events; ++i) - { - auto socket_handler = static_cast(revents[i].data.ptr); - if (revents[i].events & EPOLLIN) - socket_handler->on_recv(); - else if (revents[i].events & EPOLLOUT) - { - if (socket_handler->is_connected()) - socket_handler->on_send(); - else - socket_handler->connect(); - } - } - return nb_events; -#endif -} - -size_t Poller::size() const -{ - return this->socket_handlers.size(); -} diff --git a/src/network/poller.hpp b/src/network/poller.hpp deleted file mode 100644 index c674e4c..0000000 --- a/src/network/poller.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef POLLER_INCLUDED -# define POLLER_INCLUDED - -#include - -#include -#include -#include - -#define POLL 1 -#define EPOLL 2 -#define KQUEUE 3 -#include -#ifndef POLLER - #define POLLER POLL -#endif - -#if POLLER == POLL - #include - #define MAX_POLL_FD_NUMBER 4096 -#elif POLLER == EPOLL - #include -#else - #error Invalid POLLER value -#endif - -/** - * We pass some SocketHandlers to this Poller, which uses - * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, - * and call the callbacks when event occurs. - * - * TODO: support these pollers: - * - kqueue(2) - */ - -class Poller -{ -public: - explicit Poller(); - ~Poller(); - /** - * Add a SocketHandler to be monitored by this Poller. All receive events - * are always automatically watched. - */ - void add_socket_handler(SocketHandler* socket_handler); - /** - * Remove (and stop managing) a SocketHandler, designated by the given socket_t. - */ - void remove_socket_handler(const socket_t socket); - /** - * Signal the poller that he needs to watch for send events for the given - * SocketHandler. - */ - void watch_send_events(SocketHandler* socket_handler); - /** - * Signal the poller that he needs to stop watching for send events for - * this SocketHandler. - */ - void stop_watching_send_events(SocketHandler* socket_handler); - /** - * Wait for all watched events, and call the SocketHandlers' callbacks - * when one is ready. Returns if nothing happened before the provided - * timeout. If the timeout is 0, it waits forever. If there is no - * watched event, returns -1 immediately, ignoring the timeout value. - * Otherwise, returns the number of event handled. If 0 is returned this - * means that we were interrupted by a signal, or the timeout occured. - */ - int poll(const std::chrono::milliseconds& timeout); - /** - * Returns the number of SocketHandlers managed by the poller. - */ - size_t size() const; - -private: - /** - * A "list" of all the SocketHandlers that we manage, indexed by socket, - * because that's what is returned by select/poll/etc when an event - * occures. - */ - std::unordered_map socket_handlers; - -#if POLLER == POLL - struct pollfd fds[MAX_POLL_FD_NUMBER]; - nfds_t nfds; -#elif POLLER == EPOLL - int epfd; -#endif - - Poller(const Poller&) = delete; - Poller(Poller&&) = delete; - Poller& operator=(const Poller&) = delete; - Poller& operator=(Poller&&) = delete; -}; - -#endif // POLLER_INCLUDED diff --git a/src/network/socket_handler.hpp b/src/network/socket_handler.hpp deleted file mode 100644 index 0858474..0000000 --- a/src/network/socket_handler.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef SOCKET_HANDLER_HPP -# define SOCKET_HANDLER_HPP - -#include -#include - -class Poller; - -typedef int socket_t; - -class SocketHandler -{ -public: - explicit SocketHandler(std::shared_ptr poller, const socket_t socket): - poller(poller), - socket(socket) - {} - virtual ~SocketHandler() {} - virtual void on_recv() = 0; - virtual void on_send() = 0; - virtual void connect() = 0; - virtual bool is_connected() const = 0; - - socket_t get_socket() const - { return this->socket; } - -protected: - /** - * A pointer to the poller that manages us, because we need to communicate - * with it. - */ - std::shared_ptr poller; - /** - * The handled socket. - */ - socket_t socket; - -private: - SocketHandler(const SocketHandler&) = delete; - SocketHandler(SocketHandler&&) = delete; - SocketHandler& operator=(const SocketHandler&) = delete; - SocketHandler& operator=(SocketHandler&&) = delete; -}; - -#endif // SOCKET_HANDLER_HPP diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp deleted file mode 100644 index e9984e3..0000000 --- a/src/network/tcp_socket_handler.cpp +++ /dev/null @@ -1,572 +0,0 @@ -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#ifdef BOTAN_FOUND -# include - -Botan::AutoSeeded_RNG TCPSocketHandler::rng; -Permissive_Credentials_Manager TCPSocketHandler::credential_manager; -Botan::TLS::Policy TCPSocketHandler::policy; -Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); - -#endif - -#ifndef UIO_FASTIOV -# define UIO_FASTIOV 8 -#endif - -using namespace std::string_literals; -using namespace std::chrono_literals; - -namespace ph = std::placeholders; - -TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): - SocketHandler(poller, -1), - use_tls(false), - connected(false), - connecting(false) -#ifdef CARES_FOUND - ,resolved(false), - resolved4(false), - resolved6(false), - cares_addrinfo(nullptr), - cares_error() -#endif -{} - -TCPSocketHandler::~TCPSocketHandler() -{ -#ifdef CARES_FOUND - this->free_cares_addrinfo(); -#endif -} - -void TCPSocketHandler::init_socket(const struct addrinfo* rp) -{ - if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) - throw std::runtime_error("Could not create socket: "s + strerror(errno)); - int optval = 1; - if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) - log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); - // Set the socket on non-blocking mode. This is useful to receive a EAGAIN - // error when connect() would block, to not block the whole process if a - // remote is not responsive. - const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); - if ((existing_flags == -1) || - (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) - throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); -} - -void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) -{ - this->address = address; - this->port = port; - this->use_tls = tls; - - utils::ScopeGuard sg; - - struct addrinfo* addr_res; - - if (!this->connecting) - { - // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if - // this is the first call of this function. -#ifdef CARES_FOUND - if (!this->resolved) - { - log_info("Trying to connect to " << address << ":" << port); - // Start the asynchronous process of resolving the hostname. Once - // the addresses have been found and `resolved` has been set to true - // (but connecting will still be false), TCPSocketHandler::connect() - // needs to be called, again. - DNSHandler::instance.gethostbyname(address, this, AF_INET6); - DNSHandler::instance.gethostbyname(address, this, AF_INET); - return; - } - else - { - // The c-ares resolved the hostname and the available addresses - // where saved in the cares_addrinfo linked list. Now, just use - // this list to try to connect. - addr_res = this->cares_addrinfo; - if (!addr_res) - { - this->close(); - this->on_connection_failed(this->cares_error); - return ; - } - } -#else - log_info("Trying to connect to " << address << ":" << port); - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - - if (res != 0) - { - log_warning("getaddrinfo failed: "s + gai_strerror(res)); - this->close(); - this->on_connection_failed(gai_strerror(res)); - return ; - } - // Make sure the alloced structure is always freed at the end of the - // function - sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); -#endif - } - else - { // This function is called again, use the saved addrinfo structure, - // instead of re-doing the whole getaddrinfo process. - addr_res = &this->addrinfo; - } - - for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) - { - if (!this->connecting) - { - try { - this->init_socket(rp); - } - catch (const std::runtime_error& error) { - log_error("Failed to init socket: " << error.what()); - break; - } - } - if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 - || errno == EISCONN) - { - log_info("Connection success."); - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - this->poller->add_socket_handler(this); - this->connected = true; - this->connecting = false; -#ifdef BOTAN_FOUND - if (this->use_tls) - this->start_tls(); -#endif - this->on_connected(); - return ; - } - else if (errno == EINPROGRESS || errno == EALREADY) - { // retry this process later, when the socket - // is ready to be written on. - this->connecting = true; - this->poller->add_socket_handler(this); - this->poller->watch_send_events(this); - // Save the addrinfo structure, to use it on the next call - this->ai_addrlen = rp->ai_addrlen; - memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); - memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); - this->addrinfo.ai_addr = reinterpret_cast(&this->ai_addr); - this->addrinfo.ai_next = nullptr; - // If the connection has not succeeded or failed in 5s, we consider - // it to have failed - TimedEventsManager::instance().add_event( - TimedEvent(std::chrono::steady_clock::now() + 5s, - std::bind(&TCPSocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); - return ; - } - log_info("Connection failed:" << strerror(errno)); - } - log_error("All connection attempts failed."); - this->close(); - this->on_connection_failed(strerror(errno)); - return ; -} - -void TCPSocketHandler::on_connection_timeout() -{ - this->close(); - this->on_connection_failed("connection timed out"); -} - -void TCPSocketHandler::connect() -{ - this->connect(this->address, this->port, this->use_tls); -} - -void TCPSocketHandler::on_recv() -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_recv(); - else -#endif - this->plain_recv(); -} - -void TCPSocketHandler::plain_recv() -{ - static constexpr size_t buf_size = 4096; - char buf[buf_size]; - void* recv_buf = this->get_receive_buffer(buf_size); - - if (recv_buf == nullptr) - recv_buf = buf; - - const ssize_t size = this->do_recv(recv_buf, buf_size); - - if (size > 0) - { - if (buf == recv_buf) - { - // data needs to be placed in the in_buf string, because no buffer - // was provided to receive that data directly. The in_buf buffer - // will be handled in parse_in_buffer() - this->in_buf += std::string(buf, size); - } - this->parse_in_buffer(size); - } -} - -ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) -{ - ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); - if (0 == size) - { - this->on_connection_close(""); - this->close(); - } - else if (-1 == size) - { - log_warning("Error while reading from socket: " << strerror(errno)); - // Remember if we were connecting, or already connected when this - // happened, because close() sets this->connecting to false - const auto were_connecting = this->connecting; - this->close(); - if (were_connecting) - this->on_connection_failed(strerror(errno)); - else - this->on_connection_close(strerror(errno)); - } - return size; -} - -void TCPSocketHandler::on_send() -{ - struct iovec msg_iov[UIO_FASTIOV] = {}; - struct msghdr msg{nullptr, 0, - msg_iov, - 0, nullptr, 0, 0}; - for (std::string& s: this->out_buf) - { - // unconsting the content of s is ok, sendmsg will never modify it - msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); - msg_iov[msg.msg_iovlen].iov_len = s.size(); - if (++msg.msg_iovlen == UIO_FASTIOV) - break; - } - ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); - if (res < 0) - { - log_error("sendmsg failed: " << strerror(errno)); - this->on_connection_close(strerror(errno)); - this->close(); - } - else - { - // remove all the strings that were successfully sent. - for (auto it = this->out_buf.begin(); - it != this->out_buf.end();) - { - if (static_cast(res) >= (*it).size()) - { - res -= (*it).size(); - it = this->out_buf.erase(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); - break; - } - } - if (this->out_buf.empty()) - this->poller->stop_watching_send_events(this); - } -} - -void TCPSocketHandler::close() -{ - TimedEventsManager::instance().cancel("connection_timeout"s + - std::to_string(this->socket)); - if (this->connected || this->connecting) - this->poller->remove_socket_handler(this->get_socket()); - if (this->socket != -1) - { - ::close(this->socket); - this->socket = -1; - } - this->connected = false; - this->connecting = false; - this->in_buf.clear(); - this->out_buf.clear(); - this->port.clear(); -} - -void TCPSocketHandler::send_data(std::string&& data) -{ -#ifdef BOTAN_FOUND - if (this->use_tls) - this->tls_send(std::move(data)); - else -#endif - this->raw_send(std::move(data)); -} - -void TCPSocketHandler::raw_send(std::string&& data) -{ - if (data.empty()) - return ; - this->out_buf.emplace_back(std::move(data)); - if (this->connected) - this->poller->watch_send_events(this); -} - -void TCPSocketHandler::send_pending_data() -{ - if (this->connected && !this->out_buf.empty()) - this->poller->watch_send_events(this); -} - -bool TCPSocketHandler::is_connected() const -{ - return this->connected; -} - -bool TCPSocketHandler::is_connecting() const -{ -#ifdef CARES_FOUND - return this->connecting || !this->resolved; -#else - return this->connecting; -#endif -} - -void* TCPSocketHandler::get_receive_buffer(const size_t) const -{ - return nullptr; -} - -#ifdef BOTAN_FOUND -void TCPSocketHandler::start_tls() -{ - Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); - this->tls = std::make_unique( - std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2), - std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2), - std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), - std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1), - session_manager, credential_manager, policy, - rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); -} - -void TCPSocketHandler::tls_recv() -{ - static constexpr size_t buf_size = 4096; - char 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(); - this->tls->received_data(reinterpret_cast(recv_buf), - static_cast(size)); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } -} - -void TCPSocketHandler::tls_send(std::string&& data) -{ - if (this->tls->is_active()) - { - const bool was_active = this->tls->is_active(); - if (!this->pre_buf.empty()) - { - this->tls->send(reinterpret_cast(this->pre_buf.data()), - this->pre_buf.size()); - this->pre_buf = ""; - } - if (!data.empty()) - this->tls->send(reinterpret_cast(data.data()), - data.size()); - if (!was_active && this->tls->is_active()) - this->on_tls_activated(); - } - else - this->pre_buf += data; -} - -void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) -{ - this->in_buf += std::string(reinterpret_cast(data), - size); - if (!this->in_buf.empty()) - this->parse_in_buffer(size); -} - -void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size) -{ - this->raw_send(std::string(reinterpret_cast(data), size)); -} - -void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) -{ - log_debug("tls_alert: " << alert.type_string()); -} - -bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) -{ - log_debug("Handshake with " << session.server_info().hostname() << " complete." - << " Version: " << session.version().to_string() - << " using " << session.ciphersuite().to_string()); - if (!session.session_id().empty()) - log_debug("Session ID " << Botan::hex_encode(session.session_id())); - if (!session.session_ticket().empty()) - log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); - return true; -} - -void TCPSocketHandler::on_tls_activated() -{ - this->send_data(""); -} - -#endif // BOTAN_FOUND - -#ifdef CARES_FOUND - -void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent) -{ - this->resolved4 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo4(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->connect(); - } -} - -void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent) -{ - this->resolved6 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo6(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->connect(); - } -} - -void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - 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; - - current->ai_addr = reinterpret_cast(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - 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; - - current->ai_addr = reinterpret_cast(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::free_cares_addrinfo() -{ - while (this->cares_addrinfo) - { - delete this->cares_addrinfo->ai_addr; - auto next = this->cares_addrinfo->ai_next; - delete this->cares_addrinfo; - this->cares_addrinfo = next; - } -} - -#endif // CARES_FOUND diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp deleted file mode 100644 index 7f10cff..0000000 --- a/src/network/tcp_socket_handler.hpp +++ /dev/null @@ -1,297 +0,0 @@ -#ifndef SOCKET_HANDLER_INCLUDED -# define SOCKET_HANDLER_INCLUDED - -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "config.h" - -#ifdef CARES_FOUND -# include -#endif - -#ifdef BOTAN_FOUND -# include -# include - -/** - * A very simple credential manager that accepts any certificate. - */ -class Permissive_Credentials_Manager: public Botan::Credentials_Manager -{ -public: - void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) - { // TODO: Offer the admin to disallow connection on untrusted - // certificates - log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); - } -}; -#endif // BOTAN_FOUND - -/** - * An interface, with a series of callbacks that should be implemented in - * subclasses that deal with a socket. These callbacks are called on various events - * (read/write/timeout, etc) when they are notified to a poller - * (select/poll/epoll etc) - */ -class TCPSocketHandler: public SocketHandler -{ -protected: - ~TCPSocketHandler(); - -public: - explicit TCPSocketHandler(std::shared_ptr poller); - /** - * Connect to the remote server, and call on_connected() if this - * succeeds. If tls is true, we set use_tls to true and will also call - * start_tls() when the connection succeeds. - */ - void connect(const std::string& address, const std::string& port, const bool tls); - void connect() override final; - /** - * Reads raw data from the socket. And pass it to parse_in_buffer() - * If we are using TLS on this connection, we call tls_recv() - */ - void on_recv() override final; - /** - * Write as much data from out_buf as possible, in the socket. - */ - void on_send() override final; - /** - * Add the given data to out_buf and tell our poller that we want to be - * notified when a send event is ready. - * - * This can be overriden if we want to modify the data before sending - * it. For example if we want to encrypt it. - */ - void send_data(std::string&& data); - /** - * Watch the socket for send events, if our out buffer is not empty. - */ - void send_pending_data(); - /** - * Close the connection, remove us from the poller - */ - void close(); - /** - * Called by a TimedEvent, when the connection did not succeed or fail - * after a given time. - */ - void on_connection_timeout(); - /** - * Called when the connection is successful. - */ - virtual void on_connected() = 0; - /** - * Called when the connection fails. Not when it is closed later, just at - * the connect() call. - */ - virtual void on_connection_failed(const std::string& reason) = 0; - /** - * Called when we detect a disconnection from the remote host. - */ - virtual void on_connection_close(const std::string& error) = 0; - /** - * Handle/consume (some of) the data received so far. The data to handle - * may be in the in_buf buffer, or somewhere else, depending on what - * get_receive_buffer() returned. If some data is used from in_buf, it - * should be truncated, only the unused data should be left untouched. - * - * The size argument is the size of the last chunk of data that was added to the buffer. - */ - virtual void parse_in_buffer(const size_t size) = 0; - bool is_connected() const override final; - bool is_connecting() const; - -#ifdef CARES_FOUND - void on_hostname4_resolved(int status, struct hostent* hostent); - void on_hostname6_resolved(int status, struct hostent* hostent); - - void free_cares_addrinfo(); - - void fill_ares_addrinfo4(const struct hostent* hostent); - void fill_ares_addrinfo6(const struct hostent* hostent); -#endif - -private: - /** - * Initialize the socket with the parameters contained in the given - * addrinfo structure. - */ - void init_socket(const struct addrinfo* rp); - /** - * Reads from the socket into the provided buffer. If an error occurs - * (read returns <= 0), the handling of the error is done here (close the - * connection, log a message, etc). - * - * Returns the value returned by ::recv(), so the buffer should not be - * used if it’s not positive. - */ - ssize_t do_recv(void* recv_buf, const size_t buf_size); - /** - * Reads data from the socket and calls parse_in_buffer with it. - */ - void plain_recv(); - /** - * Mark the given data as ready to be sent, as-is, on the socket, as soon - * as we can. - */ - void raw_send(std::string&& data); - -#ifdef BOTAN_FOUND - /** - * Create the TLS::Client object, with all the callbacks etc. This must be - * called only when we know we are able to send TLS-encrypted data over - * the socket. - */ - void start_tls(); - /** - * An additional step to pass the data into our tls object to decrypt it - * before passing it to parse_in_buffer. - */ - void tls_recv(); - /** - * Pass the data to the tls object in order to encrypt it. The tls object - * will then call raw_send as a callback whenever data as been encrypted - * and can be sent on the socket. - */ - void tls_send(std::string&& data); - /** - * Called by the tls object that some data has been decrypt. We call - * parse_in_buffer() to handle that unencrypted data. - */ - void tls_data_cb(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that some data has been encrypted - * and is now ready to be sent on the socket as is. - */ - void tls_output_fn(const Botan::byte* data, size_t size); - /** - * Called by the tls object to indicate that a TLS alert has been - * received. We don’t use it, we just log some message, at the moment. - */ - void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); - /** - * Called by the tls object at the end of the TLS handshake. We don't do - * anything here appart from logging the TLS session information. - */ - bool tls_handshake_cb(const Botan::TLS::Session& session); - /** - * Called whenever the tls session goes from inactive to active. This - * means that the handshake has just been successfully done, and we can - * now proceed to send any available data into our tls object. - */ - void on_tls_activated(); -#endif // BOTAN_FOUND - /** - * Where data is added, when we want to send something to the client. - */ - std::list out_buf; - /** - * Keep the details of the addrinfo that triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. - */ - struct addrinfo addrinfo; - struct sockaddr_in6 ai_addr; - socklen_t ai_addrlen; - -protected: - /** - * Where data read from the socket is added until we can extract a full - * and meaningful “message” from it. - * - * TODO: something more efficient than a string. - */ - std::string in_buf; - /** - * Whether we are using TLS on this connection or not. - */ - bool use_tls; - /** - * Provide a buffer in which data can be directly received. This can be - * used to avoid copying data into in_buf before using it. If no buffer - * needs to be provided, nullptr is returned (the default implementation - * does that), in that case our internal in_buf will be used to save the - * data until it can be used by parse_in_buffer(). - */ - virtual void* get_receive_buffer(const size_t size) const; - /** - * Hostname we are connected/connecting to - */ - std::string address; - /** - * Port we are connected/connecting to - */ - std::string port; - - bool connected; - bool connecting; - -#ifdef CARES_FOUND - /** - * Whether or not the DNS resolution was successfully done - */ - bool resolved; - bool resolved4; - bool resolved6; - /** - * When using c-ares to resolve the host asynchronously, we need the - * c-ares callback to fill a structure (a struct addrinfo, for - * compatibility with getaddrinfo and the rest of the code that works when - * c-ares is not used) with all returned values (for example an IPv6 and - * an IPv4). The next call of connect() will then try all these values - * (exactly like we do with the result of getaddrinfo) and save the one - * that worked (or returned EINPROGRESS) in the other struct addrinfo (see - * the members addrinfo, ai_addrlen, and ai_addr). - */ - struct addrinfo* cares_addrinfo; - std::string cares_error; -#endif // CARES_FOUND - -private: - TCPSocketHandler(const TCPSocketHandler&) = delete; - TCPSocketHandler(TCPSocketHandler&&) = delete; - TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; - TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; - -#ifdef BOTAN_FOUND - /** - * Botan stuff to manipulate a TLS session. - */ - static Botan::AutoSeeded_RNG rng; - static Permissive_Credentials_Manager credential_manager; - static Botan::TLS::Policy policy; - static Botan::TLS::Session_Manager_In_Memory session_manager; - /** - * We use a unique_ptr because we may not want to create the object at - * all. The Botan::TLS::Client object generates a handshake message as - * soon and calls the output_fn callback with it as soon as it is - * created. Therefore, we do not want to create it if do not intend to do - * send any TLS-encrypted message. We create the object only when needed - * (for example after we have negociated a TLS session using a STARTTLS - * message, or stuf like that). - * - * See start_tls for the method where this object is created. - */ - std::unique_ptr tls; - /** - * An additional buffer to keep data that the user wants to send, but - * cannot because the handshake is not done. - */ - std::string pre_buf; -#endif // BOTAN_FOUND -}; - -#endif // SOCKET_HANDLER_INCLUDED diff --git a/src/utils/encoding.cpp b/src/utils/encoding.cpp deleted file mode 100644 index 3e3580c..0000000 --- a/src/utils/encoding.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -#include - -#include - -/** - * The UTF-8-encoded character used as a place holder when a character conversion fails. - * This is U+FFFD � "replacement character" - */ -static const char* invalid_char = "\xef\xbf\xbd"; -static const size_t invalid_char_len = 3; - -namespace utils -{ - /** - * Based on http://en.wikipedia.org/wiki/UTF-8#Description - */ - bool is_valid_utf8(const char* s) - { - if (!s) - return false; - - const unsigned char* str = reinterpret_cast(s); - - while (*str) - { - // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 0b11111000) == 0b11110000) - { - if (!str[1] || !str[2] || !str[3] - || ((str[1] & 0b11000000) != 0b10000000) - || ((str[2] & 0b11000000) != 0b10000000) - || ((str[3] & 0b11000000) != 0b10000000)) - return false; - str += 4; - } - // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 0b11110000) == 0b11100000) - { - if (!str[1] || !str[2] - || ((str[1] & 0b11000000) != 0b10000000) - || ((str[2] & 0b11000000) != 0b10000000)) - return false; - str += 3; - } - // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 0b11100000) == 0b11000000) - { - if (!str[1] || - ((str[1] & 0b11000000) != 0b10000000)) - return false; - str += 2; - } - // 1 byte: 0xxxxxxx - else if ((str[0] & 0b10000000) != 0) - return false; - else - str++; - } - return true; - } - - 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;}); - - // pointer where we write valid chars - unsigned char* r = res; - - const unsigned char* str = reinterpret_cast(original.c_str()); - std::bitset<20> codepoint; - - while (*str) - { - // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 0b11111000) == 0b11110000) - { - codepoint = ((str[0] & 0b00000111) << 18); - codepoint |= ((str[1] & 0b00111111) << 12); - codepoint |= ((str[2] & 0b00111111) << 6 ); - codepoint |= ((str[3] & 0b00111111) << 0 ); - if (codepoint.to_ulong() <= 0x10FFFF) - { - ::memcpy(r, str, 4); - r += 4; - } - str += 4; - } - // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 0b11110000) == 0b11100000) - { - codepoint = ((str[0] & 0b00001111) << 12); - codepoint |= ((str[1] & 0b00111111) << 6); - codepoint |= ((str[2] & 0b00111111) << 0 ); - if (codepoint.to_ulong() <= 0xD7FF || - (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) - { - ::memcpy(r, str, 3); - r += 3; - } - str += 3; - } - // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 0b11100000) == 0b11000000) - { - // All 2 bytes char are valid, don't even bother calculating - // the codepoint - ::memcpy(r, str, 2); - r += 2; - str += 2; - } - // 1 byte: 0xxxxxxx - else if ((str[0] & 0b10000000) == 0) - { - codepoint = ((str[0] & 0b01111111)); - if (codepoint.to_ulong() == 0x09 || - codepoint.to_ulong() == 0x0A || - codepoint.to_ulong() == 0x0D || - codepoint.to_ulong() >= 0x20) - { - ::memcpy(r, str, 1); - r += 1; - } - str += 1; - } - else - throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); - } - return std::string(reinterpret_cast(res), r-res); - } - - std::string convert_to_utf8(const std::string& str, const char* charset) - { - std::string res; - - const iconv_t cd = iconv_open("UTF-8", charset); - if (cd == (iconv_t)-1) - 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); }); - - size_t inbytesleft = str.size(); - - // iconv will not attempt to modify this buffer, but some plateform - // require a char** anyway -#ifdef ICONV_SECOND_ARGUMENT_IS_CONST - const char* inbuf_ptr = str.c_str(); -#else - char* inbuf_ptr = const_cast(str.c_str()); -#endif - - size_t outbytesleft = str.size() * 4; - char* outbuf = new char[outbytesleft]; - char* outbuf_ptr = outbuf; - - // Make sure outbuf is always deleted when we leave this function - sg.add_callback([&]{ delete[] outbuf; }); - - bool done = false; - while (done == false) - { - size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); - if ((size_t)-1 == error) - { - switch (errno) - { - case EILSEQ: - // Invalid byte found. Insert a placeholder instead of the - // converted character, jump one byte and continue - memcpy(outbuf_ptr, invalid_char, invalid_char_len); - outbuf_ptr += invalid_char_len; - inbytesleft--; - inbuf_ptr++; - break; - case EINVAL: - // A multibyte sequence is not terminated, but we can't - // provide any more data, so we just add a placeholder to - // indicate that the character is not properly converted, - // and we stop the conversion - memcpy(outbuf_ptr, invalid_char, invalid_char_len); - outbuf_ptr += invalid_char_len; - outbuf_ptr++; - done = true; - break; - case E2BIG: - // This should never happen - done = true; - break; - default: - // This should happen even neverer - done = true; - break; - } - } - else - { - // The conversion finished without any error, stop converting - done = true; - } - } - // Terminate the converted buffer, and copy that buffer it into the - // string we return - *outbuf_ptr = '\0'; - res = outbuf; - return res; - } - -} - diff --git a/src/utils/encoding.hpp b/src/utils/encoding.hpp deleted file mode 100644 index a3bccfc..0000000 --- a/src/utils/encoding.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ENCODING_INCLUDED -# define ENCODING_INCLUDED - -#include - -namespace utils -{ - /** - * Returns true if the given null-terminated string is valid utf-8. - * - * Based on http://en.wikipedia.org/wiki/UTF-8#Description - */ - bool is_valid_utf8(const char* s); - /** - * Remove all invalid codepoints from the given utf-8-encoded string. - * The value returned is a copy of the string, without the removed chars. - * - * See http://www.w3.org/TR/xml/#charsets for the list of valid characters - * in XML. - */ - std::string remove_invalid_xml_chars(const std::string& original); - /** - * Convert the given string (encoded is "encoding") into valid utf-8. - * If some decoding fails, insert an utf-8 placeholder character instead. - */ - std::string convert_to_utf8(const std::string& str, const char* encoding); -} - -#endif // ENCODING_INCLUDED diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp deleted file mode 100644 index 6600c75..0000000 --- a/src/utils/reload.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include - -void reload_process() -{ - // Closing the config will just force it to be reopened the next time - // a configuration option is needed - Config::close(); - // Destroy the logger instance, to be recreated the next time a log - // line needs to be written - Logger::instance().reset(); - log_debug("Configuration and logger reloaded."); -} diff --git a/src/utils/reload.hpp b/src/utils/reload.hpp deleted file mode 100644 index 16d64f7..0000000 --- a/src/utils/reload.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef RELOAD_HPP_INCLUDED -#define RELOAD_HPP_INCLUDED - -/** - * Reload the server's configuration, and close the logger (so that it - * closes its files etc, to take into account the new configuration) - */ -void reload_process(); - -#endif /* RELOAD_HPP_INCLUDED */ diff --git a/src/utils/revstr.cpp b/src/utils/revstr.cpp deleted file mode 100644 index 87fd801..0000000 --- a/src/utils/revstr.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -namespace utils -{ - std::string revstr(const std::string& original) - { - return {original.rbegin(), original.rend()}; - } -} diff --git a/src/utils/revstr.hpp b/src/utils/revstr.hpp deleted file mode 100644 index 27c9e3e..0000000 --- a/src/utils/revstr.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef REVSTR_HPP_INCLUDED -# define REVSTR_HPP_INCLUDED - -#include - -namespace utils -{ - std::string revstr(const std::string& original); -} - -#endif // REVSTR_HPP_INCLUDED diff --git a/src/utils/scopeguard.hpp b/src/utils/scopeguard.hpp deleted file mode 100644 index df78831..0000000 --- a/src/utils/scopeguard.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef SCOPEGUARD_HPP -#define SCOPEGUARD_HPP - -#include -#include - -/** - * A class to be used to make sure some functions are called when the scope - * is left, because they will be called in the ScopeGuard's destructor. It - * can for example be used to delete some pointer whenever any exception is - * called. Example: - - * { - * ScopeGuard scope; - * int* number = new int(2); - * scope.add_callback([number]() { delete number; }); - * // Do some other stuff with the number. But these stuff might throw an exception: - * throw std::runtime_error("Some error not caught here, but in our caller"); - * return true; - * } - - * In this example, our pointer will always be deleted, even when the - * exception is thrown. If we want the functions to be called only when the - * scope is left because of an unexpected exception, we can use - * ScopeGuard::disable(); - */ - -namespace utils -{ - -class ScopeGuard -{ -public: - /** - * The constructor can take a callback. But additional callbacks can be - * added later with add_callback() - */ - explicit ScopeGuard(std::function&& func): - enabled(true) - { - this->add_callback(std::move(func)); - } - /** - * default constructor, the scope guard is enabled but empty, use - * add_callback() - */ - explicit ScopeGuard(): - enabled(true) - { - } - /** - * Call all callbacks in the desctructor, unless it has been disabled. - */ - ~ScopeGuard() - { - if (this->enabled) - for (auto& func: this->callbacks) - func(); - } - /** - * Add a callback to be called in our destructor, one scope guard can be - * used for more than one task, if needed. - */ - void add_callback(std::function&& func) - { - this->callbacks.emplace_back(std::move(func)); - } - /** - * Disable that scope guard, nothing will be done when the scope is - * exited. - */ - void disable() - { - this->enabled = false; - } - -private: - bool enabled; - std::vector> callbacks; - - ScopeGuard(const ScopeGuard&) = delete; - ScopeGuard& operator=(ScopeGuard&&) = delete; - ScopeGuard(ScopeGuard&&) = delete; - ScopeGuard& operator=(const ScopeGuard&) = delete; -}; - -} - -#endif diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp deleted file mode 100644 index 76476df..0000000 --- a/src/utils/sha1.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#include "sha1.hpp" - -#define SHA1_K0 0x5a827999 -#define SHA1_K20 0x6ed9eba1 -#define SHA1_K40 0x8f1bbcdc -#define SHA1_K60 0xca62c1d6 - -const uint8_t sha1InitState[] = { - 0x01,0x23,0x45,0x67, // H0 - 0x89,0xab,0xcd,0xef, // H1 - 0xfe,0xdc,0xba,0x98, // H2 - 0x76,0x54,0x32,0x10, // H3 - 0xf0,0xe1,0xd2,0xc3 // H4 -}; - -void sha1_init(sha1nfo *s) { - memcpy(s->state.b,sha1InitState,HASH_LENGTH); - s->byteCount = 0; - s->bufferOffset = 0; -} - -uint32_t sha1_rol32(uint32_t number, uint8_t bits) { - return ((number << bits) | (number >> (32-bits))); -} - -void sha1_hashBlock(sha1nfo *s) { - uint8_t i; - uint32_t a,b,c,d,e,t; - - a=s->state.w[0]; - b=s->state.w[1]; - c=s->state.w[2]; - d=s->state.w[3]; - e=s->state.w[4]; - for (i=0; i<80; i++) { - if (i>=16) { - t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; - s->buffer.w[i&15] = sha1_rol32(t,1); - } - if (i<20) { - t = (d ^ (b & (c ^ d))) + SHA1_K0; - } else if (i<40) { - t = (b ^ c ^ d) + SHA1_K20; - } else if (i<60) { - t = ((b & c) | (d & (b | c))) + SHA1_K40; - } else { - t = (b ^ c ^ d) + SHA1_K60; - } - t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; - e=d; - d=c; - c=sha1_rol32(b,30); - b=a; - a=t; - } - s->state.w[0] += a; - s->state.w[1] += b; - s->state.w[2] += c; - s->state.w[3] += d; - s->state.w[4] += e; -} - -void sha1_addUncounted(sha1nfo *s, uint8_t data) { - s->buffer.b[s->bufferOffset ^ 3] = data; - s->bufferOffset++; - if (s->bufferOffset == BLOCK_LENGTH) { - sha1_hashBlock(s); - s->bufferOffset = 0; - } -} - -void sha1_writebyte(sha1nfo *s, uint8_t data) { - ++s->byteCount; - sha1_addUncounted(s, data); -} - -void sha1_write(sha1nfo *s, const char *data, size_t len) { - for (;len--;) sha1_writebyte(s, (uint8_t) *data++); -} - -void sha1_pad(sha1nfo *s) { - // Implement SHA-1 padding (fips180-2 §5.1.1) - - // Pad with 0x80 followed by 0x00 until the end of the block - sha1_addUncounted(s, 0x80); - while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); - - // Append length in the last 8 bytes - sha1_addUncounted(s, 0); // We're only using 32 bit lengths - sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths - sha1_addUncounted(s, 0); // So zero pad the top bits - sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 - sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as - sha1_addUncounted(s, s->byteCount >> 13); // byte. - sha1_addUncounted(s, s->byteCount >> 5); - sha1_addUncounted(s, s->byteCount << 3); -} - -uint8_t* sha1_result(sha1nfo *s) { - int i; - // Pad to complete the last block - sha1_pad(s); - - // Swap byte order back - for (i=0; i<5; i++) { - uint32_t a,b; - a=s->state.w[i]; - b=a<<24; - b|=(a<<8) & 0x00ff0000; - b|=(a>>8) & 0x0000ff00; - b|=a>>24; - s->state.w[i]=b; - } - - // 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; ikeyBuffer[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; ikeyBuffer[i] ^ HMAC_OPAD); - for (i=0; iinnerHash[i]); - return sha1_result(s); -} diff --git a/src/utils/sha1.hpp b/src/utils/sha1.hpp deleted file mode 100644 index d02de75..0000000 --- a/src/utils/sha1.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/* This code is public-domain - it is based on libcrypt - * placed in the public domain by Wei Dai and other contributors. - */ - -#include -#include - -#define HASH_LENGTH 20 -#define BLOCK_LENGTH 64 - -union _buffer { - uint8_t b[BLOCK_LENGTH]; - uint32_t w[BLOCK_LENGTH/4]; -}; - -union _state { - uint8_t b[HASH_LENGTH]; - uint32_t w[HASH_LENGTH/4]; -}; - -typedef struct sha1nfo { - union _buffer buffer; - uint8_t bufferOffset; - union _state state; - uint32_t byteCount; - uint8_t keyBuffer[BLOCK_LENGTH]; - uint8_t innerHash[HASH_LENGTH]; -} sha1nfo; - -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/src/utils/split.cpp b/src/utils/split.cpp deleted file mode 100644 index afe4300..0000000 --- a/src/utils/split.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include - -namespace utils -{ - std::vector split(const std::string& s, const char delim, const bool allow_empty) - { - std::vector ret; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) - { - if (item.empty() && !allow_empty) - continue ; - ret.emplace_back(std::move(item)); - } - return ret; - } -} diff --git a/src/utils/split.hpp b/src/utils/split.hpp deleted file mode 100644 index 9fee90a..0000000 --- a/src/utils/split.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef SPLIT_INCLUDED -# define SPLIT_INCLUDED - -#include -#include -#include - -namespace utils -{ - std::vector split(const std::string &s, const char delim, const bool allow_empty=true); -} - -#endif // SPLIT_INCLUDED diff --git a/src/utils/timed_events.cpp b/src/utils/timed_events.cpp deleted file mode 100644 index 5010a3f..0000000 --- a/src/utils/timed_events.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include - -TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback, const std::string& name): - time_point(std::move(time_point)), - callback(callback), - repeat(false), - repeat_delay(0), - name(name) -{ -} - -TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, - std::function callback, const std::string& name): - time_point(std::chrono::steady_clock::now() + duration), - callback(callback), - repeat(true), - repeat_delay(std::move(duration)), - name(name) -{ -} - -TimedEvent::TimedEvent(TimedEvent&& other): - time_point(std::move(other.time_point)), - callback(std::move(other.callback)), - repeat(other.repeat), - repeat_delay(std::move(other.repeat_delay)), - name(std::move(other.name)) -{ -} - -TimedEvent::~TimedEvent() -{ -} - -bool TimedEvent::is_after(const TimedEvent& other) const -{ - return this->is_after(other.time_point); -} - -bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const -{ - return this->time_point >= time_point; -} - -std::chrono::milliseconds TimedEvent::get_timeout() const -{ - auto now = std::chrono::steady_clock::now(); - if (now > this->time_point) - return std::chrono::milliseconds(0); - return std::chrono::duration_cast(this->time_point - now); -} - -void TimedEvent::execute() -{ - this->callback(); -} - -const std::string& TimedEvent::get_name() const -{ - return this->name; -} diff --git a/src/utils/timed_events.hpp b/src/utils/timed_events.hpp deleted file mode 100644 index 4e2800c..0000000 --- a/src/utils/timed_events.hpp +++ /dev/null @@ -1,132 +0,0 @@ -#ifndef TIMED_EVENTS_HPP -# define TIMED_EVENTS_HPP - -#include -#include -#include -#include - -using namespace std::literals::chrono_literals; - -namespace utils { -static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); -} - -class TimedEventsManager; - -/** - * A callback with an associated date. - */ - -class TimedEvent -{ - friend class TimedEventsManager; -public: - /** - * An event the occurs only once, at the given time_point - */ - explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, - std::function callback, const std::string& name=""); - explicit TimedEvent(std::chrono::milliseconds&& duration, - std::function callback, const std::string& name=""); - - explicit TimedEvent(TimedEvent&&); - ~TimedEvent(); - /** - * Whether or not this event happens after the other one. - */ - bool is_after(const TimedEvent& other) const; - bool is_after(const std::chrono::steady_clock::time_point& time_point) const; - /** - * Return the duration difference between now and the event time point. - * If the difference would be negative (i.e. the event is expired), the - * returned value is 0 instead. The value cannot then be negative. - */ - std::chrono::milliseconds get_timeout() const; - void execute(); - const std::string& get_name() const; - -private: - /** - * The next time point at which the event is executed. - */ - std::chrono::steady_clock::time_point time_point; - /** - * The function to execute. - */ - const std::function callback; - /** - * Whether or not this events repeats itself until it is destroyed. - */ - const bool repeat; - /** - * This value is added to the time_point each time the event is executed, - * if repeat is true. Otherwise it is ignored. - */ - const std::chrono::milliseconds repeat_delay; - /** - * A name that is used to identify that event. If you want to find your - * event (for example if you want to cancel it), the name should be - * unique. - */ - const std::string name; - - TimedEvent(const TimedEvent&) = delete; - TimedEvent& operator=(const TimedEvent&) = delete; - TimedEvent& operator=(TimedEvent&&) = delete; -}; - -/** - * A class managing a list of TimedEvents. - * They are sorted, new events can be added, removed, fetch, etc. - */ - -class TimedEventsManager -{ -public: - ~TimedEventsManager(); - /** - * Return the unique instance of this class - */ - static TimedEventsManager& instance(); - /** - * Add an event to the list of managed events. The list is sorted after - * this call. - */ - void add_event(TimedEvent&& event); - /** - * Returns the duration, in milliseconds, between now and the next - * available event. If the event is already expired (the duration is - * negative), 0 is returned instead (as in “it's not too late, execute it - * now”) - * Returns a negative value if no event is available. - */ - std::chrono::milliseconds get_timeout() const; - /** - * Execute all the expired events (if their expiration time is exactly - * now, or before now). The event is then removed from the list. If the - * event does repeat, its expiration time is updated and it is reinserted - * in the list at the correct position. - * Returns the number of executed events. - */ - std::size_t execute_expired_events(); - /** - * Remove (and thus cancel) all the timed events with the given name. - * Returns the number of canceled events. - */ - std::size_t cancel(const std::string& name); - /** - * Return the number of managed events. - */ - std::size_t size() const; - -private: - explicit TimedEventsManager(); - std::list events; - TimedEventsManager(const TimedEventsManager&) = delete; - TimedEventsManager(TimedEventsManager&&) = delete; - TimedEventsManager& operator=(const TimedEventsManager&) = delete; - TimedEventsManager& operator=(TimedEventsManager&&) = delete; -}; - -#endif // TIMED_EVENTS_HPP diff --git a/src/utils/timed_events_manager.cpp b/src/utils/timed_events_manager.cpp deleted file mode 100644 index 2c75e48..0000000 --- a/src/utils/timed_events_manager.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include - -TimedEventsManager& TimedEventsManager::instance() -{ - static TimedEventsManager inst; - return inst; -} - -TimedEventsManager::TimedEventsManager() -{ -} - -TimedEventsManager::~TimedEventsManager() -{ -} - -void TimedEventsManager::add_event(TimedEvent&& event) -{ - for (auto it = this->events.begin(); it != this->events.end(); ++it) - { - if (it->is_after(event)) - { - this->events.emplace(it, std::move(event)); - return; - } - } - this->events.emplace_back(std::move(event)); -} - -std::chrono::milliseconds TimedEventsManager::get_timeout() const -{ - if (this->events.empty()) - return utils::no_timeout; - return this->events.front().get_timeout() + std::chrono::milliseconds(1); -} - -std::size_t TimedEventsManager::execute_expired_events() -{ - std::size_t count = 0; - const auto now = std::chrono::steady_clock::now(); - for (auto it = this->events.begin(); it != this->events.end();) - { - if (!it->is_after(now)) - { - TimedEvent copy(std::move(*it)); - it = this->events.erase(it); - ++count; - copy.execute(); - if (copy.repeat) - { - copy.time_point += copy.repeat_delay; - this->add_event(std::move(copy)); - } - continue; - } - else - break; - } - return count; -} - -std::size_t TimedEventsManager::cancel(const std::string& name) -{ - std::size_t res = 0; - for (auto it = this->events.begin(); it != this->events.end();) - { - if (it->get_name() == name) - { - it = this->events.erase(it); - res++; - } - else - ++it; - } - return res; -} - -std::size_t TimedEventsManager::size() const -{ - return this->events.size(); -} diff --git a/src/utils/tolower.cpp b/src/utils/tolower.cpp deleted file mode 100644 index 3e518bd..0000000 --- a/src/utils/tolower.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include - -namespace utils -{ - std::string tolower(const std::string& original) - { - std::string res; - res.reserve(original.size()); - for (const char c: original) - res += static_cast(std::tolower(c)); - return res; - } -} diff --git a/src/utils/tolower.hpp b/src/utils/tolower.hpp deleted file mode 100644 index 0019182..0000000 --- a/src/utils/tolower.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef TOLOWER_INCLUDED -# define TOLOWER_INCLUDED - -#include - -namespace utils -{ - std::string tolower(const std::string& original); -} - -#endif // SPLIT_INCLUDED diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index c4e8a44..ba20eba 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include @@ -98,6 +98,8 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { + auto biboumi_component = static_cast(xmpp_component); + XmlNode x("jabber:x:data:x"); x["type"] = "form"; XmlNode title("title"); @@ -115,7 +117,7 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& XmlNode required("required"); required.close(); jids_field.add_child(std::move(required)); - for (Bridge* bridge: xmpp_component->get_bridges()) + for (Bridge* bridge: biboumi_component->get_bridges()) { XmlNode option("option"); option["label"] = bridge->get_jid(); @@ -145,6 +147,8 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) { + auto biboumi_component = static_cast(xmpp_component); + // Find out if the jids, and the quit message are provided in the form. std::string quit_message; XmlNode* x = command_node.get_child("x", "jabber:x:data"); @@ -168,7 +172,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X std::size_t num = 0; for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) { - Bridge* bridge = xmpp_component->find_user_bridge(value->get_inner()); + Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); if (bridge) { bridge->shutdown(quit_message); diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp deleted file mode 100644 index def1dcb..0000000 --- a/src/xmpp/adhoc_commands_handler.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include - -#include -#include -#include -#include - -#include - -using namespace std::string_literals; - -AdhocCommandsHandler::AdhocCommandsHandler(XmppComponent* xmpp_component): - xmpp_component(xmpp_component), - commands{ - {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, - {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, - {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} - } -{ -} - -AdhocCommandsHandler::~AdhocCommandsHandler() -{ -} - -const std::map& AdhocCommandsHandler::get_commands() const -{ - return this->commands; -} - -XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) -{ - std::string action = command_node.get_tag("action"); - if (action.empty()) - action = "execute"; - command_node.del_tag("action"); - - Jid jid(executor_jid); - - const std::string node = command_node.get_tag("node"); - auto command_it = this->commands.find(node); - if (command_it == this->commands.end()) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "cancel"; - XmlNode condition(STANZA_NS":item-not-found"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else if (command_it->second.is_admin_only() && - Config::get("admin", "") != jid.local + "@" + jid.domain) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "cancel"; - XmlNode condition(STANZA_NS":forbidden"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else - { - std::string sessionid = command_node.get_tag("sessionid"); - if (sessionid.empty()) - { // create a new session, with a new id - sessionid = XmppComponent::next_id(); - command_node["sessionid"] = sessionid; - this->sessions.emplace(std::piecewise_construct, - std::forward_as_tuple(sessionid, executor_jid), - std::forward_as_tuple(command_it->second, executor_jid)); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, - std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), - "adhocsession"s + sessionid + executor_jid)); - } - auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); - if (session_it == this->sessions.end()) - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - else if (action == "execute" || action == "next" || action == "complete") - { - // execute the step - AdhocSession& session = session_it->second; - const AdhocStep& step = session.get_next_step(); - step(this->xmpp_component, session, command_node); - if (session.remaining_steps() == 0 || - session.is_terminated()) - { - this->sessions.erase(session_it); - command_node["status"] = "completed"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else - { - command_node["status"] = "executing"; - XmlNode actions("actions"); - XmlNode next("next"); - next.close(); - actions.add_child(std::move(next)); - actions.close(); - command_node.add_child(std::move(actions)); - } - } - else if (action == "cancel") - { - this->sessions.erase(session_it); - command_node["status"] = "canceled"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); - } - else // unsupported action - { - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - } - } - return command_node; -} - -void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) -{ - auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid)); - if (session_it != this->sessions.end()) - { - this->sessions.erase(session_it); - return ; - } - log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found"); -} diff --git a/src/xmpp/adhoc_commands_handler.hpp b/src/xmpp/adhoc_commands_handler.hpp deleted file mode 100644 index 7ddad47..0000000 --- a/src/xmpp/adhoc_commands_handler.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef ADHOC_COMMANDS_HANDLER_HPP -# define ADHOC_COMMANDS_HANDLER_HPP - -/** - * Manage a list of available AdhocCommands and the list of ongoing - * AdhocCommandSessions. - */ - -#include -#include - -#include -#include -#include - -class XmppComponent; - -class AdhocCommandsHandler -{ -public: - explicit AdhocCommandsHandler(XmppComponent* xmpp_component); - ~AdhocCommandsHandler(); - /** - * Returns the list of available commands. - */ - const std::map& get_commands() const; - /** - * 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 - * result). - * - * Returns a (moved) XmlNode that will be inserted in the iq response. It - * should be a node containing one or more useful children. If - * it contains an node, the iq response will have an error type. - * - * Takes a copy of the node so we can actually edit it and use - * it as our return value. - */ - XmlNode handle_request(const std::string& executor_jid, XmlNode command_node); - /** - * Remove the session from the list. This is done to avoid filling the - * memory with waiting session (for example due to a client that starts - * multi-steps command but never finishes them). - */ - void remove_session(const std::string& session_id, const std::string& initiator_jid); -private: - /** - * A pointer to the XmppComponent, to access to basically anything in the - * gateway. - */ - XmppComponent* xmpp_component; - /** - * The list of all available commands. - */ - const std::map commands; - /** - * The list of all currently on-going commands. - * - * Of the form: {{session_id, owner_jid}, session}. - */ - std::map, AdhocSession> sessions; - - AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; - AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; -}; - -#endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/src/xmpp/adhoc_session.cpp b/src/xmpp/adhoc_session.cpp deleted file mode 100644 index fc60bb7..0000000 --- a/src/xmpp/adhoc_session.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include - -#include - -AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid): - command(command), - owner_jid(jid), - current_step(0), - terminated(false) -{ -} - -AdhocSession::~AdhocSession() -{ -} - -const AdhocStep& AdhocSession::get_next_step() -{ - assert(this->current_step < this->command.callbacks.size()); - return this->command.callbacks[this->current_step++]; -} - -size_t AdhocSession::remaining_steps() const -{ - return this->command.callbacks.size() - this->current_step; -} - -bool AdhocSession::is_terminated() const -{ - return this->terminated; -} - -void AdhocSession::terminate() -{ - this->terminated = true; -} diff --git a/src/xmpp/adhoc_session.hpp b/src/xmpp/adhoc_session.hpp deleted file mode 100644 index ddfb2fe..0000000 --- a/src/xmpp/adhoc_session.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ADHOC_SESSION_HPP -# define ADHOC_SESSION_HPP - -#include - -#include -#include - -class XmppComponent; - -class AdhocCommand; -class AdhocSession; - -/** - * A function executed as an ad-hoc command step. It takes a - * XmlNode and modifies it accordingly (inserting for example an - * node, or a data form…). - * TODO fix this: - * It also must call one of step_passed(), cancel() etc on the AdhocSession object. - */ -typedef std::function AdhocStep; - -class AdhocSession -{ -public: - explicit AdhocSession(const AdhocCommand& command, const std::string& jid); - ~AdhocSession(); - /** - * Return the function to be executed, found in our AdhocCommand, for the - * current_step. And increment the current_step. - */ - const AdhocStep& get_next_step(); - /** - * Return the number of remaining steps. - */ - size_t remaining_steps() const; - /** - * This may be modified by an AdhocStep, to indicate that this session - * should no longer exist, because we encountered an error, and we can't - * execute any more step of it. - */ - void terminate(); - bool is_terminated() const; - -private: - /** - * A reference of the command concerned by this session. Used for example - * to get the next step of that command, things like that. - */ - const AdhocCommand& command; - /** - * The full JID of the XMPP user that created this session by executing - * the first step of a command. Only that JID must be allowed to access - * this session. - */ - const std::string& owner_jid; - /** - * The current step we are at. It starts at zero. It is used to index the - * associated AdhocCommand::callbacks vector. - */ - size_t current_step; - bool terminated; - - AdhocSession(const AdhocSession&) = delete; - AdhocSession(AdhocSession&&) = delete; - AdhocSession& operator=(const AdhocSession&) = delete; - AdhocSession& operator=(AdhocSession&&) = delete; -}; - -#endif // ADHOC_SESSION_HPP diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp new file mode 100644 index 0000000..2ecf247 --- /dev/null +++ b/src/xmpp/biboumi_component.cpp @@ -0,0 +1,568 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#ifdef SYSTEMD_FOUND +# include +#endif + +using namespace std::string_literals; + +static std::set kickable_errors{ + "gone", + "internal-server-error", + "item-not-found", + "jid-malformed", + "recipient-unavailable", + "redirect", + "remote-server-not-found", + "remote-server-timeout", + "service-unavailable", + "malformed-error" + }; + + +BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): + XmppComponent(poller, hostname, secret) +{ + this->stanza_handlers.emplace("presence", + std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1)); + this->stanza_handlers.emplace("message", + std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1)); + this->stanza_handlers.emplace("iq", + std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1)); + + this->adhoc_commands_handler.get_commands()= { + {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, + {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, + {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, + {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} + }; +} + +void BiboumiComponent::shutdown() +{ + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + { + it->second->shutdown("Gateway shutdown"); + } +} + +void BiboumiComponent::clean() +{ + auto it = this->bridges.begin(); + while (it != this->bridges.end()) + { + it->second->clean(); + if (it->second->active_clients() == 0) + it = this->bridges.erase(it); + else + ++it; + } +} + +void BiboumiComponent::handle_presence(const Stanza& stanza) +{ + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + // Check for mandatory tags + if (from.empty()) + { + log_warning("Received an invalid presence stanza: tag 'from' is missing."); + return; + } + if (to_str.empty()) + { + this->send_stanza_error("presence", from, this->served_hostname, id, + "modify", "bad-request", "Missing 'to' tag"); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + Iid iid(to.local); + + // An error stanza is sent whenever we exit this function without + // disabling this scopeguard. If error_type and error_name are not + // changed, the error signaled is internal-server-error. Change their + // value to signal an other kind of error. For example + // feature-not-implemented, etc. Any non-error process should reach the + // stanza_error.disable() call at the end of the function. + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("presence", from, to_str, id, + error_type, error_name, ""); + }); + + if (iid.is_channel && !iid.get_server().empty()) + { // presence toward a MUC that corresponds to an irc channel, or a + // dummy channel if iid.chan is empty + if (type.empty()) + { + const std::string own_nick = bridge->get_own_nick(iid); + if (!own_nick.empty() && own_nick != to.resource) + bridge->send_irc_nick_change(iid, to.resource); + XmlNode* x = stanza.get_child("x", MUC_NS); + XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; + bridge->join_irc_channel(iid, to.resource, + password ? password->get_inner() : ""); + } + else if (type == "unavailable") + { + XmlNode* status = stanza.get_child("status", COMPONENT_NS); + bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); + } + } + else + { + // An user wants to join an invalid IRC channel, return a presence error to him + if (type.empty()) + this->send_invalid_room_error(to.local, to.resource, from); + } + stanza_error.disable(); +} + +void BiboumiComponent::handle_message(const Stanza& stanza) +{ + std::string from = stanza.get_tag("from"); + std::string id = stanza.get_tag("id"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + if (from.empty()) + return; + if (type.empty()) + type = "normal"; + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + Iid iid(to.local); + + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("message", from, to_str, id, + error_type, error_name, ""); + }); + XmlNode* body = stanza.get_child("body", COMPONENT_NS); + if (type == "groupchat" && iid.is_channel) + { + if (body && !body->get_inner().empty()) + { + bridge->send_channel_message(iid, body->get_inner()); + } + XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); + if (subject) + bridge->set_channel_topic(iid, subject->get_inner()); + } + else if (type == "error") + { + const XmlNode* error = stanza.get_child("error", COMPONENT_NS); + // Only a set of errors are considered “fatal”. If we encounter one of + // them, we purge (we disconnect the user from all the IRC servers). + // We consider this to be true, unless the error condition is + // specified and is not in the kickable_errors set + bool kickable_error = true; + if (error && error->has_children()) + { + const XmlNode* condition = error->get_last_child(); + if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) + kickable_error = false; + } + if (kickable_error) + bridge->shutdown("Error from remote client"); + } + else if (type == "chat") + { + if (body && !body->get_inner().empty()) + { + // a message for nick!server + if (iid.is_user && !iid.get_local().empty()) + { + bridge->send_private_message(iid, body->get_inner()); + bridge->remove_preferred_from_jid(iid.get_local()); + } + else if (!iid.is_user && !to.resource.empty()) + { // a message for chan%server@biboumi/Nick or + // server@biboumi/Nick + // Convert that into a message to nick!server + Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); + bridge->send_private_message(user_iid, body->get_inner()); + bridge->set_preferred_from_jid(user_iid.get_local(), to_str); + } + } + } + else if (iid.is_user) + this->send_invalid_user_error(to.local, from); + stanza_error.disable(); +} + +// We MUST return an iq, whatever happens, except if the type is +// "result". +// To do this, we use a scopeguard. If an exception is raised somewhere, an +// iq of type error "internal-server-error" is sent. If we handle the +// request properly (by calling a function that registers an iq to be sent +// later, or that directly sends an iq), we disable the ScopeGuard. If we +// reach the end of the function without having disabled the scopeguard, we +// send a "feature-not-implemented" iq as a result. If an other kind of +// error is found (for example the feature is implemented in biboumi, but +// the request is missing some attribute) we can just change the values of +// error_type and error_name and return from the function (without disabling +// the scopeguard); an iq error will be sent +void BiboumiComponent::handle_iq(const Stanza& stanza) +{ + std::string id = stanza.get_tag("id"); + std::string from = stanza.get_tag("from"); + std::string to_str = stanza.get_tag("to"); + std::string type = stanza.get_tag("type"); + + if (from.empty()) + return; + if (id.empty() || to_str.empty() || type.empty()) + { + this->send_stanza_error("iq", from, this->served_hostname, id, + "modify", "bad-request", ""); + return; + } + + Bridge* bridge = this->get_user_bridge(from); + Jid to(to_str); + + // These two values will be used in the error iq sent if we don't disable + // the scopeguard. + std::string error_type("cancel"); + std::string error_name("internal-server-error"); + utils::ScopeGuard stanza_error([&](){ + this->send_stanza_error("iq", from, to_str, id, + error_type, error_name, ""); + }); + if (type == "set") + { + XmlNode* query; + if ((query = stanza.get_child("query", MUC_ADMIN_NS))) + { + const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); + if (child) + { + std::string nick = child->get_tag("nick"); + std::string role = child->get_tag("role"); + std::string affiliation = child->get_tag("affiliation"); + if (!nick.empty()) + { + Iid iid(to.local); + if (role == "none") + { // This is a kick + std::string reason; + XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); + if (reason_el) + reason = reason_el->get_inner(); + bridge->send_irc_kick(iid, nick, reason, id, from); + } + else + bridge->forward_affiliation_role_change(iid, nick, affiliation, role); + stanza_error.disable(); + } + } + } + else if ((query = stanza.get_child("command", ADHOC_NS))) + { + Stanza response("iq"); + response["to"] = from; + response["from"] = this->served_hostname; + response["id"] = id; + XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); + if (inner_node.get_child("error", ADHOC_NS)) + response["type"] = "error"; + else + response["type"] = "result"; + response.add_child(std::move(inner_node)); + response.close(); + this->send_stanza(response); + stanza_error.disable(); + } + } + else if (type == "get") + { + XmlNode* query; + if ((query = stanza.get_child("query", DISCO_INFO_NS))) + { // Disco info + if (to_str == this->served_hostname) + { + const std::string node = query->get_tag("node"); + if (node.empty()) + { + // On the gateway itself + this->send_self_disco_info(id, from); + stanza_error.disable(); + } + } + } + else if ((query = stanza.get_child("query", VERSION_NS))) + { + Iid iid(to.local); + if (iid.is_user || + (iid.is_channel && !to.resource.empty())) + { + // Get the IRC user version + std::string target; + if (iid.is_user) + target = iid.get_local(); + else + target = to.resource; + bridge->send_irc_version_request(iid.get_server(), target, id, + from, to_str); + } + else + { + // On the gateway itself or on a channel + this->send_version(id, from, to_str); + } + stanza_error.disable(); + } + else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) + { + Iid iid(to.local); + const std::string node = query->get_tag("node"); + if (node == ADHOC_NS) + { + this->send_adhoc_commands_list(id, from); + stanza_error.disable(); + } + else if (node.empty() && !iid.is_user && !iid.is_channel) + { // Disco on an IRC server: get the list of channels + bridge->send_irc_channel_list_request(iid, id, from); + stanza_error.disable(); + } + } + else if ((query = stanza.get_child("ping", PING_NS))) + { + Iid iid(to.local); + if (iid.is_user) + { // Ping any user (no check on the nick done ourself) + bridge->send_irc_user_ping_request(iid.get_server(), + iid.get_local(), id, from, to_str); + } + else if (iid.is_channel && !to.resource.empty()) + { // Ping a room participant (we check if the nick is in the room) + bridge->send_irc_participant_ping_request(iid, + to.resource, id, from, to_str); + } + else + { // Ping a channel, a server or the gateway itself + bridge->on_gateway_ping(iid.get_server(), + id, from, to_str); + } + stanza_error.disable(); + } + } + else if (type == "result") + { + stanza_error.disable(); + XmlNode* query; + if ((query = stanza.get_child("query", VERSION_NS))) + { + XmlNode* name_node = query->get_child("name", VERSION_NS); + XmlNode* version_node = query->get_child("version", VERSION_NS); + XmlNode* os_node = query->get_child("os", VERSION_NS); + std::string name; + std::string version; + std::string os; + if (name_node) + name = name_node->get_inner() + " (through the biboumi gateway)"; + if (version_node) + version = version_node->get_inner(); + if (os_node) + os = os_node->get_inner(); + const Iid iid(to.local); + bridge->send_xmpp_version_to_irc(iid, name, version, os); + } + else + { + const auto it = this->waiting_iq.find(id); + if (it != this->waiting_iq.end()) + { + it->second(bridge, stanza); + this->waiting_iq.erase(it); + } + } + } + error_type = "cancel"; + error_name = "feature-not-implemented"; +} + +Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + this->bridges.emplace(user_jid, std::make_unique(user_jid, this, this->poller)); + return this->bridges.at(user_jid).get(); + } +} + +Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid) +{ + try + { + return this->bridges.at(user_jid).get(); + } + catch (const std::out_of_range& exception) + { + return nullptr; + } +} + +std::list BiboumiComponent::get_bridges() const +{ + std::list res; + for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) + res.push_back(it->second.get()); + return res; +} + +void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_INFO_NS; + XmlNode identity("identity"); + identity["category"] = "conference"; + identity["type"] = "irc"; + identity["name"] = "Biboumi XMPP-IRC gateway"; + identity.close(); + query.add_child(std::move(identity)); + for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) + { + XmlNode feature("feature"); + feature["var"] = ns; + feature.close(); + query.add_child(std::move(feature)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = requester_jid; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + query["node"] = ADHOC_NS; + for (const auto& kv: this->adhoc_commands_handler.get_commands()) + { + XmlNode item("item"); + item["jid"] = this->served_hostname; + item["node"] = kv.first; + item["name"] = kv.second.name; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_iq_version_request(const std::string& from, + const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = "version_"s + this->next_id(); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void BiboumiComponent::send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = id; + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode ping("ping"); + ping["xmlns"] = PING_NS; + ping.close(); + iq.add_child(std::move(ping)); + iq.close(); + this->send_stanza(iq); + + auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) + { + Jid to(stanza.get_tag("to")); + if (to.local != from) + { + log_error("Received a corresponding ping result, but the 'to' from " + "the response mismatches the 'from' of the request"); + } + else + bridge->send_irc_ping_result(from, id); + }; + this->waiting_iq[id] = result_cb; +} + +void BiboumiComponent::send_iq_room_list_result(const std::string& id, + const std::string to_jid, + const std::string& from, + const std::vector& rooms_list) +{ + Stanza iq("iq"); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + for (const auto& room: rooms_list) + { + XmlNode item("item"); + item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp new file mode 100644 index 0000000..e9b5d72 --- /dev/null +++ b/src/xmpp/biboumi_component.hpp @@ -0,0 +1,111 @@ +#ifndef BIBOUMI_COMPONENT_INCLUDED +# define BIBOUMI_COMPONENT_INCLUDED + +#include + +#include + +#include +#include +#include + +class ListElement; + +/** + * A callback called when the waited iq result is received (it is matched + * against the iq id) + */ +using iq_responder_callback_t = std::function; + +/** + * Interact with the Biboumi Bridge + */ +class BiboumiComponent: public XmppComponent +{ +public: + explicit BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); + ~BiboumiComponent() = default; + + /** + * Returns the bridge for the given user. If it does not exist, return + * nullptr. + */ + Bridge* find_user_bridge(const std::string& user_jid); + /** + * Return a list of all the managed bridges. + */ + std::list get_bridges() const; + + /** + * Send a "close" message to all our connected peers. That message + * depends on the protocol used (this may be a QUIT irc message, or a + * , etc). We may also directly close the connection, or we may + * wait for the remote peer to acknowledge it before closing. + */ + void shutdown(); + /** + * Run a check on all bridges, to remove all disconnected (socket is + * closed, or no channel is joined) IrcClients. Some kind of garbage collector. + */ + void clean(); + /** + * Send a result IQ with the gateway disco informations. + */ + void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send the list of all available ad-hoc commands to that JID. The list is + * different depending on what JID made the request. + */ + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); + /** + * Send an iq version request + */ + void send_iq_version_request(const std::string& from, + const std::string& jid_to); + /** + * Send a ping request + */ + void send_ping_request(const std::string& from, + const std::string& jid_to, + const std::string& id); + /** + * Send the channels list in one big stanza + */ + void send_iq_room_list_result(const std::string& id, const std::string to_jid, + const std::string& from, + const std::vector& rooms_list); + /** + * Handle the various stanza types + */ + void handle_presence(const Stanza& stanza); + void handle_message(const Stanza& stanza); + void handle_iq(const Stanza& stanza); + +private: + /** + * Return the bridge associated with the given full JID. Create a new one + * if none already exist. + */ + Bridge* get_user_bridge(const std::string& user_jid); + + /** + * A map of id -> callback. When we want to wait for an iq result, we add + * the callback to this map, with the iq id as the key. When an iq result + * is received, we look for a corresponding callback in this map. If + * found, we call it and remove it. + */ + std::map waiting_iq; + + /** + * One bridge for each user of the component. Indexed by the user's full + * jid + */ + std::unordered_map> bridges; + + BiboumiComponent(const BiboumiComponent&) = delete; + BiboumiComponent(BiboumiComponent&&) = delete; + BiboumiComponent& operator=(const BiboumiComponent&) = delete; + BiboumiComponent& operator=(BiboumiComponent&&) = delete; +}; + +#endif // BIBOUMI_COMPONENT_INCLUDED diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp index c51e011..6149ceb 100644 --- a/src/xmpp/jid.cpp +++ b/src/xmpp/jid.cpp @@ -1,8 +1,8 @@ #include -#include #include #include +#include #ifdef LIBIDN_FOUND #include #endif diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp deleted file mode 100644 index 0e2531d..0000000 --- a/src/xmpp/xmpp_component.cpp +++ /dev/null @@ -1,1194 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include - -#include - -#ifdef SYSTEMD_FOUND -# include -#endif - -using namespace std::string_literals; - -static std::set kickable_errors{ - "gone", - "internal-server-error", - "item-not-found", - "jid-malformed", - "recipient-unavailable", - "redirect", - "remote-server-not-found", - "remote-server-timeout", - "service-unavailable", - "malformed-error" - }; - -XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): - TCPSocketHandler(poller), - ever_auth(false), - first_connection_try(true), - served_hostname(hostname), - secret(secret), - authenticated(false), - doc_open(false), - adhoc_commands_handler(this) -{ - this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, - std::placeholders::_1)); - this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this, - std::placeholders::_1)); - this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, - std::placeholders::_1)); - this->stanza_handlers.emplace("handshake", - std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); - this->stanza_handlers.emplace("presence", - std::bind(&XmppComponent::handle_presence, this,std::placeholders::_1)); - this->stanza_handlers.emplace("message", - std::bind(&XmppComponent::handle_message, this,std::placeholders::_1)); - this->stanza_handlers.emplace("iq", - std::bind(&XmppComponent::handle_iq, this,std::placeholders::_1)); - this->stanza_handlers.emplace("error", - std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); -} - -XmppComponent::~XmppComponent() -{ -} - -void XmppComponent::start() -{ - this->connect("localhost", Config::get("port", "5347"), false); -} - -bool XmppComponent::is_document_open() const -{ - return this->doc_open; -} - -void XmppComponent::send_stanza(const Stanza& stanza) -{ - std::string str = stanza.to_string(); - log_debug("XMPP SENDING: " << str); - this->send_data(std::move(str)); -} - -void XmppComponent::on_connection_failed(const std::string& reason) -{ - this->first_connection_try = false; - log_error("Failed to connect to the XMPP server: " << reason); -#ifdef SYSTEMD_FOUND - sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); -#endif -} - -void XmppComponent::on_connected() -{ - log_info("connected to XMPP server"); - this->first_connection_try = true; - XmlNode node("", nullptr); - node.set_name("stream:stream"); - node["xmlns"] = COMPONENT_NS; - node["xmlns:stream"] = STREAM_NS; - node["to"] = this->served_hostname; - this->send_stanza(node); - this->doc_open = true; - // We may have some pending data to send: this happens when we try to send - // some data before we are actually connected. We send that data right now, if any - this->send_pending_data(); -} - -void XmppComponent::on_connection_close(const std::string& error) -{ - if (error.empty()) - { - log_info("XMPP server closed connection"); - } - else - { - log_info("XMPP server closed connection: " << error); - } -} - -void XmppComponent::parse_in_buffer(const size_t size) -{ - if (!this->in_buf.empty()) - { // This may happen if the parser could not allocate enough space for - // us. We try to feed it the data that was read into our in_buf - // instead. If this fails again we are in trouble. - this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); - this->in_buf.clear(); - } - else - { // Just tell the parser to parse the data that was placed into the - // buffer it provided to us with GetBuffer - this->parser.parse(size, false); - } -} - -void XmppComponent::shutdown() -{ - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - { - it->second->shutdown("Gateway shutdown"); - } -} - -void XmppComponent::clean() -{ - auto it = this->bridges.begin(); - while (it != this->bridges.end()) - { - it->second->clean(); - if (it->second->active_clients() == 0) - it = this->bridges.erase(it); - else - ++it; - } -} - -void XmppComponent::on_remote_stream_open(const XmlNode& node) -{ - log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); - this->stream_id = node.get_tag("id"); - if (this->stream_id.empty()) - { - log_error("Error: no attribute 'id' found"); - this->send_stream_error("bad-format", "missing 'id' attribute"); - this->close_document(); - return ; - } - - // 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'; - - Stanza handshake(COMPONENT_NS":handshake"); - handshake.set_inner(digest); - handshake.close(); - this->send_stanza(handshake); -} - -void XmppComponent::on_remote_stream_close(const XmlNode& node) -{ - log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); - this->doc_open = false; -} - -void XmppComponent::reset() -{ - this->parser.reset(); -} - -void XmppComponent::on_stanza(const Stanza& stanza) -{ - log_debug("XMPP RECEIVING: " << stanza.to_string()); - std::function handler; - try - { - handler = this->stanza_handlers.at(stanza.get_name()); - } - catch (const std::out_of_range& exception) - { - log_warning("No handler for stanza of type " << stanza.get_name()); - return; - } - handler(stanza); -} - -void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) -{ - XmlNode node("stream:error", nullptr); - XmlNode error(name, nullptr); - error["xmlns"] = STREAM_NS; - if (!explanation.empty()) - error.set_inner(explanation); - error.close(); - node.add_child(std::move(error)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid) -{ - Stanza node(kind); - if (!to.empty()) - node["to"] = to; - if (!from.empty()) - { - if (fulljid) - node["from"] = from; - else - node["from"] = from + "@" + this->served_hostname; - } - if (!id.empty()) - node["id"] = id; - node["type"] = "error"; - XmlNode error("error"); - error["type"] = error_type; - XmlNode inner_error(defined_condition); - inner_error["xmlns"] = STANZA_NS; - inner_error.close(); - error.add_child(std::move(inner_error)); - if (!text.empty()) - { - XmlNode text_node("text"); - text_node["xmlns"] = STANZA_NS; - text_node.set_inner(text); - text_node.close(); - error.add_child(std::move(text_node)); - } - error.close(); - node.add_child(std::move(error)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::close_document() -{ - log_debug("XMPP SENDING: "); - this->send_data(""); - this->doc_open = false; -} - -void XmppComponent::handle_handshake(const Stanza& stanza) -{ - (void)stanza; - this->authenticated = true; - this->ever_auth = true; - log_info("Authenticated with the XMPP server"); -#ifdef SYSTEMD_FOUND - sd_notify(0, "READY=1"); - // Install an event that sends a keepalive to systemd. If biboumi crashes - // or hangs for too long, systemd will restart it. - uint64_t usec; - if (sd_watchdog_enabled(0, &usec) > 0) - { - TimedEventsManager::instance().add_event(TimedEvent( - std::chrono::duration_cast(std::chrono::microseconds(usec / 2)), - []() { sd_notify(0, "WATCHDOG=1"); })); - } -#endif -} - -void XmppComponent::handle_presence(const Stanza& stanza) -{ - std::string from = stanza.get_tag("from"); - std::string id = stanza.get_tag("id"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - // Check for mandatory tags - if (from.empty()) - { - log_warning("Received an invalid presence stanza: tag 'from' is missing."); - return; - } - if (to_str.empty()) - { - this->send_stanza_error("presence", from, this->served_hostname, id, - "modify", "bad-request", "Missing 'to' tag"); - return; - } - - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - Iid iid(to.local); - - // An error stanza is sent whenever we exit this function without - // disabling this scopeguard. If error_type and error_name are not - // changed, the error signaled is internal-server-error. Change their - // value to signal an other kind of error. For example - // feature-not-implemented, etc. Any non-error process should reach the - // stanza_error.disable() call at the end of the function. - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("presence", from, to_str, id, - error_type, error_name, ""); - }); - - if (iid.is_channel && !iid.get_server().empty()) - { // presence toward a MUC that corresponds to an irc channel, or a - // dummy channel if iid.chan is empty - if (type.empty()) - { - const std::string own_nick = bridge->get_own_nick(iid); - if (!own_nick.empty() && own_nick != to.resource) - bridge->send_irc_nick_change(iid, to.resource); - XmlNode* x = stanza.get_child("x", MUC_NS); - XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; - bridge->join_irc_channel(iid, to.resource, - password ? password->get_inner() : ""); - } - else if (type == "unavailable") - { - XmlNode* status = stanza.get_child("status", COMPONENT_NS); - bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); - } - } - else - { - // An user wants to join an invalid IRC channel, return a presence error to him - if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, from); - } - stanza_error.disable(); -} - -void XmppComponent::handle_message(const Stanza& stanza) -{ - std::string from = stanza.get_tag("from"); - std::string id = stanza.get_tag("id"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - if (from.empty()) - return; - if (type.empty()) - type = "normal"; - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - Iid iid(to.local); - - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("message", from, to_str, id, - error_type, error_name, ""); - }); - XmlNode* body = stanza.get_child("body", COMPONENT_NS); - if (type == "groupchat" && iid.is_channel) - { - if (body && !body->get_inner().empty()) - { - bridge->send_channel_message(iid, body->get_inner()); - } - XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); - if (subject) - bridge->set_channel_topic(iid, subject->get_inner()); - } - else if (type == "error") - { - const XmlNode* error = stanza.get_child("error", COMPONENT_NS); - // Only a set of errors are considered “fatal”. If we encounter one of - // them, we purge (we disconnect the user from all the IRC servers). - // We consider this to be true, unless the error condition is - // specified and is not in the kickable_errors set - bool kickable_error = true; - if (error && error->has_children()) - { - const XmlNode* condition = error->get_last_child(); - if (kickable_errors.find(condition->get_name()) == kickable_errors.end()) - kickable_error = false; - } - if (kickable_error) - bridge->shutdown("Error from remote client"); - } - else if (type == "chat") - { - if (body && !body->get_inner().empty()) - { - // a message for nick!server - if (iid.is_user && !iid.get_local().empty()) - { - bridge->send_private_message(iid, body->get_inner()); - bridge->remove_preferred_from_jid(iid.get_local()); - } - else if (!iid.is_user && !to.resource.empty()) - { // a message for chan%server@biboumi/Nick or - // server@biboumi/Nick - // Convert that into a message to nick!server - Iid user_iid(utils::tolower(to.resource) + "!" + iid.get_server()); - bridge->send_private_message(user_iid, body->get_inner()); - bridge->set_preferred_from_jid(user_iid.get_local(), to_str); - } - } - } - else if (iid.is_user) - this->send_invalid_user_error(to.local, from); - stanza_error.disable(); -} - -// We MUST return an iq, whatever happens, except if the type is -// "result". -// To do this, we use a scopeguard. If an exception is raised somewhere, an -// iq of type error "internal-server-error" is sent. If we handle the -// request properly (by calling a function that registers an iq to be sent -// later, or that directly sends an iq), we disable the ScopeGuard. If we -// reach the end of the function without having disabled the scopeguard, we -// send a "feature-not-implemented" iq as a result. If an other kind of -// error is found (for example the feature is implemented in biboumi, but -// the request is missing some attribute) we can just change the values of -// error_type and error_name and return from the function (without disabling -// the scopeguard); an iq error will be sent -void XmppComponent::handle_iq(const Stanza& stanza) -{ - std::string id = stanza.get_tag("id"); - std::string from = stanza.get_tag("from"); - std::string to_str = stanza.get_tag("to"); - std::string type = stanza.get_tag("type"); - - if (from.empty()) - return; - if (id.empty() || to_str.empty() || type.empty()) - { - this->send_stanza_error("iq", from, this->served_hostname, id, - "modify", "bad-request", ""); - return; - } - - Bridge* bridge = this->get_user_bridge(from); - Jid to(to_str); - - // These two values will be used in the error iq sent if we don't disable - // the scopeguard. - std::string error_type("cancel"); - std::string error_name("internal-server-error"); - utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("iq", from, to_str, id, - error_type, error_name, ""); - }); - if (type == "set") - { - XmlNode* query; - if ((query = stanza.get_child("query", MUC_ADMIN_NS))) - { - const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); - if (child) - { - std::string nick = child->get_tag("nick"); - std::string role = child->get_tag("role"); - std::string affiliation = child->get_tag("affiliation"); - if (!nick.empty()) - { - Iid iid(to.local); - if (role == "none") - { // This is a kick - std::string reason; - XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); - if (reason_el) - reason = reason_el->get_inner(); - bridge->send_irc_kick(iid, nick, reason, id, from); - } - else - bridge->forward_affiliation_role_change(iid, nick, affiliation, role); - stanza_error.disable(); - } - } - } - else if ((query = stanza.get_child("command", ADHOC_NS))) - { - Stanza response("iq"); - response["to"] = from; - response["from"] = this->served_hostname; - response["id"] = id; - XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); - if (inner_node.get_child("error", ADHOC_NS)) - response["type"] = "error"; - else - response["type"] = "result"; - response.add_child(std::move(inner_node)); - response.close(); - this->send_stanza(response); - stanza_error.disable(); - } - } - else if (type == "get") - { - XmlNode* query; - if ((query = stanza.get_child("query", DISCO_INFO_NS))) - { // Disco info - if (to_str == this->served_hostname) - { - const std::string node = query->get_tag("node"); - if (node.empty()) - { - // On the gateway itself - this->send_self_disco_info(id, from); - stanza_error.disable(); - } - } - } - else if ((query = stanza.get_child("query", VERSION_NS))) - { - Iid iid(to.local); - if (iid.is_user || - (iid.is_channel && !to.resource.empty())) - { - // Get the IRC user version - std::string target; - if (iid.is_user) - target = iid.get_local(); - else - target = to.resource; - bridge->send_irc_version_request(iid.get_server(), target, id, - from, to_str); - } - else - { - // On the gateway itself or on a channel - this->send_version(id, from, to_str); - } - stanza_error.disable(); - } - else if ((query = stanza.get_child("query", DISCO_ITEMS_NS))) - { - Iid iid(to.local); - const std::string node = query->get_tag("node"); - if (node == ADHOC_NS) - { - this->send_adhoc_commands_list(id, from); - stanza_error.disable(); - } - else if (node.empty() && !iid.is_user && !iid.is_channel) - { // Disco on an IRC server: get the list of channels - bridge->send_irc_channel_list_request(iid, id, from); - stanza_error.disable(); - } - } - else if ((query = stanza.get_child("ping", PING_NS))) - { - Iid iid(to.local); - if (iid.is_user) - { // Ping any user (no check on the nick done ourself) - bridge->send_irc_user_ping_request(iid.get_server(), - iid.get_local(), id, from, to_str); - } - else if (iid.is_channel && !to.resource.empty()) - { // Ping a room participant (we check if the nick is in the room) - bridge->send_irc_participant_ping_request(iid, - to.resource, id, from, to_str); - } - else - { // Ping a channel, a server or the gateway itself - bridge->on_gateway_ping(iid.get_server(), - id, from, to_str); - } - stanza_error.disable(); - } - } - else if (type == "result") - { - stanza_error.disable(); - XmlNode* query; - if ((query = stanza.get_child("query", VERSION_NS))) - { - XmlNode* name_node = query->get_child("name", VERSION_NS); - XmlNode* version_node = query->get_child("version", VERSION_NS); - XmlNode* os_node = query->get_child("os", VERSION_NS); - std::string name; - std::string version; - std::string os; - if (name_node) - name = name_node->get_inner() + " (through the biboumi gateway)"; - if (version_node) - version = version_node->get_inner(); - if (os_node) - os = os_node->get_inner(); - const Iid iid(to.local); - bridge->send_xmpp_version_to_irc(iid, name, version, os); - } - else - { - const auto it = this->waiting_iq.find(id); - if (it != this->waiting_iq.end()) - { - it->second(bridge, stanza); - this->waiting_iq.erase(it); - } - } - } - error_type = "cancel"; - error_name = "feature-not-implemented"; -} - -void XmppComponent::handle_error(const Stanza& stanza) -{ - XmlNode* text = stanza.get_child("text", STREAMS_NS); - std::string error_message("Unspecified error"); - if (text) - error_message = text->get_inner(); - log_error("Stream error received from the XMPP server: " << error_message); -#ifdef SYSTEMD_FOUND - if (!this->ever_auth) - sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); -#endif - -} - -Bridge* XmppComponent::get_user_bridge(const std::string& user_jid) -{ - try - { - return this->bridges.at(user_jid).get(); - } - catch (const std::out_of_range& exception) - { - this->bridges.emplace(user_jid, std::make_unique(user_jid, this, this->poller)); - return this->bridges.at(user_jid).get(); - } -} - -Bridge* XmppComponent::find_user_bridge(const std::string& user_jid) -{ - try - { - return this->bridges.at(user_jid).get(); - } - catch (const std::out_of_range& exception) - { - return nullptr; - } -} - -std::list XmppComponent::get_bridges() const -{ - std::list res; - for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) - res.push_back(it->second.get()); - return res; -} - -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) -{ - XmlNode node("message"); - node["to"] = to; - if (fulljid) - node["from"] = from; - else - node["from"] = from + "@" + this->served_hostname; - if (!type.empty()) - node["type"] = type; - XmlNode body_node("body"); - body_node.set_inner(std::get<0>(body)); - body_node.close(); - node.add_child(std::move(body_node)); - if (std::get<1>(body)) - { - XmlNode html("html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(body).release()); - html.close(); - node.add_child(std::move(html)); - } - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self) -{ - XmlNode node("presence"); - node["to"] = to; - node["from"] = from + "@" + this->served_hostname + "/" + nick; - - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - - XmlNode item("item"); - if (!affiliation.empty()) - item["affiliation"] = affiliation; - if (!role.empty()) - item["role"] = role; - if (!realjid.empty()) - { - const std::string preped_jid = jidprep(realjid); - if (!preped_jid.empty()) - item["jid"] = preped_jid; - } - item.close(); - x.add_child(std::move(item)); - - if (self) - { - XmlNode status("status"); - status["code"] = "110"; - status.close(); - x.add_child(std::move(status)); - } - x.close(); - node.add_child(std::move(x)); - node.close(); - this->send_stanza(node); -} - -void XmppComponent::send_invalid_room_error(const std::string& muc_name, - const std::string& nick, - const std::string& to) -{ - Stanza presence("presence"); - if (!muc_name.empty()) - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else - presence["from"] = this->served_hostname; - presence["to"] = to; - presence["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - presence.add_child(std::move(x)); - XmlNode error("error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "cancel"; - XmlNode item_not_found("item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - item_not_found.close(); - error.add_child(std::move(item_not_found)); - XmlNode text("text"); - text["xmlns"] = STANZA_NS; - text["xml:lang"] = "en"; - text.set_inner(muc_name + - " is not a valid IRC channel name. A correct room jid is of the form: #%@" + - this->served_hostname); - text.close(); - error.add_child(std::move(text)); - error.close(); - presence.add_child(std::move(error)); - presence.close(); - 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; - x.close(); - 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; - item_not_found.close(); - 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: !@" + - this->served_hostname); - text.close(); - error.add_child(std::move(text)); - error.close(); - message.add_child(std::move(error)); - message.close(); - this->send_stanza(message); -} - -void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) -{ - XmlNode message("message"); - message["to"] = to; - message["from"] = from + "@" + this->served_hostname; - message["type"] = "groupchat"; - XmlNode subject("subject"); - subject.set_inner(std::get<0>(topic)); - subject.close(); - message.add_child(std::move(subject)); - message.close(); - this->send_stanza(message); -} - -void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to) -{ - Stanza message("message"); - message["to"] = jid_to; - if (!nick.empty()) - message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - else // Message from the room itself - message["from"] = muc_name + "@" + this->served_hostname; - message["type"] = "groupchat"; - XmlNode body("body"); - body.set_inner(std::get<0>(xmpp_body)); - body.close(); - message.add_child(std::move(body)); - if (std::get<1>(xmpp_body)) - { - XmlNode html("html"); - html["xmlns"] = XHTMLIM_NS; - // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(xmpp_body).release()); - html.close(); - message.add_child(std::move(html)); - } - message.close(); - 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"); - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; - presence["type"] = "unavailable"; - const std::string message_str = std::get<0>(message); - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - if (self) - { - XmlNode status("status"); - status["code"] = "110"; - status.close(); - x.add_child(std::move(status)); - } - x.close(); - presence.add_child(std::move(x)); - if (!message_str.empty()) - { - XmlNode status("status"); - status.set_inner(message_str); - status.close(); - presence.add_child(std::move(status)); - } - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self) -{ - Stanza presence("presence"); - presence["to"] = jid_to; - presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick; - presence["type"] = "unavailable"; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["nick"] = new_nick; - item.close(); - x.add_child(std::move(item)); - XmlNode status("status"); - status["code"] = "303"; - status.close(); - x.add_child(std::move(status)); - if (self) - { - XmlNode status2("status"); - status2["code"] = "110"; - status2.close(); - x.add_child(std::move(status2)); - } - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); - - 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) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - presence["type"] = "unavailable"; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["affiliation"] = "none"; - item["role"] = "none"; - XmlNode actor("actor"); - actor["nick"] = author; - actor["jid"] = author; // backward compatibility with old clients - actor.close(); - item.add_child(std::move(actor)); - XmlNode reason("reason"); - reason.set_inner(txt); - reason.close(); - item.add_child(std::move(reason)); - item.close(); - x.add_child(std::move(item)); - XmlNode status("status"); - status["code"] = "307"; - status.close(); - x.add_child(std::move(status)); - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& /* text */) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; - presence["to"] = jid_to; - presence["type"] = "error"; - XmlNode x("x"); - x["xmlns"] = MUC_NS; - x.close(); - presence.add_child(std::move(x)); - XmlNode error("error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = type; - if (!error_code.empty()) - error["code"] = error_code; - XmlNode subnode(condition); - subnode["xmlns"] = STANZA_NS; - subnode.close(); - error.add_child(std::move(subnode)); - error.close(); - presence.add_child(std::move(error)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to) -{ - Stanza presence("presence"); - presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; - presence["to"] = jid_to; - XmlNode x("x"); - x["xmlns"] = MUC_USER_NS; - XmlNode item("item"); - item["affiliation"] = affiliation; - item["role"] = role; - item.close(); - x.add_child(std::move(item)); - x.close(); - presence.add_child(std::move(x)); - presence.close(); - this->send_stanza(presence); -} - -void XmppComponent::send_self_disco_info(const std::string& id, const std::string& jid_to) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = this->served_hostname; - XmlNode query("query"); - query["xmlns"] = DISCO_INFO_NS; - XmlNode identity("identity"); - identity["category"] = "conference"; - identity["type"] = "irc"; - identity["name"] = "Biboumi XMPP-IRC gateway"; - identity.close(); - query.add_child(std::move(identity)); - for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) - { - XmlNode feature("feature"); - feature["var"] = ns; - feature.close(); - query.add_child(std::move(feature)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = jid_to; - iq["from"] = jid_from; - XmlNode query("query"); - query["xmlns"] = VERSION_NS; - if (version.empty()) - { - XmlNode name("name"); - name.set_inner("biboumi"); - name.close(); - query.add_child(std::move(name)); - XmlNode version("version"); - version.set_inner(BIBOUMI_VERSION); - version.close(); - query.add_child(std::move(version)); - XmlNode os("os"); - os.set_inner(SYSTEM_NAME); - os.close(); - query.add_child(std::move(os)); - } - else - { - XmlNode name("name"); - name.set_inner(version); - name.close(); - query.add_child(std::move(name)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = requester_jid; - iq["from"] = this->served_hostname; - XmlNode query("query"); - query["xmlns"] = DISCO_ITEMS_NS; - query["node"] = ADHOC_NS; - for (const auto& kv: this->adhoc_commands_handler.get_commands()) - { - XmlNode item("item"); - item["jid"] = this->served_hostname; - item["node"] = kv.first; - item["name"] = kv.second.name; - item.close(); - query.add_child(std::move(item)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_iq_version_request(const std::string& from, - const std::string& jid_to) -{ - Stanza iq("iq"); - iq["type"] = "get"; - iq["id"] = "version_"s + XmppComponent::next_id(); - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = jid_to; - XmlNode query("query"); - query["xmlns"] = VERSION_NS; - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_ping_request(const std::string& from, - const std::string& jid_to, - const std::string& id) -{ - Stanza iq("iq"); - iq["type"] = "get"; - iq["id"] = id; - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = jid_to; - XmlNode ping("ping"); - ping["xmlns"] = PING_NS; - ping.close(); - iq.add_child(std::move(ping)); - iq.close(); - this->send_stanza(iq); - - auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) - { - Jid to(stanza.get_tag("to")); - if (to.local != from) - { - log_error("Received a corresponding ping result, but the 'to' from " - "the response mismatches the 'from' of the request"); - } - else - bridge->send_irc_ping_result(from, id); - }; - this->waiting_iq[id] = result_cb; -} - -void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) -{ - Stanza iq("iq"); - if (!from_local_part.empty()) - iq["from"] = from_local_part + "@" + this->served_hostname; - else - iq["from"] = this->served_hostname; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - iq.close(); - this->send_stanza(iq); -} - -void XmppComponent::send_iq_room_list_result(const std::string& id, - const std::string to_jid, - const std::string& from, - const std::vector& rooms_list) -{ - Stanza iq("iq"); - iq["from"] = from + "@" + this->served_hostname; - iq["to"] = to_jid; - iq["id"] = id; - iq["type"] = "result"; - XmlNode query("query"); - query["xmlns"] = DISCO_ITEMS_NS; - for (const auto& room: rooms_list) - { - XmlNode item("item"); - item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; - item.close(); - query.add_child(std::move(item)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - -std::string XmppComponent::next_id() -{ - char uuid_str[37]; - uuid_t uuid; - uuid_generate(uuid); - uuid_unparse(uuid, uuid_str); - return uuid_str; -} diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp deleted file mode 100644 index a0b06a6..0000000 --- a/src/xmpp/xmpp_component.hpp +++ /dev/null @@ -1,303 +0,0 @@ -#ifndef XMPP_COMPONENT_INCLUDED -# define XMPP_COMPONENT_INCLUDED - -#include -#include -#include -#include - -#include -#include -#include -#include - -#define STREAM_NS "http://etherx.jabber.org/streams" -#define COMPONENT_NS "jabber:component:accept" -#define MUC_NS "http://jabber.org/protocol/muc" -#define MUC_USER_NS MUC_NS"#user" -#define MUC_ADMIN_NS MUC_NS"#admin" -#define DISCO_NS "http://jabber.org/protocol/disco" -#define DISCO_ITEMS_NS DISCO_NS"#items" -#define DISCO_INFO_NS DISCO_NS"#info" -#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" -#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" -#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" -#define VERSION_NS "jabber:iq:version" -#define ADHOC_NS "http://jabber.org/protocol/commands" -#define PING_NS "urn:xmpp:ping" - -class ListElement; - -/** - * A callback called when the waited iq result is received (it is matched - * against the iq id) - */ -using iq_responder_callback_t = std::function; - -/** - * An XMPP component, communicating with an XMPP server using the protocole - * described in XEP-0114: Jabber Component Protocol - * - * TODO: implement XEP-0225: Component Connections - */ -class XmppComponent: public TCPSocketHandler -{ -public: - explicit XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); - ~XmppComponent(); - - void on_connection_failed(const std::string& reason) override final; - void on_connected() override final; - void on_connection_close(const std::string& error) override final; - void parse_in_buffer(const size_t size) override final; - - /** - * Returns the bridge for the given user. If it does not exist, return - * nullptr. - */ - Bridge* find_user_bridge(const std::string& user_jid); - /** - * Return a list of all the managed bridges. - */ - std::list get_bridges() const; - - /** - * Returns a unique id, to be used in the 'id' element of our iq stanzas. - */ - static std::string next_id(); - /** - * Send a "close" message to all our connected peers. That message - * depends on the protocol used (this may be a QUIT irc message, or a - * , etc). We may also directly close the connection, or we may - * wait for the remote peer to acknowledge it before closing. - */ - void shutdown(); - bool is_document_open() const; - /** - * Run a check on all bridges, to remove all disconnected (socket is - * closed, or no channel is joined) IrcClients. Some kind of garbage collector. - */ - void clean(); - /** - * Connect to the XMPP server. - */ - void start(); - /** - * Reset the component so we can use the component on a new XMPP stream - */ - void reset(); - /** - * Serialize the stanza and add it to the out_buf to be sent to the - * server. - */ - void send_stanza(const Stanza& stanza); - /** - * Handle the opening of the remote stream - */ - void on_remote_stream_open(const XmlNode& node); - /** - * Handle the closing of the remote stream - */ - void on_remote_stream_close(const XmlNode& node); - /** - * Handle received stanzas - */ - void on_stanza(const Stanza& stanza); - /** - * Send an error stanza. Message being the name of the element inside the - * stanza, and explanation being a short human-readable sentence - * describing the error. - */ - void send_stream_error(const std::string& message, const std::string& explanation); - /** - * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error - */ - void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, - const std::string& id, const std::string& error_type, - const std::string& defined_condition, const std::string& text, - const bool fulljid=true); - /** - * Send the closing signal for our document (not closing the connection though). - */ - void close_document(); - /** - * Send a message from from@served_hostname, with the given body - * - * 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); - /** - * Send a join from a new participant - */ - void send_user_join(const std::string& from, - const std::string& nick, - const std::string& realjid, - const std::string& affiliation, - const std::string& role, - const std::string& to, - const bool self); - /** - * Send an error to indicate that the user tried to join an invalid room - */ - void send_invalid_room_error(const std::string& muc_jid, - 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); - /** - * Send a (non-private) message to the MUC - */ - void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); - /** - * 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); - /** - * Indicate that a participant changed his nick - */ - void send_nick_change(const std::string& muc_name, - const std::string& old_nick, - const std::string& new_nick, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to, - const bool self); - /** - * 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); - /** - * Send a generic presence error - */ - void send_presence_error(const std::string& muc_name, - const std::string& nickname, - const std::string& jid_to, - const std::string& type, - const std::string& condition, - const std::string& error_code, - const std::string& text); - /** - * Send a presence from the MUC indicating a change in the role and/or - * affiliation of a participant - */ - void send_affiliation_role_change(const std::string& muc_name, - const std::string& target, - const std::string& affiliation, - const std::string& role, - const std::string& jid_to); - /** - * Send a result IQ with the gateway disco informations. - */ - void send_self_disco_info(const std::string& id, const std::string& jid_to); - /** - * Send a result IQ with the given version, or the gateway version if the - * passed string is empty. - */ - void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, - const std::string& version=""); - /** - * Send the list of all available ad-hoc commands to that JID. The list is - * different depending on what JID made the request. - */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); - /** - * Send an iq version request - */ - void send_iq_version_request(const std::string& from, - const std::string& jid_to); - /** - * Send a ping request - */ - void send_ping_request(const std::string& from, - const std::string& jid_to, - const std::string& id); - /** - * Send an empty iq of type result - */ - void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); - /** - * Send the channels list in one big stanza - */ - void send_iq_room_list_result(const std::string& id, const std::string to_jid, - const std::string& from, - const std::vector& rooms_list); - /** - * Handle the various stanza types - */ - void handle_handshake(const Stanza& stanza); - void handle_presence(const Stanza& stanza); - void handle_message(const Stanza& stanza); - void handle_iq(const Stanza& stanza); - void handle_error(const Stanza& stanza); - - /** - * Whether or not we ever succeeded our authentication to the XMPP server - */ - bool ever_auth; - /** - * Whether or not this is the first consecutive try on connecting to the - * XMPP server. We use this to delay the connection attempt for a few - * seconds, if it is not the first try. - */ - bool first_connection_try; - -private: - /** - * Return the bridge associated with the given full JID. Create a new one - * if none already exist. - */ - Bridge* get_user_bridge(const std::string& user_jid); - /** - * Return a buffer provided by the XML parser, to read data directly into - * it, and avoiding some unnecessary copy. - */ - void* get_receive_buffer(const size_t size) const override final; - XmppParser parser; - std::string stream_id; - std::string served_hostname; - std::string secret; - bool authenticated; - /** - * Whether or not OUR XMPP document is open - */ - bool doc_open; - - std::unordered_map> stanza_handlers; - AdhocCommandsHandler adhoc_commands_handler; - - /** - * A map of id -> callback. When we want to wait for an iq result, we add - * the callback to this map, with the iq id as the key. When an iq result - * is received, we look for a corresponding callback in this map. If - * found, we call it and remove it. - */ - std::map waiting_iq; - - /** - * One bridge for each user of the component. Indexed by the user's full - * jid - */ - std::unordered_map> bridges; - - XmppComponent(const XmppComponent&) = delete; - XmppComponent(XmppComponent&&) = delete; - XmppComponent& operator=(const XmppComponent&) = delete; - XmppComponent& operator=(XmppComponent&&) = delete; -}; - -#endif // XMPP_COMPONENT_INCLUDED -- cgit v1.2.3 From 0ec82c104ded01a44ed36d20e25220fa41887fd0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 12:18:34 +0100 Subject: Add louloulibs as a submodule --- .gitmodules | 3 +++ louloulibs | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 louloulibs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..aadcbca --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "louloulibs"] + path = louloulibs + url = git://git.louiz.org/louloulibs diff --git a/louloulibs b/louloulibs new file mode 160000 index 0000000..b6af145 --- /dev/null +++ b/louloulibs @@ -0,0 +1 @@ +Subproject commit b6af145bfb9561a1bb1ecb940f50163c5ce4dbbb -- cgit v1.2.3 From e6569a1090be063f34624474f0d4578f37a169ae Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 12:40:50 +0100 Subject: Only use include_directory() if the directory path is defined --- CMakeLists.txt | 16 ++++++++++++---- louloulibs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 808d9e4..a516589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,10 +74,18 @@ include_directories(${ICONV_INCLUDE_DIRS}) include_directories(${LIBUUID_INCLUDE_DIRS}) # If they are found in louloulibs CMakeLists.txt, we inherite these values -include_directories(${LIBIDN_INCLUDE_DIRS}) -include_directories(${SYSTEMD_INCLUDE_DIRS}) -include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) -include_directories(${CARES_INCLUDE_DIRS}) +if(LIBIDN_FOUND) + include_directories(${LIBIDN_INCLUDE_DIRS}) +endif() +if(SYSTEMD_FOUND) + include_directories(${SYSTEMD_INCLUDE_DIRS}) +endif() +if(BOTAN_FOUND) + include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) +endif() +if(CARES_FOUND) + include_directories(${CARES_INCLUDE_DIRS}) +endif() # ## irclib diff --git a/louloulibs b/louloulibs index b6af145..d6a3724 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit b6af145bfb9561a1bb1ecb940f50163c5ce4dbbb +Subproject commit d6a3724c6a0127a49a9e7adb1090bb7438c8d0f2 -- cgit v1.2.3 From b3503bf11297a7982bf9e9008b408e3bf8121224 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 12:46:22 +0100 Subject: Also check if the louloulibs/CMakeLists.txt file is missing Because apparently when cloning a repository with submodules, git creates empty sub-directories --- CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a516589..9d9f073 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,8 +63,10 @@ if(RONN_EXECUTABLE) add_custom_target(doc DEPENDS ${MAN_PAGE}) endif() -if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs") - message(FATAL_ERROR "louloulibs/ directory not found. Make sure you fetched the submodules with 'git submodule init && git submodule update'") +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs" OR + NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/loulou/CMakeLists.txt") + message(FATAL_ERROR "louloulibs/ not found or empty. Make sure you fetched + the submodules with 'git submodule init && git submodule update'") endif() add_subdirectory("louloulibs") include_directories("louloulibs") -- cgit v1.2.3 From cc6a2295c38cb1044d21cddf3c9049df1210e929 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 17:50:42 +0100 Subject: Fix a typo --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d9f073..297166b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ if(RONN_EXECUTABLE) endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs" OR - NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/loulou/CMakeLists.txt") + NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs/CMakeLists.txt") message(FATAL_ERROR "louloulibs/ not found or empty. Make sure you fetched the submodules with 'git submodule init && git submodule update'") endif() -- cgit v1.2.3 From 649ebaf1b90a10ddf6da07b28a9b9b7a74297588 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 27 Feb 2015 18:32:30 +0100 Subject: Suggest the command git submodule update --init --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 297166b..29d3c74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ endif() if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs" OR NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs/CMakeLists.txt") message(FATAL_ERROR "louloulibs/ not found or empty. Make sure you fetched - the submodules with 'git submodule init && git submodule update'") + the submodules with 'git submodule update --init'") endif() add_subdirectory("louloulibs") include_directories("louloulibs") -- cgit v1.2.3 From f0e07beacb1ca14822d5ad52a7b4462f15ee47cc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Mar 2015 10:21:01 +0100 Subject: Forgot to remove some XMPP files that are now in louloulibs instead --- CMakeLists.txt | 3 - src/xmpp/jid.cpp | 94 ---------------- src/xmpp/jid.hpp | 36 ------- src/xmpp/xmpp_parser.cpp | 169 ----------------------------- src/xmpp/xmpp_parser.hpp | 126 ---------------------- src/xmpp/xmpp_stanza.cpp | 274 ----------------------------------------------- src/xmpp/xmpp_stanza.hpp | 160 --------------------------- 7 files changed, 862 deletions(-) delete mode 100644 src/xmpp/jid.cpp delete mode 100644 src/xmpp/jid.hpp delete mode 100644 src/xmpp/xmpp_parser.cpp delete mode 100644 src/xmpp/xmpp_parser.hpp delete mode 100644 src/xmpp/xmpp_stanza.cpp delete mode 100644 src/xmpp/xmpp_stanza.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 29d3c74..617c113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,9 +76,6 @@ include_directories(${ICONV_INCLUDE_DIRS}) include_directories(${LIBUUID_INCLUDE_DIRS}) # If they are found in louloulibs CMakeLists.txt, we inherite these values -if(LIBIDN_FOUND) - include_directories(${LIBIDN_INCLUDE_DIRS}) -endif() if(SYSTEMD_FOUND) include_directories(${SYSTEMD_INCLUDE_DIRS}) endif() diff --git a/src/xmpp/jid.cpp b/src/xmpp/jid.cpp deleted file mode 100644 index 6149ceb..0000000 --- a/src/xmpp/jid.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include -#include - -#include -#ifdef LIBIDN_FOUND - #include -#endif - -#include - -Jid::Jid(const std::string& jid) -{ - std::string::size_type slash = jid.find('/'); - if (slash != std::string::npos) - { - this->resource = jid.substr(slash + 1); - } - - std::string::size_type at = jid.find('@'); - if (at != std::string::npos && at < slash) - { - this->local = jid.substr(0, at); - at++; - } - else - at = 0; - - this->domain = jid.substr(at, slash - at); -} - -#include - -static constexpr size_t max_jid_part_len = 1023; - -std::string jidprep(const std::string& original) -{ -#ifdef LIBIDN_FOUND - using CacheType = std::map; - static CacheType cache; - std::pair cached = cache.insert({original, {}}); - if (std::get<1>(cached) == false) - { // Insertion failed: the result is already in the cache, return it - return std::get<0>(cached)->second; - } - - const std::string error_msg("Failed to convert " + original + " into a valid JID:"); - Jid jid(original); - - char local[max_jid_part_len] = {}; - memcpy(local, jid.local.data(), jid.local.size()); - Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, - static_cast(0), stringprep_xmpp_nodeprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - - char domain[max_jid_part_len] = {}; - memcpy(domain, jid.domain.data(), jid.domain.size()); - rc = static_cast(::stringprep(domain, max_jid_part_len, - static_cast(0), stringprep_nameprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - - // If there is no resource, stop here - if (jid.resource.empty()) - { - std::get<0>(cached)->second = std::string(local) + "@" + domain; - return std::get<0>(cached)->second; - } - - // Otherwise, also process the resource part - char resource[max_jid_part_len] = {}; - memcpy(resource, jid.resource.data(), jid.resource.size()); - rc = static_cast(::stringprep(resource, max_jid_part_len, - static_cast(0), stringprep_xmpp_resourceprep)); - if (rc != STRINGPREP_OK) - { - log_error(error_msg + stringprep_strerror(rc)); - return ""; - } - std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource; - return std::get<0>(cached)->second; - -#else - (void)original; - return ""; -#endif -} diff --git a/src/xmpp/jid.hpp b/src/xmpp/jid.hpp deleted file mode 100644 index b6975a2..0000000 --- a/src/xmpp/jid.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef JID_INCLUDED -# define JID_INCLUDED - -#include - -/** - * Parse a JID into its different subart - */ -class Jid -{ -public: - explicit Jid(const std::string& jid); - - std::string domain; - std::string local; - std::string resource; - -private: - Jid(const Jid&) = delete; - Jid(Jid&&) = delete; - Jid& operator=(const Jid&) = delete; - Jid& operator=(Jid&&) = delete; -}; - -/** - * Prepare the given UTF-8 string according to the XMPP node stringprep - * identifier profile. This is used to send properly-formed JID to the XMPP - * server. - * - * If the stringprep library is not found, we return an empty string. When - * this function is used, the result must always be checked for an empty - * value, and if this is the case it must not be used as a JID. - */ -std::string jidprep(const std::string& original); - -#endif // JID_INCLUDED diff --git a/src/xmpp/xmpp_parser.cpp b/src/xmpp/xmpp_parser.cpp deleted file mode 100644 index 6bb0d28..0000000 --- a/src/xmpp/xmpp_parser.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include - -#include - -/** - * Expat handlers. Called by the Expat library, never by ourself. - * They just forward the call to the XmppParser corresponding methods. - */ - -static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts) -{ - static_cast(user_data)->start_element(name, atts); -} - -static void end_element_handler(void* user_data, const XML_Char* name) -{ - static_cast(user_data)->end_element(name); -} - -static void character_data_handler(void *user_data, const XML_Char *s, int len) -{ - static_cast(user_data)->char_data(s, len); -} - -/** - * XmppParser class - */ - -XmppParser::XmppParser(): - level(0), - current_node(nullptr) -{ - this->init_xml_parser(); -} - -void XmppParser::init_xml_parser() -{ - // Create the expat parser - this->parser = XML_ParserCreateNS("UTF-8", ':'); - XML_SetUserData(this->parser, static_cast(this)); - - // Install Expat handlers - XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler); - XML_SetCharacterDataHandler(this->parser, &character_data_handler); -} - -XmppParser::~XmppParser() -{ - if (this->current_node) - delete this->current_node; - XML_ParserFree(this->parser); -} - -int XmppParser::feed(const char* data, const int len, const bool is_final) -{ - int res = XML_Parse(this->parser, data, len, is_final); - if (res == XML_STATUS_ERROR && - (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) - log_error("Xml_Parse encountered an error: " << - XML_ErrorString(XML_GetErrorCode(this->parser))) - return res; -} - -int XmppParser::parse(const int len, const bool is_final) -{ - int res = XML_ParseBuffer(this->parser, len, is_final); - if (res == XML_STATUS_ERROR) - log_error("Xml_Parsebuffer encountered an error: " << - XML_ErrorString(XML_GetErrorCode(this->parser))); - return res; -} - -void XmppParser::reset() -{ - XML_ParserFree(this->parser); - this->init_xml_parser(); - if (this->current_node) - delete this->current_node; - this->current_node = nullptr; - this->level = 0; -} - -void* XmppParser::get_buffer(const size_t size) const -{ - return XML_GetBuffer(this->parser, static_cast(size)); -} - -void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) -{ - level++; - - XmlNode* new_node = new XmlNode(name, this->current_node); - if (this->current_node) - this->current_node->add_child(new_node); - this->current_node = new_node; - for (size_t i = 0; attribute[i]; i += 2) - this->current_node->set_attribute(attribute[i], attribute[i+1]); - if (this->level == 1) - this->stream_open_event(*this->current_node); -} - -void XmppParser::end_element(const XML_Char* name) -{ - (void)name; - level--; - this->current_node->close(); - if (level == 1) - { - this->stanza_event(*this->current_node); - } - if (level == 0) - { - this->stream_close_event(*this->current_node); - delete this->current_node; - this->current_node = nullptr; - } - else - this->current_node = this->current_node->get_parent(); - if (level == 1) - this->current_node->delete_all_children(); -} - -void XmppParser::char_data(const XML_Char* data, int len) -{ - if (this->current_node->has_children()) - this->current_node->get_last_child()->add_to_tail(std::string(data, len)); - else - this->current_node->add_to_inner(std::string(data, len)); -} - -void XmppParser::stanza_event(const Stanza& stanza) const -{ - for (const auto& callback: this->stanza_callbacks) - { - try { - callback(stanza); - } catch (const std::exception& e) { - log_debug("Unhandled exception: " << e.what()); - } - } -} - -void XmppParser::stream_open_event(const XmlNode& node) const -{ - for (const auto& callback: this->stream_open_callbacks) - callback(node); -} - -void XmppParser::stream_close_event(const XmlNode& node) const -{ - for (const auto& callback: this->stream_close_callbacks) - callback(node); -} - -void XmppParser::add_stanza_callback(std::function&& callback) -{ - this->stanza_callbacks.emplace_back(std::move(callback)); -} - -void XmppParser::add_stream_open_callback(std::function&& callback) -{ - this->stream_open_callbacks.emplace_back(std::move(callback)); -} - -void XmppParser::add_stream_close_callback(std::function&& callback) -{ - this->stream_close_callbacks.emplace_back(std::move(callback)); -} diff --git a/src/xmpp/xmpp_parser.hpp b/src/xmpp/xmpp_parser.hpp deleted file mode 100644 index 79c9f8f..0000000 --- a/src/xmpp/xmpp_parser.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef XMPP_PARSER_INCLUDED -# define XMPP_PARSER_INCLUDED - -#include - -#include - -#include - -/** - * A SAX XML parser that builds XML nodes and spawns events when a complete - * stanza is received (an element of level 2), or when the document is - * opened/closed (an element of level 1) - * - * After a stanza_event has been spawned, we delete the whole stanza. This - * means that even with a very long document (in XMPP the document is - * potentially infinite), the memory is never exhausted as long as each - * stanza is reasonnably short. - * - * The element names generated by expat contain the namespace of the - * element, a colon (':') and then the actual name of the element. To get - * an element "x" with a namespace of "http://jabber.org/protocol/muc", you - * just look for an XmlNode named "http://jabber.org/protocol/muc:x" - * - * TODO: enforce the size-limit for the stanza (limit the number of childs - * it can contain). For example forbid the parser going further than level - * 20 (arbitrary number here), and each XML node to have more than 15 childs - * (arbitrary number again). - */ -class XmppParser -{ -public: - explicit XmppParser(); - ~XmppParser(); - -public: - /** - * Init the XML parser and install the callbacks - */ - void init_xml_parser(); - /** - * Feed the parser with some XML data - */ - int feed(const char* data, const int len, const bool is_final); - /** - * Parse the data placed in the parser buffer - */ - int parse(const int size, const bool is_final); - /** - * Reset the parser, so it can be used from scratch afterward - */ - void reset(); - /** - * Get a buffer provided by the xml parser. - */ - void* get_buffer(const size_t size) const; - /** - * Add one callback for the various events that this parser can spawn. - */ - void add_stanza_callback(std::function&& callback); - void add_stream_open_callback(std::function&& callback); - void add_stream_close_callback(std::function&& callback); - - /** - * Called when a new XML element has been opened. We instanciate a new - * XmlNode and set it as our current node. The parent of this new node is - * the previous "current" node. We have all the element's attributes in - * this event. - * - * We spawn a stream_event with this node if this is a level-1 element. - */ - void start_element(const XML_Char* name, const XML_Char** attribute); - /** - * Called when an XML element has been closed. We close the current_node, - * set our current_node as the parent of the current_node, and if that was - * a level-2 element we spawn a stanza_event with this node. - * - * And we then delete the stanza (and everything under it, its children, - * attribute, etc). - */ - void end_element(const XML_Char* name); - /** - * Some inner or tail data has been parsed - */ - void char_data(const XML_Char* data, int len); - /** - * Calls all the stanza_callbacks one by one. - */ - void stanza_event(const Stanza& stanza) const; - /** - * Calls all the stream_open_callbacks one by one. Note: the passed node is not - * closed yet. - */ - void stream_open_event(const XmlNode& node) const; - /** - * Calls all the stream_close_callbacks one by one. - */ - void stream_close_event(const XmlNode& node) const; - -private: - /** - * Expat structure. - */ - XML_Parser parser; - /** - * The current depth in the XML document - */ - size_t level; - /** - * The deepest XML node opened but not yet closed (to which we are adding - * new children, inner or tail) - */ - XmlNode* current_node; - /** - * A list of callbacks to be called on an *_event, receiving the - * concerned Stanza/XmlNode. - */ - std::vector> stanza_callbacks; - std::vector> stream_open_callbacks; - std::vector> stream_close_callbacks; - - XmppParser(const XmppParser&) = delete; - XmppParser& operator=(const XmppParser&) = delete; -}; - -#endif // XMPP_PARSER_INCLUDED diff --git a/src/xmpp/xmpp_stanza.cpp b/src/xmpp/xmpp_stanza.cpp deleted file mode 100644 index df19105..0000000 --- a/src/xmpp/xmpp_stanza.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include - -std::string xml_escape(const std::string& data) -{ - std::string res; - res.reserve(data.size()); - for (size_t pos = 0; pos != data.size(); ++pos) - { - switch(data[pos]) - { - case '&': - res += "&"; - break; - case '<': - res += "<"; - break; - case '>': - res += ">"; - break; - case '\"': - res += """; - break; - case '\'': - res += "'"; - break; - default: - res += data[pos]; - break; - } - } - return res; -} - -std::string xml_unescape(const std::string& data) -{ - std::string res; - res.reserve(data.size()); - const char* str = data.c_str(); - while (str && *str && static_cast(str - data.c_str()) < data.size()) - { - if (*str == '&') - { - if (strncmp(str+1, "amp;", 4) == 0) - { - res += "&"; - str += 4; - } - else if (strncmp(str+1, "lt;", 3) == 0) - { - res += "<"; - str += 3; - } - else if (strncmp(str+1, "gt;", 3) == 0) - { - res += ">"; - str += 3; - } - else if (strncmp(str+1, "quot;", 5) == 0) - { - res += "\""; - str += 5; - } - else if (strncmp(str+1, "apos;", 5) == 0) - { - res += "'"; - str += 5; - } - else - res += "&"; - } - else - res += *str; - str++; - } - return res; -} - -XmlNode::XmlNode(const std::string& name, XmlNode* parent): - parent(parent), - closed(false) -{ - // split the namespace and the name - auto n = name.rfind(":"); - if (n == std::string::npos) - this->name = name; - else - { - this->name = name.substr(n+1); - this->attributes["xmlns"] = name.substr(0, n); - } -} - -XmlNode::XmlNode(const std::string& name): - XmlNode(name, nullptr) -{ -} - -XmlNode::~XmlNode() -{ - this->delete_all_children(); -} - -void XmlNode::delete_all_children() -{ - for (auto& child: this->children) - { - delete child; - } - this->children.clear(); -} - -void XmlNode::set_attribute(const std::string& name, const std::string& value) -{ - this->attributes[name] = value; -} - -void XmlNode::set_tail(const std::string& data) -{ - this->tail = xml_escape(data); -} - -void XmlNode::add_to_tail(const std::string& data) -{ - this->tail += xml_escape(data); -} - -void XmlNode::set_inner(const std::string& data) -{ - this->inner = xml_escape(data); -} - -void XmlNode::add_to_inner(const std::string& data) -{ - this->inner += xml_escape(data); -} - -std::string XmlNode::get_inner() const -{ - return xml_unescape(this->inner); -} - -std::string XmlNode::get_tail() const -{ - return xml_unescape(this->tail); -} - -XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const -{ - for (auto& child: this->children) - { - if (child->name == name && child->get_tag("xmlns") == xmlns) - return child; - } - return nullptr; -} - -std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const -{ - std::vector res; - for (auto& child: this->children) - { - if (child->name == name && child->get_tag("xmlns") == xmlns) - res.push_back(child); - } - return res; -} - -XmlNode* XmlNode::add_child(XmlNode* child) -{ - child->parent = this; - this->children.push_back(child); - return child; -} - -XmlNode* XmlNode::add_child(XmlNode&& child) -{ - XmlNode* new_node = new XmlNode(std::move(child)); - return this->add_child(new_node); -} - -XmlNode* XmlNode::get_last_child() const -{ - return this->children.back(); -} - -void XmlNode::close() -{ - if (this->closed) - throw std::runtime_error("Closing an already closed XmlNode"); - this->closed = true; -} - -XmlNode* XmlNode::get_parent() const -{ - return this->parent; -} - -void XmlNode::set_name(const std::string& name) -{ - this->name = name; -} - -const std::string XmlNode::get_name() const -{ - return this->name; -} - -std::string XmlNode::to_string() const -{ - std::string res("<"); - res += this->name; - for (const auto& it: this->attributes) - res += " " + it.first + "='" + sanitize(it.second) + "'"; - if (this->closed && !this->has_children() && this->inner.empty()) - res += "/>"; - else - { - res += ">" + sanitize(this->inner); - for (const auto& child: this->children) - res += child->to_string(); - if (this->closed) - { - res += "get_name() + ">"; - } - } - res += sanitize(this->tail); - return res; -} - -bool XmlNode::has_children() const -{ - return !this->children.empty(); -} - -const std::string XmlNode::get_tag(const std::string& name) const -{ - try - { - const auto& value = this->attributes.at(name); - return value; - } - catch (const std::out_of_range& e) - { - return ""; - } -} - -bool XmlNode::del_tag(const std::string& name) -{ - if (this->attributes.erase(name) != 0) - return true; - return false; -} - -std::string& XmlNode::operator[](const std::string& name) -{ - return this->attributes[name]; -} - -std::string sanitize(const std::string& data) -{ - if (utils::is_valid_utf8(data.data())) - return xml_escape(utils::remove_invalid_xml_chars(data)); - else - return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); -} diff --git a/src/xmpp/xmpp_stanza.hpp b/src/xmpp/xmpp_stanza.hpp deleted file mode 100644 index f1a6a0f..0000000 --- a/src/xmpp/xmpp_stanza.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef XMPP_STANZA_INCLUDED -# define XMPP_STANZA_INCLUDED - -#include -#include -#include - -std::string xml_escape(const std::string& data); -std::string xml_unescape(const std::string& data); -std::string sanitize(const std::string& data); - -/** - * Represent an XML node. It has - * - A parent XML node (in the case of the first-level nodes, the parent is - nullptr) - * - zero, one or more children XML nodes - * - A name - * - A map of attributes - * - inner data (text inside the node) - * - tail data (text just after the node) - */ -class XmlNode -{ -public: - explicit XmlNode(const std::string& name, XmlNode* parent); - explicit XmlNode(const std::string& name); - XmlNode(XmlNode&& node): - name(std::move(node.name)), - parent(node.parent), - closed(node.closed), - attributes(std::move(node.attributes)), - children(std::move(node.children)), - inner(std::move(node.inner)), - tail(std::move(node.tail)) - { - node.parent = nullptr; - } - /** - * The copy constructor do not copy the parent attribute. The children - * nodes are all copied recursively. - */ - XmlNode(const XmlNode& node): - name(node.name), - parent(nullptr), - closed(node.closed), - attributes(node.attributes), - children{}, - inner(node.inner), - tail(node.tail) - { - for (XmlNode* child: node.children) - { - XmlNode* child_copy = new XmlNode(*child); - this->add_child(child_copy); - } - } - - ~XmlNode(); - - void delete_all_children(); - void set_attribute(const std::string& name, const std::string& value); - /** - * Set the content of the tail, that is the text just after this node - */ - void set_tail(const std::string& data); - /** - * Append the given data to the content of the tail. This exists because - * the expat library may provide the complete text of an element in more - * than one call - */ - void add_to_tail(const std::string& data); - /** - * Set the content of the inner, that is the text inside this node. - */ - void set_inner(const std::string& data); - /** - * Append the given data to the content of the inner. For the reason - * described in add_to_tail comment. - */ - void add_to_inner(const std::string& data); - /** - * Get the content of inner - */ - std::string get_inner() const; - /** - * Get the content of the tail - */ - std::string get_tail() const; - /** - * Get a pointer to the first child element with that name and that xml namespace - */ - XmlNode* get_child(const std::string& name, const std::string& xmlns) const; - /** - * Get a vector of all the children that have that name and that xml namespace. - */ - std::vector get_children(const std::string& name, const std::string& xmlns) const; - /** - * Add a node child to this node. Assign this node to the child’s parent. - * Returns a pointer to the newly added child. - */ - XmlNode* add_child(XmlNode* child); - XmlNode* add_child(XmlNode&& child); - /** - * Returns the last of the children. If the node doesn't have any child, - * the behaviour is undefined. The user should make sure this is the case - * by calling has_children() for example. - */ - XmlNode* get_last_child() const; - /** - * Mark this node as closed, nothing else - */ - void close(); - XmlNode* get_parent() const; - void set_name(const std::string& name); - const std::string get_name() const; - /** - * Serialize the stanza into a string - */ - std::string to_string() const; - /** - * Whether or not this node has at least one child (if not, this is a leaf - * node) - */ - bool has_children() const; - /** - * Gets the value for the given attribute, returns an empty string if the - * node as no such attribute. - */ - const std::string get_tag(const std::string& name) const; - /** - * Remove the attribute of the node. Does nothing if that attribute is not - * present. Returns true if the tag was removed, false if it was absent. - */ - bool del_tag(const std::string& name); - /** - * Use this to set an attribute's value, like node["id"] = "12"; - */ - std::string& operator[](const std::string& name); - -private: - std::string name; - XmlNode* parent; - bool closed; - std::unordered_map attributes; - std::vector children; - std::string inner; - std::string tail; - - XmlNode& operator=(const XmlNode&) = delete; - XmlNode& operator=(XmlNode&&) = delete; -}; - -/** - * An XMPP stanza is just an XML node of level 2 in the XMPP document (the - * level 1 ones are the , and the ones above 2 are just the - * content of the stanzas) - */ -typedef XmlNode Stanza; - -#endif // XMPP_STANZA_INCLUDED -- cgit v1.2.3 From d31619714b0a55ea9330d34f35480d4ae7f8f055 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Mar 2015 11:04:55 +0100 Subject: Move non-specific adhoc commands into louloulibs Only keep some biboumi-specific commands into biboumi_adhoc_commands.hpp/cpp --- src/xmpp/adhoc_command.cpp | 216 ------------------------------------ src/xmpp/adhoc_command.hpp | 45 -------- src/xmpp/biboumi_adhoc_commands.cpp | 111 ++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 13 +++ src/xmpp/biboumi_component.cpp | 1 + 5 files changed, 125 insertions(+), 261 deletions(-) delete mode 100644 src/xmpp/adhoc_command.cpp delete mode 100644 src/xmpp/adhoc_command.hpp create mode 100644 src/xmpp/biboumi_adhoc_commands.cpp create mode 100644 src/xmpp/biboumi_adhoc_commands.hpp diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp deleted file mode 100644 index ba20eba..0000000 --- a/src/xmpp/adhoc_command.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include -#include - -#include - -#include - -using namespace std::string_literals; - -AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): - name(name), - callbacks(std::move(callbacks)), - admin_only(admin_only) -{ -} - -AdhocCommand::~AdhocCommand() -{ -} - -bool AdhocCommand::is_admin_only() const -{ - return this->admin_only; -} - -void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) -{ - XmlNode note("note"); - note["type"] = "info"; - note.set_inner("Pong"); - note.close(); - command_node.add_child(std::move(note)); -} - -void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) -{ - XmlNode x("jabber:x:data:x"); - x["type"] = "form"; - XmlNode title("title"); - title.set_inner("Configure your name."); - title.close(); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); - instructions.set_inner("Please provide your name."); - instructions.close(); - x.add_child(std::move(instructions)); - XmlNode name_field("field"); - name_field["var"] = "name"; - name_field["type"] = "text-single"; - name_field["label"] = "Your name"; - XmlNode required("required"); - required.close(); - name_field.add_child(std::move(required)); - name_field.close(); - x.add_child(std::move(name_field)); - x.close(); - command_node.add_child(std::move(x)); -} - -void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) -{ - // Find out if the name was provided in the form. - XmlNode* x = command_node.get_child("x", "jabber:x:data"); - if (x) - { - XmlNode* name_field = nullptr; - for (XmlNode* field: x->get_children("field", "jabber:x:data")) - if (field->get_tag("var") == "name") - { - name_field = field; - break; - } - if (name_field) - { - XmlNode* value = name_field->get_child("value", "jabber:x:data"); - if (value) - { - XmlNode note("note"); - note["type"] = "info"; - note.set_inner("Hello "s + value->get_inner() + "!"s); - note.close(); - command_node.delete_all_children(); - command_node.add_child(std::move(note)); - return; - } - } - } - command_node.delete_all_children(); - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - session.terminate(); -} - -void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) -{ - auto biboumi_component = static_cast(xmpp_component); - - XmlNode x("jabber:x:data:x"); - x["type"] = "form"; - XmlNode title("title"); - title.set_inner("Disconnect a user from the gateway"); - title.close(); - x.add_child(std::move(title)); - XmlNode instructions("instructions"); - instructions.set_inner("Choose a user JID and a quit message"); - instructions.close(); - x.add_child(std::move(instructions)); - XmlNode jids_field("field"); - jids_field["var"] = "jids"; - jids_field["type"] = "list-multi"; - jids_field["label"] = "The JIDs to disconnect"; - XmlNode required("required"); - required.close(); - jids_field.add_child(std::move(required)); - for (Bridge* bridge: biboumi_component->get_bridges()) - { - XmlNode option("option"); - option["label"] = bridge->get_jid(); - XmlNode value("value"); - value.set_inner(bridge->get_jid()); - value.close(); - option.add_child(std::move(value)); - option.close(); - jids_field.add_child(std::move(option)); - } - jids_field.close(); - x.add_child(std::move(jids_field)); - - XmlNode message_field("field"); - message_field["var"] = "quit-message"; - message_field["type"] = "text-single"; - message_field["label"] = "Quit message"; - XmlNode message_value("value"); - message_value.set_inner("Disconnected by admin"); - message_value.close(); - message_field.add_child(std::move(message_value)); - message_field.close(); - x.add_child(std::move(message_field)); - x.close(); - command_node.add_child(std::move(x)); -} - -void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) -{ - auto biboumi_component = static_cast(xmpp_component); - - // Find out if the jids, and the quit message are provided in the form. - std::string quit_message; - XmlNode* x = command_node.get_child("x", "jabber:x:data"); - if (x) - { - XmlNode* message_field = nullptr; - XmlNode* jids_field = nullptr; - for (XmlNode* field: x->get_children("field", "jabber:x:data")) - if (field->get_tag("var") == "jids") - jids_field = field; - else if (field->get_tag("var") == "quit-message") - message_field = field; - if (message_field) - { - XmlNode* value = message_field->get_child("value", "jabber:x:data"); - if (value) - quit_message = value->get_inner(); - } - if (jids_field) - { - std::size_t num = 0; - for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) - { - Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); - if (bridge) - { - bridge->shutdown(quit_message); - num++; - } - } - command_node.delete_all_children(); - - XmlNode note("note"); - note["type"] = "info"; - if (num == 0) - note.set_inner("No user were disconnected."); - else if (num == 1) - note.set_inner("1 user has been disconnected."); - else - note.set_inner(std::to_string(num) + " users have been disconnected."); - note.close(); - command_node.add_child(std::move(note)); - return; - } - } - XmlNode error(ADHOC_NS":error"); - error["type"] = "modify"; - XmlNode condition(STANZA_NS":bad-request"); - condition.close(); - error.add_child(std::move(condition)); - error.close(); - command_node.add_child(std::move(error)); - session.terminate(); -} - -void Reload(XmppComponent*, AdhocSession&, XmlNode& command_node) -{ - ::reload_process(); - command_node.delete_all_children(); - XmlNode note("note"); - note["type"] = "info"; - note.set_inner("Configuration reloaded."); - note.close(); - command_node.add_child(std::move(note)); -} diff --git a/src/xmpp/adhoc_command.hpp b/src/xmpp/adhoc_command.hpp deleted file mode 100644 index 622d6b9..0000000 --- a/src/xmpp/adhoc_command.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef ADHOC_COMMAND_HPP -# define ADHOC_COMMAND_HPP - -/** - * Describe an ad-hoc command. - * - * Can only have zero or one step for now. When execution is requested, it - * can return a result immediately, or provide a form to be filled, and - * provide a result once the filled form is received. - */ - -#include - -#include -#include - -class AdhocCommand -{ - friend class AdhocSession; -public: - AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); - ~AdhocCommand(); - - const std::string name; - - bool is_admin_only() const; - -private: - /** - * A command may have one or more steps. Each step is a different - * callback, inserting things into a XmlNode and calling - * methods of an AdhocSession. - */ - std::vector callbacks; - const bool admin_only; -}; - -void PingStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void HelloStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node); - -#endif // ADHOC_COMMAND_HPP diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp new file mode 100644 index 0000000..089eebf --- /dev/null +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -0,0 +1,111 @@ +#include +#include +#include + +void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) +{ + auto biboumi_component = static_cast(xmpp_component); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from the gateway"); + title.close(); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose a user JID and a quit message"); + instructions.close(); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "jids"; + jids_field["type"] = "list-multi"; + jids_field["label"] = "The JIDs to disconnect"; + XmlNode required("required"); + required.close(); + jids_field.add_child(std::move(required)); + for (Bridge* bridge: biboumi_component->get_bridges()) + { + XmlNode option("option"); + option["label"] = bridge->get_jid(); + XmlNode value("value"); + value.set_inner(bridge->get_jid()); + value.close(); + option.add_child(std::move(value)); + option.close(); + jids_field.add_child(std::move(option)); + } + jids_field.close(); + x.add_child(std::move(jids_field)); + + XmlNode message_field("field"); + message_field["var"] = "quit-message"; + message_field["type"] = "text-single"; + message_field["label"] = "Quit message"; + XmlNode message_value("value"); + message_value.set_inner("Disconnected by admin"); + message_value.close(); + message_field.add_child(std::move(message_value)); + message_field.close(); + x.add_child(std::move(message_field)); + x.close(); + command_node.add_child(std::move(x)); +} + +void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + auto biboumi_component = static_cast(xmpp_component); + + // Find out if the jids, and the quit message are provided in the form. + std::string quit_message; + XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + XmlNode* message_field = nullptr; + XmlNode* jids_field = nullptr; + for (XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "jids") + jids_field = field; + else if (field->get_tag("var") == "quit-message") + message_field = field; + if (message_field) + { + XmlNode* value = message_field->get_child("value", "jabber:x:data"); + if (value) + quit_message = value->get_inner(); + } + if (jids_field) + { + std::size_t num = 0; + for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) + { + Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); + if (bridge) + { + bridge->shutdown(quit_message); + num++; + } + } + command_node.delete_all_children(); + + XmlNode note("note"); + note["type"] = "info"; + if (num == 0) + note.set_inner("No user were disconnected."); + else if (num == 1) + note.set_inner("1 user has been disconnected."); + else + note.set_inner(std::to_string(num) + " users have been disconnected."); + note.close(); + command_node.add_child(std::move(note)); + return; + } + } + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + session.terminate(); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp new file mode 100644 index 0000000..30f713a --- /dev/null +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -0,0 +1,13 @@ +#ifndef BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED +#define BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED + +#include +#include +#include + +class XmppComponent; + +void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); + +#endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 2ecf247..ba8cb49 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From e4c696861d86b62305ca0ec8136e79f147837b94 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Mar 2015 11:06:40 +0100 Subject: Update louloulibs to last revision --- louloulibs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index d6a3724..5f3a1bb 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit d6a3724c6a0127a49a9e7adb1090bb7438c8d0f2 +Subproject commit 5f3a1bb54df4de5f332282bbdf791bdce07c71c4 -- cgit v1.2.3 From d88ec5fdf10ecb168355bc38dc81d83ff59a0234 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Mar 2015 11:32:18 +0100 Subject: Update to latest louloulibs revision --- louloulibs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index 5f3a1bb..d0b8695 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit 5f3a1bb54df4de5f332282bbdf791bdce07c71c4 +Subproject commit d0b8695ceb13e0c6d72821fe605de36e494afcdf -- cgit v1.2.3 From 33cdc6010e9c19b6a74c65171c0557f0f2855f4c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 4 Mar 2015 05:56:08 +0100 Subject: Set the default value to biboumi.cfg --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 9042a57..98013eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,6 +55,9 @@ int main(int ac, char** av) { if (ac > 1) Config::filename = av[1]; + else + Config::filename = "biboumi.cfg"; + Config::file_must_exist = true; std::cerr << "Using configuration file: " << Config::filename << std::endl; -- cgit v1.2.3 From c243fea660723eba00b65e639b76d0783cb59064 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 4 Mar 2015 05:56:44 +0100 Subject: Update to latest louloulibs revision --- louloulibs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index d0b8695..99757a4 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit d0b8695ceb13e0c6d72821fe605de36e494afcdf +Subproject commit 99757a44b49619ff59cae9e6d983a3b7c20c56bf -- cgit v1.2.3 From ffcce28c7711ff69e46445c466bd439362e3d0d4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 24 Mar 2015 04:34:05 +0100 Subject: Do not log a warning when we receive a PONG command --- src/irc/irc_client.cpp | 4 ++++ src/irc/irc_client.hpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index cbbd4ea..694baf8 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -281,6 +281,10 @@ void IrcClient::send_pong_command(const IrcMessage& message) this->send_message(IrcMessage("PONG", {id})); } +void IrcClient::on_pong(const IrcMessage& message) +{ +} + void IrcClient::send_ping_command() { this->send_message(IrcMessage("PING", {"biboumi"})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 86edbab..03951be 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -70,6 +70,11 @@ public: * Send the PONG irc command */ void send_pong_command(const IrcMessage& message); + /** + * Do nothing when we receive a PONG command (but also do not log that no + * handler exist) + */ + void on_pong(const IrcMessage& message); void send_ping_command(); /** * Send the USER irc command @@ -339,6 +344,7 @@ static const std::unordered_map irc_callbacks = { {"NICK", &IrcClient::on_nick}, {"MODE", &IrcClient::on_mode}, {"PING", &IrcClient::send_pong_command}, + {"PONG", &IrcClient::on_pong}, {"KICK", &IrcClient::on_kick}, {"401", &IrcClient::on_generic_error}, -- cgit v1.2.3 From d5324ac7780e9a6297631a9c3dc83676e0c1d246 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 13 Apr 2015 01:53:53 +0200 Subject: Re-order a few things in main.cpp --- src/main.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 98013eb..cc73244 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,11 +74,6 @@ int main(int ac, char** av) if (hostname.empty()) return config_help("hostname"); - auto p = std::make_shared(); - auto xmpp_component = std::make_shared(p, - hostname, - password); - // Install the signals used to exit the process cleanly, or reload the // config sigset_t mask; @@ -100,9 +95,11 @@ int main(int ac, char** av) sigaction(SIGUSR1, &on_sigusr, nullptr); sigaction(SIGUSR2, &on_sigusr, nullptr); + auto p = std::make_shared(); + auto xmpp_component = + std::make_shared(p, hostname, password); xmpp_component->start(); - #ifdef CARES_FOUND DNSHandler::instance.watch_dns_sockets(p); #endif @@ -119,10 +116,7 @@ int main(int ac, char** av) exiting = true; stop.store(false); xmpp_component->shutdown(); -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif - // Cancel the timer for an potential reconnection + // Cancel the timer for a potential reconnection TimedEventsManager::instance().cancel("XMPP reconnection"); } if (reload) @@ -171,6 +165,9 @@ int main(int ac, char** av) else timeout = TimedEventsManager::instance().get_timeout(); } +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif log_info("All connections cleanly closed, have a nice day."); return 0; } -- cgit v1.2.3 From ad0465b32051e224f6a234f3ed36494905e59cbf Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 20 Apr 2015 20:33:02 +0200 Subject: Decode incoming JIDs local part according to xep 0106 This let users send message to nicks such as Q@CServe.quakenet.org fix #3047 --- louloulibs | 2 +- src/irc/iid.cpp | 2 ++ src/test.cpp | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index 99757a4..88d2b13 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit 99757a44b49619ff59cae9e6d983a3b7c20c56bf +Subproject commit 88d2b136e5f133f0d0dc01f59449284f663d53ea diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 9d39129..5d8dc0a 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -68,6 +69,7 @@ Iid::Iid(): void Iid::set_local(const std::string& loc) { this->local = utils::tolower(loc); + xep0106::decode(local); } void Iid::set_server(const std::string& serv) diff --git a/src/test.cpp b/src/test.cpp index 553140f..a46bfd1 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -396,7 +396,15 @@ int main() assert(iid6.get_server() == "fixed.example.com"); assert(iid6.is_channel); assert(!iid6.is_user); + + Config::set("fixed_irc_server", "", false); } + std::cout << color << "Testing JID (xep 0106) decoding…" << reset << std::endl; + assert(Iid{"space\\20cadet!"}.get_local() == "space cadet"); + assert(Iid{"call\\20me\\20\\22ishmael\\22!"}.get_local() == "call me \"ishmael\""); + assert(Iid{"\\2f.fanboy!"}.get_local() == "/.fanboy"); + assert(Iid{"Q\\40CServe.quakenet.org!"}.get_local() == "q@cserve.quakenet.org"); + return 0; } -- cgit v1.2.3 From 0d706741c6b3a8bdf6b4f8ca0b1ac00cb27bd8b8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 20 Apr 2015 20:35:32 +0200 Subject: Update louloulibs submodule to the correct revision --- louloulibs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index 88d2b13..b53ae92 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit 88d2b136e5f133f0d0dc01f59449284f663d53ea +Subproject commit b53ae922f48f1465a7fa61136f65ec39e38a452e -- cgit v1.2.3 From 6a28bde1dd21809b3c1202aab0b695b11f4d4846 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 20 Apr 2015 23:58:05 +0200 Subject: Reset the signal handlers when SIGINT or SIGTERM is received To avoid doing a double exit when receiving bot SIGINT and SIGTERM --- src/main.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index cc73244..62a28a5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -40,6 +40,18 @@ int config_help(const std::string& missing_option) static void sigint_handler(int sig, siginfo_t*, void*) { + // We reset the SIGTERM or SIGINT (the one that didn't trigger this + // handler) signal handler to its default value. This avoid calling this + // handler twice, if the process receive both signals in a quick + // succession. + int sig_to_reset = (sig == SIGINT? SIGTERM: SIGINT); + sigset_t mask; + sigemptyset(&mask); + struct sigaction sigreset = {}; + sigreset.sa_handler = SIG_DFL; + sigreset.sa_mask = mask; + sigaction(sig_to_reset, &sigreset, nullptr); + // In 2 seconds, repeat the same signal, to force the exit TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, [sig]() { raise(sig); })); -- cgit v1.2.3 From 61bfd1db008295b33350e7c6b9a1b98a7964f9c6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 21 Apr 2015 02:48:28 +0200 Subject: Revert "Decode incoming JIDs local part according to xep 0106" This reverts commit ad0465b32051e224f6a234f3ed36494905e59cbf. Conflicts: louloulibs --- src/irc/iid.cpp | 2 -- src/test.cpp | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 5d8dc0a..9d39129 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -1,6 +1,5 @@ #include #include -#include #include @@ -69,7 +68,6 @@ Iid::Iid(): void Iid::set_local(const std::string& loc) { this->local = utils::tolower(loc); - xep0106::decode(local); } void Iid::set_server(const std::string& serv) diff --git a/src/test.cpp b/src/test.cpp index a46bfd1..553140f 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -396,15 +396,7 @@ int main() assert(iid6.get_server() == "fixed.example.com"); assert(iid6.is_channel); assert(!iid6.is_user); - - Config::set("fixed_irc_server", "", false); } - std::cout << color << "Testing JID (xep 0106) decoding…" << reset << std::endl; - assert(Iid{"space\\20cadet!"}.get_local() == "space cadet"); - assert(Iid{"call\\20me\\20\\22ishmael\\22!"}.get_local() == "call me \"ishmael\""); - assert(Iid{"\\2f.fanboy!"}.get_local() == "/.fanboy"); - assert(Iid{"Q\\40CServe.quakenet.org!"}.get_local() == "q@cserve.quakenet.org"); - return 0; } -- cgit v1.2.3 From e46c3c004d56c4ebcb010caded04a429435d5364 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 21 Apr 2015 02:51:10 +0200 Subject: Do not specify an ExecStop= line in the unit file --- unit/biboumi.service | 1 - 1 file changed, 1 deletion(-) diff --git a/unit/biboumi.service b/unit/biboumi.service index 1bb4f63..59001a6 100644 --- a/unit/biboumi.service +++ b/unit/biboumi.service @@ -6,7 +6,6 @@ After=network.target Type=notify ExecStart=/usr/bin/biboumi /etc/biboumi/biboumi.cfg ExecReload=/bin/kill -s USR1 $MAINPID -ExecStop=/bin/kill -s INT $MAINPID WatchdogSec=10 Restart=always User=nobody -- cgit v1.2.3 From d79850f984cb5aa9579ebc5f5e53696fc7872eb8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 21 Apr 2015 02:54:47 +0200 Subject: Document how to address a nick with @ character fix #3047 --- doc/biboumi.1.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 3fd5a25..b69dede 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -160,6 +160,12 @@ IRC nicknames are case-insensitive, this means that the nicknames toto, Toto, tOtO and TOTO all represent the same IRC user. This means you can talk to the user toto, and this will work. +Also note that some IRC nicknames may contain characters that are not +allowed in the local part of a JID (for example '@'). If you need to send a +message to a nick containing such a character, you have to use a jid like +`%irc.example.com@biboumi.example.com/AnnoyingNickn@me`, because the JID +`AnnoyingNickn@me!irc.example.com@biboumi.example.com` would not work. + Examples: `#foo%irc.example.com@biboumi.example.com` is the #foo IRC channel, on the -- cgit v1.2.3 From a8225dc54c019788722bda3bda8d55151c1ccdef Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 21 Apr 2015 15:35:10 +0200 Subject: Properly check for connecting or connected status before reconnecting Note, in our context, is_connecting() includes the resolving part as well as the actual connection (if we are using c-ares) fix #3048 --- louloulibs | 2 +- src/irc/irc_client.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/louloulibs b/louloulibs index b53ae92..6c812cd 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit b53ae922f48f1465a7fa61136f65ec39e38a452e +Subproject commit 6c812cd86e31569db61cac4e30f77e296d207191 diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 694baf8..b0ce93a 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -55,8 +55,8 @@ IrcClient::~IrcClient() void IrcClient::start() { - if (this->connected || this->connecting) - return ; + if (this->is_connecting() || this->is_connected()) + return; std::string port; bool tls; std::tie(port, tls) = this->ports_to_try.top(); -- cgit v1.2.3 From ac30733800b69dcb1589adcba6a42b20fa763e8f Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 30 Apr 2015 09:56:13 +0200 Subject: Search for the config file in $XDG_CONFIG_DIR/biboumi. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This environment variable points to the location the user expects to find every program’s config file, or if it isn’t set, ~/.config/biboumi (with a fallback to . if $HOME isn’t set). fix #2553 --- src/main.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 62a28a5..80462cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -68,7 +69,19 @@ int main(int ac, char** av) if (ac > 1) Config::filename = av[1]; else - Config::filename = "biboumi.cfg"; + { + const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); + if (xdg_config_home) + Config::filename = std::string{xdg_config_home} + "/" "biboumi" "/" "biboumi.cfg"; + else + { + const char* home = getenv("HOME"); + if (home) + Config::filename = std::string{home} + "/" ".config" "/" "biboumi" "/" "biboumi.cfg"; + else + Config::filename = "biboumi.cfg"; + } + } Config::file_must_exist = true; std::cerr << "Using configuration file: " << Config::filename << std::endl; -- cgit v1.2.3 From e24ed4f70c22142029fa8e6cf5b874e8bc261bc3 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Mon, 4 May 2015 17:08:08 +0200 Subject: Make sure XDG_CONFIG_HOME is absolute, as per XDG basedir spec. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 80462cb..4a207b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,7 +71,7 @@ int main(int ac, char** av) else { const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); - if (xdg_config_home) + if (xdg_config_home && xdg_config_home[0] == '/') Config::filename = std::string{xdg_config_home} + "/" "biboumi" "/" "biboumi.cfg"; else { -- cgit v1.2.3 From 0a6b673b14efc4f623ea445045e6fc60e9842a25 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 May 2015 17:01:17 +0200 Subject: Support raw IRC messages Messages received on an IRC server JID are forwarded as raw IRC messages. fix #2486 --- doc/biboumi.1.md | 14 ++++++++++++++ src/bridge/bridge.cpp | 11 +++++++++++ src/bridge/bridge.hpp | 1 + src/irc/irc_client.cpp | 5 +++++ src/irc/irc_client.hpp | 1 + src/xmpp/biboumi_component.cpp | 5 +++++ 6 files changed, 37 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index b69dede..faa9618 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -357,6 +357,20 @@ Biboumi supports a few ad-hoc commands, as described in the XEP 0050. “Gateway shutdown” quit message, except that biboumi does not exit when using this ad-hoc command. +### Raw IRC messages + +Biboumi tries to support as many IRC features as possible, but doesn’t +handle everything yet (or ever). In order to let the user send any +arbitrary IRC message, biboumi forwards any XMPP message received on an IRC +Server JID (see *ADDRESSING*) as a raw command to that IRC server. + +For example, to WHOIS the user Foo on the server irc.example.com, a user can +send the message “WHOIS Foo” to “irc.example.com@biboumi.example.com”. + +The message will be forwarded as is, without any modification appart from +adding "\r\n" at the end (to make it a valid IRC message). You need to have +a little bit of understanding of the IRC protocol to use this feature. + SECURITY -------- diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 85049b9..45cdc01 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -273,6 +273,17 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const } } +void Bridge::send_raw_message(const std::string& hostname, const std::string& body) +{ + IrcClient* irc = this->get_irc_client(hostname); + if (!irc) + { + log_warning("Cannot send message: no client exist for server " << hostname); + return ; + } + irc->send_raw(body); +} + void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) { IrcClient* irc = this->get_irc_client(iid.get_server()); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c50b7ab..cc9d042 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -61,6 +61,7 @@ public: bool join_irc_channel(const Iid& iid, const std::string& username, const std::string& password); void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); + void send_raw_message(const std::string& hostname, const std::string& body); void leave_irc_channel(Iid&& iid, std::string&& status_message); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index b0ce93a..717f7e3 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -181,6 +181,11 @@ void IrcClient::send_message(IrcMessage&& message) this->send_data(std::move(res)); } +void IrcClient::send_raw(const std::string& txt) +{ + this->send_data(txt + "\r\n"); +} + void IrcClient::send_user_command(const std::string& username, const std::string& realname) { this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 03951be..08021c1 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -66,6 +66,7 @@ public: * for send events to be ready) */ void send_message(IrcMessage&& message); + void send_raw(const std::string& txt); /** * Send the PONG irc command */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ba8cb49..37383a8 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -211,6 +211,11 @@ void BiboumiComponent::handle_message(const Stanza& stanza) bridge->send_private_message(user_iid, body->get_inner()); bridge->set_preferred_from_jid(user_iid.get_local(), to_str); } + else if (!iid.is_user && !iid.is_channel) + { // Message sent to the server JID + // Convert the message body into a raw IRC message + bridge->send_raw_message(iid.get_server(), body->get_inner()); + } } } else if (iid.is_user) -- cgit v1.2.3 From 163ace553f3e65dcf9f97070faa5dbab0d31bac0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 May 2015 17:17:01 +0200 Subject: Handle all unknown IRC command by forwarding the arguments as a message body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way, the users can receive the result of any IRC command (although not parsed nor formatted in anyway) when biboumi doesn’t support it fix #2884 --- src/irc/irc_client.cpp | 23 ++++++++++++++++++++++- src/irc/irc_client.hpp | 1 + 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 717f7e3..677f6be 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -154,7 +155,11 @@ void IrcClient::parse_in_buffer(const size_t) } } else - log_info("No handler for command " << message.command); + { + log_info("No handler for command " << message.command << + ", forwarding the arguments to the user"); + this->on_unknown_message(message); + } // Try to find a waiting_iq, which response will be triggered by this IrcMessage this->bridge->trigger_on_irc_message(this->hostname, message); } @@ -797,6 +802,22 @@ void IrcClient::on_user_mode(const IrcMessage& message) " is [" + message.arguments[1] + "]"); } +void IrcClient::on_unknown_message(const IrcMessage& message) +{ + if (message.arguments.size() < 2) + return ; + std::string from = message.prefix; + const std::string to = message.arguments[0]; + std::stringstream ss; + for (auto it = message.arguments.begin() + 1; it != message.arguments.end(); ++it) + { + ss << *it; + if (it + 1 != message.arguments.end()) + ss << " "; + } + this->bridge->send_xmpp_message(this->hostname, from, ss.str()); +} + size_t IrcClient::number_of_joined_channels() const { if (this->dummy_channel.joined) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 08021c1..4ab0fcb 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -211,6 +211,7 @@ public: */ void on_channel_mode(const IrcMessage& message); void on_quit(const IrcMessage& message); + void on_unknown_message(const IrcMessage& message); /** * Return the number of joined channels */ -- cgit v1.2.3 From 221ed2553dcc5c43bfeb71f97e86147735d77856 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 May 2015 17:18:40 +0200 Subject: Two trivial fixes --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 677f6be..7dcda8e 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -291,7 +291,7 @@ void IrcClient::send_pong_command(const IrcMessage& message) this->send_message(IrcMessage("PONG", {id})); } -void IrcClient::on_pong(const IrcMessage& message) +void IrcClient::on_pong(const IrcMessage&) { } @@ -316,7 +316,7 @@ void IrcClient::on_notice(const IrcMessage& message) if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end()) { - // The notice is for the us precisely. + // The notice is for us precisely. // Find out if we already sent a private message to this user. If yes // we treat that message as a private message coming from -- cgit v1.2.3 From 71fec776c4d7b99b76a44deae6f333d9cffa1496 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 7 May 2015 17:42:37 +0200 Subject: Update to latest louloulibs fix #3042 --- louloulibs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs b/louloulibs index 6c812cd..eaa4fbb 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit 6c812cd86e31569db61cac4e30f77e296d207191 +Subproject commit eaa4fbba814b56b4fe7ffb62984fddfbb9280291 -- cgit v1.2.3 From 5475d16b574e1daf9c1e5f94b5b821bfb1b9c8f8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 May 2015 04:47:31 +0200 Subject: Return a stanza error whenever the IRCClient for a given server does not exist Instead of ignoring the stanza, we send back an error of type remote-server-not-found each time it's possible. Also avoid having to do if (!irc) return; everytime. fix #3045 --- src/bridge/bridge.cpp | 71 +++++++++++++++++++----------------------- src/bridge/bridge.hpp | 16 ++++++++-- src/xmpp/biboumi_component.cpp | 20 ++++++++++++ 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 45cdc01..16264d4 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -99,6 +99,18 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string } IrcClient* Bridge::get_irc_client(const std::string& hostname) +{ + try + { + return this->irc_clients.at(hostname).get(); + } + catch (const std::out_of_range& exception) + { + throw IRCNotConnected(hostname); + } +} + +IrcClient* Bridge::find_irc_client(const std::string& hostname) { try { @@ -145,17 +157,17 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& username, const void Bridge::send_channel_message(const Iid& iid, const std::string& body) { - if (iid.get_local().empty() || iid.get_server().empty()) + if (iid.get_server().empty()) { - log_warning("Cannot send message to channel: [" << iid.get_local() << "] on server [" << iid.get_server() << "]"); + this->xmpp->send_stanza_error("message", this->user_jid, std::to_string(iid), "", + "cancel", "remote-server-not-found", + std::to_string(iid) + " is not a valid channel name. " + "A correct room jid is of the form: #%", + false); return; } IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) - { - log_warning("Cannot send message: no client exist for server " << iid.get_server()); - return; - } + // Because an IRC message cannot contain \n, we need to convert each line // of text into a separate IRC message. For conveniance, we also cut the // message into submessages on the XMPP side, this way the user of the @@ -190,8 +202,6 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& const std::string& role) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) - return; IrcChannel* chan = irc->get_channel(iid.get_local()); if (!chan || !chan->joined) return; @@ -254,13 +264,15 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type) { if (iid.get_local().empty() || iid.get_server().empty()) - return ; - IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) { - log_warning("Cannot send message: no client exist for server " << iid.get_server()); + this->xmpp->send_stanza_error("message", this->user_jid, std::to_string(iid), "", + "cancel", "remote-server-not-found", + std::to_string(iid) + " is not a valid channel name. " + "A correct room jid is of the form: #%", + false); return; } + IrcClient* irc = this->get_irc_client(iid.get_server()); std::vector lines = utils::split(body, '\n', true); if (lines.empty()) return ; @@ -276,26 +288,19 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const void Bridge::send_raw_message(const std::string& hostname, const std::string& body) { IrcClient* irc = this->get_irc_client(hostname); - if (!irc) - { - log_warning("Cannot send message: no client exist for server " << hostname); - return ; - } irc->send_raw(body); } void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (irc) - irc->send_part_command(iid.get_local(), status_message); + irc->send_part_command(iid.get_local(), status_message); } void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (irc) - irc->send_nick_command(new_nick); + irc->send_nick_command(new_nick); } void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, @@ -303,10 +308,8 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) - return; - irc->send_list_command(); + irc_responder_callback_t cb = [this, iid, iq_id, to_jid](const std::string& irc_hostname, const IrcMessage& message) -> bool { @@ -340,9 +343,6 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) - return; - irc->send_kick_command(iid.get_local(), target, reason); irc_responder_callback_t cb = [this, target, iq_id, to_jid, iid](const std::string& irc_hostname, const IrcMessage& message) -> bool @@ -387,8 +387,7 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: void Bridge::set_channel_topic(const Iid& iid, const std::string& subject) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (irc) - irc->send_topic_command(iid.get_local(), subject); + irc->send_topic_command(iid.get_local(), subject); } void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os) @@ -447,12 +446,6 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string const std::string& from_jid) { IrcClient* irc = this->get_irc_client(iid.get_server()); - if (!irc) - { - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", - "Not connected to IRC server"s + iid.get_server(), true); - return; - } IrcChannel* chan = irc->get_channel(iid.get_local()); if (!chan->joined) { @@ -475,7 +468,7 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& const std::string& from_jid) { Jid jid(from_jid); - if (irc_hostname.empty() || this->get_irc_client(irc_hostname)) + if (irc_hostname.empty() || this->find_irc_client(irc_hostname)) this->xmpp->send_iq_result(iq_id, to_jid, jid.local); else this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", @@ -548,7 +541,7 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { this->xmpp->send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); - IrcClient* irc = this->get_irc_client(iid.get_server()); + IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); } @@ -603,7 +596,7 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam std::string Bridge::get_own_nick(const Iid& iid) { - IrcClient* irc = this->get_irc_client(iid.get_server()); + IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc) return irc->get_own_nick(); return ""; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index cc9d042..72a8e90 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -193,10 +194,14 @@ private: */ IrcClient* get_irc_client(const std::string& hostname, const std::string& username); /** - * This version does not create the IrcClient if it does not exist, and - * returns nullptr in that case + * This version does not create the IrcClient if it does not exist, throws + * a IRCServerNotConnected error in that case. */ IrcClient* get_irc_client(const std::string& hostname); + /** + * Idem, but returns nullptr if the server does not exist. + */ + IrcClient* find_irc_client(const std::string& hostname); /** * The JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. @@ -240,4 +245,11 @@ private: Bridge& operator=(Bridge&&) = delete; }; +struct IRCNotConnected: public std::exception +{ + IRCNotConnected(const std::string& hostname): + hostname(hostname) {} + const std::string hostname; +}; + #endif // BRIDGE_INCLUDED diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 37383a8..4996438 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -166,6 +166,8 @@ void BiboumiComponent::handle_message(const Stanza& stanza) error_type, error_name, ""); }); XmlNode* body = stanza.get_child("body", COMPONENT_NS); + + try { // catch IRCNotConnected exceptions if (type == "groupchat" && iid.is_channel) { if (body && !body->get_inner().empty()) @@ -220,6 +222,13 @@ void BiboumiComponent::handle_message(const Stanza& stanza) } else if (iid.is_user) this->send_invalid_user_error(to.local, from); + } catch (const IRCNotConnected& ex) + { + this->send_stanza_error("message", from, to_str, id, + "cancel", "remote-server-not-found", + "Not connected to IRC server "s + ex.hostname, + true); + } stanza_error.disable(); } @@ -262,6 +271,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->send_stanza_error("iq", from, to_str, id, error_type, error_name, ""); }); + try { if (type == "set") { XmlNode* query; @@ -412,6 +422,16 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } } } + } + catch (const IRCNotConnected& ex) + { + this->send_stanza_error("iq", from, to_str, id, + "cancel", "remote-server-not-found", + "Not connected to IRC server "s + ex.hostname, + true); + stanza_error.disable(); + return; + } error_type = "cancel"; error_name = "feature-not-implemented"; } -- cgit v1.2.3 From ad5bd99618fd4f1ee92d48d90912d627187aaa11 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 May 2015 05:03:09 +0200 Subject: Properly send error response on presence stanzas --- src/xmpp/biboumi_component.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 4996438..5d571ec 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -116,6 +116,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) error_type, error_name, ""); }); + try { if (iid.is_channel && !iid.get_server().empty()) { // presence toward a MUC that corresponds to an irc channel, or a // dummy channel if iid.chan is empty @@ -141,6 +142,14 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) if (type.empty()) this->send_invalid_room_error(to.local, to.resource, from); } + } + catch (const IRCNotConnected& ex) + { + this->send_stanza_error("presence", from, to_str, id, + "cancel", "remote-server-not-found", + "Not connected to IRC server "s + ex.hostname, + true); + } stanza_error.disable(); } -- cgit v1.2.3 From 7fe0dc2667229287ab9f1825e84403a25bc863fe Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 May 2015 05:31:11 +0200 Subject: Fix the case of the nick for ping requests fix #3041 --- src/bridge/bridge.cpp | 4 ++-- src/irc/irc_client.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 16264d4..4bfd1b2 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -409,13 +409,13 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s Iid iid(nick + "!" + irc_hostname); this->send_private_message(iid, "\01PING " + iq_id + "\01"); - irc_responder_callback_t cb = [this, nick, iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool + irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool { if (irc_hostname != hostname) return false; IrcUser user(message.prefix); const std::string body = message.arguments[1]; - if (message.command == "NOTICE" && user.nick == nick && + if (message.command == "NOTICE" && utils::tolower(user.nick) == nick && message.arguments.size() >= 2 && body.substr(0, 6) == "\01PING ") { const std::string id = body.substr(6, body.size() - 6); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7dcda8e..5f6aaaf 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -452,7 +452,7 @@ void IrcClient::on_channel_message(const IrcMessage& message) else if (body.substr(1, 8) == "VERSION\01") this->bridge->send_iq_version_request(nick, this->hostname); else if (body.substr(1, 5) == "PING ") - this->bridge->send_xmpp_ping_request(nick, this->hostname, + this->bridge->send_xmpp_ping_request(utils::tolower(nick), this->hostname, body.substr(6, body.size() - 7)); } else -- cgit v1.2.3 From cc1255d216bf7ac6029a5dd3bc8aadae06df446b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 May 2015 05:31:47 +0200 Subject: Fix the way we check for the PING id --- src/bridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4bfd1b2..8c364ee 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -418,7 +418,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s if (message.command == "NOTICE" && utils::tolower(user.nick) == nick && message.arguments.size() >= 2 && body.substr(0, 6) == "\01PING ") { - const std::string id = body.substr(6, body.size() - 6); + const std::string id = body.substr(6, body.size() - 7); if (id != iq_id) return false; Jid jid(from_jid); -- cgit v1.2.3 From 763be827c9349d39adbdd8cf731565bef316a01a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 11 May 2015 05:43:32 +0200 Subject: Fix a message.arguments size check --- src/bridge/bridge.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 8c364ee..ef20351 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -411,13 +411,14 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid](const std::string& hostname, const IrcMessage& message) -> bool { - if (irc_hostname != hostname) + if (irc_hostname != hostname || message.arguments.size() < 2) return false; IrcUser user(message.prefix); const std::string body = message.arguments[1]; - if (message.command == "NOTICE" && utils::tolower(user.nick) == nick && - message.arguments.size() >= 2 && body.substr(0, 6) == "\01PING ") + if (message.command == "NOTICE" && utils::tolower(user.nick) == nick + && body.substr(0, 6) == "\01PING ") { + return false; const std::string id = body.substr(6, body.size() - 7); if (id != iq_id) return false; @@ -425,8 +426,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s this->xmpp->send_iq_result(iq_id, to_jid, jid.local); return true; } - if (message.command == "401" && message.arguments.size() >= 2 - && message.arguments[1] == nick) + if (message.command == "401" && message.arguments[1] == nick) { std::string error_message = "No such nick"; if (message.arguments.size() >= 3) -- cgit v1.2.3 From fbeb5af364db54c8a82f5ea30b83df441988ea4b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 May 2015 20:17:43 +0200 Subject: Update to latest louloulibs revision, and add test for hostname validity fix #2694 --- louloulibs | 2 +- src/test.cpp | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/louloulibs b/louloulibs index eaa4fbb..89398b5 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit eaa4fbba814b56b4fe7ffb62984fddfbb9280291 +Subproject commit 89398b5d886744c3812b65195308cae57eca2b53 diff --git a/src/test.cpp b/src/test.cpp index 553140f..842dfed 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -295,11 +295,16 @@ int main() assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - const std::string& badjid2("Zigougou@poez.io"); + const std::string badjid2("Zigougou@poez.io"); const std::string correctjid2 = jidprep(badjid2); std::cout << correctjid2 << std::endl; assert(correctjid2 == "zigougou@poez.io"); + const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); + const std::string fixed_crappy = jidprep(crappy); + std::cout << fixed_crappy << std::endl; + assert(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); + /** * IID parsing */ -- cgit v1.2.3 From 501a3f7be25f4dbb4526bf645d7df8d29f6a54a5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 05:13:55 +0200 Subject: Fix the way we we forward an XMPP ping result to the IRC server Our CTCP notice didn't include a \01 char at the end. We thus failed to check the PING id when we received it ourself, because one char was missing --- src/bridge/bridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ef20351..6081986 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -399,7 +399,7 @@ void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, c void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id) { - this->send_private_message(iid, "\01PING "s + utils::revstr(id), "NOTICE"); + this->send_private_message(iid, "\01PING "s + utils::revstr(id) + "\01", "NOTICE"); } void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick, -- cgit v1.2.3 From 2195292919110bad41f507f45fa1b7d86a369539 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 05:15:49 +0200 Subject: Remove a debug line (breaking our PING stuf) that should not have been commited --- src/bridge/bridge.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6081986..467923b 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -418,7 +418,6 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s if (message.command == "NOTICE" && utils::tolower(user.nick) == nick && body.substr(0, 6) == "\01PING ") { - return false; const std::string id = body.substr(6, body.size() - 7); if (id != iq_id) return false; -- cgit v1.2.3 From fa466f33f4c8009e69fe0ebf31c7eef1c394377b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 06:00:07 +0200 Subject: Ignore commands that flood the user with private messages when listing chans ref #2472 --- src/irc/irc_client.cpp | 12 ++++++++++++ src/irc/irc_client.hpp | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 5f6aaaf..d048e47 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -459,6 +459,18 @@ void IrcClient::on_channel_message(const IrcMessage& message) this->bridge->send_message(iid, nick, body, muc); } +void IrcClient::on_rpl_liststart(const IrcMessage&) +{ +} + +void IrcClient::on_rpl_list(const IrcMessage&) +{ +} + +void IrcClient::on_rpl_listend(const IrcMessage&) +{ +} + void IrcClient::empty_motd(const IrcMessage& message) { (void)message; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4ab0fcb..0d1604d 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -151,6 +151,23 @@ public: * IrcUsers in the IrcChannel */ void set_and_forward_user_list(const IrcMessage& message); + /** + * Signal the start of the LIST response. The RFC says its obsolete and + * “not used”, but I we receive it on some servers, so just ignore it. + */ + void on_rpl_liststart(const IrcMessage& message); + /** + * A single LIST response line (one channel) + * + * The command is handled in a wait_irc callback. This general handler is + * empty and just used to avoid sending a message stanza for each received + * channel. + */ + void on_rpl_list(const IrcMessage& message); + /** + * Signal the end of the LIST response, ignore. + */ + void on_rpl_listend(const IrcMessage& message); /** * Remember our nick and host, when we are joined to the channel. The list * of user comes after so we do not send the self-presence over XMPP yet. @@ -324,6 +341,12 @@ static const std::unordered_map irc_callbacks = { {"002", &IrcClient::forward_server_message}, {"003", &IrcClient::forward_server_message}, {"005", &IrcClient::on_isupport_message}, + {"RPL_LISTSTART", &IrcClient::on_rpl_liststart}, + {"321", &IrcClient::on_rpl_liststart}, + {"RPL_LIST", &IrcClient::on_rpl_list}, + {"322", &IrcClient::on_rpl_list}, + {"RPL_LISTEND", &IrcClient::on_rpl_listend}, + {"323", &IrcClient::on_rpl_listend}, {"RPL_MOTDSTART", &IrcClient::empty_motd}, {"375", &IrcClient::empty_motd}, {"RPL_MOTD", &IrcClient::on_motd_line}, -- cgit v1.2.3 From e8d7965115fe6abe25d76bccedd450532549891e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 06:16:49 +0200 Subject: Handle errors for the LIST irc command ref #2472 --- src/bridge/bridge.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 467923b..c63b3da 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -317,8 +317,14 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq if (irc_hostname != iid.get_server()) return false; - if (message.command == "263" || message.command == "RPL_TRYAGAIN") - { // TODO send an error iq + if (message.command == "263" || message.command == "RPL_TRYAGAIN" || + message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER") + { + std::string text; + if (message.arguments.size() >= 2) + text = message.arguments[1]; + this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, + "wait", "service-unavailable", text, false); return true; } else if (message.command == "322" || message.command == "RPL_LIST") -- cgit v1.2.3 From a2b0f21a96a3c9ba217937d4762799e038fd5e9a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 06:21:08 +0200 Subject: Document the listing of channels fix #2472 --- doc/biboumi.1.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index faa9618..836b99d 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -224,6 +224,13 @@ messages to other users. This means that the order of the messages displayed in your XMPP client may not be the same than the order on other IRC users’. +### List channels + +You can list the IRC channels on a given IRC server by sending an XMPP disco +items request on the IRC server JID. The number of channels on some servers +is huge, and biboumi does not (yet) support result set management (XEP 0059) +so the result stanza may be very big. + ### Nicknames On IRC, nicknames are server-wide. This means that one user only has one -- cgit v1.2.3 From e454c12bd7f695d6450dc804c07aa5637a8b1c00 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 06:43:38 +0200 Subject: =?UTF-8?q?Do=20not=20forward=20CTCP=20commands=20(PING,=20VERSION?= =?UTF-8?q?=E2=80=A6)=20to=20the=20user=20as=20private=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some XMPP client ping themselves inside MUCs, to know if they are still in there, this created a flood of PING message in private. If the user is interested in knowing when they receive a ping or version request, they can still read their XML logs --- src/irc/irc_client.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d048e47..905a336 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -314,6 +314,10 @@ void IrcClient::on_notice(const IrcMessage& message) const std::string to = message.arguments[0]; const std::string body = message.arguments[1]; + if (!body.empty() && body[0] == '\01' && body[body.size() - 1] == '\01') + // Do not forward the notice to the user if it's a CTCP command + return ; + if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end()) { // The notice is for us precisely. -- cgit v1.2.3 From 2f495a60cff3fa3104f36ff3ee55f7984b6115bc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 07:01:39 +0200 Subject: Update the changelog --- CHANGELOG | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9b1acfe..15fec3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ Version 2.0 - - Support PING requests in all directions + + - List channels on an IRC server through an XMPP disco items request + - Let the user send any arbitrary raw IRC command by sending a + message to the IRC server’s JID. + - By default, look for the configuration file as per the XDG + basedir spec. + - Support PING requests in all directions. - Improve the way we forward received NOTICEs by remembering to which users we previously sent a private message. This improves the user experience when talking to NickServ. -- cgit v1.2.3 From 074286efa94163cff9d6bfc643c786d866fcde5d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 19 May 2015 23:09:17 +0200 Subject: Fix the date format in CHANGELOG file --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 15fec3d..04a7a88 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,11 +21,11 @@ Version 2.0 - Add a fixed_irc_server option. With this option enabled, biboumi can only connect to the one single IRC server configured -Version 1.1 2014-16-07 +Version 1.1 2014-07-16 - Fix a segmentation fault when connecting to an IRC server using IPv6 -Version 1.0 2014-12-07 +Version 1.0 2014-07-12 - First stable release. - Mostly complete MUC to IRC, and IRC to MUC support -- cgit v1.2.3 From 54f96debcaa80ea2d49f722b0df11d227943ebba Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 26 May 2015 14:00:25 +0200 Subject: Use git-archive-all in the make dist, to include the submodules in the archive --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 617c113..301d91d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,5 @@ endif() ## Generate a release tarball from the git sources # add_custom_target(dist - COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD - | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz + COMMAND git-archive-all ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -- cgit v1.2.3 From 897b281e67dc82700db9fd9c2dedc5e01e5871ee Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 27 May 2015 23:44:23 +0200 Subject: Avoid some potential race conditions by blocking the signals we manage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They are atomically unblocked in the ppoll/epoll_pwait calls, avoiding any race condition on the check of the “stop” or “reload” booleans. --- louloulibs | 2 +- src/main.cpp | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/louloulibs b/louloulibs index 89398b5..0f3c118 160000 --- a/louloulibs +++ b/louloulibs @@ -1 +1 @@ -Subproject commit 89398b5d886744c3812b65195308cae57eca2b53 +Subproject commit 0f3c1183e2bf0941ae2bffd3f31577bce4f3001c diff --git a/src/main.cpp b/src/main.cpp index 4a207b9..adc0c7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -99,9 +99,19 @@ int main(int ac, char** av) if (hostname.empty()) return config_help("hostname"); + // Block the signals we want to manage. They will be unblocked only during + // the epoll_pwait or ppoll calls. This avoids some race conditions, + // explained in man 2 pselect on linux + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigprocmask(SIG_BLOCK, &mask, nullptr); + // Install the signals used to exit the process cleanly, or reload the // config - sigset_t mask; sigemptyset(&mask); struct sigaction on_sigint; on_sigint.sa_sigaction = &sigint_handler; -- cgit v1.2.3 From e1a7114c8daa10589c830ce972cf461c3540111b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 May 2015 23:42:52 +0200 Subject: louloulibs is directly included, instead of being a submodule Because this is a nightmare to manage --- .gitmodules | 3 - louloulibs | 1 - louloulibs/CMakeLists.txt | 146 ++++++ louloulibs/cmake/Modules/FindBOTAN.cmake | 35 ++ louloulibs/cmake/Modules/FindCARES.cmake | 37 ++ louloulibs/cmake/Modules/FindICONV.cmake | 60 +++ louloulibs/cmake/Modules/FindLIBIDN.cmake | 41 ++ louloulibs/cmake/Modules/FindLIBUUID.cmake | 41 ++ louloulibs/cmake/Modules/FindSYSTEMD.cmake | 39 ++ louloulibs/config/config.cpp | 122 +++++ louloulibs/config/config.hpp | 103 +++++ louloulibs/logger/logger.cpp | 38 ++ louloulibs/logger/logger.hpp | 80 ++++ louloulibs/louloulibs.h.cmake | 8 + louloulibs/network/dns_handler.cpp | 112 +++++ louloulibs/network/dns_handler.hpp | 62 +++ louloulibs/network/dns_socket_handler.cpp | 45 ++ louloulibs/network/dns_socket_handler.hpp | 46 ++ louloulibs/network/poller.cpp | 225 +++++++++ louloulibs/network/poller.hpp | 95 ++++ louloulibs/network/socket_handler.hpp | 45 ++ louloulibs/network/tcp_socket_handler.cpp | 590 ++++++++++++++++++++++++ louloulibs/network/tcp_socket_handler.hpp | 293 ++++++++++++ louloulibs/utils/encoding.cpp | 254 ++++++++++ louloulibs/utils/encoding.hpp | 38 ++ louloulibs/utils/reload.cpp | 13 + louloulibs/utils/reload.hpp | 10 + louloulibs/utils/revstr.cpp | 9 + louloulibs/utils/revstr.hpp | 11 + louloulibs/utils/scopeguard.hpp | 89 ++++ louloulibs/utils/sha1.cpp | 154 +++++++ louloulibs/utils/sha1.hpp | 35 ++ louloulibs/utils/split.cpp | 19 + louloulibs/utils/split.hpp | 12 + louloulibs/utils/timed_events.cpp | 62 +++ louloulibs/utils/timed_events.hpp | 132 ++++++ louloulibs/utils/timed_events_manager.cpp | 81 ++++ louloulibs/utils/tolower.cpp | 13 + louloulibs/utils/tolower.hpp | 11 + louloulibs/xmpp/adhoc_command.cpp | 105 +++++ louloulibs/xmpp/adhoc_command.hpp | 43 ++ louloulibs/xmpp/adhoc_commands_handler.cpp | 133 ++++++ louloulibs/xmpp/adhoc_commands_handler.hpp | 74 +++ louloulibs/xmpp/adhoc_session.cpp | 37 ++ louloulibs/xmpp/adhoc_session.hpp | 70 +++ louloulibs/xmpp/body.hpp | 12 + louloulibs/xmpp/jid.cpp | 101 ++++ louloulibs/xmpp/jid.hpp | 36 ++ louloulibs/xmpp/roster.cpp | 21 + louloulibs/xmpp/roster.hpp | 71 +++ louloulibs/xmpp/xmpp_component.cpp | 716 +++++++++++++++++++++++++++++ louloulibs/xmpp/xmpp_component.hpp | 240 ++++++++++ louloulibs/xmpp/xmpp_parser.cpp | 169 +++++++ louloulibs/xmpp/xmpp_parser.hpp | 126 +++++ louloulibs/xmpp/xmpp_stanza.cpp | 274 +++++++++++ louloulibs/xmpp/xmpp_stanza.hpp | 160 +++++++ 56 files changed, 5594 insertions(+), 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 louloulibs create mode 100644 louloulibs/CMakeLists.txt create mode 100644 louloulibs/cmake/Modules/FindBOTAN.cmake create mode 100644 louloulibs/cmake/Modules/FindCARES.cmake create mode 100644 louloulibs/cmake/Modules/FindICONV.cmake create mode 100644 louloulibs/cmake/Modules/FindLIBIDN.cmake create mode 100644 louloulibs/cmake/Modules/FindLIBUUID.cmake create mode 100644 louloulibs/cmake/Modules/FindSYSTEMD.cmake create mode 100644 louloulibs/config/config.cpp create mode 100644 louloulibs/config/config.hpp create mode 100644 louloulibs/logger/logger.cpp create mode 100644 louloulibs/logger/logger.hpp create mode 100644 louloulibs/louloulibs.h.cmake create mode 100644 louloulibs/network/dns_handler.cpp create mode 100644 louloulibs/network/dns_handler.hpp create mode 100644 louloulibs/network/dns_socket_handler.cpp create mode 100644 louloulibs/network/dns_socket_handler.hpp create mode 100644 louloulibs/network/poller.cpp create mode 100644 louloulibs/network/poller.hpp create mode 100644 louloulibs/network/socket_handler.hpp create mode 100644 louloulibs/network/tcp_socket_handler.cpp create mode 100644 louloulibs/network/tcp_socket_handler.hpp create mode 100644 louloulibs/utils/encoding.cpp create mode 100644 louloulibs/utils/encoding.hpp create mode 100644 louloulibs/utils/reload.cpp create mode 100644 louloulibs/utils/reload.hpp create mode 100644 louloulibs/utils/revstr.cpp create mode 100644 louloulibs/utils/revstr.hpp create mode 100644 louloulibs/utils/scopeguard.hpp create mode 100644 louloulibs/utils/sha1.cpp create mode 100644 louloulibs/utils/sha1.hpp create mode 100644 louloulibs/utils/split.cpp create mode 100644 louloulibs/utils/split.hpp create mode 100644 louloulibs/utils/timed_events.cpp create mode 100644 louloulibs/utils/timed_events.hpp create mode 100644 louloulibs/utils/timed_events_manager.cpp create mode 100644 louloulibs/utils/tolower.cpp create mode 100644 louloulibs/utils/tolower.hpp create mode 100644 louloulibs/xmpp/adhoc_command.cpp create mode 100644 louloulibs/xmpp/adhoc_command.hpp create mode 100644 louloulibs/xmpp/adhoc_commands_handler.cpp create mode 100644 louloulibs/xmpp/adhoc_commands_handler.hpp create mode 100644 louloulibs/xmpp/adhoc_session.cpp create mode 100644 louloulibs/xmpp/adhoc_session.hpp create mode 100644 louloulibs/xmpp/body.hpp create mode 100644 louloulibs/xmpp/jid.cpp create mode 100644 louloulibs/xmpp/jid.hpp create mode 100644 louloulibs/xmpp/roster.cpp create mode 100644 louloulibs/xmpp/roster.hpp create mode 100644 louloulibs/xmpp/xmpp_component.cpp create mode 100644 louloulibs/xmpp/xmpp_component.hpp create mode 100644 louloulibs/xmpp/xmpp_parser.cpp create mode 100644 louloulibs/xmpp/xmpp_parser.hpp create mode 100644 louloulibs/xmpp/xmpp_stanza.cpp create mode 100644 louloulibs/xmpp/xmpp_stanza.hpp diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index aadcbca..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "louloulibs"] - path = louloulibs - url = git://git.louiz.org/louloulibs diff --git a/louloulibs b/louloulibs deleted file mode 160000 index 0f3c118..0000000 --- a/louloulibs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0f3c1183e2bf0941ae2bffd3f31577bce4f3001c diff --git a/louloulibs/CMakeLists.txt b/louloulibs/CMakeLists.txt new file mode 100644 index 0000000..bf53504 --- /dev/null +++ b/louloulibs/CMakeLists.txt @@ -0,0 +1,146 @@ +cmake_minimum_required(VERSION 2.6) + +set(${PROJECT_NAME}_VERSION_MAJOR 1) +set(${PROJECT_NAME}_VERSION_MINOR 0) +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") + +# Define a __FILENAME__ macro to get the filename of each file, instead of +# the full path as in __FILE__ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'") + +# +## Look for external libraries +# +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") +include(FindEXPAT) +find_package(EXPAT REQUIRED) +find_package(ICONV REQUIRED) +find_package(LIBUUID REQUIRED) + +if(WITH_LIBIDN) + find_package(LIBIDN REQUIRED) +elseif(NOT WITHOUT_LIBIDN) + find_package(LIBIDN) +endif() + +if(WITH_SYSTEMD) + find_package(SYSTEMD REQUIRED) +elseif(NOT WITHOUT_SYSTEMD) + find_package(SYSTEMD) +endif() + +if(WITH_BOTAN) + find_package(BOTAN REQUIRED) +elseif(NOT WITHOUT_BOTAN) + find_package(BOTAN) +endif() + +if(WITH_CARES) + find_package(CARES REQUIRED) +elseif(NOT WITHOUT_CARES) + find_package(CARES) +endif() + +# To be able to include the config.h file generated by cmake +include_directories("${CMAKE_CURRENT_BINARY_DIR}") +include_directories("${CMAKE_CURRENT_SOURCE_DIR}") +include_directories(${EXPAT_INCLUDE_DIRS}) +include_directories(${ICONV_INCLUDE_DIRS}) +include_directories(${LIBUUID_INCLUDE_DIRS}) + +set(EXPAT_INCLUDE_DIRS ${EXPAT_INCLUDE_DIRS} PARENT_SCOPE) +set(ICONV_INCLUDE_DIRS ${ICONV_INCLUDE_DIRS} PARENT_SCOPE) +set(LIBUUID_INCLUDE_DIRS ${LIBUUID_INCLUDE_DIRS} PARENT_SCOPE) + +if(LIBIDN_FOUND) + include_directories(${LIBIDN_INCLUDE_DIRS}) + set(LIBDIN_FOUND ${LIBDIN_FOUND} PARENT_SCOPE) + set(LIBDIN_INCLUDE_DIRS ${LIBDIN_INCLUDE_DIRS} PARENT_SCOPE) +endif() + +if(SYSTEMD_FOUND) + include_directories(${SYSTEMD_INCLUDE_DIRS}) + set(SYSTEMD_FOUND ${SYSTEMD_FOUND} PARENT_SCOPE) + set(SYSTEMD_INCLUDE_DIRS ${SYSTEMD_INCLUDE_DIRS} PARENT_SCOPE) +endif() + +if(BOTAN_FOUND) + include_directories(SYSTEM ${BOTAN_INCLUDE_DIRS}) + set(BOTAN_FOUND ${BOTAN_FOUND} PARENT_SCOPE) + set(BOTAN_INCLUDE_DIRS ${BOTAN_INCLUDE_DIRS} PARENT_SCOPE) +endif() + +if(CARES_FOUND) + include_directories(${CARES_INCLUDE_DIRS}) + set(CARES_FOUND ${CARES_FOUND} PARENT_SCOPE) + set(CARES_INCLUDE_DIRS ${CARES_INCLUDE_DIRS} PARENT_SCOPE) +endif() + +set(POLLER_DOCSTRING "Choose the poller between POLL and EPOLL (Linux-only)") +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(POLLER "EPOLL" CACHE STRING ${POLLER_DOCSTRING}) +else() + set(POLLER "POLL" CACHE STRING ${POLLER_DOCSTRING}) +endif() +if((NOT ${POLLER} MATCHES "POLL") AND + (NOT ${POLLER} MATCHES "EPOLL")) + message(FATAL_ERROR "POLLER must be either POLL or EPOLL") +endif() + +# +## utils +# +file(GLOB source_utils + utils/*.[hc]pp) +add_library(utils STATIC ${source_utils}) +target_link_libraries(utils ${ICONV_LIBRARIES}) + +# +## config +# +file(GLOB source_config + config/*.[hc]pp) +add_library(config STATIC ${source_config}) +target_link_libraries(config utils) + +# +## logger +# +file(GLOB source_logger + logger/*.[hc]pp) +add_library(logger STATIC ${source_logger}) +target_link_libraries(logger config) + +# +## network +# +file(GLOB source_network + network/*.[hc]pp) +add_library(network STATIC ${source_network}) +target_link_libraries(network logger) +if(BOTAN_FOUND) + target_link_libraries(network ${BOTAN_LIBRARIES}) +endif() +if(CARES_FOUND) + target_link_libraries(network ${CARES_LIBRARIES}) +endif() + +# +## xmpplib +# +file(GLOB source_xmpplib + xmpp/*.[hc]pp) +add_library(xmpplib STATIC ${source_xmpplib}) +target_link_libraries(xmpplib network utils logger + ${EXPAT_LIBRARIES} + ${LIBUUID_LIBRARIES}) +if(LIBIDN_FOUND) + target_link_libraries(xmpplib ${LIBIDN_LIBRARIES}) +endif() +if(SYSTEMD_FOUND) + target_link_libraries(xmpplib ${SYSTEMD_LIBRARIES}) +endif() + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/louloulibs.h.cmake ${CMAKE_BINARY_DIR}/src/louloulibs.h) diff --git a/louloulibs/cmake/Modules/FindBOTAN.cmake b/louloulibs/cmake/Modules/FindBOTAN.cmake new file mode 100644 index 0000000..a12bd35 --- /dev/null +++ b/louloulibs/cmake/Modules/FindBOTAN.cmake @@ -0,0 +1,35 @@ +# - Find botan +# Find the botan cryptographic library +# +# This module defines the following variables: +# BOTAN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# BOTAN_INCLUDE_DIRS - The directory where to find the header file +# BOTAN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# BOTAN_LIBRARY +# BOTAN_INCLUDE_DIR +# +# This file is in the public domain + +find_path(BOTAN_INCLUDE_DIRS NAMES botan/botan.h + PATH_SUFFIXES botan-1.11 + DOC "The botan include directory") + +find_library(BOTAN_LIBRARIES NAMES botan botan-1.11 + DOC "The botan library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set BOTAN_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(BOTAN REQUIRED_VARS BOTAN_LIBRARIES BOTAN_INCLUDE_DIRS) + +if(BOTAN_FOUND) + set(BOTAN_LIBRARY ${BOTAN_LIBRARIES}) + set(BOTAN_INCLUDE_DIR ${BOTAN_INCLUDE_DIRS}) +endif() + +mark_as_advanced(BOTAN_INCLUDE_DIRS BOTAN_LIBRARIES) diff --git a/louloulibs/cmake/Modules/FindCARES.cmake b/louloulibs/cmake/Modules/FindCARES.cmake new file mode 100644 index 0000000..c4c757a --- /dev/null +++ b/louloulibs/cmake/Modules/FindCARES.cmake @@ -0,0 +1,37 @@ +# - Find c-ares +# Find the c-ares library, and more particularly the stringprep header. +# +# This module defines the following variables: +# CARES_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# CARES_INCLUDE_DIRS - The directory where to find the header file +# CARES_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# CARES_INCLUDE_DIR +# CARES_LIBRARY +# +# This file is in the public domain + +if(NOT CARES_FOUND) + find_path(CARES_INCLUDE_DIRS NAMES ares.h + DOC "The c-ares include directory") + + find_library(CARES_LIBRARIES NAMES cares + DOC "The c-ares library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set CARES_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CARES REQUIRED_VARS CARES_LIBRARIES CARES_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(CARES_FOUND) + set(CARES_INCLUDE_DIR ${CARES_INCLUDE_DIRS}) + set(CARES_LIBRARY ${CARES_LIBRARIES}) + endif() +endif() + +mark_as_advanced(CARES_INCLUDE_DIRS CARES_LIBRARIES) diff --git a/louloulibs/cmake/Modules/FindICONV.cmake b/louloulibs/cmake/Modules/FindICONV.cmake new file mode 100644 index 0000000..7ca173f --- /dev/null +++ b/louloulibs/cmake/Modules/FindICONV.cmake @@ -0,0 +1,60 @@ +# - Find iconv +# Find the iconv (character set conversion) library +# +# This module defines the following variables: +# ICONV_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# ICONV_INCLUDE_DIRS - The directory where to find the header file +# ICONV_LIBRARIES - Where to find the library file +# ICONV_SECOND_ARGUMENT_IS_CONST - The second argument for iconv() is const +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# ICONV_LIBRARY +# ICONV_INCLUDE_DIR +# +# This file is in the public domain + +find_path(ICONV_INCLUDE_DIRS NAMES iconv.h + DOC "The iconv include directory") + +find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c + DOC "The iconv library") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set ICONV_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Iconv REQUIRED_VARS ICONV_LIBRARIES ICONV_INCLUDE_DIRS) + +# Check if the prototype is +# size_t iconv(iconv_t cd, char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +# or +# size_t iconv (iconv_t cd, const char** inbuf, size_t* inbytesleft, +# char** outbuf, size_t* outbytesleft); +if(ICONV_FOUND) + include(CheckCXXSourceCompiles) + + # Set the parameters needed to compile the following code. + set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARIES}) + + check_cxx_source_compiles(" + #include + int main(){ + iconv_t conv = 0; + const char* in = 0; + size_t ilen = 0; + char* out = 0; + size_t olen = 0; + iconv(conv, &in, &ilen, &out, &olen); + return 0;}" + ICONV_SECOND_ARGUMENT_IS_CONST) + +# Compatibility for all the ways of writing these variables + set(ICONV_LIBRARY ${ICONV_LIBRARIES}) + set(ICONV_INCLUDE_DIR ${ICONV_INCLUDE_DIRS}) +endif() + +mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES ICONV_SECOND_ARGUMENT_IS_CONST) \ No newline at end of file diff --git a/louloulibs/cmake/Modules/FindLIBIDN.cmake b/louloulibs/cmake/Modules/FindLIBIDN.cmake new file mode 100644 index 0000000..611a6a8 --- /dev/null +++ b/louloulibs/cmake/Modules/FindLIBIDN.cmake @@ -0,0 +1,41 @@ +# - Find libidn +# Find the libidn library, and more particularly the stringprep header. +# +# This module defines the following variables: +# LIBIDN_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBIDN_INCLUDE_DIRS - The directory where to find the header file +# LIBIDN_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LIBIDN_INCLUDE_DIR +# LIBIDN_LIBRARY +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(LIBIDN libidn) + +if(NOT LIBIDN_FOUND) + find_path(LIBIDN_INCLUDE_DIRS NAMES stringprep.h + DOC "The libidn include directory") + + # The library containing the stringprep module is libidn + find_library(LIBIDN_LIBRARIES NAMES idn + DOC "The libidn library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBIDN_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBIDN REQUIRED_VARS LIBIDN_LIBRARIES LIBIDN_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBIDN_FOUND) + set(LIBIDN_INCLUDE_DIR ${LIBIDN_INCLUDE_DIRS}) + set(LIBIDN_LIBRARY ${LIBIDN_LIBRARIES}) + endif() +endif() + +mark_as_advanced(LIBIDN_INCLUDE_DIRS LIBIDN_LIBRARIES) \ No newline at end of file diff --git a/louloulibs/cmake/Modules/FindLIBUUID.cmake b/louloulibs/cmake/Modules/FindLIBUUID.cmake new file mode 100644 index 0000000..17d3c42 --- /dev/null +++ b/louloulibs/cmake/Modules/FindLIBUUID.cmake @@ -0,0 +1,41 @@ +# - Find libuuid +# Find the libuuid library +# +# This module defines the following variables: +# LIBUUID_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LIBUUID_INCLUDE_DIRS - The directory where to find the header file +# LIBUUID_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LIBUUID_INCLUDE_DIR +# LIBUUID_LIBRARY +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(LIBUUID uuid) + +if(NOT LIBUUID_FOUND) + find_path(LIBUUID_INCLUDE_DIRS NAMES uuid.h + PATH_SUFFIXES uuid + DOC "The libuuid include directory") + + find_library(LIBUUID_LIBRARIES NAMES uuid + DOC "The libuuid library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set LIBUUID_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LIBUUID REQUIRED_VARS LIBUUID_LIBRARIES LIBUUID_INCLUDE_DIRS) + + # Compatibility for all the ways of writing these variables + if(LIBUUID_FOUND) + set(LIBUUID_INCLUDE_DIR ${LIBUUID_INCLUDE_DIRS}) + set(LIBUUID_LIBRARY ${LIBUUID_LIBRARIES}) + endif() +endif() + +mark_as_advanced(LIBUUID_INCLUDE_DIRS LIBUUID_LIBRARIES) diff --git a/louloulibs/cmake/Modules/FindSYSTEMD.cmake b/louloulibs/cmake/Modules/FindSYSTEMD.cmake new file mode 100644 index 0000000..c7decde --- /dev/null +++ b/louloulibs/cmake/Modules/FindSYSTEMD.cmake @@ -0,0 +1,39 @@ +# - Find SystemdDaemon +# Find the systemd daemon library +# +# This module defines the following variables: +# SYSTEMD_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# SYSTEMD_INCLUDE_DIRS - The directory where to find the header file +# SYSTEMD_LIBRARIES - Where to find the library file +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# SYSTEMD_LIBRARY +# SYSTEMD_INCLUDE_DIR +# +# This file is in the public domain + +include(FindPkgConfig) +pkg_check_modules(SYSTEMD libsystemd) + +if(NOT SYSTEMD_FOUND) + find_path(SYSTEMD_INCLUDE_DIRS NAMES systemd/sd-daemon.h + DOC "The Systemd include directory") + + find_library(SYSTEMD_LIBRARIES NAMES systemd + DOC "The Systemd library") + + # Use some standard module to handle the QUIETLY and REQUIRED arguments, and + # set SYSTEMD_FOUND to TRUE if these two variables are set. + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SYSTEMD REQUIRED_VARS SYSTEMD_LIBRARIES SYSTEMD_INCLUDE_DIRS) + + if(SYSTEMD_FOUND) + set(SYSTEMD_LIBRARY ${SYSTEMD_LIBRARIES}) + set(SYSTEMD_INCLUDE_DIR ${SYSTEMD_INCLUDE_DIRS}) + endif() +endif() + +mark_as_advanced(SYSTEMD_INCLUDE_DIRS SYSTEMD_LIBRARIES) \ No newline at end of file diff --git a/louloulibs/config/config.cpp b/louloulibs/config/config.cpp new file mode 100644 index 0000000..3e016f4 --- /dev/null +++ b/louloulibs/config/config.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include + +#include + +std::string Config::filename{}; +bool Config::file_must_exist = false; + +std::string Config::get(const std::string& option, const std::string& def) +{ + Config* self = Config::instance().get(); + auto it = self->values.find(option); + + if (it == self->values.end()) + return def; + return it->second; +} + +int Config::get_int(const std::string& option, const int& def) +{ + Config* self = Config::instance().get(); + std::string res = self->get(option, ""); + if (!res.empty()) + return atoi(res.c_str()); + else + return def; +} + +void Config::set(const std::string& option, const std::string& value, bool save) +{ + Config* self = Config::instance().get(); + self->values[option] = value; + if (save) + { + self->save_to_file(); + self->trigger_configuration_change(); + } +} + +void Config::connect(t_config_changed_callback callback) +{ + Config* self = Config::instance().get(); + self->callbacks.push_back(callback); +} + +void Config::close() +{ + Config* self = Config::instance().get(); + self->values.clear(); + Config::instance().reset(); +} + +/** + * Private methods + */ + +void Config::trigger_configuration_change() +{ + std::vector::iterator it; + for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it) + (*it)(); +} + +std::unique_ptr& Config::instance() +{ + static std::unique_ptr instance; + + if (!instance) + { + instance = std::make_unique(); + instance->read_conf(); + } + return instance; +} + +bool Config::read_conf() +{ + std::ifstream file; + file.open(filename.data()); + if (!file.is_open()) + { + if (Config::file_must_exist) + { + perror(("Error while opening file " + filename + " for reading.").c_str()); + file.exceptions(std::ifstream::failbit); + } + return false; + } + + std::string line; + size_t pos; + std::string option; + std::string value; + while (file.good()) + { + std::getline(file, line); + if (line == "" || line[0] == '#') + continue ; + pos = line.find('='); + if (pos == std::string::npos) + continue ; + option = line.substr(0, pos); + value = line.substr(pos+1); + this->values[option] = value; + } + return true; +} + +void Config::save_to_file() const +{ + std::ofstream file(this->filename.data()); + if (file.fail()) + { + std::cerr << "Could not save config file." << std::endl; + return ; + } + for (auto& it: this->values) + file << it.first << "=" << it.second << std::endl; + file.close(); +} diff --git a/louloulibs/config/config.hpp b/louloulibs/config/config.hpp new file mode 100644 index 0000000..e070816 --- /dev/null +++ b/louloulibs/config/config.hpp @@ -0,0 +1,103 @@ +/** + * Read the config file and save all the values in a map. + * Also, a singleton. + * + * Use Config::filename = "bla" to set the filename you want to use. + * + * If you want to exit if the file does not exist when it is open for + * reading, set Config::file_must_exist = true. + * + * Config::get() can the be used to access the values in the conf. + * + * Use Config::close() when you're done getting/setting value. This will + * save the config into the file. + */ + +#ifndef CONFIG_INCLUDED +# define CONFIG_INCLUDED + +#include +#include +#include +#include +#include +#include + +typedef std::function t_config_changed_callback; + +class Config +{ +public: + Config(){}; + ~Config(){}; + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + * @param option The option we want + * @param def The default value in case the option does not exist + */ + static std::string get(const std::string&, const std::string&); + /** + * returns a value from the config. If it doesn’t exist, use + * the second argument as the default. + * @param option The option we want + * @param def The default value in case the option does not exist + */ + static int get_int(const std::string&, const int&); + /** + * Set a value for the given option. And write all the config + * in the file from which it was read if boolean is set. + * @param option The option to set + * @param value The value to use + * @param save if true, save the config file + */ + static void set(const std::string&, const std::string&, bool save = false); + /** + * Adds a function to a list. This function will be called whenever a + * configuration change occurs. + */ + static void connect(t_config_changed_callback); + /** + * Close the config file, saving it to the file is save == true. + */ + static void close(); + + /** + * Set the value of the filename to use, before calling any method. + */ + static std::string filename; + /** + * Set to true if you want an exception to be raised if the file does not + * exist when reading it. + */ + static bool file_must_exist; + +private: + /** + * Get the singleton instance + */ + static std::unique_ptr& instance(); + /** + * Read the configuration file at the given path. + */ + bool read_conf(); + /** + * Write all the config values into the configuration file + */ + void save_to_file() const; + /** + * Call all the callbacks previously registered using connect(). + * This is used to notify any class that a configuration change occured. + */ + void trigger_configuration_change(); + + std::map values; + std::vector callbacks; + + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; +}; + +#endif // CONFIG_INCLUDED diff --git a/louloulibs/logger/logger.cpp b/louloulibs/logger/logger.cpp new file mode 100644 index 0000000..7336579 --- /dev/null +++ b/louloulibs/logger/logger.cpp @@ -0,0 +1,38 @@ +#include +#include + +Logger::Logger(const int log_level): + log_level(log_level), + stream(std::cout.rdbuf()) +{ +} + +Logger::Logger(const int log_level, const std::string& log_file): + log_level(log_level), + ofstream(log_file.data(), std::ios_base::app), + stream(ofstream.rdbuf()) +{ +} + +std::unique_ptr& Logger::instance() +{ + static std::unique_ptr instance; + + if (!instance) + { + const std::string log_file = Config::get("log_file", ""); + const int log_level = Config::get_int("log_level", 0); + if (log_file.empty()) + instance = std::make_unique(log_level); + else + instance = std::make_unique(log_level, log_file); + } + return instance; +} + +std::ostream& Logger::get_stream(const int lvl) +{ + if (lvl >= this->log_level) + return this->stream; + return this->null_stream; +} diff --git a/louloulibs/logger/logger.hpp b/louloulibs/logger/logger.hpp new file mode 100644 index 0000000..78b1278 --- /dev/null +++ b/louloulibs/logger/logger.hpp @@ -0,0 +1,80 @@ +#ifndef LOGGER_INCLUDED +# define LOGGER_INCLUDED + +/** + * Singleton used in logger macros to write into files or stdout, with + * various levels of severity. + * Only the macros should be used. + * @class Logger + */ + +#include +#include +#include + +#define debug_lvl 0 +#define info_lvl 1 +#define warning_lvl 2 +#define error_lvl 3 + +#include "louloulibs.h" +#ifdef SYSTEMD_FOUND +# include +#else +# define SD_DEBUG "[DEBUG]: " +# define SD_INFO "[INFO]: " +# define SD_WARNING "[WARNING]: " +# define SD_ERR "[ERROR]: " +#endif + +// Macro defined to get the filename instead of the full path. But if it is +// not properly defined by the build system, we fallback to __FILE__ +#ifndef __FILENAME__ +# define __FILENAME__ __FILE__ +#endif + +#define WHERE\ + __FILENAME__ << ":" << __LINE__ + +#define log_debug(text)\ + Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl; + +#define log_info(text)\ + Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl; + +#define log_warning(text)\ + Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl; + +#define log_error(text)\ + Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl; + +/** + * Juste a structure representing a stream doing nothing with its input. + */ +class nullstream: public std::ostream +{ +public: + nullstream(): + std::ostream(0) + { } +}; + +class Logger +{ +public: + static std::unique_ptr& instance(); + std::ostream& get_stream(const int); + Logger(const int log_level, const std::string& log_file); + Logger(const int log_level); + +private: + Logger(const Logger&); + Logger& operator=(const Logger&); + + const int log_level; + std::ofstream ofstream; + nullstream null_stream; + std::ostream stream; +}; + +#endif // LOGGER_INCLUDED diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake new file mode 100644 index 0000000..707d3fe --- /dev/null +++ b/louloulibs/louloulibs.h.cmake @@ -0,0 +1,8 @@ +#define SYSTEM_NAME "${CMAKE_SYSTEM}" +#cmakedefine ICONV_SECOND_ARGUMENT_IS_CONST +#cmakedefine LIBIDN_FOUND +#cmakedefine SYSTEMD_FOUND +#cmakedefine POLLER ${POLLER} +#cmakedefine BOTAN_FOUND +#cmakedefine CARES_FOUND +#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" \ No newline at end of file diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp new file mode 100644 index 0000000..ec53683 --- /dev/null +++ b/louloulibs/network/dns_handler.cpp @@ -0,0 +1,112 @@ +#include +#ifdef CARES_FOUND + +#include +#include +#include +#include + +#include +#include + +DNSHandler DNSHandler::instance; + +using namespace std::string_literals; + +void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent) +{ + TCPSocketHandler* socket_handler = static_cast(arg); + socket_handler->on_hostname4_resolved(status, hostent); +} + +void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent) +{ + TCPSocketHandler* socket_handler = static_cast(arg); + socket_handler->on_hostname6_resolved(status, hostent); +} + +DNSHandler::DNSHandler() +{ + int ares_error; + if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) + throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); + if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS) + throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); +} + +ares_channel& DNSHandler::get_channel() +{ + return this->channel; +} + +void DNSHandler::destroy() +{ + this->socket_handlers.clear(); + ::ares_destroy(this->channel); + ::ares_library_cleanup(); +} + +void DNSHandler::gethostbyname(const std::string& name, + TCPSocketHandler* socket_handler, int family) +{ + socket_handler->free_cares_addrinfo(); + if (family == AF_INET) + ::ares_gethostbyname(this->channel, name.data(), family, + &::on_hostname4_resolved, socket_handler); + else + ::ares_gethostbyname(this->channel, name.data(), family, + &::on_hostname6_resolved, socket_handler); +} + +void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) +{ + fd_set readers; + fd_set writers; + + FD_ZERO(&readers); + FD_ZERO(&writers); + + int ndfs = ::ares_fds(this->channel, &readers, &writers); + // For each existing DNS socket, see if we are still supposed to watch it, + // if not then erase it + this->socket_handlers.erase( + std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), + [&readers](const auto& dns_socket) + { + return !FD_ISSET(dns_socket->get_socket(), &readers); + }), + this->socket_handlers.end()); + + for (auto i = 0; i < ndfs; ++i) + { + bool read = FD_ISSET(i, &readers); + bool write = FD_ISSET(i, &writers); + // Look for the DNSSocketHandler with this fd + auto it = std::find_if(this->socket_handlers.begin(), + this->socket_handlers.end(), + [i](const auto& socket_handler) + { + return i == socket_handler->get_socket(); + }); + if (!read && !write) // No need to read or write to it + { // If found, erase it and stop watching it because it is not + // needed anymore + if (it != this->socket_handlers.end()) + // The socket destructor removes it from the poller + this->socket_handlers.erase(it); + } + else // We need to write and/or read to it + { // If not found, create it because we need to watch it + if (it == this->socket_handlers.end()) + { + this->socket_handlers.emplace_front(std::make_unique(poller, i)); + it = this->socket_handlers.begin(); + } + poller->add_socket_handler(it->get()); + if (write) + poller->watch_send_events(it->get()); + } + } +} + +#endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp new file mode 100644 index 0000000..a515f52 --- /dev/null +++ b/louloulibs/network/dns_handler.hpp @@ -0,0 +1,62 @@ +#ifndef DNS_HANDLER_HPP_INCLUDED +#define DNS_HANDLER_HPP_INCLUDED + +#include +#ifdef CARES_FOUND + +class TCPSocketHandler; +class Poller; +class DNSSocketHandler; + +# include +# include +# include +# include + +void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); +void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); + +/** + * Class managing DNS resolution. It should only be statically instanciated + * once in SocketHandler. It manages ares channel and calls various + * functions of that library. + */ + +class DNSHandler +{ +public: + DNSHandler(); + ~DNSHandler() = default; + void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler, + int family); + /** + * Call ares_fds to know what fd needs to be watched by the poller, create + * or destroy DNSSocketHandlers depending on the result. + */ + void watch_dns_sockets(std::shared_ptr& poller); + /** + * Destroy and stop watching all the DNS sockets. Then de-init the channel + * and library. + */ + void destroy(); + ares_channel& get_channel(); + + static DNSHandler instance; + +private: + /** + * The list of sockets that needs to be watched, according to the last + * call to ares_fds. DNSSocketHandlers are added to it or removed from it + * in the watch_dns_sockets() method + */ + std::list> socket_handlers; + ares_channel channel; + + DNSHandler(const DNSHandler&) = delete; + DNSHandler(DNSHandler&&) = delete; + DNSHandler& operator=(const DNSHandler&) = delete; + DNSHandler& operator=(DNSHandler&&) = delete; +}; + +#endif /* CARES_FOUND */ +#endif /* DNS_HANDLER_HPP_INCLUDED */ diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp new file mode 100644 index 0000000..124c9b2 --- /dev/null +++ b/louloulibs/network/dns_socket_handler.cpp @@ -0,0 +1,45 @@ +#include +#ifdef CARES_FOUND + +#include +#include +#include + +#include + +DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, + const socket_t socket): + SocketHandler(poller, socket) +{ +} + +DNSSocketHandler::~DNSSocketHandler() +{ +} + +void DNSSocketHandler::connect() +{ +} + +void DNSSocketHandler::on_recv() +{ + // always stop watching send and read events. We will re-watch them if the + // next call to ares_fds tell us to + this->poller->remove_socket_handler(this->socket); + ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); +} + +void DNSSocketHandler::on_send() +{ + // always stop watching send and read events. We will re-watch them if the + // next call to ares_fds tell us to + this->poller->remove_socket_handler(this->socket); + ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); +} + +bool DNSSocketHandler::is_connected() const +{ + return true; +} + +#endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp new file mode 100644 index 0000000..ad119e1 --- /dev/null +++ b/louloulibs/network/dns_socket_handler.hpp @@ -0,0 +1,46 @@ +#ifndef DNS_SOCKET_HANDLER_HPP +# define DNS_SOCKET_HANDLER_HPP + +#include +#ifdef CARES_FOUND + +#include +#include + +/** + * Manage a socket returned by ares_fds. We do not create, open or close the + * socket ourself: this is done by c-ares. We just call ares_process_fd() + * with the correct parameters, depending on what can be done on that socket + * (Poller reported it to be writable or readeable) + */ + +class DNSSocketHandler: public SocketHandler +{ +public: + explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); + ~DNSSocketHandler(); + /** + * Just call dns_process_fd, c-ares will do its work of send()ing or + * recv()ing the data it wants on that socket. + */ + void on_recv() override final; + void on_send() override final; + /** + * Do nothing, because we are always considered to be connected, since the + * connection is done by c-ares and not by us. + */ + void connect() override final; + /** + * Always true, see the comment for connect() + */ + bool is_connected() const override final; + +private: + DNSSocketHandler(const DNSSocketHandler&) = delete; + DNSSocketHandler(DNSSocketHandler&&) = delete; + DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; + DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; +}; + +#endif // CARES_FOUND +#endif // DNS_SOCKET_HANDLER_HPP diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp new file mode 100644 index 0000000..329e1c8 --- /dev/null +++ b/louloulibs/network/poller.cpp @@ -0,0 +1,225 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +Poller::Poller() +{ +#if POLLER == POLL + this->nfds = 0; +#elif POLLER == EPOLL + this->epfd = ::epoll_create1(0); + if (this->epfd == -1) + { + log_error("epoll failed: " << strerror(errno)); + throw std::runtime_error("Could not create epoll instance"); + } +#endif +} + +Poller::~Poller() +{ +} + +void Poller::add_socket_handler(SocketHandler* socket_handler) +{ + // Don't do anything if the socket is already managed + const auto it = this->socket_handlers.find(socket_handler->get_socket()); + if (it != this->socket_handlers.end()) + return ; + + this->socket_handlers.emplace(socket_handler->get_socket(), socket_handler); + + // We always watch all sockets for receive events +#if POLLER == POLL + this->fds[this->nfds].fd = socket_handler->get_socket(); + this->fds[this->nfds].events = POLLIN; + this->nfds++; +#endif +#if POLLER == EPOLL + struct epoll_event event = {EPOLLIN, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: " << strerror(errno)); + throw std::runtime_error("Could not add socket to epoll"); + } +#endif +} + +void Poller::remove_socket_handler(const socket_t socket) +{ + const auto it = this->socket_handlers.find(socket); + if (it == this->socket_handlers.end()) + throw std::runtime_error("Trying to remove a SocketHandler that is not managed"); + this->socket_handlers.erase(it); + +#if POLLER == POLL + for (size_t i = 0; i < this->nfds; i++) + { + if (this->fds[i].fd == socket) + { + // Move all subsequent pollfd by one on the left, erasing the + // value of the one we remove + for (size_t j = i; j < this->nfds - 1; ++j) + { + this->fds[j].fd = this->fds[j+1].fd; + this->fds[j].events= this->fds[j+1].events; + } + this->nfds--; + } + } +#elif POLLER == EPOLL + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); + if (res == -1) + { + log_error("epoll_ctl failed: " << strerror(errno)); + throw std::runtime_error("Could not remove socket from epoll"); + } +#endif +} + +void Poller::watch_send_events(SocketHandler* socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i <= this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN|POLLOUT; + return; + } + } + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event = {EPOLLIN|EPOLLOUT, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: " << strerror(errno)); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif +} + +void Poller::stop_watching_send_events(SocketHandler* socket_handler) +{ +#if POLLER == POLL + for (size_t i = 0; i <= this->nfds; ++i) + { + if (this->fds[i].fd == socket_handler->get_socket()) + { + this->fds[i].events = POLLIN; + return; + } + } + throw std::runtime_error("Cannot watch a non-registered socket for send events"); +#elif POLLER == EPOLL + struct epoll_event event = {EPOLLIN, {socket_handler}}; + const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); + if (res == -1) + { + log_error("epoll_ctl failed: " << strerror(errno)); + throw std::runtime_error("Could not modify socket flags in epoll"); + } +#endif +} + +int Poller::poll(const std::chrono::milliseconds& timeout) +{ + if (this->socket_handlers.empty() && timeout == utils::no_timeout) + return -1; +#if POLLER == POLL + // Convert our nice timeout into this ugly struct + struct timespec timeout_ts; + struct timespec* timeout_tsp; + if (timeout > 0s) + { + auto seconds = std::chrono::duration_cast(timeout); + timeout_ts.tv_sec = seconds.count(); + timeout_ts.tv_nsec = std::chrono::duration_cast(timeout - seconds).count(); + timeout_tsp = &timeout_ts; + } + else + timeout_tsp = nullptr; + + // Unblock all signals, only during the ppoll call + sigset_t empty_signal_set; + sigemptyset(&empty_signal_set); + int nb_events = ::ppoll(this->fds, this->nfds, timeout_tsp, + &empty_signal_set); + if (nb_events < 0) + { + if (errno == EINTR) + return true; + log_error("poll failed: " << strerror(errno)); + throw std::runtime_error("Poll failed"); + } + // We cannot possibly have more ready events than the number of fds we are + // watching + assert(static_cast(nb_events) <= this->nfds); + for (size_t i = 0; i <= this->nfds && nb_events != 0; ++i) + { + if (this->fds[i].revents == 0) + continue; + else if (this->fds[i].revents & POLLIN) + { + auto socket_handler = this->socket_handlers.at(this->fds[i].fd); + socket_handler->on_recv(); + nb_events--; + } + else if (this->fds[i].revents & POLLOUT) + { + auto socket_handler = this->socket_handlers.at(this->fds[i].fd); + if (socket_handler->is_connected()) + socket_handler->on_send(); + else + socket_handler->connect(); + nb_events--; + } + } + return 1; +#elif POLLER == EPOLL + static const size_t max_events = 12; + struct epoll_event revents[max_events]; + // Unblock all signals, only during the epoll_pwait call + sigset_t empty_signal_set; + sigemptyset(&empty_signal_set); + const int nb_events = ::epoll_pwait(this->epfd, revents, max_events, timeout.count(), + &empty_signal_set); + if (nb_events == -1) + { + if (errno == EINTR) + return 0; + log_error("epoll wait: " << strerror(errno)); + throw std::runtime_error("Epoll_wait failed"); + } + for (int i = 0; i < nb_events; ++i) + { + auto socket_handler = static_cast(revents[i].data.ptr); + if (revents[i].events & EPOLLIN) + socket_handler->on_recv(); + else if (revents[i].events & EPOLLOUT) + { + if (socket_handler->is_connected()) + socket_handler->on_send(); + else + socket_handler->connect(); + } + } + return nb_events; +#endif +} + +size_t Poller::size() const +{ + return this->socket_handlers.size(); +} diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp new file mode 100644 index 0000000..de0cb48 --- /dev/null +++ b/louloulibs/network/poller.hpp @@ -0,0 +1,95 @@ +#ifndef POLLER_INCLUDED +# define POLLER_INCLUDED + +#include + +#include +#include +#include + +#define POLL 1 +#define EPOLL 2 +#define KQUEUE 3 +#include +#ifndef POLLER + #define POLLER POLL +#endif + +#if POLLER == POLL + #include + #define MAX_POLL_FD_NUMBER 4096 +#elif POLLER == EPOLL + #include +#else + #error Invalid POLLER value +#endif + +/** + * We pass some SocketHandlers to this Poller, which uses + * poll/epoll/kqueue/select etc to wait for events on these SocketHandlers, + * and call the callbacks when event occurs. + * + * TODO: support these pollers: + * - kqueue(2) + */ + +class Poller +{ +public: + explicit Poller(); + ~Poller(); + /** + * Add a SocketHandler to be monitored by this Poller. All receive events + * are always automatically watched. + */ + void add_socket_handler(SocketHandler* socket_handler); + /** + * Remove (and stop managing) a SocketHandler, designated by the given socket_t. + */ + void remove_socket_handler(const socket_t socket); + /** + * Signal the poller that he needs to watch for send events for the given + * SocketHandler. + */ + void watch_send_events(SocketHandler* socket_handler); + /** + * Signal the poller that he needs to stop watching for send events for + * this SocketHandler. + */ + void stop_watching_send_events(SocketHandler* socket_handler); + /** + * Wait for all watched events, and call the SocketHandlers' callbacks + * when one is ready. Returns if nothing happened before the provided + * timeout. If the timeout is 0, it waits forever. If there is no + * watched event, returns -1 immediately, ignoring the timeout value. + * Otherwise, returns the number of event handled. If 0 is returned this + * means that we were interrupted by a signal, or the timeout occured. + */ + int poll(const std::chrono::milliseconds& timeout); + /** + * Returns the number of SocketHandlers managed by the poller. + */ + size_t size() const; + +private: + /** + * A "list" of all the SocketHandlers that we manage, indexed by socket, + * because that's what is returned by select/poll/etc when an event + * occures. + */ + std::unordered_map socket_handlers; + +#if POLLER == POLL + struct pollfd fds[MAX_POLL_FD_NUMBER]; + nfds_t nfds; +#elif POLLER == EPOLL + int epfd; +#endif + + Poller(const Poller&) = delete; + Poller(Poller&&) = delete; + Poller& operator=(const Poller&) = delete; + Poller& operator=(Poller&&) = delete; +}; + +#endif // POLLER_INCLUDED diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp new file mode 100644 index 0000000..d01ac5d --- /dev/null +++ b/louloulibs/network/socket_handler.hpp @@ -0,0 +1,45 @@ +#ifndef SOCKET_HANDLER_HPP +# define SOCKET_HANDLER_HPP + +#include +#include + +class Poller; + +typedef int socket_t; + +class SocketHandler +{ +public: + explicit SocketHandler(std::shared_ptr poller, const socket_t socket): + poller(poller), + socket(socket) + {} + virtual ~SocketHandler() {} + virtual void on_recv() = 0; + virtual void on_send() = 0; + virtual void connect() = 0; + virtual bool is_connected() const = 0; + + socket_t get_socket() const + { return this->socket; } + +protected: + /** + * A pointer to the poller that manages us, because we need to communicate + * with it. + */ + std::shared_ptr poller; + /** + * The handled socket. + */ + socket_t socket; + +private: + SocketHandler(const SocketHandler&) = delete; + SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; +}; + +#endif // SOCKET_HANDLER_HPP diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp new file mode 100644 index 0000000..f647b86 --- /dev/null +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -0,0 +1,590 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef BOTAN_FOUND +# include + +Botan::AutoSeeded_RNG TCPSocketHandler::rng; +Permissive_Credentials_Manager TCPSocketHandler::credential_manager; +Botan::TLS::Policy TCPSocketHandler::policy; +Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); + +#endif + +#ifndef UIO_FASTIOV +# define UIO_FASTIOV 8 +#endif + +using namespace std::string_literals; +using namespace std::chrono_literals; + +namespace ph = std::placeholders; + +TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): + SocketHandler(poller, -1), + use_tls(false), + connected(false), + connecting(false) +#ifdef CARES_FOUND + ,resolving(false), + resolved(false), + resolved4(false), + resolved6(false), + cares_addrinfo(nullptr), + cares_error() +#endif +{} + +TCPSocketHandler::~TCPSocketHandler() +{ +#ifdef CARES_FOUND + this->free_cares_addrinfo(); +#endif +} + +void TCPSocketHandler::init_socket(const struct addrinfo* rp) +{ + if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) + throw std::runtime_error("Could not create socket: "s + strerror(errno)); + int optval = 1; + if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) + log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); + // Set the socket on non-blocking mode. This is useful to receive a EAGAIN + // error when connect() would block, to not block the whole process if a + // remote is not responsive. + const int existing_flags = ::fcntl(this->socket, F_GETFL, 0); + if ((existing_flags == -1) || + (::fcntl(this->socket, F_SETFL, existing_flags | O_NONBLOCK) == -1)) + throw std::runtime_error("Could not initialize socket: "s + strerror(errno)); +} + +void TCPSocketHandler::connect(const std::string& address, const std::string& port, const bool tls) +{ + this->address = address; + this->port = port; + this->use_tls = tls; + + utils::ScopeGuard sg; + + struct addrinfo* addr_res; + + if (!this->connecting) + { + // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if + // this is the first call of this function. +#ifdef CARES_FOUND + if (!this->resolved) + { + log_info("Trying to connect to " << address << ":" << port); + // Start the asynchronous process of resolving the hostname. Once + // the addresses have been found and `resolved` has been set to true + // (but connecting will still be false), TCPSocketHandler::connect() + // needs to be called, again. + this->resolving = true; + DNSHandler::instance.gethostbyname(address, this, AF_INET6); + DNSHandler::instance.gethostbyname(address, this, AF_INET); + return; + } + else + { + // The c-ares resolved the hostname and the available addresses + // where saved in the cares_addrinfo linked list. Now, just use + // this list to try to connect. + addr_res = this->cares_addrinfo; + if (!addr_res) + { + this->close(); + this->on_connection_failed(this->cares_error); + return ; + } + } +#else + log_info("Trying to connect to " << address << ":" << port); + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); + + if (res != 0) + { + log_warning("getaddrinfo failed: "s + gai_strerror(res)); + this->close(); + this->on_connection_failed(gai_strerror(res)); + return ; + } + // Make sure the alloced structure is always freed at the end of the + // function + sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); +#endif + } + else + { // This function is called again, use the saved addrinfo structure, + // instead of re-doing the whole getaddrinfo process. + addr_res = &this->addrinfo; + } + + for (struct addrinfo* rp = addr_res; rp; rp = rp->ai_next) + { + if (!this->connecting) + { + try { + this->init_socket(rp); + } + catch (const std::runtime_error& error) { + log_error("Failed to init socket: " << error.what()); + break; + } + } + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 + || errno == EISCONN) + { + log_info("Connection success."); + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + this->poller->add_socket_handler(this); + this->connected = true; + this->connecting = false; +#ifdef BOTAN_FOUND + if (this->use_tls) + this->start_tls(); +#endif + this->on_connected(); + return ; + } + else if (errno == EINPROGRESS || errno == EALREADY) + { // retry this process later, when the socket + // is ready to be written on. + this->connecting = true; + this->poller->add_socket_handler(this); + this->poller->watch_send_events(this); + // Save the addrinfo structure, to use it on the next call + this->ai_addrlen = rp->ai_addrlen; + memcpy(&this->ai_addr, rp->ai_addr, this->ai_addrlen); + memcpy(&this->addrinfo, rp, sizeof(struct addrinfo)); + this->addrinfo.ai_addr = reinterpret_cast(&this->ai_addr); + this->addrinfo.ai_next = nullptr; + // If the connection has not succeeded or failed in 5s, we consider + // it to have failed + TimedEventsManager::instance().add_event( + TimedEvent(std::chrono::steady_clock::now() + 5s, + std::bind(&TCPSocketHandler::on_connection_timeout, this), + "connection_timeout"s + std::to_string(this->socket))); + return ; + } + log_info("Connection failed:" << strerror(errno)); + } + log_error("All connection attempts failed."); + this->close(); + this->on_connection_failed(strerror(errno)); + return ; +} + +void TCPSocketHandler::on_connection_timeout() +{ + this->close(); + this->on_connection_failed("connection timed out"); +} + +void TCPSocketHandler::connect() +{ + this->connect(this->address, this->port, this->use_tls); +} + +void TCPSocketHandler::on_recv() +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_recv(); + else +#endif + this->plain_recv(); +} + +void TCPSocketHandler::plain_recv() +{ + static constexpr size_t buf_size = 4096; + char buf[buf_size]; + void* recv_buf = this->get_receive_buffer(buf_size); + + if (recv_buf == nullptr) + recv_buf = buf; + + const ssize_t size = this->do_recv(recv_buf, buf_size); + + if (size > 0) + { + if (buf == recv_buf) + { + // data needs to be placed in the in_buf string, because no buffer + // was provided to receive that data directly. The in_buf buffer + // will be handled in parse_in_buffer() + this->in_buf += std::string(buf, size); + } + this->parse_in_buffer(size); + } +} + +ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) +{ + ssize_t size = ::recv(this->socket, recv_buf, buf_size, 0); + if (0 == size) + { + this->on_connection_close(""); + this->close(); + } + else if (-1 == size) + { + log_warning("Error while reading from socket: " << strerror(errno)); + // Remember if we were connecting, or already connected when this + // happened, because close() sets this->connecting to false + const auto were_connecting = this->connecting; + this->close(); + if (were_connecting) + this->on_connection_failed(strerror(errno)); + else + this->on_connection_close(strerror(errno)); + } + return size; +} + +void TCPSocketHandler::on_send() +{ + struct iovec msg_iov[UIO_FASTIOV] = {}; + struct msghdr msg{nullptr, 0, + msg_iov, + 0, nullptr, 0, 0}; + for (std::string& s: this->out_buf) + { + // unconsting the content of s is ok, sendmsg will never modify it + msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); + msg_iov[msg.msg_iovlen].iov_len = s.size(); + if (++msg.msg_iovlen == UIO_FASTIOV) + break; + } + ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); + if (res < 0) + { + log_error("sendmsg failed: " << strerror(errno)); + this->on_connection_close(strerror(errno)); + this->close(); + } + else + { + // remove all the strings that were successfully sent. + for (auto it = this->out_buf.begin(); + it != this->out_buf.end();) + { + if (static_cast(res) >= (*it).size()) + { + res -= (*it).size(); + it = this->out_buf.erase(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); + break; + } + } + if (this->out_buf.empty()) + this->poller->stop_watching_send_events(this); + } +} + +void TCPSocketHandler::close() +{ + TimedEventsManager::instance().cancel("connection_timeout"s + + std::to_string(this->socket)); + if (this->connected || this->connecting) + this->poller->remove_socket_handler(this->get_socket()); + if (this->socket != -1) + { + ::close(this->socket); + this->socket = -1; + } + this->connected = false; + this->connecting = false; +#ifdef CARES_FOUND + this->resolving = false; + this->resolved = false; + this->resolved4 = false; + this->resolved6 = false; + this->free_cares_addrinfo(); + this->cares_error.clear(); +#endif + this->in_buf.clear(); + this->out_buf.clear(); + this->port.clear(); +} + +void TCPSocketHandler::send_data(std::string&& data) +{ +#ifdef BOTAN_FOUND + if (this->use_tls) + this->tls_send(std::move(data)); + else +#endif + this->raw_send(std::move(data)); +} + +void TCPSocketHandler::raw_send(std::string&& data) +{ + if (data.empty()) + return ; + this->out_buf.emplace_back(std::move(data)); + if (this->connected) + this->poller->watch_send_events(this); +} + +void TCPSocketHandler::send_pending_data() +{ + if (this->connected && !this->out_buf.empty()) + this->poller->watch_send_events(this); +} + +bool TCPSocketHandler::is_connected() const +{ + return this->connected; +} + +bool TCPSocketHandler::is_connecting() const +{ +#ifdef CARES_FOUND + return this->connecting || this->resolving; +#else + return this->connecting; +#endif +} + +void* TCPSocketHandler::get_receive_buffer(const size_t) const +{ + return nullptr; +} + +#ifdef BOTAN_FOUND +void TCPSocketHandler::start_tls() +{ + Botan::TLS::Server_Information server_info(this->address, "irc", std::stoul(this->port)); + this->tls = std::make_unique( + std::bind(&TCPSocketHandler::tls_output_fn, this, ph::_1, ph::_2), + std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2), + std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), + std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1), + session_manager, credential_manager, policy, + rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); +} + +void TCPSocketHandler::tls_recv() +{ + static constexpr size_t buf_size = 4096; + char 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(); + this->tls->received_data(reinterpret_cast(recv_buf), + static_cast(size)); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } +} + +void TCPSocketHandler::tls_send(std::string&& data) +{ + if (this->tls->is_active()) + { + const bool was_active = this->tls->is_active(); + if (!this->pre_buf.empty()) + { + this->tls->send(reinterpret_cast(this->pre_buf.data()), + this->pre_buf.size()); + this->pre_buf = ""; + } + if (!data.empty()) + this->tls->send(reinterpret_cast(data.data()), + data.size()); + if (!was_active && this->tls->is_active()) + this->on_tls_activated(); + } + else + this->pre_buf += data; +} + +void TCPSocketHandler::tls_data_cb(const Botan::byte* data, size_t size) +{ + this->in_buf += std::string(reinterpret_cast(data), + size); + if (!this->in_buf.empty()) + this->parse_in_buffer(size); +} + +void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size) +{ + this->raw_send(std::string(reinterpret_cast(data), size)); +} + +void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) +{ + log_debug("tls_alert: " << alert.type_string()); +} + +bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) +{ + log_debug("Handshake with " << session.server_info().hostname() << " complete." + << " Version: " << session.version().to_string() + << " using " << session.ciphersuite().to_string()); + if (!session.session_id().empty()) + log_debug("Session ID " << Botan::hex_encode(session.session_id())); + if (!session.session_ticket().empty()) + log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); + return true; +} + +void TCPSocketHandler::on_tls_activated() +{ + this->send_data(""); +} + +void Permissive_Credentials_Manager::verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector&) +{ // TODO: Offer the admin to disallow connection on untrusted + // certificates + log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); +} + +#endif // BOTAN_FOUND + +#ifdef CARES_FOUND + +void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent) +{ + this->resolved4 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo4(hostent); + else + this->cares_error = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + { + this->resolved = true; + this->resolving = false; + this->connect(); + } +} + +void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent) +{ + this->resolved6 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo6(hostent); + else + this->cares_error = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + { + this->resolved = true; + this->resolving = false; + this->connect(); + } +} + +void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent) +{ + struct addrinfo* prev = this->cares_addrinfo; + struct in_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent) +{ + struct addrinfo* prev = this->cares_addrinfo; + struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +void TCPSocketHandler::free_cares_addrinfo() +{ + while (this->cares_addrinfo) + { + delete this->cares_addrinfo->ai_addr; + auto next = this->cares_addrinfo->ai_next; + delete this->cares_addrinfo; + this->cares_addrinfo = next; + } +} + +#endif // CARES_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp new file mode 100644 index 0000000..2b5cd49 --- /dev/null +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -0,0 +1,293 @@ +#ifndef SOCKET_HANDLER_INCLUDED +# define SOCKET_HANDLER_INCLUDED + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "louloulibs.h" + +#ifdef CARES_FOUND +# include +#endif + +#ifdef BOTAN_FOUND +# include +# include + +/** + * A very simple credential manager that accepts any certificate. + */ +class Permissive_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + void verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector&); +}; +#endif // BOTAN_FOUND + +/** + * An interface, with a series of callbacks that should be implemented in + * subclasses that deal with a socket. These callbacks are called on various events + * (read/write/timeout, etc) when they are notified to a poller + * (select/poll/epoll etc) + */ +class TCPSocketHandler: public SocketHandler +{ +protected: + ~TCPSocketHandler(); + +public: + explicit TCPSocketHandler(std::shared_ptr poller); + /** + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. + */ + void connect(const std::string& address, const std::string& port, const bool tls); + void connect() override final; + /** + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() + */ + void on_recv() override final; + /** + * Write as much data from out_buf as possible, in the socket. + */ + void on_send() override final; + /** + * Add the given data to out_buf and tell our poller that we want to be + * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. + */ + void send_data(std::string&& data); + /** + * Watch the socket for send events, if our out buffer is not empty. + */ + void send_pending_data(); + /** + * Close the connection, remove us from the poller + */ + void close(); + /** + * Called by a TimedEvent, when the connection did not succeed or fail + * after a given time. + */ + void on_connection_timeout(); + /** + * Called when the connection is successful. + */ + virtual void on_connected() = 0; + /** + * Called when the connection fails. Not when it is closed later, just at + * the connect() call. + */ + virtual void on_connection_failed(const std::string& reason) = 0; + /** + * Called when we detect a disconnection from the remote host. + */ + virtual void on_connection_close(const std::string& error) = 0; + /** + * Handle/consume (some of) the data received so far. The data to handle + * may be in the in_buf buffer, or somewhere else, depending on what + * get_receive_buffer() returned. If some data is used from in_buf, it + * should be truncated, only the unused data should be left untouched. + * + * The size argument is the size of the last chunk of data that was added to the buffer. + */ + virtual void parse_in_buffer(const size_t size) = 0; + bool is_connected() const override final; + bool is_connecting() const; + +#ifdef CARES_FOUND + void on_hostname4_resolved(int status, struct hostent* hostent); + void on_hostname6_resolved(int status, struct hostent* hostent); + + void free_cares_addrinfo(); + + void fill_ares_addrinfo4(const struct hostent* hostent); + void fill_ares_addrinfo6(const struct hostent* hostent); +#endif + +private: + /** + * Initialize the socket with the parameters contained in the given + * addrinfo structure. + */ + void init_socket(const struct addrinfo* rp); + /** + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. + */ + ssize_t do_recv(void* recv_buf, const size_t buf_size); + /** + * Reads data from the socket and calls parse_in_buffer with it. + */ + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(); + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_data_cb(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_output_fn(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_handshake_cb(const Botan::TLS::Session& session); + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * Where data is added, when we want to send something to the client. + */ + std::list out_buf; + /** + * Keep the details of the addrinfo that triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct addrinfo addrinfo; + struct sockaddr_in6 ai_addr; + socklen_t ai_addrlen; + +protected: + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + + bool connected; + bool connecting; + +#ifdef CARES_FOUND + bool resolving; + /** + * Whether or not the DNS resolution was successfully done + */ + bool resolved; + bool resolved4; + bool resolved6; + /** + * When using c-ares to resolve the host asynchronously, we need the + * c-ares callback to fill a structure (a struct addrinfo, for + * compatibility with getaddrinfo and the rest of the code that works when + * c-ares is not used) with all returned values (for example an IPv6 and + * an IPv4). The next call of connect() will then try all these values + * (exactly like we do with the result of getaddrinfo) and save the one + * that worked (or returned EINPROGRESS) in the other struct addrinfo (see + * the members addrinfo, ai_addrlen, and ai_addr). + */ + struct addrinfo* cares_addrinfo; + std::string cares_error; +#endif // CARES_FOUND + +private: + TCPSocketHandler(const TCPSocketHandler&) = delete; + TCPSocketHandler(TCPSocketHandler&&) = delete; + TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; + TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + +#ifdef BOTAN_FOUND + /** + * Botan stuff to manipulate a TLS session. + */ + static Botan::AutoSeeded_RNG rng; + static Permissive_Credentials_Manager credential_manager; + static Botan::TLS::Policy policy; + static Botan::TLS::Session_Manager_In_Memory session_manager; + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message as + * soon and calls the output_fn callback with it as soon as it is + * created. Therefore, we do not want to create it if do not intend to do + * send any TLS-encrypted message. We create the object only when needed + * (for example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::string pre_buf; +#endif // BOTAN_FOUND +}; + +#endif // SOCKET_HANDLER_INCLUDED diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp new file mode 100644 index 0000000..f738ce2 --- /dev/null +++ b/louloulibs/utils/encoding.cpp @@ -0,0 +1,254 @@ +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +/** + * The UTF-8-encoded character used as a place holder when a character conversion fails. + * This is U+FFFD � "replacement character" + */ +static const char* invalid_char = "\xef\xbf\xbd"; +static const size_t invalid_char_len = 3; + +namespace utils +{ + /** + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s) + { + if (!s) + return false; + + const unsigned char* str = reinterpret_cast(s); + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 0b11111000) == 0b11110000) + { + if (!str[1] || !str[2] || !str[3] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000) + || ((str[3] & 0b11000000) != 0b10000000)) + return false; + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 0b11110000) == 0b11100000) + { + if (!str[1] || !str[2] + || ((str[1] & 0b11000000) != 0b10000000) + || ((str[2] & 0b11000000) != 0b10000000)) + return false; + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 0b11100000) == 0b11000000) + { + if (!str[1] || + ((str[1] & 0b11000000) != 0b10000000)) + return false; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 0b10000000) != 0) + return false; + else + str++; + } + return true; + } + + 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;}); + + // pointer where we write valid chars + unsigned char* r = res; + + const unsigned char* str = reinterpret_cast(original.c_str()); + std::bitset<20> codepoint; + + while (*str) + { + // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + if ((str[0] & 0b11111000) == 0b11110000) + { + codepoint = ((str[0] & 0b00000111) << 18); + codepoint |= ((str[1] & 0b00111111) << 12); + codepoint |= ((str[2] & 0b00111111) << 6 ); + codepoint |= ((str[3] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0x10FFFF) + { + ::memcpy(r, str, 4); + r += 4; + } + str += 4; + } + // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + else if ((str[0] & 0b11110000) == 0b11100000) + { + codepoint = ((str[0] & 0b00001111) << 12); + codepoint |= ((str[1] & 0b00111111) << 6); + codepoint |= ((str[2] & 0b00111111) << 0 ); + if (codepoint.to_ulong() <= 0xD7FF || + (codepoint.to_ulong() >= 0xE000 && codepoint.to_ulong() <= 0xFFFD)) + { + ::memcpy(r, str, 3); + r += 3; + } + str += 3; + } + // 2 bytes: 110xxxxx 10xxxxxx + else if (((str[0]) & 0b11100000) == 0b11000000) + { + // All 2 bytes char are valid, don't even bother calculating + // the codepoint + ::memcpy(r, str, 2); + r += 2; + str += 2; + } + // 1 byte: 0xxxxxxx + else if ((str[0] & 0b10000000) == 0) + { + codepoint = ((str[0] & 0b01111111)); + if (codepoint.to_ulong() == 0x09 || + codepoint.to_ulong() == 0x0A || + codepoint.to_ulong() == 0x0D || + codepoint.to_ulong() >= 0x20) + { + ::memcpy(r, str, 1); + r += 1; + } + str += 1; + } + else + throw std::runtime_error("Invalid UTF-8 passed to remove_invalid_xml_chars"); + } + return std::string(reinterpret_cast(res), r-res); + } + + std::string convert_to_utf8(const std::string& str, const char* charset) + { + std::string res; + + const iconv_t cd = iconv_open("UTF-8", charset); + if (cd == (iconv_t)-1) + 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); }); + + size_t inbytesleft = str.size(); + + // iconv will not attempt to modify this buffer, but some plateform + // require a char** anyway +#ifdef ICONV_SECOND_ARGUMENT_IS_CONST + const char* inbuf_ptr = str.c_str(); +#else + char* inbuf_ptr = const_cast(str.c_str()); +#endif + + size_t outbytesleft = str.size() * 4; + char* outbuf = new char[outbytesleft]; + char* outbuf_ptr = outbuf; + + // Make sure outbuf is always deleted when we leave this function + sg.add_callback([&]{ delete[] outbuf; }); + + bool done = false; + while (done == false) + { + size_t error = iconv(cd, &inbuf_ptr, &inbytesleft, &outbuf_ptr, &outbytesleft); + if ((size_t)-1 == error) + { + switch (errno) + { + case EILSEQ: + // Invalid byte found. Insert a placeholder instead of the + // converted character, jump one byte and continue + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + inbytesleft--; + inbuf_ptr++; + break; + case EINVAL: + // A multibyte sequence is not terminated, but we can't + // provide any more data, so we just add a placeholder to + // indicate that the character is not properly converted, + // and we stop the conversion + memcpy(outbuf_ptr, invalid_char, invalid_char_len); + outbuf_ptr += invalid_char_len; + outbuf_ptr++; + done = true; + break; + case E2BIG: + // This should never happen + done = true; + break; + default: + // This should happen even neverer + done = true; + break; + } + } + else + { + // The conversion finished without any error, stop converting + done = true; + } + } + // Terminate the converted buffer, and copy that buffer it into the + // string we return + *outbuf_ptr = '\0'; + res = outbuf; + return res; + } + +} + +namespace xep0106 +{ + static const std::map encode_map = { + {' ', "\\20"}, + {'"', "\\22"}, + {'&', "\\26"}, + {'\'',"\\27"}, + {'/', "\\2f"}, + {':', "\\3a"}, + {'<', "\\3c"}, + {'>', "\\3e"}, + {'@', "\\40"}, + }; + + void decode(std::string& s) + { + std::string::size_type pos; + for (const auto& pair: encode_map) + while ((pos = s.find(pair.second)) != std::string::npos) + s.replace(pos, pair.second.size(), + 1, pair.first); + } + + void encode(std::string& s) + { + std::string::size_type pos; + while ((pos = s.find_first_of(" \"&'/:<>@")) != std::string::npos) + { + auto it = encode_map.find(s[pos]); + assert(it != encode_map.end()); + s.replace(pos, 1, it->second); + } + } +} diff --git a/louloulibs/utils/encoding.hpp b/louloulibs/utils/encoding.hpp new file mode 100644 index 0000000..6b7ccd2 --- /dev/null +++ b/louloulibs/utils/encoding.hpp @@ -0,0 +1,38 @@ +#ifndef ENCODING_INCLUDED +# define ENCODING_INCLUDED + +#include + +namespace utils +{ + /** + * Returns true if the given null-terminated string is valid utf-8. + * + * Based on http://en.wikipedia.org/wiki/UTF-8#Description + */ + bool is_valid_utf8(const char* s); + /** + * Remove all invalid codepoints from the given utf-8-encoded string. + * The value returned is a copy of the string, without the removed chars. + * + * See http://www.w3.org/TR/xml/#charsets for the list of valid characters + * in XML. + */ + std::string remove_invalid_xml_chars(const std::string& original); + /** + * Convert the given string (encoded is "encoding") into valid utf-8. + * If some decoding fails, insert an utf-8 placeholder character instead. + */ + std::string convert_to_utf8(const std::string& str, const char* encoding); +} + +namespace xep0106 +{ + /** + * Decode and encode inplace. + */ + void decode(std::string&); + void encode(std::string&); +} + +#endif // ENCODING_INCLUDED diff --git a/louloulibs/utils/reload.cpp b/louloulibs/utils/reload.cpp new file mode 100644 index 0000000..6600c75 --- /dev/null +++ b/louloulibs/utils/reload.cpp @@ -0,0 +1,13 @@ +#include +#include + +void reload_process() +{ + // Closing the config will just force it to be reopened the next time + // a configuration option is needed + Config::close(); + // Destroy the logger instance, to be recreated the next time a log + // line needs to be written + Logger::instance().reset(); + log_debug("Configuration and logger reloaded."); +} diff --git a/louloulibs/utils/reload.hpp b/louloulibs/utils/reload.hpp new file mode 100644 index 0000000..16d64f7 --- /dev/null +++ b/louloulibs/utils/reload.hpp @@ -0,0 +1,10 @@ +#ifndef RELOAD_HPP_INCLUDED +#define RELOAD_HPP_INCLUDED + +/** + * Reload the server's configuration, and close the logger (so that it + * closes its files etc, to take into account the new configuration) + */ +void reload_process(); + +#endif /* RELOAD_HPP_INCLUDED */ diff --git a/louloulibs/utils/revstr.cpp b/louloulibs/utils/revstr.cpp new file mode 100644 index 0000000..87fd801 --- /dev/null +++ b/louloulibs/utils/revstr.cpp @@ -0,0 +1,9 @@ +#include + +namespace utils +{ + std::string revstr(const std::string& original) + { + return {original.rbegin(), original.rend()}; + } +} diff --git a/louloulibs/utils/revstr.hpp b/louloulibs/utils/revstr.hpp new file mode 100644 index 0000000..27c9e3e --- /dev/null +++ b/louloulibs/utils/revstr.hpp @@ -0,0 +1,11 @@ +#ifndef REVSTR_HPP_INCLUDED +# define REVSTR_HPP_INCLUDED + +#include + +namespace utils +{ + std::string revstr(const std::string& original); +} + +#endif // REVSTR_HPP_INCLUDED diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp new file mode 100644 index 0000000..df78831 --- /dev/null +++ b/louloulibs/utils/scopeguard.hpp @@ -0,0 +1,89 @@ +#ifndef SCOPEGUARD_HPP +#define SCOPEGUARD_HPP + +#include +#include + +/** + * A class to be used to make sure some functions are called when the scope + * is left, because they will be called in the ScopeGuard's destructor. It + * can for example be used to delete some pointer whenever any exception is + * called. Example: + + * { + * ScopeGuard scope; + * int* number = new int(2); + * scope.add_callback([number]() { delete number; }); + * // Do some other stuff with the number. But these stuff might throw an exception: + * throw std::runtime_error("Some error not caught here, but in our caller"); + * return true; + * } + + * In this example, our pointer will always be deleted, even when the + * exception is thrown. If we want the functions to be called only when the + * scope is left because of an unexpected exception, we can use + * ScopeGuard::disable(); + */ + +namespace utils +{ + +class ScopeGuard +{ +public: + /** + * The constructor can take a callback. But additional callbacks can be + * added later with add_callback() + */ + explicit ScopeGuard(std::function&& func): + enabled(true) + { + this->add_callback(std::move(func)); + } + /** + * default constructor, the scope guard is enabled but empty, use + * add_callback() + */ + explicit ScopeGuard(): + enabled(true) + { + } + /** + * Call all callbacks in the desctructor, unless it has been disabled. + */ + ~ScopeGuard() + { + if (this->enabled) + for (auto& func: this->callbacks) + func(); + } + /** + * Add a callback to be called in our destructor, one scope guard can be + * used for more than one task, if needed. + */ + void add_callback(std::function&& func) + { + this->callbacks.emplace_back(std::move(func)); + } + /** + * Disable that scope guard, nothing will be done when the scope is + * exited. + */ + void disable() + { + this->enabled = false; + } + +private: + bool enabled; + std::vector> callbacks; + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; +}; + +} + +#endif diff --git a/louloulibs/utils/sha1.cpp b/louloulibs/utils/sha1.cpp new file mode 100644 index 0000000..76476df --- /dev/null +++ b/louloulibs/utils/sha1.cpp @@ -0,0 +1,154 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include "sha1.hpp" + +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +const uint8_t sha1InitState[] = { + 0x01,0x23,0x45,0x67, // H0 + 0x89,0xab,0xcd,0xef, // H1 + 0xfe,0xdc,0xba,0x98, // H2 + 0x76,0x54,0x32,0x10, // H3 + 0xf0,0xe1,0xd2,0xc3 // H4 +}; + +void sha1_init(sha1nfo *s) { + memcpy(s->state.b,sha1InitState,HASH_LENGTH); + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state.w[0]; + b=s->state.w[1]; + c=s->state.w[2]; + d=s->state.w[3]; + e=s->state.w[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15]; + s->buffer.w[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer.w[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state.w[0] += a; + s->state.w[1] += b; + s->state.w[2] += c; + s->state.w[3] += d; + s->state.w[4] += e; +} + +void sha1_addUncounted(sha1nfo *s, uint8_t data) { + s->buffer.b[s->bufferOffset ^ 3] = data; + s->bufferOffset++; + if (s->bufferOffset == BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + int i; + // Pad to complete the last block + sha1_pad(s); + + // Swap byte order back + for (i=0; i<5; i++) { + uint32_t a,b; + a=s->state.w[i]; + b=a<<24; + b|=(a<<8) & 0x00ff0000; + b|=(a>>8) & 0x0000ff00; + b|=a>>24; + s->state.w[i]=b; + } + + // 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; ikeyBuffer[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; ikeyBuffer[i] ^ HMAC_OPAD); + for (i=0; iinnerHash[i]); + return sha1_result(s); +} diff --git a/louloulibs/utils/sha1.hpp b/louloulibs/utils/sha1.hpp new file mode 100644 index 0000000..d02de75 --- /dev/null +++ b/louloulibs/utils/sha1.hpp @@ -0,0 +1,35 @@ +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include +#include + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +union _buffer { + uint8_t b[BLOCK_LENGTH]; + uint32_t w[BLOCK_LENGTH/4]; +}; + +union _state { + uint8_t b[HASH_LENGTH]; + uint32_t w[HASH_LENGTH/4]; +}; + +typedef struct sha1nfo { + union _buffer buffer; + uint8_t bufferOffset; + union _state state; + uint32_t byteCount; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +} sha1nfo; + +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/split.cpp b/louloulibs/utils/split.cpp new file mode 100644 index 0000000..80f8dae --- /dev/null +++ b/louloulibs/utils/split.cpp @@ -0,0 +1,19 @@ +#include +#include + +namespace utils +{ + std::vector split(const std::string& s, const char delim, const bool allow_empty) + { + std::vector ret; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) + { + if (item.empty() && !allow_empty) + continue ; + ret.emplace_back(std::move(item)); + } + return ret; + } +} diff --git a/louloulibs/utils/split.hpp b/louloulibs/utils/split.hpp new file mode 100644 index 0000000..6b487a9 --- /dev/null +++ b/louloulibs/utils/split.hpp @@ -0,0 +1,12 @@ +#ifndef SPLIT_INCLUDED +# define SPLIT_INCLUDED + +#include +#include + +namespace utils +{ + std::vector split(const std::string &s, const char delim, const bool allow_empty=true); +} + +#endif // SPLIT_INCLUDED diff --git a/louloulibs/utils/timed_events.cpp b/louloulibs/utils/timed_events.cpp new file mode 100644 index 0000000..5010a3f --- /dev/null +++ b/louloulibs/utils/timed_events.cpp @@ -0,0 +1,62 @@ +#include + +TimedEvent::TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name): + time_point(std::move(time_point)), + callback(callback), + repeat(false), + repeat_delay(0), + name(name) +{ +} + +TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name): + time_point(std::chrono::steady_clock::now() + duration), + callback(callback), + repeat(true), + repeat_delay(std::move(duration)), + name(name) +{ +} + +TimedEvent::TimedEvent(TimedEvent&& other): + time_point(std::move(other.time_point)), + callback(std::move(other.callback)), + repeat(other.repeat), + repeat_delay(std::move(other.repeat_delay)), + name(std::move(other.name)) +{ +} + +TimedEvent::~TimedEvent() +{ +} + +bool TimedEvent::is_after(const TimedEvent& other) const +{ + return this->is_after(other.time_point); +} + +bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const +{ + return this->time_point >= time_point; +} + +std::chrono::milliseconds TimedEvent::get_timeout() const +{ + auto now = std::chrono::steady_clock::now(); + if (now > this->time_point) + return std::chrono::milliseconds(0); + return std::chrono::duration_cast(this->time_point - now); +} + +void TimedEvent::execute() +{ + this->callback(); +} + +const std::string& TimedEvent::get_name() const +{ + return this->name; +} diff --git a/louloulibs/utils/timed_events.hpp b/louloulibs/utils/timed_events.hpp new file mode 100644 index 0000000..4e2800c --- /dev/null +++ b/louloulibs/utils/timed_events.hpp @@ -0,0 +1,132 @@ +#ifndef TIMED_EVENTS_HPP +# define TIMED_EVENTS_HPP + +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; + +namespace utils { +static constexpr std::chrono::milliseconds no_timeout = std::chrono::milliseconds(-1); +} + +class TimedEventsManager; + +/** + * A callback with an associated date. + */ + +class TimedEvent +{ + friend class TimedEventsManager; +public: + /** + * An event the occurs only once, at the given time_point + */ + explicit TimedEvent(std::chrono::steady_clock::time_point&& time_point, + std::function callback, const std::string& name=""); + explicit TimedEvent(std::chrono::milliseconds&& duration, + std::function callback, const std::string& name=""); + + explicit TimedEvent(TimedEvent&&); + ~TimedEvent(); + /** + * Whether or not this event happens after the other one. + */ + bool is_after(const TimedEvent& other) const; + bool is_after(const std::chrono::steady_clock::time_point& time_point) const; + /** + * Return the duration difference between now and the event time point. + * If the difference would be negative (i.e. the event is expired), the + * returned value is 0 instead. The value cannot then be negative. + */ + std::chrono::milliseconds get_timeout() const; + void execute(); + const std::string& get_name() const; + +private: + /** + * The next time point at which the event is executed. + */ + std::chrono::steady_clock::time_point time_point; + /** + * The function to execute. + */ + const std::function callback; + /** + * Whether or not this events repeats itself until it is destroyed. + */ + const bool repeat; + /** + * This value is added to the time_point each time the event is executed, + * if repeat is true. Otherwise it is ignored. + */ + const std::chrono::milliseconds repeat_delay; + /** + * A name that is used to identify that event. If you want to find your + * event (for example if you want to cancel it), the name should be + * unique. + */ + const std::string name; + + TimedEvent(const TimedEvent&) = delete; + TimedEvent& operator=(const TimedEvent&) = delete; + TimedEvent& operator=(TimedEvent&&) = delete; +}; + +/** + * A class managing a list of TimedEvents. + * They are sorted, new events can be added, removed, fetch, etc. + */ + +class TimedEventsManager +{ +public: + ~TimedEventsManager(); + /** + * Return the unique instance of this class + */ + static TimedEventsManager& instance(); + /** + * Add an event to the list of managed events. The list is sorted after + * this call. + */ + void add_event(TimedEvent&& event); + /** + * Returns the duration, in milliseconds, between now and the next + * available event. If the event is already expired (the duration is + * negative), 0 is returned instead (as in “it's not too late, execute it + * now”) + * Returns a negative value if no event is available. + */ + std::chrono::milliseconds get_timeout() const; + /** + * Execute all the expired events (if their expiration time is exactly + * now, or before now). The event is then removed from the list. If the + * event does repeat, its expiration time is updated and it is reinserted + * in the list at the correct position. + * Returns the number of executed events. + */ + std::size_t execute_expired_events(); + /** + * Remove (and thus cancel) all the timed events with the given name. + * Returns the number of canceled events. + */ + std::size_t cancel(const std::string& name); + /** + * Return the number of managed events. + */ + std::size_t size() const; + +private: + explicit TimedEventsManager(); + std::list events; + TimedEventsManager(const TimedEventsManager&) = delete; + TimedEventsManager(TimedEventsManager&&) = delete; + TimedEventsManager& operator=(const TimedEventsManager&) = delete; + TimedEventsManager& operator=(TimedEventsManager&&) = delete; +}; + +#endif // TIMED_EVENTS_HPP diff --git a/louloulibs/utils/timed_events_manager.cpp b/louloulibs/utils/timed_events_manager.cpp new file mode 100644 index 0000000..2c75e48 --- /dev/null +++ b/louloulibs/utils/timed_events_manager.cpp @@ -0,0 +1,81 @@ +#include + +TimedEventsManager& TimedEventsManager::instance() +{ + static TimedEventsManager inst; + return inst; +} + +TimedEventsManager::TimedEventsManager() +{ +} + +TimedEventsManager::~TimedEventsManager() +{ +} + +void TimedEventsManager::add_event(TimedEvent&& event) +{ + for (auto it = this->events.begin(); it != this->events.end(); ++it) + { + if (it->is_after(event)) + { + this->events.emplace(it, std::move(event)); + return; + } + } + this->events.emplace_back(std::move(event)); +} + +std::chrono::milliseconds TimedEventsManager::get_timeout() const +{ + if (this->events.empty()) + return utils::no_timeout; + return this->events.front().get_timeout() + std::chrono::milliseconds(1); +} + +std::size_t TimedEventsManager::execute_expired_events() +{ + std::size_t count = 0; + const auto now = std::chrono::steady_clock::now(); + for (auto it = this->events.begin(); it != this->events.end();) + { + if (!it->is_after(now)) + { + TimedEvent copy(std::move(*it)); + it = this->events.erase(it); + ++count; + copy.execute(); + if (copy.repeat) + { + copy.time_point += copy.repeat_delay; + this->add_event(std::move(copy)); + } + continue; + } + else + break; + } + return count; +} + +std::size_t TimedEventsManager::cancel(const std::string& name) +{ + std::size_t res = 0; + for (auto it = this->events.begin(); it != this->events.end();) + { + if (it->get_name() == name) + { + it = this->events.erase(it); + res++; + } + else + ++it; + } + return res; +} + +std::size_t TimedEventsManager::size() const +{ + return this->events.size(); +} diff --git a/louloulibs/utils/tolower.cpp b/louloulibs/utils/tolower.cpp new file mode 100644 index 0000000..3e518bd --- /dev/null +++ b/louloulibs/utils/tolower.cpp @@ -0,0 +1,13 @@ +#include + +namespace utils +{ + std::string tolower(const std::string& original) + { + std::string res; + res.reserve(original.size()); + for (const char c: original) + res += static_cast(std::tolower(c)); + return res; + } +} diff --git a/louloulibs/utils/tolower.hpp b/louloulibs/utils/tolower.hpp new file mode 100644 index 0000000..0019182 --- /dev/null +++ b/louloulibs/utils/tolower.hpp @@ -0,0 +1,11 @@ +#ifndef TOLOWER_INCLUDED +# define TOLOWER_INCLUDED + +#include + +namespace utils +{ + std::string tolower(const std::string& original); +} + +#endif // SPLIT_INCLUDED diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp new file mode 100644 index 0000000..24145f5 --- /dev/null +++ b/louloulibs/xmpp/adhoc_command.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +using namespace std::string_literals; + +AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string& name, const bool admin_only): + name(name), + callbacks(std::move(callbacks)), + admin_only(admin_only) +{ +} + +AdhocCommand::~AdhocCommand() +{ +} + +bool AdhocCommand::is_admin_only() const +{ + return this->admin_only; +} + +void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) +{ + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Pong"); + note.close(); + command_node.add_child(std::move(note)); +} + +void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) +{ + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure your name."); + title.close(); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Please provide your name."); + instructions.close(); + x.add_child(std::move(instructions)); + XmlNode name_field("field"); + name_field["var"] = "name"; + name_field["type"] = "text-single"; + name_field["label"] = "Your name"; + XmlNode required("required"); + required.close(); + name_field.add_child(std::move(required)); + name_field.close(); + x.add_child(std::move(name_field)); + x.close(); + command_node.add_child(std::move(x)); +} + +void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) +{ + // Find out if the name was provided in the form. + XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + XmlNode* name_field = nullptr; + for (XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "name") + { + name_field = field; + break; + } + if (name_field) + { + XmlNode* value = name_field->get_child("value", "jabber:x:data"); + if (value) + { + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Hello "s + value->get_inner() + "!"s); + note.close(); + command_node.delete_all_children(); + command_node.add_child(std::move(note)); + return; + } + } + } + command_node.delete_all_children(); + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + session.terminate(); +} + +void Reload(XmppComponent*, AdhocSession&, XmlNode& command_node) +{ + ::reload_process(); + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration reloaded."); + note.close(); + command_node.add_child(std::move(note)); +} diff --git a/louloulibs/xmpp/adhoc_command.hpp b/louloulibs/xmpp/adhoc_command.hpp new file mode 100644 index 0000000..1ff2bcf --- /dev/null +++ b/louloulibs/xmpp/adhoc_command.hpp @@ -0,0 +1,43 @@ +#ifndef ADHOC_COMMAND_HPP +# define ADHOC_COMMAND_HPP + +/** + * Describe an ad-hoc command. + * + * Can only have zero or one step for now. When execution is requested, it + * can return a result immediately, or provide a form to be filled, and + * provide a result once the filled form is received. + */ + +#include + +#include +#include + +class AdhocCommand +{ + friend class AdhocSession; +public: + AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); + ~AdhocCommand(); + + const std::string name; + + bool is_admin_only() const; + +private: + /** + * A command may have one or more steps. Each step is a different + * callback, inserting things into a XmlNode and calling + * methods of an AdhocSession. + */ + std::vector callbacks; + const bool admin_only; +}; + +void PingStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void HelloStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node); + +#endif // ADHOC_COMMAND_HPP diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp new file mode 100644 index 0000000..46c8a32 --- /dev/null +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -0,0 +1,133 @@ +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::string_literals; + +const std::map& AdhocCommandsHandler::get_commands() const +{ + return this->commands; +} + +std::map& AdhocCommandsHandler::get_commands() +{ + return this->commands; +} + +XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) +{ + std::string action = command_node.get_tag("action"); + if (action.empty()) + action = "execute"; + command_node.del_tag("action"); + + Jid jid(executor_jid); + + const std::string node = command_node.get_tag("node"); + auto command_it = this->commands.find(node); + if (command_it == this->commands.end()) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "cancel"; + XmlNode condition(STANZA_NS":item-not-found"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + else if (command_it->second.is_admin_only() && + Config::get("admin", "") != jid.local + "@" + jid.domain) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "cancel"; + XmlNode condition(STANZA_NS":forbidden"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + else + { + std::string sessionid = command_node.get_tag("sessionid"); + if (sessionid.empty()) + { // create a new session, with a new id + sessionid = XmppComponent::next_id(); + command_node["sessionid"] = sessionid; + this->sessions.emplace(std::piecewise_construct, + std::forward_as_tuple(sessionid, executor_jid), + std::forward_as_tuple(command_it->second, executor_jid)); + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, + std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), + "adhocsession"s + sessionid + executor_jid)); + } + auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); + if (session_it == this->sessions.end()) + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + else if (action == "execute" || action == "next" || action == "complete") + { + // execute the step + AdhocSession& session = session_it->second; + const AdhocStep& step = session.get_next_step(); + step(this->xmpp_component, session, command_node); + if (session.remaining_steps() == 0 || + session.is_terminated()) + { + this->sessions.erase(session_it); + command_node["status"] = "completed"; + TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + } + else + { + command_node["status"] = "executing"; + XmlNode actions("actions"); + XmlNode next("next"); + next.close(); + actions.add_child(std::move(next)); + actions.close(); + command_node.add_child(std::move(actions)); + } + } + else if (action == "cancel") + { + this->sessions.erase(session_it); + command_node["status"] = "canceled"; + TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + } + else // unsupported action + { + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + condition.close(); + error.add_child(std::move(condition)); + error.close(); + command_node.add_child(std::move(error)); + } + } + return command_node; +} + +void AdhocCommandsHandler::remove_session(const std::string& session_id, const std::string& initiator_jid) +{ + auto session_it = this->sessions.find(std::make_pair(session_id, initiator_jid)); + if (session_it != this->sessions.end()) + { + this->sessions.erase(session_it); + return ; + } + log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found"); +} diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp new file mode 100644 index 0000000..1083c44 --- /dev/null +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -0,0 +1,74 @@ +#ifndef ADHOC_COMMANDS_HANDLER_HPP +# define ADHOC_COMMANDS_HANDLER_HPP + +/** + * Manage a list of available AdhocCommands and the list of ongoing + * AdhocCommandSessions. + */ + +#include +#include + +#include +#include +#include + +class AdhocCommandsHandler +{ +public: + explicit AdhocCommandsHandler(XmppComponent* xmpp_component): + xmpp_component(xmpp_component), + commands{} + { } + ~AdhocCommandsHandler() = default; + /** + * Returns the list of available commands. + */ + const std::map& get_commands() const; + /** + * This one can be used to add new commands. + */ + std::map& get_commands(); + /** + * 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 + * result). + * + * Returns a (moved) XmlNode that will be inserted in the iq response. It + * should be a node containing one or more useful children. If + * it contains an node, the iq response will have an error type. + * + * Takes a copy of the node so we can actually edit it and use + * it as our return value. + */ + XmlNode handle_request(const std::string& executor_jid, XmlNode command_node); + /** + * Remove the session from the list. This is done to avoid filling the + * memory with waiting session (for example due to a client that starts + * multi-steps command but never finishes them). + */ + void remove_session(const std::string& session_id, const std::string& initiator_jid); +private: + /** + * A pointer to the XmppComponent, to access to basically anything in the + * gateway. + */ + XmppComponent* xmpp_component; + /** + * The list of all available commands. + */ + std::map commands; + /** + * The list of all currently on-going commands. + * + * Of the form: {{session_id, owner_jid}, session}. + */ + std::map, AdhocSession> sessions; + + AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; + AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; +}; + +#endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/louloulibs/xmpp/adhoc_session.cpp b/louloulibs/xmpp/adhoc_session.cpp new file mode 100644 index 0000000..fc60bb7 --- /dev/null +++ b/louloulibs/xmpp/adhoc_session.cpp @@ -0,0 +1,37 @@ +#include +#include + +#include + +AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid): + command(command), + owner_jid(jid), + current_step(0), + terminated(false) +{ +} + +AdhocSession::~AdhocSession() +{ +} + +const AdhocStep& AdhocSession::get_next_step() +{ + assert(this->current_step < this->command.callbacks.size()); + return this->command.callbacks[this->current_step++]; +} + +size_t AdhocSession::remaining_steps() const +{ + return this->command.callbacks.size() - this->current_step; +} + +bool AdhocSession::is_terminated() const +{ + return this->terminated; +} + +void AdhocSession::terminate() +{ + this->terminated = true; +} diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp new file mode 100644 index 0000000..ddfb2fe --- /dev/null +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -0,0 +1,70 @@ +#ifndef ADHOC_SESSION_HPP +# define ADHOC_SESSION_HPP + +#include + +#include +#include + +class XmppComponent; + +class AdhocCommand; +class AdhocSession; + +/** + * A function executed as an ad-hoc command step. It takes a + * XmlNode and modifies it accordingly (inserting for example an + * node, or a data form…). + * TODO fix this: + * It also must call one of step_passed(), cancel() etc on the AdhocSession object. + */ +typedef std::function AdhocStep; + +class AdhocSession +{ +public: + explicit AdhocSession(const AdhocCommand& command, const std::string& jid); + ~AdhocSession(); + /** + * Return the function to be executed, found in our AdhocCommand, for the + * current_step. And increment the current_step. + */ + const AdhocStep& get_next_step(); + /** + * Return the number of remaining steps. + */ + size_t remaining_steps() const; + /** + * This may be modified by an AdhocStep, to indicate that this session + * should no longer exist, because we encountered an error, and we can't + * execute any more step of it. + */ + void terminate(); + bool is_terminated() const; + +private: + /** + * A reference of the command concerned by this session. Used for example + * to get the next step of that command, things like that. + */ + const AdhocCommand& command; + /** + * The full JID of the XMPP user that created this session by executing + * the first step of a command. Only that JID must be allowed to access + * this session. + */ + const std::string& owner_jid; + /** + * The current step we are at. It starts at zero. It is used to index the + * associated AdhocCommand::callbacks vector. + */ + size_t current_step; + bool terminated; + + AdhocSession(const AdhocSession&) = delete; + AdhocSession(AdhocSession&&) = delete; + AdhocSession& operator=(const AdhocSession&) = delete; + AdhocSession& operator=(AdhocSession&&) = delete; +}; + +#endif // ADHOC_SESSION_HPP diff --git a/louloulibs/xmpp/body.hpp b/louloulibs/xmpp/body.hpp new file mode 100644 index 0000000..6ac678e --- /dev/null +++ b/louloulibs/xmpp/body.hpp @@ -0,0 +1,12 @@ +#ifndef XMPP_BODY_HPP_INCLUDED +#define XMPP_BODY_HPP_INCLUDED + +namespace Xmpp +{ +// Contains: +// - an XMPP-valid UTF-8 body +// - an XML node representing the XHTML-IM body, or null + typedef std::tuple> body; +} + +#endif /* XMPP_BODY_HPP_INCLUDED */ diff --git a/louloulibs/xmpp/jid.cpp b/louloulibs/xmpp/jid.cpp new file mode 100644 index 0000000..e6fee45 --- /dev/null +++ b/louloulibs/xmpp/jid.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include + +#include +#ifdef LIBIDN_FOUND + #include +#endif + +#include + +Jid::Jid(const std::string& jid) +{ + std::string::size_type slash = jid.find('/'); + if (slash != std::string::npos) + { + this->resource = jid.substr(slash + 1); + } + + std::string::size_type at = jid.find('@'); + if (at != std::string::npos && at < slash) + { + this->local = jid.substr(0, at); + at++; + } + else + at = 0; + + this->domain = jid.substr(at, slash - at); +} + +#include + +static constexpr size_t max_jid_part_len = 1023; + +std::string jidprep(const std::string& original) +{ +#ifdef LIBIDN_FOUND + using CacheType = std::map; + static CacheType cache; + std::pair cached = cache.insert({original, {}}); + if (std::get<1>(cached) == false) + { // Insertion failed: the result is already in the cache, return it + return std::get<0>(cached)->second; + } + + const std::string error_msg("Failed to convert " + original + " into a valid JID:"); + Jid jid(original); + + char local[max_jid_part_len] = {}; + memcpy(local, jid.local.data(), jid.local.size()); + Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, + static_cast(0), stringprep_xmpp_nodeprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + + char domain[max_jid_part_len] = {}; + memcpy(domain, jid.domain.data(), jid.domain.size()); + rc = static_cast(::stringprep(domain, max_jid_part_len, + static_cast(0), stringprep_nameprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + std::replace_if(std::begin(domain), domain + ::strlen(domain), + [](const char c) -> bool + { + return !((c >= 'a' && c <= 'z') || c == '-' || + (c >= '0' && c <= '9') || c == '.'); + }, '-'); + + // If there is no resource, stop here + if (jid.resource.empty()) + { + std::get<0>(cached)->second = std::string(local) + "@" + domain; + return std::get<0>(cached)->second; + } + + // Otherwise, also process the resource part + char resource[max_jid_part_len] = {}; + memcpy(resource, jid.resource.data(), jid.resource.size()); + rc = static_cast(::stringprep(resource, max_jid_part_len, + static_cast(0), stringprep_xmpp_resourceprep)); + if (rc != STRINGPREP_OK) + { + log_error(error_msg + stringprep_strerror(rc)); + return ""; + } + std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource; + return std::get<0>(cached)->second; + +#else + (void)original; + return ""; +#endif +} diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp new file mode 100644 index 0000000..b6975a2 --- /dev/null +++ b/louloulibs/xmpp/jid.hpp @@ -0,0 +1,36 @@ +#ifndef JID_INCLUDED +# define JID_INCLUDED + +#include + +/** + * Parse a JID into its different subart + */ +class Jid +{ +public: + explicit Jid(const std::string& jid); + + std::string domain; + std::string local; + std::string resource; + +private: + Jid(const Jid&) = delete; + Jid(Jid&&) = delete; + Jid& operator=(const Jid&) = delete; + Jid& operator=(Jid&&) = delete; +}; + +/** + * Prepare the given UTF-8 string according to the XMPP node stringprep + * identifier profile. This is used to send properly-formed JID to the XMPP + * server. + * + * If the stringprep library is not found, we return an empty string. When + * this function is used, the result must always be checked for an empty + * value, and if this is the case it must not be used as a JID. + */ +std::string jidprep(const std::string& original); + +#endif // JID_INCLUDED diff --git a/louloulibs/xmpp/roster.cpp b/louloulibs/xmpp/roster.cpp new file mode 100644 index 0000000..a14a384 --- /dev/null +++ b/louloulibs/xmpp/roster.cpp @@ -0,0 +1,21 @@ +#include + +RosterItem::RosterItem(const std::string& jid, const std::string& name, + std::vector& 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 new file mode 100644 index 0000000..0aebca5 --- /dev/null +++ b/louloulibs/xmpp/roster.hpp @@ -0,0 +1,71 @@ +#ifndef ROSTER_HPP_INCLUDED +#define ROSTER_HPP_INCLUDED + +#include +#include +#include + +class RosterItem +{ +public: + RosterItem(const std::string& jid, const std::string& name, + std::vector& 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 groups; + +private: +}; + +/** + * Keep track of the last known stat of a JID's roster + */ +class Roster +{ +public: + Roster() = default; + ~Roster() = default; + + void clear(); + + template + RosterItem* add_item(ArgsType&&... args) + { + this->items.emplace_back(std::forward(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& get_items() const + { + return this->items; + } + +private: + std::vector items; + + Roster(const Roster&) = delete; + Roster(Roster&&) = delete; + Roster& operator=(const Roster&) = delete; + Roster& operator=(Roster&&) = delete; +}; + +#endif /* ROSTER_HPP_INCLUDED */ diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp new file mode 100644 index 0000000..1048f86 --- /dev/null +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -0,0 +1,716 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#ifdef SYSTEMD_FOUND +# include +#endif + +using namespace std::string_literals; + +static std::set kickable_errors{ + "gone", + "internal-server-error", + "item-not-found", + "jid-malformed", + "recipient-unavailable", + "redirect", + "remote-server-not-found", + "remote-server-timeout", + "service-unavailable", + "malformed-error" + }; + +XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): + TCPSocketHandler(poller), + ever_auth(false), + first_connection_try(true), + secret(secret), + authenticated(false), + doc_open(false), + served_hostname(hostname), + stanza_handlers{}, + adhoc_commands_handler(this) +{ + this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, + std::placeholders::_1)); + this->parser.add_stanza_callback(std::bind(&XmppComponent::on_stanza, this, + std::placeholders::_1)); + this->parser.add_stream_close_callback(std::bind(&XmppComponent::on_remote_stream_close, this, + std::placeholders::_1)); + this->stanza_handlers.emplace("handshake", + std::bind(&XmppComponent::handle_handshake, this,std::placeholders::_1)); + this->stanza_handlers.emplace("error", + std::bind(&XmppComponent::handle_error, this,std::placeholders::_1)); +} + +void XmppComponent::start() +{ + this->connect("127.0.0.1", Config::get("port", "5347"), false); +} + +bool XmppComponent::is_document_open() const +{ + return this->doc_open; +} + +void XmppComponent::send_stanza(const Stanza& stanza) +{ + std::string str = stanza.to_string(); + log_debug("XMPP SENDING: " << str); + this->send_data(std::move(str)); +} + +void XmppComponent::on_connection_failed(const std::string& reason) +{ + this->first_connection_try = false; + log_error("Failed to connect to the XMPP server: " << reason); +#ifdef SYSTEMD_FOUND + sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); +#endif +} + +void XmppComponent::on_connected() +{ + log_info("connected to XMPP server"); + this->first_connection_try = true; + XmlNode node("", nullptr); + node.set_name("stream:stream"); + node["xmlns"] = COMPONENT_NS; + node["xmlns:stream"] = STREAM_NS; + node["to"] = this->served_hostname; + this->send_stanza(node); + this->doc_open = true; + // We may have some pending data to send: this happens when we try to send + // some data before we are actually connected. We send that data right now, if any + this->send_pending_data(); +} + +void XmppComponent::on_connection_close(const std::string& error) +{ + if (error.empty()) + { + log_info("XMPP server closed connection"); + } + else + { + log_info("XMPP server closed connection: " << error); + } +} + +void XmppComponent::parse_in_buffer(const size_t size) +{ + if (!this->in_buf.empty()) + { // This may happen if the parser could not allocate enough space for + // us. We try to feed it the data that was read into our in_buf + // instead. If this fails again we are in trouble. + this->parser.feed(this->in_buf.data(), this->in_buf.size(), false); + this->in_buf.clear(); + } + else + { // Just tell the parser to parse the data that was placed into the + // buffer it provided to us with GetBuffer + this->parser.parse(size, false); + } +} + +void XmppComponent::on_remote_stream_open(const XmlNode& node) +{ + log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); + this->stream_id = node.get_tag("id"); + if (this->stream_id.empty()) + { + log_error("Error: no attribute 'id' found"); + this->send_stream_error("bad-format", "missing 'id' attribute"); + this->close_document(); + return ; + } + + // 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'; + + Stanza handshake(COMPONENT_NS":handshake"); + handshake.set_inner(digest); + handshake.close(); + this->send_stanza(handshake); +} + +void XmppComponent::on_remote_stream_close(const XmlNode& node) +{ + log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); + this->doc_open = false; +} + +void XmppComponent::reset() +{ + this->parser.reset(); +} + +void XmppComponent::on_stanza(const Stanza& stanza) +{ + log_debug("XMPP RECEIVING: " << stanza.to_string()); + std::function handler; + try + { + handler = this->stanza_handlers.at(stanza.get_name()); + } + catch (const std::out_of_range& exception) + { + log_warning("No handler for stanza of type " << stanza.get_name()); + return; + } + handler(stanza); +} + +void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation) +{ + XmlNode node("stream:error", nullptr); + XmlNode error(name, nullptr); + error["xmlns"] = STREAM_NS; + if (!explanation.empty()) + error.set_inner(explanation); + error.close(); + node.add_child(std::move(error)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, + const std::string& id, const std::string& error_type, + const std::string& defined_condition, const std::string& text, + const bool fulljid) +{ + Stanza node(kind); + if (!to.empty()) + node["to"] = to; + if (!from.empty()) + { + if (fulljid) + node["from"] = from; + else + node["from"] = from + "@" + this->served_hostname; + } + if (!id.empty()) + node["id"] = id; + node["type"] = "error"; + XmlNode error("error"); + error["type"] = error_type; + XmlNode inner_error(defined_condition); + inner_error["xmlns"] = STANZA_NS; + inner_error.close(); + error.add_child(std::move(inner_error)); + if (!text.empty()) + { + XmlNode text_node("text"); + text_node["xmlns"] = STANZA_NS; + text_node.set_inner(text); + text_node.close(); + error.add_child(std::move(text_node)); + } + error.close(); + node.add_child(std::move(error)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::close_document() +{ + log_debug("XMPP SENDING: "); + this->send_data(""); + this->doc_open = false; +} + +void XmppComponent::handle_handshake(const Stanza& stanza) +{ + (void)stanza; + this->authenticated = true; + this->ever_auth = true; + log_info("Authenticated with the XMPP server"); +#ifdef SYSTEMD_FOUND + sd_notify(0, "READY=1"); + // Install an event that sends a keepalive to systemd. If biboumi crashes + // or hangs for too long, systemd will restart it. + uint64_t usec; + if (sd_watchdog_enabled(0, &usec) > 0) + { + TimedEventsManager::instance().add_event(TimedEvent( + std::chrono::duration_cast(std::chrono::microseconds(usec / 2)), + []() { sd_notify(0, "WATCHDOG=1"); })); + } +#endif + this->after_handshake(); +} + +void XmppComponent::handle_error(const Stanza& stanza) +{ + XmlNode* text = stanza.get_child("text", STREAMS_NS); + std::string error_message("Unspecified error"); + if (text) + error_message = text->get_inner(); + log_error("Stream error received from the XMPP server: " << error_message); +#ifdef SYSTEMD_FOUND + if (!this->ever_auth) + sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); +#endif + +} + +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) +{ + XmlNode node("message"); + node["to"] = to; + if (fulljid) + node["from"] = from; + else + node["from"] = from + "@" + this->served_hostname; + if (!type.empty()) + node["type"] = type; + XmlNode body_node("body"); + body_node.set_inner(std::get<0>(body)); + body_node.close(); + node.add_child(std::move(body_node)); + if (std::get<1>(body)) + { + XmlNode html("html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::get<1>(body).release()); + html.close(); + node.add_child(std::move(html)); + } + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_user_join(const std::string& from, + const std::string& nick, + const std::string& realjid, + const std::string& affiliation, + const std::string& role, + const std::string& to, + const bool self) +{ + XmlNode node("presence"); + node["to"] = to; + node["from"] = from + "@" + this->served_hostname + "/" + nick; + + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + + XmlNode item("item"); + if (!affiliation.empty()) + item["affiliation"] = affiliation; + if (!role.empty()) + item["role"] = role; + if (!realjid.empty()) + { + const std::string preped_jid = jidprep(realjid); + if (!preped_jid.empty()) + item["jid"] = preped_jid; + } + item.close(); + x.add_child(std::move(item)); + + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + status.close(); + x.add_child(std::move(status)); + } + x.close(); + node.add_child(std::move(x)); + node.close(); + this->send_stanza(node); +} + +void XmppComponent::send_invalid_room_error(const std::string& muc_name, + const std::string& nick, + const std::string& to) +{ + Stanza presence("presence"); + if (!muc_name.empty()) + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else + presence["from"] = this->served_hostname; + presence["to"] = to; + presence["type"] = "error"; + XmlNode x("x"); + x["xmlns"] = MUC_NS; + x.close(); + presence.add_child(std::move(x)); + XmlNode error("error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = "cancel"; + XmlNode item_not_found("item-not-found"); + item_not_found["xmlns"] = STANZA_NS; + item_not_found.close(); + error.add_child(std::move(item_not_found)); + XmlNode text("text"); + text["xmlns"] = STANZA_NS; + text["xml:lang"] = "en"; + text.set_inner(muc_name + + " is not a valid IRC channel name. A correct room jid is of the form: #%@" + + this->served_hostname); + text.close(); + error.add_child(std::move(text)); + error.close(); + presence.add_child(std::move(error)); + presence.close(); + 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; + x.close(); + 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; + item_not_found.close(); + 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: !@" + + this->served_hostname); + text.close(); + error.add_child(std::move(text)); + error.close(); + message.add_child(std::move(error)); + message.close(); + this->send_stanza(message); +} + +void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) +{ + XmlNode message("message"); + message["to"] = to; + message["from"] = from + "@" + this->served_hostname; + message["type"] = "groupchat"; + XmlNode subject("subject"); + subject.set_inner(std::get<0>(topic)); + subject.close(); + message.add_child(std::move(subject)); + message.close(); + this->send_stanza(message); +} + +void XmppComponent::send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& xmpp_body, const std::string& jid_to) +{ + Stanza message("message"); + message["to"] = jid_to; + if (!nick.empty()) + message["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + else // Message from the room itself + message["from"] = muc_name + "@" + this->served_hostname; + message["type"] = "groupchat"; + XmlNode body("body"); + body.set_inner(std::get<0>(xmpp_body)); + body.close(); + message.add_child(std::move(body)); + if (std::get<1>(xmpp_body)) + { + XmlNode html("html"); + html["xmlns"] = XHTMLIM_NS; + // Pass the ownership of the pointer to this xmlnode + html.add_child(std::get<1>(xmpp_body).release()); + html.close(); + message.add_child(std::move(html)); + } + message.close(); + 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"); + presence["to"] = jid_to; + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick; + presence["type"] = "unavailable"; + const std::string message_str = std::get<0>(message); + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + if (self) + { + XmlNode status("status"); + status["code"] = "110"; + status.close(); + x.add_child(std::move(status)); + } + x.close(); + presence.add_child(std::move(x)); + if (!message_str.empty()) + { + XmlNode status("status"); + status.set_inner(message_str); + status.close(); + presence.add_child(std::move(status)); + } + presence.close(); + this->send_stanza(presence); +} + +void XmppComponent::send_nick_change(const std::string& muc_name, + const std::string& old_nick, + const std::string& new_nick, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to, + const bool self) +{ + Stanza presence("presence"); + presence["to"] = jid_to; + presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick; + presence["type"] = "unavailable"; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["nick"] = new_nick; + item.close(); + x.add_child(std::move(item)); + XmlNode status("status"); + status["code"] = "303"; + status.close(); + x.add_child(std::move(status)); + if (self) + { + XmlNode status2("status"); + status2["code"] = "110"; + status2.close(); + x.add_child(std::move(status2)); + } + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); + + 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) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; + presence["to"] = jid_to; + presence["type"] = "unavailable"; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["affiliation"] = "none"; + item["role"] = "none"; + XmlNode actor("actor"); + actor["nick"] = author; + actor["jid"] = author; // backward compatibility with old clients + actor.close(); + item.add_child(std::move(actor)); + XmlNode reason("reason"); + reason.set_inner(txt); + reason.close(); + item.add_child(std::move(reason)); + item.close(); + x.add_child(std::move(item)); + XmlNode status("status"); + status["code"] = "307"; + status.close(); + x.add_child(std::move(status)); + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); +} + +void XmppComponent::send_presence_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string& error_code, + const std::string& /* text */) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname; + presence["to"] = jid_to; + presence["type"] = "error"; + XmlNode x("x"); + x["xmlns"] = MUC_NS; + x.close(); + presence.add_child(std::move(x)); + XmlNode error("error"); + error["by"] = muc_name + "@" + this->served_hostname; + error["type"] = type; + if (!error_code.empty()) + error["code"] = error_code; + XmlNode subnode(condition); + subnode["xmlns"] = STANZA_NS; + subnode.close(); + error.add_child(std::move(subnode)); + error.close(); + presence.add_child(std::move(error)); + presence.close(); + this->send_stanza(presence); +} + +void XmppComponent::send_affiliation_role_change(const std::string& muc_name, + const std::string& target, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to) +{ + Stanza presence("presence"); + presence["from"] = muc_name + "@" + this->served_hostname + "/" + target; + presence["to"] = jid_to; + XmlNode x("x"); + x["xmlns"] = MUC_USER_NS; + XmlNode item("item"); + item["affiliation"] = affiliation; + item["role"] = role; + item.close(); + x.add_child(std::move(item)); + x.close(); + presence.add_child(std::move(x)); + presence.close(); + this->send_stanza(presence); +} + +void XmppComponent::send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, + const std::string& version) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = jid_to; + iq["from"] = jid_from; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + if (version.empty()) + { + XmlNode name("name"); + name.set_inner("biboumi"); + name.close(); + query.add_child(std::move(name)); + XmlNode version("version"); + version.set_inner(SOFTWARE_VERSION); + version.close(); + query.add_child(std::move(version)); + XmlNode os("os"); + os.set_inner(SYSTEM_NAME); + os.close(); + query.add_child(std::move(os)); + } + else + { + XmlNode name("name"); + name.set_inner(version); + name.close(); + query.add_child(std::move(name)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) +{ + Stanza iq("iq"); + iq["type"] = "result"; + iq["id"] = id; + iq["to"] = requester_jid; + iq["from"] = this->served_hostname; + XmlNode query("query"); + query["xmlns"] = DISCO_ITEMS_NS; + query["node"] = ADHOC_NS; + for (const auto& kv: this->adhoc_commands_handler.get_commands()) + { + XmlNode item("item"); + item["jid"] = this->served_hostname; + item["node"] = kv.first; + item["name"] = kv.second.name; + item.close(); + query.add_child(std::move(item)); + } + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void XmppComponent::send_iq_version_request(const std::string& from, + const std::string& jid_to) +{ + Stanza iq("iq"); + iq["type"] = "get"; + iq["id"] = "version_"s + XmppComponent::next_id(); + iq["from"] = from + "@" + this->served_hostname; + iq["to"] = jid_to; + XmlNode query("query"); + query["xmlns"] = VERSION_NS; + query.close(); + iq.add_child(std::move(query)); + iq.close(); + this->send_stanza(iq); +} + +void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) +{ + Stanza iq("iq"); + if (!from_local_part.empty()) + iq["from"] = from_local_part + "@" + this->served_hostname; + else + iq["from"] = this->served_hostname; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + iq.close(); + this->send_stanza(iq); +} + +std::string XmppComponent::next_id() +{ + char uuid_str[37]; + uuid_t uuid; + uuid_generate(uuid); + uuid_unparse(uuid, uuid_str); + return uuid_str; +} diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp new file mode 100644 index 0000000..1bea54e --- /dev/null +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -0,0 +1,240 @@ +#ifndef XMPP_COMPONENT_INCLUDED +# define XMPP_COMPONENT_INCLUDED + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define STREAM_NS "http://etherx.jabber.org/streams" +#define COMPONENT_NS "jabber:component:accept" +#define MUC_NS "http://jabber.org/protocol/muc" +#define MUC_USER_NS MUC_NS"#user" +#define MUC_ADMIN_NS MUC_NS"#admin" +#define DISCO_NS "http://jabber.org/protocol/disco" +#define DISCO_ITEMS_NS DISCO_NS"#items" +#define DISCO_INFO_NS DISCO_NS"#info" +#define XHTMLIM_NS "http://jabber.org/protocol/xhtml-im" +#define STANZA_NS "urn:ietf:params:xml:ns:xmpp-stanzas" +#define STREAMS_NS "urn:ietf:params:xml:ns:xmpp-streams" +#define VERSION_NS "jabber:iq:version" +#define ADHOC_NS "http://jabber.org/protocol/commands" +#define PING_NS "urn:xmpp:ping" + +/** + * An XMPP component, communicating with an XMPP server using the protocole + * described in XEP-0114: Jabber Component Protocol + * + * TODO: implement XEP-0225: Component Connections + */ +class XmppComponent: public TCPSocketHandler +{ +public: + explicit XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); + virtual ~XmppComponent() = default; + + void on_connection_failed(const std::string& reason) override final; + void on_connected() override final; + void on_connection_close(const std::string& error) override final; + void parse_in_buffer(const size_t size) override final; + + /** + * Returns a unique id, to be used in the 'id' element of our iq stanzas. + */ + static std::string next_id(); + bool is_document_open() const; + /** + * Connect to the XMPP server. + */ + void start(); + /** + * Reset the component so we can use the component on a new XMPP stream + */ + void reset(); + /** + * Serialize the stanza and add it to the out_buf to be sent to the + * server. + */ + void send_stanza(const Stanza& stanza); + /** + * Handle the opening of the remote stream + */ + void on_remote_stream_open(const XmlNode& node); + /** + * Handle the closing of the remote stream + */ + void on_remote_stream_close(const XmlNode& node); + /** + * Handle received stanzas + */ + void on_stanza(const Stanza& stanza); + /** + * Send an error stanza. Message being the name of the element inside the + * stanza, and explanation being a short human-readable sentence + * describing the error. + */ + void send_stream_error(const std::string& message, const std::string& explanation); + /** + * Send error stanza, described in http://xmpp.org/rfcs/rfc6120.html#stanzas-error + */ + void send_stanza_error(const std::string& kind, const std::string& to, const std::string& from, + const std::string& id, const std::string& error_type, + const std::string& defined_condition, const std::string& text, + const bool fulljid=true); + /** + * Send the closing signal for our document (not closing the connection though). + */ + void close_document(); + /** + * Send a message from from@served_hostname, with the given body + * + * 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); + /** + * Send a join from a new participant + */ + void send_user_join(const std::string& from, + const std::string& nick, + const std::string& realjid, + const std::string& affiliation, + const std::string& role, + const std::string& to, + const bool self); + /** + * Send an error to indicate that the user tried to join an invalid room + */ + void send_invalid_room_error(const std::string& muc_jid, + 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); + /** + * Send a (non-private) message to the MUC + */ + void send_muc_message(const std::string& muc_name, const std::string& nick, Xmpp::body&& body, const std::string& jid_to); + /** + * 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); + /** + * Indicate that a participant changed his nick + */ + void send_nick_change(const std::string& muc_name, + const std::string& old_nick, + const std::string& new_nick, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to, + const bool self); + /** + * 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); + /** + * Send a generic presence error + */ + void send_presence_error(const std::string& muc_name, + const std::string& nickname, + const std::string& jid_to, + const std::string& type, + const std::string& condition, + const std::string& error_code, + const std::string& text); + /** + * Send a presence from the MUC indicating a change in the role and/or + * affiliation of a participant + */ + void send_affiliation_role_change(const std::string& muc_name, + const std::string& target, + const std::string& affiliation, + const std::string& role, + const std::string& jid_to); + /** + * Send a result IQ with the gateway disco informations. + */ + void send_self_disco_info(const std::string& id, const std::string& jid_to); + /** + * Send a result IQ with the given version, or the gateway version if the + * passed string is empty. + */ + void send_version(const std::string& id, const std::string& jid_to, const std::string& jid_from, + const std::string& version=""); + /** + * Send the list of all available ad-hoc commands to that JID. The list is + * different depending on what JID made the request. + */ + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); + /** + * Send an iq version request + */ + void send_iq_version_request(const std::string& from, + const std::string& jid_to); + /** + * Send an empty iq of type result + */ + void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); + + void handle_handshake(const Stanza& stanza); + void handle_error(const Stanza& stanza); + + virtual void after_handshake() {} + + /** + * Whether or not we ever succeeded our authentication to the XMPP server + */ + bool ever_auth; + /** + * Whether or not this is the first consecutive try on connecting to the + * XMPP server. We use this to delay the connection attempt for a few + * seconds, if it is not the first try. + */ + bool first_connection_try; + +private: + /** + * Return a buffer provided by the XML parser, to read data directly into + * it, and avoiding some unnecessary copy. + */ + void* get_receive_buffer(const size_t size) const override final; + XmppParser parser; + std::string stream_id; + std::string secret; + bool authenticated; + /** + * Whether or not OUR XMPP document is open + */ + bool doc_open; +protected: + std::string served_hostname; + + std::unordered_map> stanza_handlers; + AdhocCommandsHandler adhoc_commands_handler; + + XmppComponent(const XmppComponent&) = delete; + XmppComponent(XmppComponent&&) = delete; + XmppComponent& operator=(const XmppComponent&) = delete; + XmppComponent& operator=(XmppComponent&&) = delete; +}; + +#endif // XMPP_COMPONENT_INCLUDED diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp new file mode 100644 index 0000000..6bb0d28 --- /dev/null +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -0,0 +1,169 @@ +#include +#include + +#include + +/** + * Expat handlers. Called by the Expat library, never by ourself. + * They just forward the call to the XmppParser corresponding methods. + */ + +static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts) +{ + static_cast(user_data)->start_element(name, atts); +} + +static void end_element_handler(void* user_data, const XML_Char* name) +{ + static_cast(user_data)->end_element(name); +} + +static void character_data_handler(void *user_data, const XML_Char *s, int len) +{ + static_cast(user_data)->char_data(s, len); +} + +/** + * XmppParser class + */ + +XmppParser::XmppParser(): + level(0), + current_node(nullptr) +{ + this->init_xml_parser(); +} + +void XmppParser::init_xml_parser() +{ + // Create the expat parser + this->parser = XML_ParserCreateNS("UTF-8", ':'); + XML_SetUserData(this->parser, static_cast(this)); + + // Install Expat handlers + XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler); + XML_SetCharacterDataHandler(this->parser, &character_data_handler); +} + +XmppParser::~XmppParser() +{ + if (this->current_node) + delete this->current_node; + XML_ParserFree(this->parser); +} + +int XmppParser::feed(const char* data, const int len, const bool is_final) +{ + int res = XML_Parse(this->parser, data, len, is_final); + if (res == XML_STATUS_ERROR && + (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) + log_error("Xml_Parse encountered an error: " << + XML_ErrorString(XML_GetErrorCode(this->parser))) + return res; +} + +int XmppParser::parse(const int len, const bool is_final) +{ + int res = XML_ParseBuffer(this->parser, len, is_final); + if (res == XML_STATUS_ERROR) + log_error("Xml_Parsebuffer encountered an error: " << + XML_ErrorString(XML_GetErrorCode(this->parser))); + return res; +} + +void XmppParser::reset() +{ + XML_ParserFree(this->parser); + this->init_xml_parser(); + if (this->current_node) + delete this->current_node; + this->current_node = nullptr; + this->level = 0; +} + +void* XmppParser::get_buffer(const size_t size) const +{ + return XML_GetBuffer(this->parser, static_cast(size)); +} + +void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) +{ + level++; + + XmlNode* new_node = new XmlNode(name, this->current_node); + if (this->current_node) + this->current_node->add_child(new_node); + this->current_node = new_node; + for (size_t i = 0; attribute[i]; i += 2) + this->current_node->set_attribute(attribute[i], attribute[i+1]); + if (this->level == 1) + this->stream_open_event(*this->current_node); +} + +void XmppParser::end_element(const XML_Char* name) +{ + (void)name; + level--; + this->current_node->close(); + if (level == 1) + { + this->stanza_event(*this->current_node); + } + if (level == 0) + { + this->stream_close_event(*this->current_node); + delete this->current_node; + this->current_node = nullptr; + } + else + this->current_node = this->current_node->get_parent(); + if (level == 1) + this->current_node->delete_all_children(); +} + +void XmppParser::char_data(const XML_Char* data, int len) +{ + if (this->current_node->has_children()) + this->current_node->get_last_child()->add_to_tail(std::string(data, len)); + else + this->current_node->add_to_inner(std::string(data, len)); +} + +void XmppParser::stanza_event(const Stanza& stanza) const +{ + for (const auto& callback: this->stanza_callbacks) + { + try { + callback(stanza); + } catch (const std::exception& e) { + log_debug("Unhandled exception: " << e.what()); + } + } +} + +void XmppParser::stream_open_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_open_callbacks) + callback(node); +} + +void XmppParser::stream_close_event(const XmlNode& node) const +{ + for (const auto& callback: this->stream_close_callbacks) + callback(node); +} + +void XmppParser::add_stanza_callback(std::function&& callback) +{ + this->stanza_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_open_callback(std::function&& callback) +{ + this->stream_open_callbacks.emplace_back(std::move(callback)); +} + +void XmppParser::add_stream_close_callback(std::function&& callback) +{ + this->stream_close_callbacks.emplace_back(std::move(callback)); +} diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp new file mode 100644 index 0000000..79c9f8f --- /dev/null +++ b/louloulibs/xmpp/xmpp_parser.hpp @@ -0,0 +1,126 @@ +#ifndef XMPP_PARSER_INCLUDED +# define XMPP_PARSER_INCLUDED + +#include + +#include + +#include + +/** + * A SAX XML parser that builds XML nodes and spawns events when a complete + * stanza is received (an element of level 2), or when the document is + * opened/closed (an element of level 1) + * + * After a stanza_event has been spawned, we delete the whole stanza. This + * means that even with a very long document (in XMPP the document is + * potentially infinite), the memory is never exhausted as long as each + * stanza is reasonnably short. + * + * The element names generated by expat contain the namespace of the + * element, a colon (':') and then the actual name of the element. To get + * an element "x" with a namespace of "http://jabber.org/protocol/muc", you + * just look for an XmlNode named "http://jabber.org/protocol/muc:x" + * + * TODO: enforce the size-limit for the stanza (limit the number of childs + * it can contain). For example forbid the parser going further than level + * 20 (arbitrary number here), and each XML node to have more than 15 childs + * (arbitrary number again). + */ +class XmppParser +{ +public: + explicit XmppParser(); + ~XmppParser(); + +public: + /** + * Init the XML parser and install the callbacks + */ + void init_xml_parser(); + /** + * Feed the parser with some XML data + */ + int feed(const char* data, const int len, const bool is_final); + /** + * Parse the data placed in the parser buffer + */ + int parse(const int size, const bool is_final); + /** + * Reset the parser, so it can be used from scratch afterward + */ + void reset(); + /** + * Get a buffer provided by the xml parser. + */ + void* get_buffer(const size_t size) const; + /** + * Add one callback for the various events that this parser can spawn. + */ + void add_stanza_callback(std::function&& callback); + void add_stream_open_callback(std::function&& callback); + void add_stream_close_callback(std::function&& callback); + + /** + * Called when a new XML element has been opened. We instanciate a new + * XmlNode and set it as our current node. The parent of this new node is + * the previous "current" node. We have all the element's attributes in + * this event. + * + * We spawn a stream_event with this node if this is a level-1 element. + */ + void start_element(const XML_Char* name, const XML_Char** attribute); + /** + * Called when an XML element has been closed. We close the current_node, + * set our current_node as the parent of the current_node, and if that was + * a level-2 element we spawn a stanza_event with this node. + * + * And we then delete the stanza (and everything under it, its children, + * attribute, etc). + */ + void end_element(const XML_Char* name); + /** + * Some inner or tail data has been parsed + */ + void char_data(const XML_Char* data, int len); + /** + * Calls all the stanza_callbacks one by one. + */ + void stanza_event(const Stanza& stanza) const; + /** + * Calls all the stream_open_callbacks one by one. Note: the passed node is not + * closed yet. + */ + void stream_open_event(const XmlNode& node) const; + /** + * Calls all the stream_close_callbacks one by one. + */ + void stream_close_event(const XmlNode& node) const; + +private: + /** + * Expat structure. + */ + XML_Parser parser; + /** + * The current depth in the XML document + */ + size_t level; + /** + * The deepest XML node opened but not yet closed (to which we are adding + * new children, inner or tail) + */ + XmlNode* current_node; + /** + * A list of callbacks to be called on an *_event, receiving the + * concerned Stanza/XmlNode. + */ + std::vector> stanza_callbacks; + std::vector> stream_open_callbacks; + std::vector> stream_close_callbacks; + + XmppParser(const XmppParser&) = delete; + XmppParser& operator=(const XmppParser&) = delete; +}; + +#endif // XMPP_PARSER_INCLUDED diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp new file mode 100644 index 0000000..01d1d2e --- /dev/null +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -0,0 +1,274 @@ +#include + +#include +#include + +#include +#include + +#include + +std::string xml_escape(const std::string& data) +{ + std::string res; + res.reserve(data.size()); + for (size_t pos = 0; pos != data.size(); ++pos) + { + switch(data[pos]) + { + case '&': + res += "&"; + break; + case '<': + res += "<"; + break; + case '>': + res += ">"; + break; + case '\"': + res += """; + break; + case '\'': + res += "'"; + break; + default: + res += data[pos]; + break; + } + } + return res; +} + +std::string xml_unescape(const std::string& data) +{ + std::string res; + res.reserve(data.size()); + const char* str = data.c_str(); + while (str && *str && static_cast(str - data.c_str()) < data.size()) + { + if (*str == '&') + { + if (strncmp(str+1, "amp;", 4) == 0) + { + res += "&"; + str += 4; + } + else if (strncmp(str+1, "lt;", 3) == 0) + { + res += "<"; + str += 3; + } + else if (strncmp(str+1, "gt;", 3) == 0) + { + res += ">"; + str += 3; + } + else if (strncmp(str+1, "quot;", 5) == 0) + { + res += "\""; + str += 5; + } + else if (strncmp(str+1, "apos;", 5) == 0) + { + res += "'"; + str += 5; + } + else + res += "&"; + } + else + res += *str; + str++; + } + return res; +} + +XmlNode::XmlNode(const std::string& name, XmlNode* parent): + parent(parent), + closed(false) +{ + // split the namespace and the name + auto n = name.rfind(":"); + if (n == std::string::npos) + this->name = name; + else + { + this->name = name.substr(n+1); + this->attributes["xmlns"] = name.substr(0, n); + } +} + +XmlNode::XmlNode(const std::string& name): + XmlNode(name, nullptr) +{ +} + +XmlNode::~XmlNode() +{ + this->delete_all_children(); +} + +void XmlNode::delete_all_children() +{ + for (auto& child: this->children) + { + delete child; + } + this->children.clear(); +} + +void XmlNode::set_attribute(const std::string& name, const std::string& value) +{ + this->attributes[name] = value; +} + +void XmlNode::set_tail(const std::string& data) +{ + this->tail = data; +} + +void XmlNode::add_to_tail(const std::string& data) +{ + this->tail += data; +} + +void XmlNode::set_inner(const std::string& data) +{ + this->inner = data; +} + +void XmlNode::add_to_inner(const std::string& data) +{ + this->inner += data; +} + +std::string XmlNode::get_inner() const +{ + return this->inner; +} + +std::string XmlNode::get_tail() const +{ + return this->tail; +} + +XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const +{ + for (auto& child: this->children) + { + if (child->name == name && child->get_tag("xmlns") == xmlns) + return child; + } + return nullptr; +} + +std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const +{ + std::vector res; + for (auto& child: this->children) + { + if (child->name == name && child->get_tag("xmlns") == xmlns) + res.push_back(child); + } + return res; +} + +XmlNode* XmlNode::add_child(XmlNode* child) +{ + child->parent = this; + this->children.push_back(child); + return child; +} + +XmlNode* XmlNode::add_child(XmlNode&& child) +{ + XmlNode* new_node = new XmlNode(std::move(child)); + return this->add_child(new_node); +} + +XmlNode* XmlNode::get_last_child() const +{ + return this->children.back(); +} + +void XmlNode::close() +{ + if (this->closed) + throw std::runtime_error("Closing an already closed XmlNode"); + this->closed = true; +} + +XmlNode* XmlNode::get_parent() const +{ + return this->parent; +} + +void XmlNode::set_name(const std::string& name) +{ + this->name = name; +} + +const std::string XmlNode::get_name() const +{ + return this->name; +} + +std::string XmlNode::to_string() const +{ + std::string res("<"); + res += this->name; + for (const auto& it: this->attributes) + res += " " + it.first + "='" + sanitize(it.second) + "'"; + if (this->closed && !this->has_children() && this->inner.empty()) + res += "/>"; + else + { + res += ">" + sanitize(this->inner); + for (const auto& child: this->children) + res += child->to_string(); + if (this->closed) + { + res += "get_name() + ">"; + } + } + res += sanitize(this->tail); + return res; +} + +bool XmlNode::has_children() const +{ + return !this->children.empty(); +} + +const std::string XmlNode::get_tag(const std::string& name) const +{ + try + { + const auto& value = this->attributes.at(name); + return value; + } + catch (const std::out_of_range& e) + { + return ""; + } +} + +bool XmlNode::del_tag(const std::string& name) +{ + if (this->attributes.erase(name) != 0) + return true; + return false; +} + +std::string& XmlNode::operator[](const std::string& name) +{ + return this->attributes[name]; +} + +std::string sanitize(const std::string& data) +{ + if (utils::is_valid_utf8(data.data())) + return xml_escape(utils::remove_invalid_xml_chars(data)); + else + return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); +} diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp new file mode 100644 index 0000000..f1a6a0f --- /dev/null +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -0,0 +1,160 @@ +#ifndef XMPP_STANZA_INCLUDED +# define XMPP_STANZA_INCLUDED + +#include +#include +#include + +std::string xml_escape(const std::string& data); +std::string xml_unescape(const std::string& data); +std::string sanitize(const std::string& data); + +/** + * Represent an XML node. It has + * - A parent XML node (in the case of the first-level nodes, the parent is + nullptr) + * - zero, one or more children XML nodes + * - A name + * - A map of attributes + * - inner data (text inside the node) + * - tail data (text just after the node) + */ +class XmlNode +{ +public: + explicit XmlNode(const std::string& name, XmlNode* parent); + explicit XmlNode(const std::string& name); + XmlNode(XmlNode&& node): + name(std::move(node.name)), + parent(node.parent), + closed(node.closed), + attributes(std::move(node.attributes)), + children(std::move(node.children)), + inner(std::move(node.inner)), + tail(std::move(node.tail)) + { + node.parent = nullptr; + } + /** + * The copy constructor do not copy the parent attribute. The children + * nodes are all copied recursively. + */ + XmlNode(const XmlNode& node): + name(node.name), + parent(nullptr), + closed(node.closed), + attributes(node.attributes), + children{}, + inner(node.inner), + tail(node.tail) + { + for (XmlNode* child: node.children) + { + XmlNode* child_copy = new XmlNode(*child); + this->add_child(child_copy); + } + } + + ~XmlNode(); + + void delete_all_children(); + void set_attribute(const std::string& name, const std::string& value); + /** + * Set the content of the tail, that is the text just after this node + */ + void set_tail(const std::string& data); + /** + * Append the given data to the content of the tail. This exists because + * the expat library may provide the complete text of an element in more + * than one call + */ + void add_to_tail(const std::string& data); + /** + * Set the content of the inner, that is the text inside this node. + */ + void set_inner(const std::string& data); + /** + * Append the given data to the content of the inner. For the reason + * described in add_to_tail comment. + */ + void add_to_inner(const std::string& data); + /** + * Get the content of inner + */ + std::string get_inner() const; + /** + * Get the content of the tail + */ + std::string get_tail() const; + /** + * Get a pointer to the first child element with that name and that xml namespace + */ + XmlNode* get_child(const std::string& name, const std::string& xmlns) const; + /** + * Get a vector of all the children that have that name and that xml namespace. + */ + std::vector get_children(const std::string& name, const std::string& xmlns) const; + /** + * Add a node child to this node. Assign this node to the child’s parent. + * Returns a pointer to the newly added child. + */ + XmlNode* add_child(XmlNode* child); + XmlNode* add_child(XmlNode&& child); + /** + * Returns the last of the children. If the node doesn't have any child, + * the behaviour is undefined. The user should make sure this is the case + * by calling has_children() for example. + */ + XmlNode* get_last_child() const; + /** + * Mark this node as closed, nothing else + */ + void close(); + XmlNode* get_parent() const; + void set_name(const std::string& name); + const std::string get_name() const; + /** + * Serialize the stanza into a string + */ + std::string to_string() const; + /** + * Whether or not this node has at least one child (if not, this is a leaf + * node) + */ + bool has_children() const; + /** + * Gets the value for the given attribute, returns an empty string if the + * node as no such attribute. + */ + const std::string get_tag(const std::string& name) const; + /** + * Remove the attribute of the node. Does nothing if that attribute is not + * present. Returns true if the tag was removed, false if it was absent. + */ + bool del_tag(const std::string& name); + /** + * Use this to set an attribute's value, like node["id"] = "12"; + */ + std::string& operator[](const std::string& name); + +private: + std::string name; + XmlNode* parent; + bool closed; + std::unordered_map attributes; + std::vector children; + std::string inner; + std::string tail; + + XmlNode& operator=(const XmlNode&) = delete; + XmlNode& operator=(XmlNode&&) = delete; +}; + +/** + * An XMPP stanza is just an XML node of level 2 in the XMPP document (the + * level 1 ones are the , and the ones above 2 are just the + * content of the stanzas) + */ +typedef XmlNode Stanza; + +#endif // XMPP_STANZA_INCLUDED -- cgit v1.2.3 From 427df5fc2a5f1ea0ee4e31ceafba9c6cc5803fd2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 28 May 2015 23:44:44 +0200 Subject: Revert "Use git-archive-all in the make dist, to include the submodules in the archive" This reverts commit 54f96debcaa80ea2d49f722b0df11d227943ebba. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 301d91d..617c113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,5 +153,6 @@ endif() ## Generate a release tarball from the git sources # add_custom_target(dist - COMMAND git-archive-all ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz + COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD + | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -- cgit v1.2.3 From c649e56deb0d107931980b622de4d1cb832a2c33 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 29 May 2015 00:00:00 +0200 Subject: Release version 2.0 --- CHANGELOG | 2 +- CMakeLists.txt | 2 +- COPYING | 2 +- packaging/biboumi.spec | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 04a7a88..020e7ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -Version 2.0 +Version 2.0 2015-05-29 - List channels on an IRC server through an XMPP disco items request - Let the user send any arbitrary raw IRC command by sending a diff --git a/CMakeLists.txt b/CMakeLists.txt index 617c113..a362b7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 2) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") diff --git a/COPYING b/COPYING index 8806baf..e9d67c3 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (c) 2013 Florent Le Coz +Copyright (c) 2015 Florent Le Coz This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index aba47c1..fb9a30f 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -1,6 +1,6 @@ Name: biboumi -Version: 1.1 -Release: 2%{?dist} +Version: 2.0 +Release: 1%{?dist} Summary: Lightweight XMPP to IRC gateway License: zlib @@ -73,6 +73,9 @@ make test_suite/fast VERBOSE=1 %changelog +* Fri May 29 2015 Le Coz Florent - 2.0-1 +- Update to 2.0 sources + * Wed Nov 13 2014 Le Coz Florent - 1.1-2 - Use the -DWITH(OUT) cmake flags for all optional dependencies - Build with the correct optflags -- cgit v1.2.3 From a0042617335386abfba3f36917e6ab9b8a4bf0e2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 4 Jun 2015 04:32:03 +0200 Subject: Bump to 3.0~dev --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a362b7e..cd7aed7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 2.6) project(biboumi) -set(${PROJECT_NAME}_VERSION_MAJOR 2) +set(${PROJECT_NAME}_VERSION_MAJOR 3) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "") +set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") -- cgit v1.2.3 From 8da03f98307e03a767cb68659c8473c896cbd325 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 9 Jun 2015 14:51:55 +0200 Subject: Remove a useless duplicate method --- src/xmpp/biboumi_component.cpp | 25 ------------------------- src/xmpp/biboumi_component.hpp | 5 ----- 2 files changed, 30 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 5d571ec..b7613d3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -506,31 +506,6 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st this->send_stanza(iq); } -void BiboumiComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) -{ - Stanza iq("iq"); - iq["type"] = "result"; - iq["id"] = id; - iq["to"] = requester_jid; - iq["from"] = this->served_hostname; - XmlNode query("query"); - query["xmlns"] = DISCO_ITEMS_NS; - query["node"] = ADHOC_NS; - for (const auto& kv: this->adhoc_commands_handler.get_commands()) - { - XmlNode item("item"); - item["jid"] = this->served_hostname; - item["node"] = kv.first; - item["name"] = kv.second.name; - item.close(); - query.add_child(std::move(item)); - } - query.close(); - iq.add_child(std::move(query)); - iq.close(); - this->send_stanza(iq); -} - void BiboumiComponent::send_iq_version_request(const std::string& from, const std::string& jid_to) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index e9b5d72..8b0b3da 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -52,11 +52,6 @@ public: * Send a result IQ with the gateway disco informations. */ void send_self_disco_info(const std::string& id, const std::string& jid_to); - /** - * Send the list of all available ad-hoc commands to that JID. The list is - * different depending on what JID made the request. - */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); /** * Send an iq version request */ -- cgit v1.2.3 From 7f7c429ae4c49e856a43816138991135ffb7f840 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 9 Jun 2015 14:52:46 +0200 Subject: Do not send the admin-only adhoc commands to non-admin users They were not able to execute them anyway, so this was just a little usability issue. --- louloulibs/xmpp/xmpp_component.cpp | 4 +++- louloulibs/xmpp/xmpp_component.hpp | 3 ++- src/xmpp/biboumi_component.cpp | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 1048f86..8e89208 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -651,7 +651,7 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t this->send_stanza(iq); } -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid) +void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const bool with_admin_only) { Stanza iq("iq"); iq["type"] = "result"; @@ -663,6 +663,8 @@ void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::s query["node"] = ADHOC_NS; for (const auto& kv: this->adhoc_commands_handler.get_commands()) { + if (kv.second.is_admin_only() && !with_admin_only) + continue; XmlNode item("item"); item["jid"] = this->served_hostname; item["node"] = kv.first; diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 1bea54e..e45bb36 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -184,7 +184,8 @@ public: * Send the list of all available ad-hoc commands to that JID. The list is * different depending on what JID made the request. */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid); + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, + const bool with_admin_only); /** * Send an iq version request */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index b7613d3..c1fa339 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -50,7 +50,7 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->stanza_handlers.emplace("iq", std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1)); - this->adhoc_commands_handler.get_commands()= { + this->adhoc_commands_handler.get_commands() = { {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, @@ -370,7 +370,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) const std::string node = query->get_tag("node"); if (node == ADHOC_NS) { - this->send_adhoc_commands_list(id, from); + Jid from_jid(from); + this->send_adhoc_commands_list(id, from, + (Config::get("admin", "") == + from_jid.local + "@" + from_jid.domain)); stanza_error.disable(); } else if (node.empty() && !iid.is_user && !iid.is_channel) -- cgit v1.2.3 From 810ea19fe32c64a93bbe15b9b10541259bcc9321 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 10 Jun 2015 16:47:13 +0200 Subject: Exit with 1 if the connection to the XMPP server fails This helps the system understand if the process started correctly or not, when systemd is not used fix #3078 --- src/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index adc0c7c..159dcbe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -203,6 +203,8 @@ int main(int ac, char** av) #ifdef CARES_FOUND DNSHandler::instance.destroy(); #endif + if (!xmpp_component->ever_auth) + return 1; // To signal that the process did not properly start log_info("All connections cleanly closed, have a nice day."); return 0; } -- cgit v1.2.3 From 0af536a2be6d23fa221f61a77e89436b8fb2a015 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 8 Jul 2015 18:51:05 +0200 Subject: Minor comment typo --- louloulibs/network/tcp_socket_handler.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 2b5cd49..0dde2cf 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -272,11 +272,11 @@ private: static Botan::TLS::Session_Manager_In_Memory session_manager; /** * We use a unique_ptr because we may not want to create the object at - * all. The Botan::TLS::Client object generates a handshake message as - * soon and calls the output_fn callback with it as soon as it is - * created. Therefore, we do not want to create it if do not intend to do - * send any TLS-encrypted message. We create the object only when needed - * (for example after we have negociated a TLS session using a STARTTLS + * all. The Botan::TLS::Client object generates a handshake message and + * calls the output_fn callback with it as soon as it is created. + * Therefore, we do not want to create it if we do not intend to send any + * TLS-encrypted message. We create the object only when needed (for + * example after we have negociated a TLS session using a STARTTLS * message, or stuf like that). * * See start_tls for the method where this object is created. -- cgit v1.2.3 From a42af9e700bcf54777b53b5d0ca999b3b0c107e9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 9 Jul 2015 13:49:08 +0200 Subject: Fix the log_* macros to not have two ; at the end of lines --- louloulibs/logger/logger.hpp | 8 ++++---- louloulibs/xmpp/xmpp_parser.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/louloulibs/logger/logger.hpp b/louloulibs/logger/logger.hpp index 78b1278..ab8bdf1 100644 --- a/louloulibs/logger/logger.hpp +++ b/louloulibs/logger/logger.hpp @@ -37,16 +37,16 @@ __FILENAME__ << ":" << __LINE__ #define log_debug(text)\ - Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl #define log_info(text)\ - Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl #define log_warning(text)\ - Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl #define log_error(text)\ - Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl; + Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl /** * Juste a structure representing a stream doing nothing with its input. diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index 6bb0d28..fbe525a 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -58,7 +58,7 @@ int XmppParser::feed(const char* data, const int len, const bool is_final) if (res == XML_STATUS_ERROR && (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) log_error("Xml_Parse encountered an error: " << - XML_ErrorString(XML_GetErrorCode(this->parser))) + XML_ErrorString(XML_GetErrorCode(this->parser))); return res; } -- cgit v1.2.3 From 4130f1679d909e90d691d47d07cd0e0d4fbe64e1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 9 Jul 2015 13:54:24 +0200 Subject: Check for timeouts on the DNS resolution using c-ares ref #3083 --- louloulibs/network/dns_handler.cpp | 30 ++++++++++++++++++++++++++++-- louloulibs/network/dns_handler.hpp | 4 ++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index ec53683..324784b 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -30,7 +32,13 @@ DNSHandler::DNSHandler() int ares_error; if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) throw std::runtime_error("Failed to initialize c-ares lib: "s + ares_strerror(ares_error)); - if ((ares_error = ::ares_init(&this->channel)) != ARES_SUCCESS) + struct ares_options options = {}; + // The default timeout values are way too high + options.timeout = 1000; + options.tries = 3; + if ((ares_error = ::ares_init_options(&this->channel, + &options, + ARES_OPT_TIMEOUTMS|ARES_OPT_TRIES)) != ARES_SUCCESS) throw std::runtime_error("Failed to initialize c-ares channel: "s + ares_strerror(ares_error)); } @@ -99,7 +107,8 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) { // If not found, create it because we need to watch it if (it == this->socket_handlers.end()) { - this->socket_handlers.emplace_front(std::make_unique(poller, i)); + this->socket_handlers.emplace(this->socket_handlers.begin(), + std::make_unique(poller, i)); it = this->socket_handlers.begin(); } poller->add_socket_handler(it->get()); @@ -107,6 +116,23 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) poller->watch_send_events(it->get()); } } + // Cancel previous timer, if any. + TimedEventsManager::instance().cancel("DNS timeout"); + struct timeval tv; + struct timeval* tvp; + tvp = ::ares_timeout(this->channel, NULL, &tv); + if (tvp) + { + auto future_time = std::chrono::steady_clock::now() + std::chrono::seconds(tvp->tv_sec) + \ + std::chrono::microseconds(tvp->tv_usec); + TimedEventsManager::instance().add_event(TimedEvent(std::move(future_time), + [this]() + { + for (auto& dns_socket_handler: this->socket_handlers) + dns_socket_handler->on_recv(); + }, + "DNS timeout")); + } } #endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index a515f52..ec35374 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -11,7 +11,7 @@ class DNSSocketHandler; # include # include # include -# include +# include void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); @@ -49,7 +49,7 @@ private: * call to ares_fds. DNSSocketHandlers are added to it or removed from it * in the watch_dns_sockets() method */ - std::list> socket_handlers; + std::vector> socket_handlers; ares_channel channel; DNSHandler(const DNSHandler&) = delete; -- cgit v1.2.3 From ac6a74595c679c22ae13eca0609a745e431b339c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 9 Jul 2015 13:56:22 +0200 Subject: Send the cares error message before cleaning it fix #3083 --- louloulibs/network/tcp_socket_handler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index f647b86..e20357b 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -109,8 +109,9 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po addr_res = this->cares_addrinfo; if (!addr_res) { + const auto msg = this->cares_error; this->close(); - this->on_connection_failed(this->cares_error); + this->on_connection_failed(msg); return ; } } -- cgit v1.2.3 From ece4b4969b296a3da010fb22768348650e70962d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 9 Jul 2015 15:30:24 +0200 Subject: If hostname resolution fails, do not try all possible ports --- louloulibs/network/tcp_socket_handler.cpp | 9 ++++++--- louloulibs/network/tcp_socket_handler.hpp | 2 ++ src/irc/irc_client.cpp | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index e20357b..04bb321 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -40,15 +40,16 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): SocketHandler(poller, -1), use_tls(false), connected(false), - connecting(false) + connecting(false), #ifdef CARES_FOUND - ,resolving(false), + resolving(false), resolved(false), resolved4(false), resolved6(false), cares_addrinfo(nullptr), - cares_error() + cares_error(), #endif + hostname_resolution_failed(false) {} TCPSocketHandler::~TCPSocketHandler() @@ -109,6 +110,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po addr_res = this->cares_addrinfo; if (!addr_res) { + this->hostname_resolution_failed = true; const auto msg = this->cares_error; this->close(); this->on_connection_failed(msg); @@ -129,6 +131,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po if (res != 0) { log_warning("getaddrinfo failed: "s + gai_strerror(res)); + this->hostname_resolution_failed = true; this->close(); this->on_connection_failed(gai_strerror(res)); return ; diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 0dde2cf..8069825 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -256,6 +256,8 @@ protected: std::string cares_error; #endif // CARES_FOUND + bool hostname_resolution_failed; + private: TCPSocketHandler(const TCPSocketHandler&) = delete; TCPSocketHandler(TCPSocketHandler&&) = delete; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 905a336..4e8385c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -72,6 +72,11 @@ void IrcClient::on_connection_failed(const std::string& reason) { this->bridge->send_xmpp_message(this->hostname, "", "Connection failed: "s + reason); + + if (this->hostname_resolution_failed) + while (!this->ports_to_try.empty()) + this->ports_to_try.pop(); + if (this->ports_to_try.empty()) { // Send an error message for all room that the user wanted to join -- cgit v1.2.3 From 45fd41eb38ce9cd0c5fd14d7278514b79d7debcd Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 9 Jul 2015 15:30:54 +0200 Subject: Display the resolved IP in debug logs --- louloulibs/network/tcp_socket_handler.cpp | 17 +++++++++++++++++ louloulibs/network/tcp_socket_handler.hpp | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 04bb321..cca6cd2 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -16,6 +16,7 @@ #include #include +#include #ifdef BOTAN_FOUND # include @@ -159,6 +160,9 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po break; } } + + this->display_resolved_ip(rp); + if (::connect(this->socket, rp->ai_addr, rp->ai_addrlen) == 0 || errno == EISCONN) { @@ -343,6 +347,19 @@ void TCPSocketHandler::close() this->port.clear(); } +void TCPSocketHandler::display_resolved_ip(struct addrinfo* rp) const +{ + char buf[INET6_ADDRSTRLEN]; + if (rp->ai_family == AF_INET) + log_debug("Connecting to IP address " << ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin_addr, + buf, sizeof(buf))); + else if (rp->ai_family == AF_INET6) + log_debug("Connecting to IPv6 address " << ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin6_addr, + buf, sizeof(buf))); +} + void TCPSocketHandler::send_data(std::string&& data) { #ifdef BOTAN_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 8069825..835c509 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -264,6 +264,11 @@ private: TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + /** + * Display the resolved IP, just for information purpose. + */ + void display_resolved_ip(struct addrinfo* rp) const; + #ifdef BOTAN_FOUND /** * Botan stuff to manipulate a TLS session. -- cgit v1.2.3 From 525f452cbca1741112dbfd830998eb4dd79bdd3d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 15 Jul 2015 21:17:34 +0200 Subject: louloulibs is part of the repository, so no need to check for its presence --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd7aed7..93e91ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,11 +63,6 @@ if(RONN_EXECUTABLE) add_custom_target(doc DEPENDS ${MAN_PAGE}) endif() -if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs" OR - NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/louloulibs/CMakeLists.txt") - message(FATAL_ERROR "louloulibs/ not found or empty. Make sure you fetched - the submodules with 'git submodule update --init'") -endif() add_subdirectory("louloulibs") include_directories("louloulibs") -- cgit v1.2.3 From df006a191603c4a9f0bb364affa3731c2944fef5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 19 Aug 2015 21:21:42 +0200 Subject: //mode with no argument should work The server will respond with the current channel mode, in private or something --- src/bridge/bridge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index c63b3da..1d46e07 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -181,9 +181,9 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) return ; for (const std::string& line: lines) { - if (line.substr(0, 6) == "/mode ") + if (line.substr(0, 5) == "/mode") { - std::vector args = utils::split(line.substr(6), ' ', false); + std::vector args = utils::split(line.substr(5), ' ', false); irc->send_mode_command(iid.get_local(), args); continue; // We do not want to send that back to the // XMPP user, that’s not a textual message. -- cgit v1.2.3 From e8f22efe34415db0e1e5cb94635b089b18efe055 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Sep 2015 04:42:12 +0200 Subject: XmlNodes are now always closed Remove the close() method and closed attribute. Remove all the calls to close(). (Save one bool per XmlNode, yay, and save a few ifs and some useless function calls. At best it should be unnoticeably faster and lighter and save a few keystrokes in the future) --- louloulibs/xmpp/adhoc_command.cpp | 10 ---- louloulibs/xmpp/adhoc_commands_handler.cpp | 10 ---- louloulibs/xmpp/xmpp_component.cpp | 78 +++--------------------------- louloulibs/xmpp/xmpp_parser.cpp | 1 - louloulibs/xmpp/xmpp_stanza.cpp | 17 ++----- louloulibs/xmpp/xmpp_stanza.hpp | 7 --- src/bridge/colors.cpp | 8 +-- src/xmpp/biboumi_adhoc_commands.cpp | 12 ----- src/xmpp/biboumi_component.cpp | 12 ----- 9 files changed, 10 insertions(+), 145 deletions(-) diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp index 24145f5..c17f1d9 100644 --- a/louloulibs/xmpp/adhoc_command.cpp +++ b/louloulibs/xmpp/adhoc_command.cpp @@ -25,7 +25,6 @@ void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) XmlNode note("note"); note["type"] = "info"; note.set_inner("Pong"); - note.close(); command_node.add_child(std::move(note)); } @@ -35,22 +34,17 @@ void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) x["type"] = "form"; XmlNode title("title"); title.set_inner("Configure your name."); - title.close(); x.add_child(std::move(title)); XmlNode instructions("instructions"); instructions.set_inner("Please provide your name."); - instructions.close(); x.add_child(std::move(instructions)); XmlNode name_field("field"); name_field["var"] = "name"; name_field["type"] = "text-single"; name_field["label"] = "Your name"; XmlNode required("required"); - required.close(); name_field.add_child(std::move(required)); - name_field.close(); x.add_child(std::move(name_field)); - x.close(); command_node.add_child(std::move(x)); } @@ -75,7 +69,6 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) XmlNode note("note"); note["type"] = "info"; note.set_inner("Hello "s + value->get_inner() + "!"s); - note.close(); command_node.delete_all_children(); command_node.add_child(std::move(note)); return; @@ -86,9 +79,7 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) XmlNode error(ADHOC_NS":error"); error["type"] = "modify"; XmlNode condition(STANZA_NS":bad-request"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); session.terminate(); } @@ -100,6 +91,5 @@ void Reload(XmppComponent*, AdhocSession&, XmlNode& command_node) XmlNode note("note"); note["type"] = "info"; note.set_inner("Configuration reloaded."); - note.close(); command_node.add_child(std::move(note)); } diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 46c8a32..458a22c 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -36,9 +36,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm XmlNode error(ADHOC_NS":error"); error["type"] = "cancel"; XmlNode condition(STANZA_NS":item-not-found"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); } else if (command_it->second.is_admin_only() && @@ -47,9 +45,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm XmlNode error(ADHOC_NS":error"); error["type"] = "cancel"; XmlNode condition(STANZA_NS":forbidden"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); } else @@ -72,9 +68,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm XmlNode error(ADHOC_NS":error"); error["type"] = "modify"; XmlNode condition(STANZA_NS":bad-request"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); } else if (action == "execute" || action == "next" || action == "complete") @@ -95,9 +89,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm command_node["status"] = "executing"; XmlNode actions("actions"); XmlNode next("next"); - next.close(); actions.add_child(std::move(next)); - actions.close(); command_node.add_child(std::move(actions)); } } @@ -112,9 +104,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm XmlNode error(ADHOC_NS":error"); error["type"] = "modify"; XmlNode condition(STANZA_NS":bad-request"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); } } diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 8e89208..19111ba 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -89,12 +89,9 @@ void XmppComponent::on_connected() { log_info("connected to XMPP server"); this->first_connection_try = true; - XmlNode node("", nullptr); - node.set_name("stream:stream"); - node["xmlns"] = COMPONENT_NS; - node["xmlns:stream"] = STREAM_NS; - node["to"] = this->served_hostname; - this->send_stanza(node); + this->send_data(""); this->doc_open = true; // We may have some pending data to send: this happens when we try to send // some data before we are actually connected. We send that data right now, if any @@ -152,10 +149,9 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) sprintf(digest + (i*2), "%02x", result[i]); digest[HASH_LENGTH * 2] = '\0'; - Stanza handshake(COMPONENT_NS":handshake"); - handshake.set_inner(digest); - handshake.close(); - this->send_stanza(handshake); + this->send_data(""); + this->send_data(digest); + this->send_data(""); } void XmppComponent::on_remote_stream_close(const XmlNode& node) @@ -192,9 +188,7 @@ void XmppComponent::send_stream_error(const std::string& name, const std::string error["xmlns"] = STREAM_NS; if (!explanation.empty()) error.set_inner(explanation); - error.close(); node.add_child(std::move(error)); - node.close(); this->send_stanza(node); } @@ -220,19 +214,15 @@ void XmppComponent::send_stanza_error(const std::string& kind, const std::string error["type"] = error_type; XmlNode inner_error(defined_condition); inner_error["xmlns"] = STANZA_NS; - inner_error.close(); error.add_child(std::move(inner_error)); if (!text.empty()) { XmlNode text_node("text"); text_node["xmlns"] = STANZA_NS; text_node.set_inner(text); - text_node.close(); error.add_child(std::move(text_node)); } - error.close(); node.add_child(std::move(error)); - node.close(); this->send_stanza(node); } @@ -295,7 +285,6 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con node["type"] = type; XmlNode body_node("body"); body_node.set_inner(std::get<0>(body)); - body_node.close(); node.add_child(std::move(body_node)); if (std::get<1>(body)) { @@ -303,10 +292,8 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con html["xmlns"] = XHTMLIM_NS; // Pass the ownership of the pointer to this xmlnode html.add_child(std::get<1>(body).release()); - html.close(); node.add_child(std::move(html)); } - node.close(); this->send_stanza(node); } @@ -336,19 +323,15 @@ void XmppComponent::send_user_join(const std::string& from, if (!preped_jid.empty()) item["jid"] = preped_jid; } - item.close(); x.add_child(std::move(item)); if (self) { XmlNode status("status"); status["code"] = "110"; - status.close(); x.add_child(std::move(status)); } - x.close(); node.add_child(std::move(x)); - node.close(); this->send_stanza(node); } @@ -365,14 +348,12 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, presence["type"] = "error"; XmlNode x("x"); x["xmlns"] = MUC_NS; - x.close(); presence.add_child(std::move(x)); XmlNode error("error"); error["by"] = muc_name + "@" + this->served_hostname; error["type"] = "cancel"; XmlNode item_not_found("item-not-found"); item_not_found["xmlns"] = STANZA_NS; - item_not_found.close(); error.add_child(std::move(item_not_found)); XmlNode text("text"); text["xmlns"] = STANZA_NS; @@ -380,11 +361,8 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name, text.set_inner(muc_name + " is not a valid IRC channel name. A correct room jid is of the form: #%@" + this->served_hostname); - text.close(); error.add_child(std::move(text)); - error.close(); presence.add_child(std::move(error)); - presence.close(); this->send_stanza(presence); } @@ -396,13 +374,11 @@ void XmppComponent::send_invalid_user_error(const std::string& user_name, const message["type"] = "error"; XmlNode x("x"); x["xmlns"] = MUC_NS; - x.close(); 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; - item_not_found.close(); error.add_child(std::move(item_not_found)); XmlNode text("text"); text["xmlns"] = STANZA_NS; @@ -410,11 +386,8 @@ void XmppComponent::send_invalid_user_error(const std::string& user_name, const text.set_inner(user_name + " is not a valid IRC user name. A correct user jid is of the form: !@" + this->served_hostname); - text.close(); error.add_child(std::move(text)); - error.close(); message.add_child(std::move(error)); - message.close(); this->send_stanza(message); } @@ -426,9 +399,7 @@ void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, cons message["type"] = "groupchat"; XmlNode subject("subject"); subject.set_inner(std::get<0>(topic)); - subject.close(); message.add_child(std::move(subject)); - message.close(); this->send_stanza(message); } @@ -443,7 +414,6 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str message["type"] = "groupchat"; XmlNode body("body"); body.set_inner(std::get<0>(xmpp_body)); - body.close(); message.add_child(std::move(body)); if (std::get<1>(xmpp_body)) { @@ -451,10 +421,8 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str html["xmlns"] = XHTMLIM_NS; // Pass the ownership of the pointer to this xmlnode html.add_child(std::get<1>(xmpp_body).release()); - html.close(); message.add_child(std::move(html)); } - message.close(); this->send_stanza(message); } @@ -471,19 +439,15 @@ void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& ni { XmlNode status("status"); status["code"] = "110"; - status.close(); x.add_child(std::move(status)); } - x.close(); presence.add_child(std::move(x)); if (!message_str.empty()) { XmlNode status("status"); status.set_inner(message_str); - status.close(); presence.add_child(std::move(status)); } - presence.close(); this->send_stanza(presence); } @@ -503,22 +467,17 @@ void XmppComponent::send_nick_change(const std::string& muc_name, x["xmlns"] = MUC_USER_NS; XmlNode item("item"); item["nick"] = new_nick; - item.close(); x.add_child(std::move(item)); XmlNode status("status"); status["code"] = "303"; - status.close(); x.add_child(std::move(status)); if (self) { XmlNode status2("status"); status2["code"] = "110"; - status2.close(); x.add_child(std::move(status2)); } - x.close(); presence.add_child(std::move(x)); - presence.close(); this->send_stanza(presence); this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self); @@ -542,21 +501,15 @@ void XmppComponent::kick_user(const std::string& muc_name, XmlNode actor("actor"); actor["nick"] = author; actor["jid"] = author; // backward compatibility with old clients - actor.close(); item.add_child(std::move(actor)); XmlNode reason("reason"); reason.set_inner(txt); - reason.close(); item.add_child(std::move(reason)); - item.close(); x.add_child(std::move(item)); XmlNode status("status"); status["code"] = "307"; - status.close(); x.add_child(std::move(status)); - x.close(); presence.add_child(std::move(x)); - presence.close(); this->send_stanza(presence); } @@ -574,7 +527,6 @@ void XmppComponent::send_presence_error(const std::string& muc_name, presence["type"] = "error"; XmlNode x("x"); x["xmlns"] = MUC_NS; - x.close(); presence.add_child(std::move(x)); XmlNode error("error"); error["by"] = muc_name + "@" + this->served_hostname; @@ -583,11 +535,8 @@ void XmppComponent::send_presence_error(const std::string& muc_name, error["code"] = error_code; XmlNode subnode(condition); subnode["xmlns"] = STANZA_NS; - subnode.close(); error.add_child(std::move(subnode)); - error.close(); presence.add_child(std::move(error)); - presence.close(); this->send_stanza(presence); } @@ -605,11 +554,8 @@ void XmppComponent::send_affiliation_role_change(const std::string& muc_name, XmlNode item("item"); item["affiliation"] = affiliation; item["role"] = role; - item.close(); x.add_child(std::move(item)); - x.close(); presence.add_child(std::move(x)); - presence.close(); this->send_stanza(presence); } @@ -627,27 +573,21 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t { XmlNode name("name"); name.set_inner("biboumi"); - name.close(); query.add_child(std::move(name)); XmlNode version("version"); version.set_inner(SOFTWARE_VERSION); - version.close(); query.add_child(std::move(version)); XmlNode os("os"); os.set_inner(SYSTEM_NAME); - os.close(); query.add_child(std::move(os)); } else { XmlNode name("name"); name.set_inner(version); - name.close(); query.add_child(std::move(name)); } - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } @@ -669,12 +609,9 @@ void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::s item["jid"] = this->served_hostname; item["node"] = kv.first; item["name"] = kv.second.name; - item.close(); query.add_child(std::move(item)); } - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } @@ -688,9 +625,7 @@ void XmppComponent::send_iq_version_request(const std::string& from, iq["to"] = jid_to; XmlNode query("query"); query["xmlns"] = VERSION_NS; - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } @@ -704,7 +639,6 @@ void XmppComponent::send_iq_result(const std::string& id, const std::string& to_ iq["to"] = to_jid; iq["id"] = id; iq["type"] = "result"; - iq.close(); this->send_stanza(iq); } diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index fbe525a..3903b73 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -104,7 +104,6 @@ void XmppParser::end_element(const XML_Char* name) { (void)name; level--; - this->current_node->close(); if (level == 1) { this->stanza_event(*this->current_node); diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index 01d1d2e..c66b4be 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -84,8 +84,7 @@ std::string xml_unescape(const std::string& data) } XmlNode::XmlNode(const std::string& name, XmlNode* parent): - parent(parent), - closed(false) + parent(parent) { // split the namespace and the name auto n = name.rfind(":"); @@ -191,13 +190,6 @@ XmlNode* XmlNode::get_last_child() const return this->children.back(); } -void XmlNode::close() -{ - if (this->closed) - throw std::runtime_error("Closing an already closed XmlNode"); - this->closed = true; -} - XmlNode* XmlNode::get_parent() const { return this->parent; @@ -219,17 +211,14 @@ std::string XmlNode::to_string() const res += this->name; for (const auto& it: this->attributes) res += " " + it.first + "='" + sanitize(it.second) + "'"; - if (this->closed && !this->has_children() && this->inner.empty()) + if (!this->has_children() && this->inner.empty()) res += "/>"; else { res += ">" + sanitize(this->inner); for (const auto& child: this->children) res += child->to_string(); - if (this->closed) - { - res += "get_name() + ">"; - } + res += "get_name() + ">"; } res += sanitize(this->tail); return res; diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index f1a6a0f..3d5b0c5 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -27,7 +27,6 @@ public: XmlNode(XmlNode&& node): name(std::move(node.name)), parent(node.parent), - closed(node.closed), attributes(std::move(node.attributes)), children(std::move(node.children)), inner(std::move(node.inner)), @@ -42,7 +41,6 @@ public: XmlNode(const XmlNode& node): name(node.name), parent(nullptr), - closed(node.closed), attributes(node.attributes), children{}, inner(node.inner), @@ -106,10 +104,6 @@ public: * by calling has_children() for example. */ XmlNode* get_last_child() const; - /** - * Mark this node as closed, nothing else - */ - void close(); XmlNode* get_parent() const; void set_name(const std::string& name); const std::string get_name() const; @@ -140,7 +134,6 @@ public: private: std::string name; XmlNode* parent; - bool closed; std::unordered_map attributes; std::vector children; std::string inner; diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index 3d40ac4..bdc34bf 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -80,7 +80,6 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) else if (s[pos_end] == IRC_FORMAT_NEWLINE_CHAR) { XmlNode* br_node = new XmlNode("br"); - br_node->close(); current_node->add_child(br_node); cleaned += '\n'; } @@ -126,7 +125,6 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) // close opened span, if any if (current_node != result.get()) { - current_node->close(); result->add_child(current_node); current_node = result.get(); } @@ -163,12 +161,8 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) current_node->add_to_inner(txt); if (current_node != result.get()) - { - current_node->close(); - result->add_child(current_node); - } + result->add_child(current_node); - result->close(); Xmpp::body body_res = std::make_tuple(cleaned, std::move(result)); return body_res; } diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 089eebf..0dcaf0c 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -10,18 +10,15 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& x["type"] = "form"; XmlNode title("title"); title.set_inner("Disconnect a user from the gateway"); - title.close(); x.add_child(std::move(title)); XmlNode instructions("instructions"); instructions.set_inner("Choose a user JID and a quit message"); - instructions.close(); x.add_child(std::move(instructions)); XmlNode jids_field("field"); jids_field["var"] = "jids"; jids_field["type"] = "list-multi"; jids_field["label"] = "The JIDs to disconnect"; XmlNode required("required"); - required.close(); jids_field.add_child(std::move(required)); for (Bridge* bridge: biboumi_component->get_bridges()) { @@ -29,12 +26,9 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& option["label"] = bridge->get_jid(); XmlNode value("value"); value.set_inner(bridge->get_jid()); - value.close(); option.add_child(std::move(value)); - option.close(); jids_field.add_child(std::move(option)); } - jids_field.close(); x.add_child(std::move(jids_field)); XmlNode message_field("field"); @@ -43,11 +37,8 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& message_field["label"] = "Quit message"; XmlNode message_value("value"); message_value.set_inner("Disconnected by admin"); - message_value.close(); message_field.add_child(std::move(message_value)); - message_field.close(); x.add_child(std::move(message_field)); - x.close(); command_node.add_child(std::move(x)); } @@ -95,7 +86,6 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X note.set_inner("1 user has been disconnected."); else note.set_inner(std::to_string(num) + " users have been disconnected."); - note.close(); command_node.add_child(std::move(note)); return; } @@ -103,9 +93,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X XmlNode error(ADHOC_NS":error"); error["type"] = "modify"; XmlNode condition(STANZA_NS":bad-request"); - condition.close(); error.add_child(std::move(condition)); - error.close(); command_node.add_child(std::move(error)); session.terminate(); } diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index c1fa339..f578c30 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -321,7 +321,6 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) else response["type"] = "result"; response.add_child(std::move(inner_node)); - response.close(); this->send_stanza(response); stanza_error.disable(); } @@ -494,18 +493,14 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["category"] = "conference"; identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; - identity.close(); query.add_child(std::move(identity)); for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) { XmlNode feature("feature"); feature["var"] = ns; - feature.close(); query.add_child(std::move(feature)); } - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } @@ -519,9 +514,7 @@ void BiboumiComponent::send_iq_version_request(const std::string& from, iq["to"] = jid_to; XmlNode query("query"); query["xmlns"] = VERSION_NS; - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } @@ -536,9 +529,7 @@ void BiboumiComponent::send_ping_request(const std::string& from, iq["to"] = jid_to; XmlNode ping("ping"); ping["xmlns"] = PING_NS; - ping.close(); iq.add_child(std::move(ping)); - iq.close(); this->send_stanza(iq); auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza) @@ -571,11 +562,8 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, { XmlNode item("item"); item["jid"] = room.channel + "%" + from + "@" + this->served_hostname; - item.close(); query.add_child(std::move(item)); } - query.close(); iq.add_child(std::move(query)); - iq.close(); this->send_stanza(iq); } -- cgit v1.2.3 From d7e1214cbcff2d34f45687eff7c083a89bf04802 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Sep 2015 04:53:12 +0200 Subject: XmlNode::to_string uses an ostringstream instead of a string On my poor benchmark, it was infinitesimally faster. --- louloulibs/xmpp/xmpp_stanza.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index c66b4be..aec91a7 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -207,21 +208,21 @@ const std::string XmlNode::get_name() const std::string XmlNode::to_string() const { - std::string res("<"); - res += this->name; + std::ostringstream res; + res << "<" << this->name; for (const auto& it: this->attributes) - res += " " + it.first + "='" + sanitize(it.second) + "'"; + res << " " << it.first << "='" << sanitize(it.second) + "'"; if (!this->has_children() && this->inner.empty()) - res += "/>"; + res << "/>"; else { - res += ">" + sanitize(this->inner); + res << ">" + sanitize(this->inner); for (const auto& child: this->children) - res += child->to_string(); - res += "get_name() + ">"; + res << child->to_string(); + res << "get_name() << ">"; } - res += sanitize(this->tail); - return res; + res << sanitize(this->tail); + return res.str(); } bool XmlNode::has_children() const -- cgit v1.2.3 From 38564d77c7679dd4de4562d321146322b6211d61 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Sep 2015 04:56:06 +0200 Subject: Little cleanup of the XmlNode class Use map instead of unordered map (it's not slower, and it's shorter). Use the default move constructor. --- louloulibs/xmpp/xmpp_stanza.hpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 3d5b0c5..ee6b25b 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -1,7 +1,7 @@ #ifndef XMPP_STANZA_INCLUDED # define XMPP_STANZA_INCLUDED -#include +#include #include #include @@ -24,18 +24,9 @@ class XmlNode public: explicit XmlNode(const std::string& name, XmlNode* parent); explicit XmlNode(const std::string& name); - XmlNode(XmlNode&& node): - name(std::move(node.name)), - parent(node.parent), - attributes(std::move(node.attributes)), - children(std::move(node.children)), - inner(std::move(node.inner)), - tail(std::move(node.tail)) - { - node.parent = nullptr; - } + XmlNode(XmlNode&& node) = default; /** - * The copy constructor do not copy the parent attribute. The children + * The copy constructor does not copy the parent attribute. The children * nodes are all copied recursively. */ XmlNode(const XmlNode& node): @@ -134,7 +125,7 @@ public: private: std::string name; XmlNode* parent; - std::unordered_map attributes; + std::map attributes; std::vector children; std::string inner; std::string tail; -- cgit v1.2.3 From f3b3d937ae274d0eec4a737d11ba19a7f4ceef03 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Sep 2015 14:47:57 +0200 Subject: =?UTF-8?q?Use=20unique=5Fptr=20to=20store=20the=20XmlNode?= =?UTF-8?q?=E2=80=99s=20children?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fix some constness things --- louloulibs/xmpp/adhoc_command.cpp | 8 ++++---- louloulibs/xmpp/xmpp_component.cpp | 6 +++--- louloulibs/xmpp/xmpp_parser.cpp | 19 ++++++++++--------- louloulibs/xmpp/xmpp_parser.hpp | 5 +++++ louloulibs/xmpp/xmpp_stanza.cpp | 36 ++++++++++++++---------------------- louloulibs/xmpp/xmpp_stanza.hpp | 18 ++++++++---------- src/bridge/colors.cpp | 12 +++++++----- src/xmpp/biboumi_adhoc_commands.cpp | 12 ++++++------ src/xmpp/biboumi_component.cpp | 24 ++++++++++++------------ 9 files changed, 69 insertions(+), 71 deletions(-) diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp index c17f1d9..b8b07c6 100644 --- a/louloulibs/xmpp/adhoc_command.cpp +++ b/louloulibs/xmpp/adhoc_command.cpp @@ -51,11 +51,11 @@ void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) { // Find out if the name was provided in the form. - XmlNode* x = command_node.get_child("x", "jabber:x:data"); + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) { - XmlNode* name_field = nullptr; - for (XmlNode* field: x->get_children("field", "jabber:x:data")) + const XmlNode* name_field = nullptr; + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) if (field->get_tag("var") == "name") { name_field = field; @@ -63,7 +63,7 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) } if (name_field) { - XmlNode* value = name_field->get_child("value", "jabber:x:data"); + const XmlNode* value = name_field->get_child("value", "jabber:x:data"); if (value) { XmlNode note("note"); diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 19111ba..8cd722d 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -256,7 +256,7 @@ void XmppComponent::handle_handshake(const Stanza& stanza) void XmppComponent::handle_error(const Stanza& stanza) { - XmlNode* text = stanza.get_child("text", STREAMS_NS); + const XmlNode* text = stanza.get_child("text", STREAMS_NS); std::string error_message("Unspecified error"); if (text) error_message = text->get_inner(); @@ -291,7 +291,7 @@ void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, con XmlNode html("html"); html["xmlns"] = XHTMLIM_NS; // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(body).release()); + html.add_child(std::move(std::get<1>(body))); node.add_child(std::move(html)); } this->send_stanza(node); @@ -420,7 +420,7 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str XmlNode html("html"); html["xmlns"] = XHTMLIM_NS; // Pass the ownership of the pointer to this xmlnode - html.add_child(std::get<1>(xmpp_body).release()); + html.add_child(std::move(std::get<1>(xmpp_body))); message.add_child(std::move(html)); } this->send_stanza(message); diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index 3903b73..c1fd63a 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -29,7 +29,8 @@ static void character_data_handler(void *user_data, const XML_Char *s, int len) XmppParser::XmppParser(): level(0), - current_node(nullptr) + current_node(nullptr), + root(nullptr) { this->init_xml_parser(); } @@ -47,8 +48,6 @@ void XmppParser::init_xml_parser() XmppParser::~XmppParser() { - if (this->current_node) - delete this->current_node; XML_ParserFree(this->parser); } @@ -75,9 +74,8 @@ void XmppParser::reset() { XML_ParserFree(this->parser); this->init_xml_parser(); - if (this->current_node) - delete this->current_node; this->current_node = nullptr; + this->root.reset(nullptr); this->level = 0; } @@ -90,10 +88,13 @@ void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) { level++; - XmlNode* new_node = new XmlNode(name, this->current_node); + auto new_node = std::make_unique(name, this->current_node); + auto new_node_ptr = new_node.get(); if (this->current_node) - this->current_node->add_child(new_node); - this->current_node = new_node; + this->current_node->add_child(std::move(new_node)); + else + this->root = std::move(new_node); + this->current_node = new_node_ptr; for (size_t i = 0; attribute[i]; i += 2) this->current_node->set_attribute(attribute[i], attribute[i+1]); if (this->level == 1) @@ -111,8 +112,8 @@ void XmppParser::end_element(const XML_Char* name) if (level == 0) { this->stream_close_event(*this->current_node); - delete this->current_node; this->current_node = nullptr; + this->root.reset(); } else this->current_node = this->current_node->get_parent(); diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp index 79c9f8f..4de639d 100644 --- a/louloulibs/xmpp/xmpp_parser.hpp +++ b/louloulibs/xmpp/xmpp_parser.hpp @@ -111,6 +111,11 @@ private: * new children, inner or tail) */ XmlNode* current_node; + /** + * The root node has no parent, so we keep it here: the XmppParser object + * is its owner. + */ + std::unique_ptr root; /** * A list of callbacks to be called on an *_event, receiving the * concerned Stanza/XmlNode. diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index aec91a7..3ba8483 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -103,17 +103,8 @@ XmlNode::XmlNode(const std::string& name): { } -XmlNode::~XmlNode() -{ - this->delete_all_children(); -} - void XmlNode::delete_all_children() { - for (auto& child: this->children) - { - delete child; - } this->children.clear(); } @@ -152,43 +143,44 @@ std::string XmlNode::get_tail() const return this->tail; } -XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const +const XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const { - for (auto& child: this->children) + for (const auto& child: this->children) { if (child->name == name && child->get_tag("xmlns") == xmlns) - return child; + return child.get(); } return nullptr; } -std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const +std::vector XmlNode::get_children(const std::string& name, const std::string& xmlns) const { - std::vector res; - for (auto& child: this->children) + std::vector res; + for (const auto& child: this->children) { if (child->name == name && child->get_tag("xmlns") == xmlns) - res.push_back(child); + res.push_back(child.get()); } return res; } -XmlNode* XmlNode::add_child(XmlNode* child) +XmlNode* XmlNode::add_child(std::unique_ptr child) { child->parent = this; - this->children.push_back(child); - return child; + auto ret = child.get(); + this->children.push_back(std::move(child)); + return ret; } XmlNode* XmlNode::add_child(XmlNode&& child) { - XmlNode* new_node = new XmlNode(std::move(child)); - return this->add_child(new_node); + auto new_node = std::make_unique(std::move(child)); + return this->add_child(std::move(new_node)); } XmlNode* XmlNode::get_last_child() const { - return this->children.back(); + return this->children.back().get(); } XmlNode* XmlNode::get_parent() const diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index ee6b25b..4a54379 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -4,6 +4,7 @@ #include #include #include +#include std::string xml_escape(const std::string& data); std::string xml_unescape(const std::string& data); @@ -37,14 +38,11 @@ public: inner(node.inner), tail(node.tail) { - for (XmlNode* child: node.children) - { - XmlNode* child_copy = new XmlNode(*child); - this->add_child(child_copy); - } + for (const auto& child: node.children) + this->add_child(std::make_unique(*child)); } - ~XmlNode(); + ~XmlNode() = default; void delete_all_children(); void set_attribute(const std::string& name, const std::string& value); @@ -78,16 +76,16 @@ public: /** * Get a pointer to the first child element with that name and that xml namespace */ - XmlNode* get_child(const std::string& name, const std::string& xmlns) const; + const XmlNode* get_child(const std::string& name, const std::string& xmlns) const; /** * Get a vector of all the children that have that name and that xml namespace. */ - std::vector get_children(const std::string& name, const std::string& xmlns) const; + std::vector get_children(const std::string& name, const std::string& xmlns) const; /** * Add a node child to this node. Assign this node to the child’s parent. * Returns a pointer to the newly added child. */ - XmlNode* add_child(XmlNode* child); + XmlNode* add_child(std::unique_ptr child); XmlNode* add_child(XmlNode&& child); /** * Returns the last of the children. If the node doesn't have any child, @@ -126,7 +124,7 @@ private: std::string name; XmlNode* parent; std::map attributes; - std::vector children; + std::vector> children; std::string inner; std::string tail; diff --git a/src/bridge/colors.cpp b/src/bridge/colors.cpp index bdc34bf..66f51ee 100644 --- a/src/bridge/colors.cpp +++ b/src/bridge/colors.cpp @@ -62,7 +62,9 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) std::unique_ptr result = std::make_unique("body"); (*result)["xmlns"] = XHTML_NS; + std::unique_ptr current_node_up; XmlNode* current_node = result.get(); + std::string::size_type pos_start = 0; std::string::size_type pos_end; @@ -79,8 +81,7 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) styles.strong = !styles.strong; else if (s[pos_end] == IRC_FORMAT_NEWLINE_CHAR) { - XmlNode* br_node = new XmlNode("br"); - current_node->add_child(br_node); + current_node->add_child(std::make_unique("br")); cleaned += '\n'; } else if (s[pos_end] == IRC_FORMAT_UNDERLINE_CHAR) @@ -125,7 +126,7 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) // close opened span, if any if (current_node != result.get()) { - result->add_child(current_node); + result->add_child(std::move(current_node_up)); current_node = result.get(); } // Take all currently-applied style and create a new span with it @@ -144,7 +145,8 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) irc_colors_to_css[styles.bg % IRC_NUM_COLORS] + ";"; if (!styles_str.empty()) { - current_node = new XmlNode("span"); + current_node_up = std::make_unique("span"); + current_node = current_node_up.get(); (*current_node)["style"] = styles_str; } @@ -161,7 +163,7 @@ Xmpp::body irc_format_to_xhtmlim(const std::string& s) current_node->add_to_inner(txt); if (current_node != result.get()) - result->add_child(current_node); + result->add_child(std::move(current_node_up)); Xmpp::body body_res = std::make_tuple(cleaned, std::move(result)); return body_res; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 0dcaf0c..3dbee81 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -48,26 +48,26 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X // Find out if the jids, and the quit message are provided in the form. std::string quit_message; - XmlNode* x = command_node.get_child("x", "jabber:x:data"); + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) { - XmlNode* message_field = nullptr; - XmlNode* jids_field = nullptr; - for (XmlNode* field: x->get_children("field", "jabber:x:data")) + const XmlNode* message_field = nullptr; + const XmlNode* jids_field = nullptr; + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) if (field->get_tag("var") == "jids") jids_field = field; else if (field->get_tag("var") == "quit-message") message_field = field; if (message_field) { - XmlNode* value = message_field->get_child("value", "jabber:x:data"); + const XmlNode* value = message_field->get_child("value", "jabber:x:data"); if (value) quit_message = value->get_inner(); } if (jids_field) { std::size_t num = 0; - for (XmlNode* value: jids_field->get_children("value", "jabber:x:data")) + for (const XmlNode* value: jids_field->get_children("value", "jabber:x:data")) { Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); if (bridge) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f578c30..51775ab 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -125,14 +125,14 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) const std::string own_nick = bridge->get_own_nick(iid); if (!own_nick.empty() && own_nick != to.resource) bridge->send_irc_nick_change(iid, to.resource); - XmlNode* x = stanza.get_child("x", MUC_NS); - XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; + const XmlNode* x = stanza.get_child("x", MUC_NS); + const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; bridge->join_irc_channel(iid, to.resource, password ? password->get_inner() : ""); } else if (type == "unavailable") { - XmlNode* status = stanza.get_child("status", COMPONENT_NS); + const XmlNode* status = stanza.get_child("status", COMPONENT_NS); bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); } } @@ -174,7 +174,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) this->send_stanza_error("message", from, to_str, id, error_type, error_name, ""); }); - XmlNode* body = stanza.get_child("body", COMPONENT_NS); + const XmlNode* body = stanza.get_child("body", COMPONENT_NS); try { // catch IRCNotConnected exceptions if (type == "groupchat" && iid.is_channel) @@ -183,7 +183,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) { bridge->send_channel_message(iid, body->get_inner()); } - XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); + const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS); if (subject) bridge->set_channel_topic(iid, subject->get_inner()); } @@ -283,7 +283,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) try { if (type == "set") { - XmlNode* query; + const XmlNode* query; if ((query = stanza.get_child("query", MUC_ADMIN_NS))) { const XmlNode* child = query->get_child("item", MUC_ADMIN_NS); @@ -298,7 +298,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (role == "none") { // This is a kick std::string reason; - XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); + const XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS); if (reason_el) reason = reason_el->get_inner(); bridge->send_irc_kick(iid, nick, reason, id, from); @@ -327,7 +327,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) } else if (type == "get") { - XmlNode* query; + const XmlNode* query; if ((query = stanza.get_child("query", DISCO_INFO_NS))) { // Disco info if (to_str == this->served_hostname) @@ -405,12 +405,12 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) else if (type == "result") { stanza_error.disable(); - XmlNode* query; + const XmlNode* query; if ((query = stanza.get_child("query", VERSION_NS))) { - XmlNode* name_node = query->get_child("name", VERSION_NS); - XmlNode* version_node = query->get_child("version", VERSION_NS); - XmlNode* os_node = query->get_child("os", VERSION_NS); + const XmlNode* name_node = query->get_child("name", VERSION_NS); + const XmlNode* version_node = query->get_child("version", VERSION_NS); + const XmlNode* os_node = query->get_child("os", VERSION_NS); std::string name; std::string version; std::string os; -- cgit v1.2.3 From c3309d0acfedaed867ae1bc14cd7a65fe5be1419 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Sep 2015 20:01:44 +0200 Subject: Trivial cleanup --- louloulibs/xmpp/xmpp_component.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 8cd722d..2214ecf 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -101,13 +101,9 @@ void XmppComponent::on_connected() void XmppComponent::on_connection_close(const std::string& error) { if (error.empty()) - { - log_info("XMPP server closed connection"); - } + log_info("XMPP server closed connection"); else - { - log_info("XMPP server closed connection: " << error); - } + log_info("XMPP server closed connection: " << error); } void XmppComponent::parse_in_buffer(const size_t size) -- cgit v1.2.3 From 4cfcc79114d89096219039104674d35ca1aba5ca Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 6 Sep 2015 18:55:48 +0200 Subject: Check the number of argument of every IRC command received from the server Each IrcClient callback has a max and min size of argument, we call the callback only if the parsed message has a correct number of arguments, otherwise it is ignored (with a warning logged). --- src/irc/irc_client.cpp | 26 +++++--- src/irc/irc_client.hpp | 167 +++++++++++++++++++++++++------------------------ 2 files changed, 103 insertions(+), 90 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4e8385c..6ab19b7 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -150,14 +150,26 @@ void IrcClient::parse_in_buffer(const size_t) // Call the standard callback (if any), associated with the command // name that we just received. - auto cb = irc_callbacks.find(message.command); - if (cb != irc_callbacks.end()) + auto it = irc_callbacks.find(message.command); + if (it != irc_callbacks.end()) { - try { - (this->*(cb->second))(message); - } catch (const std::exception& e) { - log_error("Unhandled exception: " << e.what()); - } + const auto& limits = it->second.second; + // Check that the Message is well formed before actually calling + // the callback. limits.first is the min number of arguments, + // second is the max + if (message.arguments.size() < limits.first || + (limits.second > 0 && message.arguments.size() > limits.second)) + log_warning("Invalid number of arguments for IRC command “" << message.command << + "”: " << message.arguments.size()); + else + { + const auto& cb = it->second.first; + try { + (this->*(cb))(message); + } catch (const std::exception& e) { + log_error("Unhandled exception: " << e.what()); + } + } } else { diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 0d1604d..a3f69a0 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -336,90 +336,91 @@ private: */ typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); -static const std::unordered_map irc_callbacks = { - {"NOTICE", &IrcClient::on_notice}, - {"002", &IrcClient::forward_server_message}, - {"003", &IrcClient::forward_server_message}, - {"005", &IrcClient::on_isupport_message}, - {"RPL_LISTSTART", &IrcClient::on_rpl_liststart}, - {"321", &IrcClient::on_rpl_liststart}, - {"RPL_LIST", &IrcClient::on_rpl_list}, - {"322", &IrcClient::on_rpl_list}, - {"RPL_LISTEND", &IrcClient::on_rpl_listend}, - {"323", &IrcClient::on_rpl_listend}, - {"RPL_MOTDSTART", &IrcClient::empty_motd}, - {"375", &IrcClient::empty_motd}, - {"RPL_MOTD", &IrcClient::on_motd_line}, - {"372", &IrcClient::on_motd_line}, - {"RPL_MOTDEND", &IrcClient::send_motd}, - {"376", &IrcClient::send_motd}, - {"JOIN", &IrcClient::on_channel_join}, - {"PRIVMSG", &IrcClient::on_channel_message}, - {"353", &IrcClient::set_and_forward_user_list}, - {"332", &IrcClient::on_topic_received}, - {"TOPIC", &IrcClient::on_topic_received}, - {"366", &IrcClient::on_channel_completely_joined}, - {"432", &IrcClient::on_erroneous_nickname}, - {"433", &IrcClient::on_nickname_conflict}, - {"438", &IrcClient::on_nickname_change_too_fast}, - {"001", &IrcClient::on_welcome_message}, - {"PART", &IrcClient::on_part}, - {"ERROR", &IrcClient::on_error}, - {"QUIT", &IrcClient::on_quit}, - {"NICK", &IrcClient::on_nick}, - {"MODE", &IrcClient::on_mode}, - {"PING", &IrcClient::send_pong_command}, - {"PONG", &IrcClient::on_pong}, - {"KICK", &IrcClient::on_kick}, +static const std::unordered_map>> irc_callbacks = { + {"NOTICE", {&IrcClient::on_notice, {2, 0}}}, + {"002", {&IrcClient::forward_server_message, {2, 0}}}, + {"003", {&IrcClient::forward_server_message, {2, 0}}}, + {"005", {&IrcClient::on_isupport_message, {0, 0}}}, + {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}}, + {"321", {&IrcClient::on_rpl_liststart, {0, 0}}}, + {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}}, + {"322", {&IrcClient::on_rpl_list, {0, 0}}}, + {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}}, + {"323", {&IrcClient::on_rpl_listend, {0, 0}}}, + {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}}, + {"375", {&IrcClient::empty_motd, {0, 0}}}, + {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}}, + {"372", {&IrcClient::on_motd_line, {2, 0}}}, + {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}}, + {"376", {&IrcClient::send_motd, {0, 0}}}, + {"JOIN", {&IrcClient::on_channel_join, {1, 0}}}, + {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}}, + {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}}, + {"332", {&IrcClient::on_topic_received, {2, 0}}}, + {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}}, + {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}}, + {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}}, + {"433", {&IrcClient::on_nickname_conflict, {2, 0}}}, + {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}}, + {"001", {&IrcClient::on_welcome_message, {1, 0}}}, + {"PART", {&IrcClient::on_part, {1, 0}}}, + {"ERROR", {&IrcClient::on_error, {1, 0}}}, + {"QUIT", {&IrcClient::on_quit, {0, 0}}}, + {"NICK", {&IrcClient::on_nick, {1, 0}}}, + {"MODE", {&IrcClient::on_mode, {1, 0}}}, + {"PING", {&IrcClient::send_pong_command, {1, 0}}}, + {"PONG", {&IrcClient::on_pong, {0, 0}}}, + {"KICK", {&IrcClient::on_kick, {3, 0}}}, - {"401", &IrcClient::on_generic_error}, - {"402", &IrcClient::on_generic_error}, - {"403", &IrcClient::on_generic_error}, - {"404", &IrcClient::on_generic_error}, - {"405", &IrcClient::on_generic_error}, - {"406", &IrcClient::on_generic_error}, - {"407", &IrcClient::on_generic_error}, - {"408", &IrcClient::on_generic_error}, - {"409", &IrcClient::on_generic_error}, - {"410", &IrcClient::on_generic_error}, - {"411", &IrcClient::on_generic_error}, - {"412", &IrcClient::on_generic_error}, - {"414", &IrcClient::on_generic_error}, - {"421", &IrcClient::on_generic_error}, - {"422", &IrcClient::on_generic_error}, - {"423", &IrcClient::on_generic_error}, - {"424", &IrcClient::on_generic_error}, - {"431", &IrcClient::on_generic_error}, - {"436", &IrcClient::on_generic_error}, - {"441", &IrcClient::on_generic_error}, - {"442", &IrcClient::on_generic_error}, - {"443", &IrcClient::on_generic_error}, - {"444", &IrcClient::on_generic_error}, - {"446", &IrcClient::on_generic_error}, - {"451", &IrcClient::on_generic_error}, - {"461", &IrcClient::on_generic_error}, - {"462", &IrcClient::on_generic_error}, - {"463", &IrcClient::on_generic_error}, - {"464", &IrcClient::on_generic_error}, - {"465", &IrcClient::on_generic_error}, - {"467", &IrcClient::on_generic_error}, - {"470", &IrcClient::on_generic_error}, - {"471", &IrcClient::on_generic_error}, - {"472", &IrcClient::on_generic_error}, - {"473", &IrcClient::on_generic_error}, - {"474", &IrcClient::on_generic_error}, - {"475", &IrcClient::on_generic_error}, - {"476", &IrcClient::on_generic_error}, - {"477", &IrcClient::on_generic_error}, - {"481", &IrcClient::on_generic_error}, - {"482", &IrcClient::on_generic_error}, - {"483", &IrcClient::on_generic_error}, - {"484", &IrcClient::on_generic_error}, - {"485", &IrcClient::on_generic_error}, - {"487", &IrcClient::on_generic_error}, - {"491", &IrcClient::on_generic_error}, - {"501", &IrcClient::on_generic_error}, - {"502", &IrcClient::on_generic_error}, + {"401", {&IrcClient::on_generic_error, {2, 0}}}, + {"402", {&IrcClient::on_generic_error, {2, 0}}}, + {"403", {&IrcClient::on_generic_error, {2, 0}}}, + {"404", {&IrcClient::on_generic_error, {2, 0}}}, + {"405", {&IrcClient::on_generic_error, {2, 0}}}, + {"406", {&IrcClient::on_generic_error, {2, 0}}}, + {"407", {&IrcClient::on_generic_error, {2, 0}}}, + {"408", {&IrcClient::on_generic_error, {2, 0}}}, + {"409", {&IrcClient::on_generic_error, {2, 0}}}, + {"410", {&IrcClient::on_generic_error, {2, 0}}}, + {"411", {&IrcClient::on_generic_error, {2, 0}}}, + {"412", {&IrcClient::on_generic_error, {2, 0}}}, + {"414", {&IrcClient::on_generic_error, {2, 0}}}, + {"421", {&IrcClient::on_generic_error, {2, 0}}}, + {"422", {&IrcClient::on_generic_error, {2, 0}}}, + {"423", {&IrcClient::on_generic_error, {2, 0}}}, + {"424", {&IrcClient::on_generic_error, {2, 0}}}, + {"431", {&IrcClient::on_generic_error, {2, 0}}}, + {"436", {&IrcClient::on_generic_error, {2, 0}}}, + {"441", {&IrcClient::on_generic_error, {2, 0}}}, + {"442", {&IrcClient::on_generic_error, {2, 0}}}, + {"443", {&IrcClient::on_generic_error, {2, 0}}}, + {"444", {&IrcClient::on_generic_error, {2, 0}}}, + {"446", {&IrcClient::on_generic_error, {2, 0}}}, + {"451", {&IrcClient::on_generic_error, {2, 0}}}, + {"461", {&IrcClient::on_generic_error, {2, 0}}}, + {"462", {&IrcClient::on_generic_error, {2, 0}}}, + {"463", {&IrcClient::on_generic_error, {2, 0}}}, + {"464", {&IrcClient::on_generic_error, {2, 0}}}, + {"465", {&IrcClient::on_generic_error, {2, 0}}}, + {"467", {&IrcClient::on_generic_error, {2, 0}}}, + {"470", {&IrcClient::on_generic_error, {2, 0}}}, + {"471", {&IrcClient::on_generic_error, {2, 0}}}, + {"472", {&IrcClient::on_generic_error, {2, 0}}}, + {"473", {&IrcClient::on_generic_error, {2, 0}}}, + {"474", {&IrcClient::on_generic_error, {2, 0}}}, + {"475", {&IrcClient::on_generic_error, {2, 0}}}, + {"476", {&IrcClient::on_generic_error, {2, 0}}}, + {"477", {&IrcClient::on_generic_error, {2, 0}}}, + {"481", {&IrcClient::on_generic_error, {2, 0}}}, + {"482", {&IrcClient::on_generic_error, {2, 0}}}, + {"483", {&IrcClient::on_generic_error, {2, 0}}}, + {"484", {&IrcClient::on_generic_error, {2, 0}}}, + {"485", {&IrcClient::on_generic_error, {2, 0}}}, + {"487", {&IrcClient::on_generic_error, {2, 0}}}, + {"491", {&IrcClient::on_generic_error, {2, 0}}}, + {"501", {&IrcClient::on_generic_error, {2, 0}}}, + {"502", {&IrcClient::on_generic_error, {2, 0}}}, }; #endif // IRC_CLIENT_INCLUDED -- cgit v1.2.3 From 73573ebb2abb2aea119a6c99e2bf4a302f2ba834 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 6 Sep 2015 18:57:35 +0200 Subject: Trivial cleanup in irc_client.cpp --- src/irc/irc_client.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 6ab19b7..29f0b54 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -492,9 +492,8 @@ void IrcClient::on_rpl_listend(const IrcMessage&) { } -void IrcClient::empty_motd(const IrcMessage& message) +void IrcClient::empty_motd(const IrcMessage&) { - (void)message; this->motd.erase(); } @@ -507,16 +506,13 @@ void IrcClient::on_motd_line(const IrcMessage& message) this->motd += body+"\n"; } -void IrcClient::send_motd(const IrcMessage& message) +void IrcClient::send_motd(const IrcMessage&) { - (void)message; this->bridge->send_xmpp_message(this->hostname, "", this->motd); } void IrcClient::on_topic_received(const IrcMessage& message) { - if (message.arguments.size() < 2) - return; const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]); IrcChannel* channel = this->get_channel(chan_name); channel->topic = message.arguments[message.arguments.size() - 1]; @@ -709,9 +705,9 @@ void IrcClient::on_nick(const IrcMessage& message) void IrcClient::on_kick(const IrcMessage& message) { + const std::string chan_name = utils::tolower(message.arguments[0]); const std::string target = message.arguments[1]; const std::string reason = message.arguments[2]; - const std::string chan_name = utils::tolower(message.arguments[0]); IrcChannel* channel = this->get_channel(chan_name); if (!channel->joined) return ; -- cgit v1.2.3 From ea0b2f2bb871e7760d1936bb9b193655682df413 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 09:18:33 +0200 Subject: Create a xdg_path function --- louloulibs/louloulibs.h.cmake | 3 ++- louloulibs/utils/xdg.cpp | 20 ++++++++++++++++++++ louloulibs/utils/xdg.hpp | 13 +++++++++++++ src/main.cpp | 16 ++-------------- src/test.cpp | 22 ++++++++++++++++++++++ 5 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 louloulibs/utils/xdg.cpp create mode 100644 louloulibs/utils/xdg.hpp diff --git a/louloulibs/louloulibs.h.cmake b/louloulibs/louloulibs.h.cmake index 707d3fe..2feaf4e 100644 --- a/louloulibs/louloulibs.h.cmake +++ b/louloulibs/louloulibs.h.cmake @@ -5,4 +5,5 @@ #cmakedefine POLLER ${POLLER} #cmakedefine BOTAN_FOUND #cmakedefine CARES_FOUND -#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" \ No newline at end of file +#cmakedefine SOFTWARE_VERSION "${SOFTWARE_VERSION}" +#cmakedefine PROJECT_NAME "${PROJECT_NAME}" \ No newline at end of file diff --git a/louloulibs/utils/xdg.cpp b/louloulibs/utils/xdg.cpp new file mode 100644 index 0000000..7a60dd8 --- /dev/null +++ b/louloulibs/utils/xdg.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include "louloulibs.h" + +std::string xdg_config_path(const std::string& filename) +{ + const char* xdg_config_home = ::getenv("XDG_CONFIG_HOME"); + if (xdg_config_home && xdg_config_home[0] == '/') + return std::string{xdg_config_home} + "/" PROJECT_NAME "/" + filename; + else + { + const char* home = ::getenv("HOME"); + if (home) + return std::string{home} + "/" ".config" "/" PROJECT_NAME "/" + filename; + else + return filename; + } +} + diff --git a/louloulibs/utils/xdg.hpp b/louloulibs/utils/xdg.hpp new file mode 100644 index 0000000..20f5bb7 --- /dev/null +++ b/louloulibs/utils/xdg.hpp @@ -0,0 +1,13 @@ +#ifndef XDG_HPP_INCLUDED +#define XDG_HPP_INCLUDED + +#include + +/** + * Returns a path for the given filename, according to the XDG base + * directory specification, see + * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ +std::string xdg_config_path(const std::string& filename); + +#endif /* XDG_HPP_INCLUDED */ diff --git a/src/main.cpp b/src/main.cpp index 159dcbe..5c9e071 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,11 +4,11 @@ #include #include #include +#include #include #include #include -#include #include @@ -69,19 +69,7 @@ int main(int ac, char** av) if (ac > 1) Config::filename = av[1]; else - { - const char* xdg_config_home = getenv("XDG_CONFIG_HOME"); - if (xdg_config_home && xdg_config_home[0] == '/') - Config::filename = std::string{xdg_config_home} + "/" "biboumi" "/" "biboumi.cfg"; - else - { - const char* home = getenv("HOME"); - if (home) - Config::filename = std::string{home} + "/" ".config" "/" "biboumi" "/" "biboumi.cfg"; - else - Config::filename = "biboumi.cfg"; - } - } + Config::filename = xdg_path("biboumi.cfg"); Config::file_must_exist = true; std::cerr << "Using configuration file: " << Config::filename << std::endl; diff --git a/src/test.cpp b/src/test.cpp index 842dfed..b8737e6 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -402,6 +402,28 @@ int main() assert(iid6.is_channel); assert(!iid6.is_user); } + { + + { + std::cout << color << "Testing the xdg_path function…" << reset << std::endl; + std::string res; + + ::unsetenv("XDG_CONFIG_HOME"); + ::unsetenv("HOME"); + res = xdg_config_path("coucou.txt"); + std::cout << res << std::endl; + assert(res == "coucou.txt"); + + ::setenv("HOME", "/home/user", 1); + res = xdg_config_path("coucou.txt"); + std::cout << res << std::endl; + assert(res == "/home/user/.config/biboumi/coucou.txt"); + + ::setenv("XDG_CONFIG_HOME", "/some_weird_dir", 1); + res = xdg_config_path("coucou.txt"); + std::cout << res << std::endl; + assert(res == "/some_weird_dir/biboumi/coucou.txt"); + } return 0; } -- cgit v1.2.3 From 33fa1dcd5a87035de1d9b8df65e5c7551b4bbd1b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 09:39:49 +0200 Subject: Also a xdg_data_path --- louloulibs/utils/xdg.cpp | 17 +++++++++++++---- louloulibs/utils/xdg.hpp | 1 + src/main.cpp | 2 +- src/test.cpp | 7 +++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/louloulibs/utils/xdg.cpp b/louloulibs/utils/xdg.cpp index 7a60dd8..48212a1 100644 --- a/louloulibs/utils/xdg.cpp +++ b/louloulibs/utils/xdg.cpp @@ -3,11 +3,11 @@ #include "louloulibs.h" -std::string xdg_config_path(const std::string& filename) +std::string xdg_path(const std::string& filename, const char* env_var) { - const char* xdg_config_home = ::getenv("XDG_CONFIG_HOME"); - if (xdg_config_home && xdg_config_home[0] == '/') - return std::string{xdg_config_home} + "/" PROJECT_NAME "/" + filename; + const char* xdg_home = ::getenv(env_var); + if (xdg_home && xdg_home[0] == '/') + return std::string{xdg_home} + "/" PROJECT_NAME "/" + filename; else { const char* home = ::getenv("HOME"); @@ -18,3 +18,12 @@ std::string xdg_config_path(const std::string& filename) } } +std::string xdg_config_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_CONFIG_HOME"); +} + +std::string xdg_data_path(const std::string& filename) +{ + return xdg_path(filename, "XDG_DATA_HOME"); +} diff --git a/louloulibs/utils/xdg.hpp b/louloulibs/utils/xdg.hpp index 20f5bb7..15f3d0b 100644 --- a/louloulibs/utils/xdg.hpp +++ b/louloulibs/utils/xdg.hpp @@ -9,5 +9,6 @@ * http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html */ std::string xdg_config_path(const std::string& filename); +std::string xdg_data_path(const std::string& filename); #endif /* XDG_HPP_INCLUDED */ diff --git a/src/main.cpp b/src/main.cpp index 5c9e071..a160df9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,7 +69,7 @@ int main(int ac, char** av) if (ac > 1) Config::filename = av[1]; else - Config::filename = xdg_path("biboumi.cfg"); + Config::filename = xdg_config_path("biboumi.cfg"); Config::file_must_exist = true; std::cerr << "Using configuration file: " << Config::filename << std::endl; diff --git a/src/test.cpp b/src/test.cpp index b8737e6..3ac0332 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -423,6 +424,12 @@ int main() res = xdg_config_path("coucou.txt"); std::cout << res << std::endl; assert(res == "/some_weird_dir/biboumi/coucou.txt"); + + ::setenv("XDG_DATA_HOME", "/datadir", 1); + res = xdg_data_path("bonjour.txt"); + std::cout << res << std::endl; + assert(res == "/datadir/biboumi/bonjour.txt"); + } return 0; -- cgit v1.2.3 From 88ae2599f6dbf655e8806c9b4619ec089425683b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 21:49:54 +0200 Subject: Introduce an optional Database module Uses litesql --- CMakeLists.txt | 31 +++++++++++++++++ INSTALL | 6 ++++ biboumi.h.cmake | 1 + cmake/Modules/FindLITESQL.cmake | 75 +++++++++++++++++++++++++++++++++++++++++ database/database.xml | 18 ++++++++++ src/database/database.cpp | 54 +++++++++++++++++++++++++++++ src/database/database.hpp | 47 ++++++++++++++++++++++++++ src/test.cpp | 37 +++++++++++++++++++- 8 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 biboumi.h.cmake create mode 100644 cmake/Modules/FindLITESQL.cmake create mode 100644 database/database.xml create mode 100644 src/database/database.cpp create mode 100644 src/database/database.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 93e91ba..1bad544 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,8 +44,11 @@ set(SOFTWARE_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX}) # To be able to include the config.h file generated by cmake + +# To be able to include the config.h and other files generated by cmake include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") include_directories("${CMAKE_CURRENT_SOURCE_DIR}/src/") +include_directories("${CMAKE_CURRENT_BINARY_DIR}/") # ## Documentation @@ -63,6 +66,24 @@ if(RONN_EXECUTABLE) add_custom_target(doc DEPENDS ${MAN_PAGE}) endif() +# Look for litesql and enable the database if found +if(WITH_LITESQL) + find_package(LITESQL REQUIRED) +elseif(NOT WITHOUT_LITESQL) + find_package(LITESQL) +endif() + +if(LITESQL_FOUND) + LITESQL_GENERATE_CPP("database/database.xml" + "biboudb" + LITESQL_GENERATED_SOURCES) + + add_library(database STATIC src/database/database.cpp + ${LITESQL_GENERATED_SOURCES}) + target_link_libraries(database ${LITESQL_LIBRARIES} ${BOTAN_LIBRARIES}) + set(USE_DATABASE TRUE) +endif() + add_subdirectory("louloulibs") include_directories("louloulibs") @@ -97,6 +118,9 @@ file(GLOB source_xmpp add_library(xmpp STATIC ${source_xmpp}) target_link_libraries(xmpp xmpplib bridge network utils logger) +if(USE_DATABASE) + target_link_libraries(xmpp database) +endif() # ## bridge # @@ -134,6 +158,11 @@ target_link_libraries(test_suite config logger) +if(USE_DATABASE) + target_link_libraries(test_suite + database) +endif() + # ## Install target # @@ -151,3 +180,5 @@ add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/biboumi.h.cmake ${CMAKE_BINARY_DIR}/src/biboumi.h) \ No newline at end of file diff --git a/INSTALL b/INSTALL index 09a1590..8306978 100644 --- a/INSTALL +++ b/INSTALL @@ -49,6 +49,12 @@ Libraries: Other branches than the 1.11 are not supported. http://botan.randombit.net/ +- litesql (optional) + Provides a way to store various options in a (sqlite3) database. Each user + of the gateway can store their own values (for example their prefered port, + or their IRC password). + http://git.louiz.org/litesql + - systemd (optional) Provides the support for a systemd service of Type=notify. This is useful only if you are packaging biboumi in a distribution with Systemd. diff --git a/biboumi.h.cmake b/biboumi.h.cmake new file mode 100644 index 0000000..beb67d0 --- /dev/null +++ b/biboumi.h.cmake @@ -0,0 +1 @@ +#cmakedefine USE_DATABASE diff --git a/cmake/Modules/FindLITESQL.cmake b/cmake/Modules/FindLITESQL.cmake new file mode 100644 index 0000000..2ce8fd6 --- /dev/null +++ b/cmake/Modules/FindLITESQL.cmake @@ -0,0 +1,75 @@ +# - Find LiteSQL +# +# Find the LiteSQL library, and defines a function to generate C++ files +# from the database xml file using litesql-gen fro +# +# This module defines the following variables: +# LITESQL_FOUND - True if library and include directory are found +# If set to TRUE, the following are also defined: +# LITESQL_INCLUDE_DIRS - The directory where to find the header file +# LITESQL_LIBRARIES - Where to find the library file +# LITESQL_GENERATE_CPP - A function, to be used like this: +# LITESQL_GENERATE_CPP("db/database.xml" # The file defining the db schemas +# "database" # The name of the C++ “module” +# # that will be generated +# LITESQL_GENERATED_SOURCES # Variable containing the +# resulting C++ files to compile +# +# For conveniance, these variables are also set. They have the same values +# than the variables above. The user can thus choose his/her prefered way +# to write them. +# LITESQL_INCLUDE_DIR +# LITESQL_LIBRARY +# +# This file is in the public domain + +find_path(LITESQL_INCLUDE_DIRS NAMES litesql.hpp + DOC "The LiteSQL include directory") + +find_library(LITESQL_LIBRARIES NAMES litesql + DOC "The LiteSQL library") + +foreach(DB_TYPE sqlite postgresql mysql ocilib) + string(TOUPPER ${DB_TYPE} DB_TYPE_UPPER) + find_library(LITESQL_${DB_TYPE_UPPER}_LIB_PATH NAMES litesql_${DB_TYPE} + DOC "The ${DB_TYPE} backend for LiteSQL") + if(LITESQL_${DB_TYPE_UPPER}_LIB_PATH) + list(APPEND LITESQL_LIBRARIES ${LITESQL_${DB_TYPE_UPPER}_LIB_PATH}) + endif() +endforeach() + +find_program(LITESQLGEN_EXECUTABLE NAMES litesql-gen + DOC "The utility that creates .h and .cpp files from a xml database description") + +# Use some standard module to handle the QUIETLY and REQUIRED arguments, and +# set LITESQL_FOUND to TRUE if these two variables are set. +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LITESQL REQUIRED_VARS LITESQL_LIBRARIES LITESQL_INCLUDE_DIRS + LITESQLGEN_EXECUTABLE) + +# Compatibility for all the ways of writing these variables +if(LITESQL_FOUND) + set(LITESQL_INCLUDE_DIR ${LITESQL_INCLUDE_DIRS}) + set(LITESQL_LIBRARY ${LITESQL_LIBRARIES}) +endif() + +mark_as_advanced(LITESQL_INCLUDE_DIRS LITESQL_LIBRARIES) + + +# LITESQL_GENERATE_CPP function + +function(LITESQL_GENERATE_CPP + SOURCE_FILE OUTPUT_NAME OUTPUT_SOURCES) + set(${OUTPUT_SOURCES}) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.cpp" + "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.hpp" + COMMAND ${LITESQLGEN_EXECUTABLE} + ARGS -t c++ --output-dir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE} + DEPENDS ${SOURCE_FILE} + COMMENT "Running litesql-gen on ${SOURCE_FILE}" + VERBATIM) + list(APPEND ${OUTPUT_SOURCES} "${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_NAME}.cpp") + set_source_files_properties(${${OUTPUT_SOURCES}} PROPERTIES GENERATED TRUE) + set(${OUTPUT_SOURCES} ${${OUTPUT_SOURCES}} PARENT_SCOPE) +endfunction() diff --git a/database/database.xml b/database/database.xml new file mode 100644 index 0000000..f3a005c --- /dev/null +++ b/database/database.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/database/database.cpp b/src/database/database.cpp new file mode 100644 index 0000000..e16465c --- /dev/null +++ b/src/database/database.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +std::unique_ptr Database::db; + +db::BibouDB& Database::get_db() +{ + if (!Database::db) + { + const std::string db_filename = Config::get("db_name", + xdg_data_path("biboumi.sqlite")); + // log_info("Opening database: " << db_filename); + std::cout << "Opening database: " << db_filename << std::endl; + Database::db = std::make_unique("sqlite3", + "database="s + db_filename); + } + + if (Database::db->needsUpgrade()) + Database::db->upgrade(); + + return *Database::db.get(); +} + +void Database::set_verbose(const bool val) +{ + Database::get_db().verbose = val; +} + +db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, + const std::string& server) +{ + try { + auto options = litesql::select(Database::get_db(), + db::IrcServerOptions::Owner == owner && + db::IrcServerOptions::Server == server).one(); + return options; + } catch (const litesql::NotFound& e) { + db::IrcServerOptions options(Database::get_db()); + options.owner = owner; + options.server = server; + // options.update(); + return options; + } +} + +void Database::close() +{ + Database::db.reset(nullptr); +} diff --git a/src/database/database.hpp b/src/database/database.hpp new file mode 100644 index 0000000..d8dc735 --- /dev/null +++ b/src/database/database.hpp @@ -0,0 +1,47 @@ +#ifndef DATABASE_HPP_INCLUDED +#define DATABASE_HPP_INCLUDED + +#include +#ifdef USE_DATABASE + +#include "biboudb.hpp" + +#include + +#include + +class Database +{ +public: + Database() = default; + ~Database() = default; + + static void set_verbose(const bool val); + + template + static size_t count() + { + return litesql::select(Database::get_db()).count(); + } + /** + * Return the object from the db. Create it beforehand (with all default + * values) if it is not already present. + */ + static db::IrcServerOptions get_irc_server_options(const std::string& owner, + const std::string& server); + + static void close(); + +private: + static std::unique_ptr db; + + static db::BibouDB& get_db(); + + Database(const Database&) = delete; + Database(Database&&) = delete; + Database& operator=(const Database&) = delete; + Database& operator=(Database&&) = delete; +}; +#endif /* USE_DATABASE */ + +#endif /* DATABASE_HPP_INCLUDED */ diff --git a/src/test.cpp b/src/test.cpp index 3ac0332..bc85cb0 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -16,12 +17,15 @@ #include #include #include +#include #include #include #include #include +#include "biboumi.h" + #undef NDEBUG #include @@ -403,8 +407,39 @@ int main() assert(iid6.is_channel); assert(!iid6.is_user); } +#ifdef USE_DATABASE { - + std::cout << color << "Testing the Database…" << reset << std::endl; + // Remove any potential existing db + unlink("./test.db"); + Config::set("db_name", "test.db"); + Database::set_verbose(true); + auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + o.requireTls = false; + o.update(); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + assert(a.requireTls == false); + auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); + assert(b.requireTls == true); + + // b does not yet exist in the db, the object is created but not yet + // inserted + assert(1 == Database::count()); + + b.update(); + assert(2 == Database::count()); + + assert(b.pass == ""); + assert(b.pass.value() == ""); + + std::vector ftypes; + db::IrcServerOptions::getFieldTypes(ftypes); + for (const auto& type: ftypes) + { + std::cout << type.type() << std::endl; + } + } +#endif { std::cout << color << "Testing the xdg_path function…" << reset << std::endl; std::string res; -- cgit v1.2.3 From 19666d2ffd3ce36b7edd4044143fe535ef85dbd9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 21:52:11 +0200 Subject: Store the target jid in the AdhocSession objects --- louloulibs/xmpp/adhoc_commands_handler.cpp | 4 ++-- louloulibs/xmpp/adhoc_commands_handler.hpp | 2 +- louloulibs/xmpp/adhoc_session.cpp | 6 ++++-- louloulibs/xmpp/adhoc_session.hpp | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 458a22c..714c440 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -20,7 +20,7 @@ std::map& AdhocCommandsHandler::get_comma return this->commands; } -XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, XmlNode command_node) +XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node) { std::string action = command_node.get_tag("action"); if (action.empty()) @@ -57,7 +57,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, Xm command_node["sessionid"] = sessionid; this->sessions.emplace(std::piecewise_construct, std::forward_as_tuple(sessionid, executor_jid), - std::forward_as_tuple(command_it->second, executor_jid)); + std::forward_as_tuple(command_it->second, executor_jid, to)); TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), "adhocsession"s + sessionid + executor_jid)); diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index 1083c44..cf9ca17 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -41,7 +41,7 @@ public: * Takes a copy of the node so we can actually edit it and use * it as our return value. */ - XmlNode handle_request(const std::string& executor_jid, XmlNode command_node); + XmlNode handle_request(const std::string& executor_jid, const std::string& to, XmlNode command_node); /** * Remove the session from the list. This is done to avoid filling the * memory with waiting session (for example due to a client that starts diff --git a/louloulibs/xmpp/adhoc_session.cpp b/louloulibs/xmpp/adhoc_session.cpp index fc60bb7..bf8d292 100644 --- a/louloulibs/xmpp/adhoc_session.cpp +++ b/louloulibs/xmpp/adhoc_session.cpp @@ -3,9 +3,11 @@ #include -AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& jid): +AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner_jid, + const std::string& to_jid): command(command), - owner_jid(jid), + owner_jid(owner_jid), + to_jid(to_jid), current_step(0), terminated(false) { diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index ddfb2fe..da7913f 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -23,7 +23,8 @@ typedef std::function AdhocStep; class AdhocSession { public: - explicit AdhocSession(const AdhocCommand& command, const std::string& jid); + explicit AdhocSession(const AdhocCommand& command, const std::string& owner_jid, + const std::string& to_jid); ~AdhocSession(); /** * Return the function to be executed, found in our AdhocCommand, for the @@ -41,6 +42,15 @@ public: */ void terminate(); bool is_terminated() const; + std::string get_target_jid() const + { + return this->to_jid; + } + std::string get_owner_jid() const + { + return this->owner_jid; + } + private: /** @@ -54,6 +64,10 @@ private: * this session. */ const std::string& owner_jid; + /** + * The 'to' attribute in the request stanza. This is the target of the current session. + */ + const std::string& to_jid; /** * The current step we are at. It starts at zero. It is used to index the * associated AdhocCommand::callbacks vector. -- cgit v1.2.3 From 45e8fe56a688ec03201cdfc3dfea6ae186af682d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 21:55:21 +0200 Subject: Add an AdhocCommandsHandler to store commands specific to IRC servers --- louloulibs/xmpp/xmpp_component.cpp | 8 ++++---- louloulibs/xmpp/xmpp_component.hpp | 4 ++-- src/xmpp/biboumi_component.cpp | 39 +++++++++++++++++++++++++++++++------- src/xmpp/biboumi_component.hpp | 2 ++ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 2214ecf..3017c0b 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -587,22 +587,22 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t this->send_stanza(iq); } -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const bool with_admin_only) +void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const std::string& from_jid, const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler) { Stanza iq("iq"); iq["type"] = "result"; iq["id"] = id; iq["to"] = requester_jid; - iq["from"] = this->served_hostname; + iq["from"] = from_jid; XmlNode query("query"); query["xmlns"] = DISCO_ITEMS_NS; query["node"] = ADHOC_NS; - for (const auto& kv: this->adhoc_commands_handler.get_commands()) + for (const auto& kv: adhoc_handler.get_commands()) { if (kv.second.is_admin_only() && !with_admin_only) continue; XmlNode item("item"); - item["jid"] = this->served_hostname; + item["jid"] = from_jid; item["node"] = kv.first; item["name"] = kv.second.name; query.add_child(std::move(item)); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index e45bb36..06236fe 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -184,8 +184,8 @@ public: * Send the list of all available ad-hoc commands to that JID. The list is * different depending on what JID made the request. */ - void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, - const bool with_admin_only); + void send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const std::string& from_jid, + const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler); /** * Send an iq version request */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 51775ab..974676b 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -41,7 +41,8 @@ static std::set kickable_errors{ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): - XmppComponent(poller, hostname, secret) + XmppComponent(poller, hostname, secret), + irc_server_adhoc_commands_handler(this) { this->stanza_handlers.emplace("presence", std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1)); @@ -313,9 +314,21 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) { Stanza response("iq"); response["to"] = from; - response["from"] = this->served_hostname; + response["from"] = to_str; response["id"] = id; - XmlNode inner_node = this->adhoc_commands_handler.handle_request(from, *query); + + // Depending on the 'to' jid in the request, we use one adhoc + // command handler or an other + Iid iid(to.local); + AdhocCommandsHandler* adhoc_handler; + if (!to.local.empty() && !iid.is_user && !iid.is_channel) + adhoc_handler = &this->irc_server_adhoc_commands_handler; + else + adhoc_handler = &this->adhoc_commands_handler; + + // Execute the command, if any, and get a result XmlNode that we + // insert in our response + XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query); if (inner_node.get_child("error", ADHOC_NS)) response["type"] = "error"; else @@ -370,10 +383,22 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) if (node == ADHOC_NS) { Jid from_jid(from); - this->send_adhoc_commands_list(id, from, - (Config::get("admin", "") == - from_jid.local + "@" + from_jid.domain)); - stanza_error.disable(); + if (to.local.empty()) + { // Get biboumi's adhoc commands + this->send_adhoc_commands_list(id, from, this->served_hostname, + (Config::get("admin", "") == + from_jid.local + "@" + from_jid.domain), + this->adhoc_commands_handler); + stanza_error.disable(); + } + else if (!iid.is_user && !iid.is_channel) + { // Get the server's adhoc commands + this->send_adhoc_commands_list(id, from, to_str, + (Config::get("admin", "") == + from_jid.local + "@" + from_jid.domain), + this->irc_server_adhoc_commands_handler); + stanza_error.disable(); + } } else if (node.empty() && !iid.is_user && !iid.is_channel) { // Disco on an IRC server: get the list of channels diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 8b0b3da..fe99f2d 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -97,6 +97,8 @@ private: */ std::unordered_map> bridges; + AdhocCommandsHandler irc_server_adhoc_commands_handler; + BiboumiComponent(const BiboumiComponent&) = delete; BiboumiComponent(BiboumiComponent&&) = delete; BiboumiComponent& operator=(const BiboumiComponent&) = delete; -- cgit v1.2.3 From 1031989fe26f219ccb4b9a602a599e30f18cb9d2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 21:56:23 +0200 Subject: XmlNode::add_child can also take a copy of a node --- louloulibs/xmpp/xmpp_stanza.cpp | 6 ++++++ louloulibs/xmpp/xmpp_stanza.hpp | 1 + 2 files changed, 7 insertions(+) diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index 3ba8483..4c0f5c7 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -178,6 +178,12 @@ XmlNode* XmlNode::add_child(XmlNode&& child) return this->add_child(std::move(new_node)); } +XmlNode* XmlNode::add_child(const XmlNode& child) +{ + auto new_node = std::make_unique(child); + return this->add_child(std::move(new_node)); +} + XmlNode* XmlNode::get_last_child() const { return this->children.back().get(); diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 4a54379..9cf7d8d 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -87,6 +87,7 @@ public: */ XmlNode* add_child(std::unique_ptr child); XmlNode* add_child(XmlNode&& child); + XmlNode* add_child(const XmlNode& child); /** * Returns the last of the children. If the node doesn't have any child, * the behaviour is undefined. The user should make sure this is the case -- cgit v1.2.3 From f1de6d032091bd141e12e8345969495d6f23c3e6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 21:58:05 +0200 Subject: Add Bridge::get_bare_jid --- src/bridge/bridge.cpp | 6 ++++++ src/bridge/bridge.hpp | 1 + 2 files changed, 7 insertions(+) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1d46e07..ba70069 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -74,6 +74,12 @@ const std::string& Bridge::get_jid() const return this->user_jid; } +std::string Bridge::get_bare_jid() const +{ + Jid jid(this->user_jid); + return jid.local + "@" + jid.domain; +} + Xmpp::body Bridge::make_xmpp_body(const std::string& str) { std::string res; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 72a8e90..dfe0aa7 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -47,6 +47,7 @@ public: * Return the jid of the XMPP user using this bridge */ const std::string& get_jid() const; + std::string get_bare_jid() const; static Xmpp::body make_xmpp_body(const std::string& str); /*** -- cgit v1.2.3 From 1691cf8f64d6175c61ddf9a4f4a95b44c32d698e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 22:01:02 +0200 Subject: Introduce the configure ad-hoc command on irc servers Provides two options for now, and they have no effect yet --- louloulibs/utils/string.cpp | 6 +++ louloulibs/utils/string.hpp | 8 +++ src/test.cpp | 8 +++ src/xmpp/biboumi_adhoc_commands.cpp | 97 +++++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 3 ++ src/xmpp/biboumi_component.cpp | 7 +++ 6 files changed, 129 insertions(+) create mode 100644 louloulibs/utils/string.cpp create mode 100644 louloulibs/utils/string.hpp diff --git a/louloulibs/utils/string.cpp b/louloulibs/utils/string.cpp new file mode 100644 index 0000000..3977feb --- /dev/null +++ b/louloulibs/utils/string.cpp @@ -0,0 +1,6 @@ +#include + +bool to_bool(const std::string& val) +{ + return (val == "1" || val == "true"); +} diff --git a/louloulibs/utils/string.hpp b/louloulibs/utils/string.hpp new file mode 100644 index 0000000..3775c36 --- /dev/null +++ b/louloulibs/utils/string.hpp @@ -0,0 +1,8 @@ +#ifndef STRING_UTILS_HPP_INCLUDED +#define STRING_UTILS_HPP_INCLUDED + +#include + +bool to_bool(const std::string& val); + +#endif /* STRING_UTILS_HPP_INCLUDED */ diff --git a/src/test.cpp b/src/test.cpp index bc85cb0..79fcd0b 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -170,6 +171,13 @@ int main() const std::string ltr = "coucou"; assert(utils::revstr(ltr) == "uocuoc"); + assert(to_bool("true")); + assert(!to_bool("trou")); + assert(to_bool("1")); + assert(!to_bool("0")); + assert(!to_bool("-1")); + assert(!to_bool("false")); + /** * XML parsing */ diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 3dbee81..1be38d3 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,6 +1,16 @@ #include #include #include +#include +#include + +#include + +#ifdef USE_DATABASE +#include +#endif + +using namespace std::string_literals; void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) { @@ -97,3 +107,90 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X command_node.add_child(std::move(error)); session.terminate(); } + +#ifdef USE_DATABASE +void ConfigureIrcServerStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + auto biboumi_component = static_cast(xmpp_component); + + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, + target.local); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure the IRC server "s + target.local); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + target.local); + x.add_child(std::move(instructions)); + + XmlNode require_tls("field"); + require_tls["var"] = "require_tls"; + require_tls["type"] = "boolean"; + require_tls["label"] = "Require TLS (refuse to connect insecurely)"; + XmlNode require_tls_value("value"); + require_tls_value.set_inner(options.requireTls ? "true": "false"); + require_tls.add_child(std::move(require_tls_value)); + XmlNode required("required"); + require_tls.add_child(required); + x.add_child(std::move(require_tls)); + + XmlNode pass("field"); + pass["var"] = "pass"; + pass["type"] = "text-private"; + pass["label"] = "Server password (to be used in a PASS command when connecting)"; + if (!options.pass.value().empty()) + { + XmlNode pass_value("value"); + pass_value.set_inner(options.pass.value()); + pass.add_child(std::move(pass_value)); + } + pass.add_child(required); + x.add_child(std::move(pass)); + + command_node.add_child(std::move(x)); +} + +void ConfigureIrcServerStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + auto biboumi_component = static_cast(xmpp_component); + + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, + target.local); + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + const XmlNode* value = field->get_child("value", "jabber:x:data"); + if (field->get_tag("var") == "require_tls" && + value && !value->get_inner().empty()) + options.requireTls = to_bool(value->get_inner()); + + else if (field->get_tag("var") == "pass" && + value && !value->get_inner().empty()) + options.pass = value->get_inner(); + } + + options.update(); + + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration successfully applied."); + command_node.add_child(std::move(note)); + return; + } + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + error.add_child(std::move(condition)); + command_node.add_child(std::move(error)); + session.terminate(); +} +#endif // USE_DATABASE diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 30f713a..e530fa7 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -10,4 +10,7 @@ class XmppComponent; void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); + #endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 974676b..920a2a3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -57,6 +58,12 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} }; + + this->irc_server_adhoc_commands_handler.get_commands() = { +#ifdef USE_DATABASE + {"configure", AdhocCommand({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false)}, +#endif + }; } void BiboumiComponent::shutdown() -- cgit v1.2.3 From 532228a3cefd92fe43ad0f52149b7f0f5ab5cb79 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 18 Sep 2015 22:02:01 +0200 Subject: =?UTF-8?q?Send=20a=20PASS=20IRC=20command=20if=20the=20=E2=80=9Cp?= =?UTF-8?q?ass=E2=80=9D=20config=20is=20sot=20by=20a=20user,=20on=20an=20I?= =?UTF-8?q?RC=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #3068 --- src/irc/irc_client.cpp | 12 ++++++++++++ src/irc/irc_client.hpp | 1 + 2 files changed, 13 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 29f0b54..bac3e34 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -94,6 +95,12 @@ void IrcClient::on_connection_failed(const std::string& reason) void IrcClient::on_connected() { +#ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + this->get_hostname()); + if (!options.pass.value().empty()) + this->send_pass_command(options.pass.value()); +#endif this->send_nick_command(this->username); this->send_user_command(this->username, this->username); this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); @@ -218,6 +225,11 @@ void IrcClient::send_nick_command(const std::string& nick) this->send_message(IrcMessage("NICK", {nick})); } +void IrcClient::send_pass_command(const std::string& password) +{ + this->send_message(IrcMessage("PASS", {password})); +} + void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason) { this->send_message(IrcMessage("KICK", {chan_name, target, reason})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index a3f69a0..4e61d14 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -85,6 +85,7 @@ public: * Send the NICK irc command */ void send_nick_command(const std::string& username); + void send_pass_command(const std::string& password); /** * Send the JOIN irc command. */ -- cgit v1.2.3 From 890cfe90996ac4c3916c84d178049d9b3b23465b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Sep 2015 04:27:23 +0200 Subject: Provide Ports and TLS Ports IRC-server ad-hoc options This let any user choose which ports to use when connecting to the IRC server. This also lets the user choose whether or not to force TLS usage (by setting no non-TLS port). fix #2731 --- database/database.xml | 5 +-- src/irc/irc_client.cpp | 22 +++++++++--- src/test.cpp | 10 ------ src/xmpp/biboumi_adhoc_commands.cpp | 67 ++++++++++++++++++++++++++++++------- 4 files changed, 75 insertions(+), 29 deletions(-) diff --git a/database/database.xml b/database/database.xml index f3a005c..fdeb05e 100644 --- a/database/database.xml +++ b/database/database.xml @@ -6,8 +6,9 @@ - - + + + diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index bac3e34..c6174bf 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -16,11 +16,13 @@ #include #include +#include "biboumi.h" #include "louloulibs.h" using namespace std::string_literals; using namespace std::chrono_literals; + IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge): TCPSocketHandler(poller), hostname(hostname), @@ -39,13 +41,25 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname "alive without having to join a real channel of that server. " "To disconnect from the IRC server, leave this room and all " "other IRC channels of that server."; - // TODO: get the values from the preferences of the user, and only use the - // list of default ports if the user didn't specify anything +#ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + this->get_hostname()); + std::vector ports = utils::split(options.ports, ';', false); + for (auto it = ports.rbegin(); it != ports.rend(); ++it) + this->ports_to_try.emplace(*it, false); +# ifdef BOTAN_FOUND + ports = utils::split(options.tlsPorts, ';', false); + for (auto it = ports.rbegin(); it != ports.rend(); ++it) + this->ports_to_try.emplace(*it, true); +# endif // BOTAN_FOUND + +#else // not USE_DATABASE this->ports_to_try.emplace("6667", false); // standard non-encrypted port -#ifdef BOTAN_FOUND +# ifdef BOTAN_FOUND this->ports_to_try.emplace("6670", true); // non-standard but I want it for some servers this->ports_to_try.emplace("6697", true); // standard encrypted port -#endif // BOTAN_FOUND +# endif // BOTAN_FOUND +#endif // USE_DATABASE } IrcClient::~IrcClient() diff --git a/src/test.cpp b/src/test.cpp index 79fcd0b..1a59041 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -423,12 +423,9 @@ int main() Config::set("db_name", "test.db"); Database::set_verbose(true); auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - o.requireTls = false; o.update(); auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - assert(a.requireTls == false); auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - assert(b.requireTls == true); // b does not yet exist in the db, the object is created but not yet // inserted @@ -439,13 +436,6 @@ int main() assert(b.pass == ""); assert(b.pass.value() == ""); - - std::vector ftypes; - db::IrcServerOptions::getFieldTypes(ftypes); - for (const auto& type: ftypes) - { - std::cout << type.type() << std::endl; - } } #endif { diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 1be38d3..2964b22 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -10,6 +11,8 @@ #include #endif +#include + using namespace std::string_literals; void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) @@ -127,16 +130,39 @@ void ConfigureIrcServerStep1(XmppComponent* xmpp_component, AdhocSession& sessio instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + target.local); x.add_child(std::move(instructions)); - XmlNode require_tls("field"); - require_tls["var"] = "require_tls"; - require_tls["type"] = "boolean"; - require_tls["label"] = "Require TLS (refuse to connect insecurely)"; - XmlNode require_tls_value("value"); - require_tls_value.set_inner(options.requireTls ? "true": "false"); - require_tls.add_child(std::move(require_tls_value)); XmlNode required("required"); - require_tls.add_child(required); - x.add_child(std::move(require_tls)); + + XmlNode ports("field"); + ports["var"] = "ports"; + ports["type"] = "text-multi"; + ports["label"] = "Ports"; + ports["desc"] = "List of ports to try, without TLS. Defaults: 6667."; + auto vals = utils::split(options.ports.value(), ';', false); + for (const auto& val: vals) + { + XmlNode ports_value("value"); + ports_value.set_inner(val); + ports.add_child(std::move(ports_value)); + } + ports.add_child(required); + x.add_child(std::move(ports)); + +#ifdef BOTAN_FOUND + XmlNode tls_ports("field"); + tls_ports["var"] = "tls_ports"; + tls_ports["type"] = "text-multi"; + tls_ports["label"] = "TLS ports"; + tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670."; + vals = utils::split(options.tlsPorts.value(), ';', false); + for (const auto& val: vals) + { + XmlNode tls_ports_value("value"); + tls_ports_value.set_inner(val); + tls_ports.add_child(std::move(tls_ports_value)); + } + tls_ports.add_child(required); + x.add_child(std::move(tls_ports)); +#endif XmlNode pass("field"); pass["var"] = "pass"; @@ -168,12 +194,27 @@ void ConfigureIrcServerStep2(XmppComponent* xmpp_component, AdhocSession& sessio for (const XmlNode* field: x->get_children("field", "jabber:x:data")) { const XmlNode* value = field->get_child("value", "jabber:x:data"); - if (field->get_tag("var") == "require_tls" && - value && !value->get_inner().empty()) - options.requireTls = to_bool(value->get_inner()); + const std::vector values = field->get_children("value", "jabber:x:data"); + if (field->get_tag("var") == "ports") + { + std::string ports; + for (const auto& val: values) + ports += val->get_inner() + ";"; + options.ports = ports; + } + +#ifdef BOTAN_FOUND + else if (field->get_tag("var") == "tls_ports") + { + std::string ports; + for (const auto& val: values) + ports += val->get_inner() + ";"; + options.tlsPorts = ports; + } +#endif // BOTAN_FOUND else if (field->get_tag("var") == "pass" && - value && !value->get_inner().empty()) + value && !value->get_inner().empty()) options.pass = value->get_inner(); } -- cgit v1.2.3 From ceec98907776dcd73b0c02a46ca135196e5f223e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Sep 2015 18:56:37 +0200 Subject: Add a field (in the configure form) to specifiy an after-connect IRC command --- database/database.xml | 1 + src/irc/irc_client.cpp | 7 +++++++ src/xmpp/biboumi_adhoc_commands.cpp | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/database/database.xml b/database/database.xml index fdeb05e..8fffe16 100644 --- a/database/database.xml +++ b/database/database.xml @@ -7,6 +7,7 @@ + diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index c6174bf..26f9a46 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -226,6 +226,7 @@ void IrcClient::send_message(IrcMessage&& message) void IrcClient::send_raw(const std::string& txt) { + log_debug("IRC SENDING (raw): (" << this->get_hostname() << ") " << txt); this->send_data(txt + "\r\n"); } @@ -608,6 +609,12 @@ void IrcClient::on_welcome_message(const IrcMessage& message) { this->current_nick = message.arguments[0]; this->welcomed = true; +#ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + this->get_hostname()); + if (!options.afterConnectionCommand.value().empty()) + this->send_raw(options.afterConnectionCommand.value()); +#endif // Install a repeated events to regularly send a PING TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), "PING"s + this->hostname + this->bridge->get_jid())); diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 2964b22..55acd3d 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -177,6 +177,20 @@ void ConfigureIrcServerStep1(XmppComponent* xmpp_component, AdhocSession& sessio pass.add_child(required); x.add_child(std::move(pass)); + XmlNode after_cnt_cmd("field"); + after_cnt_cmd["var"] = "after_connect_command"; + after_cnt_cmd["type"] = "text-single"; + after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server."; + after_cnt_cmd["label"] = "After-connection IRC command"; + if (!options.afterConnectionCommand.value().empty()) + { + XmlNode after_cnt_cmd_value("value"); + after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value()); + after_cnt_cmd.add_child(std::move(after_cnt_cmd_value)); + } + after_cnt_cmd.add_child(required); + x.add_child(std::move(after_cnt_cmd)); + command_node.add_child(std::move(x)); } @@ -216,6 +230,10 @@ void ConfigureIrcServerStep2(XmppComponent* xmpp_component, AdhocSession& sessio else if (field->get_tag("var") == "pass" && value && !value->get_inner().empty()) options.pass = value->get_inner(); + + else if (field->get_tag("var") == "after_connect_command" && + value && !value->get_inner().empty()) + options.afterConnectionCommand = value->get_inner(); } options.update(); -- cgit v1.2.3 From 7247228e9785fd7c8d796d4aa0eb3b9c6fc8f221 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Sep 2015 03:50:08 +0200 Subject: Connection may be closed from our side too --- src/irc/irc_client.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 26f9a46..b4df7dd 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -123,9 +123,11 @@ void IrcClient::on_connected() void IrcClient::on_connection_close(const std::string& error_msg) { - std::string message = "Connection closed by remote server."; + std::string message = "Connection closed"; if (!error_msg.empty()) message += ": " + error_msg; + else + message += "."; const IrcMessage error{"ERROR", {message}}; this->on_error(error); log_warning(message); -- cgit v1.2.3 From 2928598e773ca6708efdda1b6c35786cd3fa5587 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Sep 2015 03:59:14 +0200 Subject: Catch TLS exceptions, close the connection and inform the user of the error --- louloulibs/network/tcp_socket_handler.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index cca6cd2..9424a1a 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -20,6 +20,7 @@ #ifdef BOTAN_FOUND # include +# include Botan::AutoSeeded_RNG TCPSocketHandler::rng; Permissive_Credentials_Manager TCPSocketHandler::credential_manager; @@ -364,7 +365,13 @@ void TCPSocketHandler::send_data(std::string&& data) { #ifdef BOTAN_FOUND if (this->use_tls) - this->tls_send(std::move(data)); + try { + this->tls_send(std::move(data)); + } catch (const Botan::TLS::TLS_Exception& e) { + this->on_connection_close("TLS error: "s + e.what()); + this->close(); + return ; + } else #endif this->raw_send(std::move(data)); @@ -426,8 +433,17 @@ void TCPSocketHandler::tls_recv() if (size > 0) { const bool was_active = this->tls->is_active(); - this->tls->received_data(reinterpret_cast(recv_buf), - static_cast(size)); + try { + this->tls->received_data(reinterpret_cast(recv_buf), + static_cast(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 + // plain-text) + this->on_connection_close("TLS error: "s + e.what()); + this->close(); + return ; + } if (!was_active && this->tls->is_active()) this->on_tls_activated(); } @@ -441,7 +457,7 @@ void TCPSocketHandler::tls_send(std::string&& data) if (!this->pre_buf.empty()) { this->tls->send(reinterpret_cast(this->pre_buf.data()), - this->pre_buf.size()); + this->pre_buf.size()); this->pre_buf = ""; } if (!data.empty()) -- cgit v1.2.3 From f6d9b7d7a1d670b3b3d2bd1ca831de8578de6206 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Sep 2015 04:01:09 +0200 Subject: Fix two small warnings --- src/xmpp/biboumi_adhoc_commands.cpp | 8 ++------ src/xmpp/biboumi_component.hpp | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 55acd3d..b7d2020 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -112,10 +112,8 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X } #ifdef USE_DATABASE -void ConfigureIrcServerStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node) { - auto biboumi_component = static_cast(xmpp_component); - const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, @@ -194,10 +192,8 @@ void ConfigureIrcServerStep1(XmppComponent* xmpp_component, AdhocSession& sessio command_node.add_child(std::move(x)); } -void ConfigureIrcServerStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) { - auto biboumi_component = static_cast(xmpp_component); - const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index fe99f2d..b8fca39 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -9,7 +9,7 @@ #include #include -class ListElement; +struct ListElement; /** * A callback called when the waited iq result is received (it is matched -- cgit v1.2.3 From f904d57940699e332f75a77062912431c6e51188 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 23 Sep 2015 18:24:06 +0200 Subject: Provide username and realname IRC server options Used in the USER command when connecting to the IRC server, instead of the first nick. fix #3028 --- CMakeLists.txt | 17 ++++++++++++++++- database/database.xml | 2 ++ src/irc/irc_client.cpp | 10 ++++++++++ src/xmpp/biboumi_adhoc_commands.cpp | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bad544..e9560cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,8 @@ endif() set(SOFTWARE_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}${${PROJECT_NAME}_VERSION_SUFFIX}) -# To be able to include the config.h file generated by cmake +include(CheckFunctionExists) +check_function_exists(ppoll HAVE_PPOLL_FUNCTION) # To be able to include the config.h and other files generated by cmake include_directories("${CMAKE_CURRENT_BINARY_DIR}/src/") @@ -121,6 +122,7 @@ target_link_libraries(xmpp xmpplib bridge network utils logger) if(USE_DATABASE) target_link_libraries(xmpp database) endif() + # ## bridge # @@ -181,4 +183,17 @@ add_custom_target(dist | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +if(BOTAN_FOUND) + set(STR_WITH_BOTAN "Botan: yes") +else() + set(STR_WITH_BOTAN "Botan: no") +endif() +if(CARES_FOUND) + set(STR_WITH_CARES "c-ares: yes") +else() + set(STR_WITH_CARES "c-ares: no") +endif() +add_custom_target(PrintBuildParameters ALL + ${CMAKE_COMMAND} -E cmake_echo_color --cyan "Compiling ${PROJECT_NAME} with ${STR_WITH_BOTAN}, ${STR_WITH_CARES}") + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/biboumi.h.cmake ${CMAKE_BINARY_DIR}/src/biboumi.h) \ No newline at end of file diff --git a/database/database.xml b/database/database.xml index 8fffe16..1292e71 100644 --- a/database/database.xml +++ b/database/database.xml @@ -10,6 +10,8 @@ + + diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index b4df7dd..00d9f43 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -116,7 +116,17 @@ void IrcClient::on_connected() this->send_pass_command(options.pass.value()); #endif this->send_nick_command(this->username); +#ifdef USE_DATABASE + std::string username = this->username; + if (!options.username.value().empty()) + username = options.username.value(); + std::string realname = this->username; + if (!options.realname.value().empty()) + realname = options.realname.value(); + this->send_user_command(username, realname); +#else this->send_user_command(this->username, this->username); +#endif // USE_DATABASE this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); this->send_pending_data(); } diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index b7d2020..10951cd 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -189,6 +189,32 @@ void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& com after_cnt_cmd.add_child(required); x.add_child(std::move(after_cnt_cmd)); + XmlNode username("field"); + username["var"] = "username"; + username["type"] = "text-single"; + username["label"] = "Username"; + if (!options.username.value().empty()) + { + XmlNode username_value("value"); + username_value.set_inner(options.username.value()); + username.add_child(std::move(username_value)); + } + username.add_child(required); + x.add_child(std::move(username)); + + XmlNode realname("field"); + realname["var"] = "realname"; + realname["type"] = "text-single"; + realname["label"] = "Realname"; + if (!options.realname.value().empty()) + { + XmlNode realname_value("value"); + realname_value.set_inner(options.realname.value()); + realname.add_child(std::move(realname_value)); + } + realname.add_child(required); + x.add_child(std::move(realname)); + command_node.add_child(std::move(x)); } @@ -230,6 +256,14 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com else if (field->get_tag("var") == "after_connect_command" && value && !value->get_inner().empty()) options.afterConnectionCommand = value->get_inner(); + + else if (field->get_tag("var") == "username" && + value && !value->get_inner().empty()) + options.username = value->get_inner(); + + else if (field->get_tag("var") == "realname" && + value && !value->get_inner().empty()) + options.realname = value->get_inner(); } options.update(); -- cgit v1.2.3 From 2380a84ba5d304861cd21eb3a2e57d76e32536a0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 23 Sep 2015 19:08:18 +0200 Subject: Make sure the user-provided username does not contain spaces --- src/xmpp/biboumi_adhoc_commands.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 10951cd..d9162d7 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -13,6 +13,8 @@ #include +#include + using namespace std::string_literals; void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) @@ -259,7 +261,13 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com else if (field->get_tag("var") == "username" && value && !value->get_inner().empty()) - options.username = value->get_inner(); + { + auto username = value->get_inner(); + // The username must not contain spaces + std::replace(&username[0], &username[username.size() - 1], + ' ', '_'); + options.username = username; + } else if (field->get_tag("var") == "realname" && value && !value->get_inner().empty()) -- cgit v1.2.3 From 60d340be19aa4370620181b56a6d4ccdcfa014dc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 23 Sep 2015 19:11:48 +0200 Subject: =?UTF-8?q?TIL=20std::string::end=20and=20std::string::begin?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/biboumi_adhoc_commands.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index d9162d7..19e6a71 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -264,8 +264,7 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com { auto username = value->get_inner(); // The username must not contain spaces - std::replace(&username[0], &username[username.size() - 1], - ' ', '_'); + std::replace(username.begin(), username.end(), ' ', '_'); options.username = username; } -- cgit v1.2.3 From 6512f830fa2baa85a7c688843ebd42eb528e2ad6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 12 Oct 2015 16:35:50 +0200 Subject: The realname is also saved as an IrcClient member --- src/irc/irc_client.cpp | 15 ++++++++------- src/irc/irc_client.hpp | 8 ++++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 00d9f43..4692eef 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -27,6 +27,7 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname TCPSocketHandler(poller), hostname(hostname), username(username), + realname(username), current_nick(username), bridge(bridge), welcomed(false), @@ -115,18 +116,18 @@ void IrcClient::on_connected() if (!options.pass.value().empty()) this->send_pass_command(options.pass.value()); #endif + this->send_nick_command(this->username); + #ifdef USE_DATABASE - std::string username = this->username; if (!options.username.value().empty()) - username = options.username.value(); - std::string realname = this->username; + this->username = options.username.value(); if (!options.realname.value().empty()) - realname = options.realname.value(); + this->realname = options.realname.value(); this->send_user_command(username, realname); -#else - this->send_user_command(this->username, this->username); -#endif // USE_DATABASE +#endif + this->send_user_command(this->username, this->realname); + this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); this->send_pending_data(); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 4e61d14..a6b51ce 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -254,9 +254,13 @@ private: */ const std::string hostname; /** - * The user name used in the USER irc command + * The username used in the USER irc command */ - const std::string username; + std::string username; + /** + * The realname used in the USER irc command + */ + std::string realname; /** * Our current nickname on the server */ -- cgit v1.2.3 From 84aafab6040f8fd126e6c4941631d44f122c4b9a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 12 Oct 2015 16:47:27 +0200 Subject: =?UTF-8?q?Provide=20the=20=E2=80=9Crealname=5Fcustomization?= =?UTF-8?q?=E2=80=9D=20option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref #3136 --- doc/biboumi.1.md | 6 +++++ src/irc/irc_client.cpp | 14 +++++++---- src/xmpp/biboumi_adhoc_commands.cpp | 48 ++++++++++++++++++++----------------- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 836b99d..d28c3a3 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -77,6 +77,12 @@ The configuration file uses a simple format of the form users join their own IRC server using an XMPP client, while forbidding access to any other IRC server. +`realname_customization` + + If this option is set to “false” (default is “true”), the users will not be + able to use the ad-hoc commands that lets them configure their realname and + username. + `log_file` A filename into which logs are written. If none is provided, the logs are diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4692eef..d6c7021 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -120,11 +121,14 @@ void IrcClient::on_connected() this->send_nick_command(this->username); #ifdef USE_DATABASE - if (!options.username.value().empty()) - this->username = options.username.value(); - if (!options.realname.value().empty()) - this->realname = options.realname.value(); - this->send_user_command(username, realname); + if (Config::get("realname_customization", "true") == "true") + { + if (!options.username.value().empty()) + this->username = options.username.value(); + if (!options.realname.value().empty()) + this->realname = options.realname.value(); + this->send_user_command(username, realname); + } #endif this->send_user_command(this->username, this->realname); diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 19e6a71..0a23715 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -191,31 +192,34 @@ void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& com after_cnt_cmd.add_child(required); x.add_child(std::move(after_cnt_cmd)); - XmlNode username("field"); - username["var"] = "username"; - username["type"] = "text-single"; - username["label"] = "Username"; - if (!options.username.value().empty()) + if (Config::get("realname_customization", "true") == "true") { - XmlNode username_value("value"); - username_value.set_inner(options.username.value()); - username.add_child(std::move(username_value)); - } - username.add_child(required); - x.add_child(std::move(username)); + XmlNode username("field"); + username["var"] = "username"; + username["type"] = "text-single"; + username["label"] = "Username"; + if (!options.username.value().empty()) + { + XmlNode username_value("value"); + username_value.set_inner(options.username.value()); + username.add_child(std::move(username_value)); + } + username.add_child(required); + x.add_child(std::move(username)); - XmlNode realname("field"); - realname["var"] = "realname"; - realname["type"] = "text-single"; - realname["label"] = "Realname"; - if (!options.realname.value().empty()) - { - XmlNode realname_value("value"); - realname_value.set_inner(options.realname.value()); - realname.add_child(std::move(realname_value)); + XmlNode realname("field"); + realname["var"] = "realname"; + realname["type"] = "text-single"; + realname["label"] = "Realname"; + if (!options.realname.value().empty()) + { + XmlNode realname_value("value"); + realname_value.set_inner(options.realname.value()); + realname.add_child(std::move(realname_value)); + } + realname.add_child(required); + x.add_child(std::move(realname)); } - realname.add_child(required); - x.add_child(std::move(realname)); command_node.add_child(std::move(x)); } -- cgit v1.2.3 From 1aa2c2d857037f3274297527ca3971a75203d39c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 12 Oct 2015 17:14:29 +0200 Subject: Introduce the realname_from_jid option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When set to true, the realname and username are extracted (by default) from the user’s JID fix #3136 --- doc/biboumi.1.md | 13 +++++++++++++ src/bridge/bridge.cpp | 19 +++++++++++++++---- src/bridge/bridge.hpp | 5 +++-- src/irc/irc_client.cpp | 8 +++++--- src/irc/irc_client.hpp | 4 +++- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index d28c3a3..763be56 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -83,6 +83,19 @@ The configuration file uses a simple format of the form able to use the ad-hoc commands that lets them configure their realname and username. +`realname_from_jid` + + If this option is set to “true”, the realname and username of each biboumi + user will be extracted from their JID. The realname is their bare JID, and + the username is the node-part of their JID. Note that if + `realname_customization` is “true”, each user will still be able to + customize their realname and username, this option just decides the default + realname and username. + + If this option is set to “false” (the default value), the realname and + username of each user will be set to the nick they used to connect to the + IRC server. + `log_file` A filename into which logs are written. If none is provided, the logs are diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ba70069..ec143f0 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -90,7 +90,7 @@ Xmpp::body Bridge::make_xmpp_body(const std::string& str) return irc_format_to_xhtmlim(res); } -IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string& username) +IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname) { try { @@ -98,7 +98,18 @@ IrcClient* Bridge::get_irc_client(const std::string& hostname, const std::string } catch (const std::out_of_range& exception) { - this->irc_clients.emplace(hostname, std::make_shared(this->poller, hostname, username, this)); + auto username = nickname; + auto realname = nickname; + if (Config::get("realname_from_jid", "false") == "true") + { + Jid jid(this->user_jid); + username = jid.local; + realname = this->get_bare_jid(); + } + this->irc_clients.emplace(hostname, + std::make_shared(this->poller, hostname, + nickname, username, + realname, this)); std::shared_ptr irc = this->irc_clients.at(hostname); return irc.get(); } @@ -128,9 +139,9 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) } } -bool Bridge::join_irc_channel(const Iid& iid, const std::string& username, const std::string& password) +bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password) { - IrcClient* irc = this->get_irc_client(iid.get_server(), username); + IrcClient* irc = this->make_irc_client(iid.get_server(), nickname); if (iid.get_local().empty()) { // Join the dummy channel if (irc->is_welcomed()) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index dfe0aa7..f1ecf25 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -60,7 +60,8 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& username, const std::string& password); + bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password); + void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void send_raw_message(const std::string& hostname, const std::string& body); @@ -193,7 +194,7 @@ private: * username in this case) if none is found, and connect that newly-created * client immediately. */ - IrcClient* get_irc_client(const std::string& hostname, const std::string& username); + IrcClient* make_irc_client(const std::string& hostname, const std::string& nickname); /** * This version does not create the IrcClient if it does not exist, throws * a IRCServerNotConnected error in that case. diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d6c7021..0289d72 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -24,12 +24,14 @@ using namespace std::string_literals; using namespace std::chrono_literals; -IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge): +IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, + const std::string& nickname, const std::string& username, + const std::string& realname, Bridge* bridge): TCPSocketHandler(poller), hostname(hostname), username(username), - realname(username), - current_nick(username), + realname(realname), + current_nick(nickname), bridge(bridge), welcomed(false), chanmodes({"", "", "", ""}), diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index a6b51ce..7a04164 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -25,7 +25,9 @@ class Bridge; class IrcClient: public TCPSocketHandler { public: - explicit IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& username, Bridge* bridge); + explicit IrcClient(std::shared_ptr poller, const std::string& hostname, + const std::string& nickname, const std::string& username, + const std::string& realname, Bridge* bridge); ~IrcClient(); /** * Connect to the IRC server -- cgit v1.2.3 From aa340e1c5e4e28397ef212aa210633e9dcb81f98 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 15 Oct 2015 04:32:02 +0200 Subject: Separate the DNS resolution logic from the TCP communication logic fix #3137 --- louloulibs/network/dns_handler.cpp | 23 +--- louloulibs/network/dns_handler.hpp | 7 +- louloulibs/network/resolver.cpp | 219 ++++++++++++++++++++++++++++++ louloulibs/network/resolver.hpp | 129 ++++++++++++++++++ louloulibs/network/tcp_socket_handler.cpp | 202 +++------------------------ louloulibs/network/tcp_socket_handler.hpp | 49 ++----- src/test.cpp | 80 +++++++++++ 7 files changed, 463 insertions(+), 246 deletions(-) create mode 100644 louloulibs/network/resolver.cpp create mode 100644 louloulibs/network/resolver.hpp diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index 324784b..a8d0f9c 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -2,7 +2,6 @@ #ifdef CARES_FOUND #include -#include #include #include @@ -14,19 +13,6 @@ DNSHandler DNSHandler::instance; using namespace std::string_literals; - -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast(arg); - socket_handler->on_hostname4_resolved(status, hostent); -} - -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent) -{ - TCPSocketHandler* socket_handler = static_cast(arg); - socket_handler->on_hostname6_resolved(status, hostent); -} - DNSHandler::DNSHandler() { int ares_error; @@ -54,16 +40,15 @@ void DNSHandler::destroy() ::ares_library_cleanup(); } -void DNSHandler::gethostbyname(const std::string& name, - TCPSocketHandler* socket_handler, int family) +void DNSHandler::gethostbyname(const std::string& name, ares_host_callback callback, + void* data, int family) { - socket_handler->free_cares_addrinfo(); if (family == AF_INET) ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname4_resolved, socket_handler); + callback, data); else ::ares_gethostbyname(this->channel, name.data(), family, - &::on_hostname6_resolved, socket_handler); + callback, data); } void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index ec35374..61e2302 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -13,9 +13,6 @@ class DNSSocketHandler; # include # include -void on_hostname4_resolved(void* arg, int status, int, struct hostent* hostent); -void on_hostname6_resolved(void* arg, int status, int, struct hostent* hostent); - /** * Class managing DNS resolution. It should only be statically instanciated * once in SocketHandler. It manages ares channel and calls various @@ -27,8 +24,8 @@ class DNSHandler public: DNSHandler(); ~DNSHandler() = default; - void gethostbyname(const std::string& name, TCPSocketHandler* socket_handler, - int family); + void gethostbyname(const std::string& name, ares_host_callback callback, + void* socket_handler, int family); /** * Call ares_fds to know what fd needs to be watched by the poller, create * or destroy DNSSocketHandlers depending on the result. diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp new file mode 100644 index 0000000..bfd75d7 --- /dev/null +++ b/louloulibs/network/resolver.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include + +// remove me +#include + +using namespace std::string_literals; + +Resolver::Resolver(): +#ifdef CARES_FOUND + resolved4(false), + resolved6(false), + resolving(false), + cares_addrinfo(nullptr), + port{}, +#endif + resolved(false), + error_msg{} +{ +} + +void Resolver::resolve(const std::string& hostname, const std::string& port, + SuccessCallbackType success_cb, ErrorCallbackType error_cb) +{ + this->error_cb = error_cb; + this->success_cb = success_cb; + this->port = port; + + this->start_resolving(hostname, port); +} + +#ifdef CARES_FOUND +void Resolver::start_resolving(const std::string& hostname, const std::string& port) +{ + std::cout << "start_resolving: " << hostname << port << std::endl; + this->resolving = true; + this->resolved = false; + this->resolved4 = false; + this->resolved6 = false; + + this->error_msg.clear(); + this->cares_addrinfo = nullptr; + + auto hostname4_resolved = [](void* arg, int status, int, + struct hostent* hostent) + { + Resolver* resolver = static_cast(arg); + resolver->on_hostname4_resolved(status, hostent); + }; + auto hostname6_resolved = [](void* arg, int status, int, + struct hostent* hostent) + { + Resolver* resolver = static_cast(arg); + resolver->on_hostname6_resolved(status, hostent); + }; + + DNSHandler::instance.gethostbyname(hostname, hostname6_resolved, + this, AF_INET6); + DNSHandler::instance.gethostbyname(hostname, hostname4_resolved, + this, AF_INET); +} + +void Resolver::on_hostname4_resolved(int status, struct hostent* hostent) +{ + this->resolved4 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo4(hostent); + else + this->error_msg = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + this->on_resolved(); +} + +void Resolver::on_hostname6_resolved(int status, struct hostent* hostent) +{ + this->resolved6 = true; + if (status == ARES_SUCCESS) + this->fill_ares_addrinfo6(hostent); + else + this->error_msg = ::ares_strerror(status); + + if (this->resolved4 && this->resolved6) + this->on_resolved(); +} + +void Resolver::on_resolved() +{ + this->resolved = true; + this->resolving = false; + if (!this->cares_addrinfo) + { + if (this->error_cb) + this->error_cb(this->error_msg.data()); + } + else + { + this->addr.reset(this->cares_addrinfo); + if (this->success_cb) + this->success_cb(this->addr.get()); + } +} + +void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) +{ + std::cout << "fill_ares_addrinfo4" << this->port << std::endl; + struct addrinfo* prev = this->cares_addrinfo; + struct in_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) +{ + std::cout << "fill_ares_addrinfo6" << this->port << std::endl; + struct addrinfo* prev = this->cares_addrinfo; + struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); + + while (*address) + { + // Create a new addrinfo list element, and fill it + struct addrinfo* current = new struct addrinfo; + current->ai_flags = 0; + current->ai_family = hostent->h_addrtype; + current->ai_socktype = SOCK_STREAM; + 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; + + current->ai_addr = reinterpret_cast(addr); + current->ai_next = nullptr; + current->ai_canonname = nullptr; + + current->ai_next = prev; + this->cares_addrinfo = current; + prev = current; + ++address; + } +} + +#else // ifdef CARES_FOUND + +void Resolver::start_resolving(const std::string& hostname, const std::string& port) +{ + // If the resolution fails, the addr will be unset + this->addr.reset(nullptr); + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = 0; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + struct addrinfo* addr_res = nullptr; + const int res = ::getaddrinfo(hostname.data(), port.data(), + &hints, &addr_res); + + this->resolved = true; + + if (res != 0) + { + this->error_msg = gai_strerror(res); + if (this->error_cb) + this->error_cb(this->error_msg.data()); + } + else + { + this->addr.reset(addr_res); + if (this->success_cb) + this->success_cb(this->addr.get()); + } +} +#endif // ifdef CARES_FOUND + +std::string addr_to_string(const struct addrinfo* rp) +{ + char buf[INET6_ADDRSTRLEN]; + if (rp->ai_family == AF_INET) + return ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin_addr, + buf, sizeof(buf)); + else if (rp->ai_family == AF_INET6) + return ::inet_ntop(rp->ai_family, + &reinterpret_cast(rp->ai_addr)->sin6_addr, + buf, sizeof(buf)); + return {}; +} diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp new file mode 100644 index 0000000..511afd2 --- /dev/null +++ b/louloulibs/network/resolver.hpp @@ -0,0 +1,129 @@ +#ifndef RESOLVER_HPP_INCLUDED +#define RESOLVER_HPP_INCLUDED + +#include "louloulibs.h" + +#include +#include +#include + +#include +#include +#include + +struct AddrinfoDeleter +{ + void operator()(struct addrinfo* addr) + { +#ifdef CARES_FOUND + while (addr) + { + delete addr->ai_addr; + auto next = addr->ai_next; + delete addr; + addr = next; + } +#else + freeaddrinfo(addr); +#endif + } +}; + +class Resolver +{ +public: + using ErrorCallbackType = std::function; + using SuccessCallbackType = std::function; + + Resolver(); + ~Resolver() = default; + + bool is_resolving() const + { +#ifdef CARES_FOUND + return this->resolving; +#else + return false; +#endif + } + + bool is_resolved() const + { + return this->resolved; + } + + const auto& get_result() const + { + return this->addr; + } + std::string get_error_message() const + { + return this->error_msg; + } + + void clear() + { +#ifdef CARES_FOUND + this->resolved6 = false; + this->resolved4 = false; +#endif + this->resolved = false; + this->resolving = false; + this->addr.reset(); + this->cares_addrinfo = nullptr; + this->port.clear(); + this->error_msg.clear(); + } + + void resolve(const std::string& hostname, const std::string& port, + SuccessCallbackType success_cb, ErrorCallbackType error_cb); + +private: + void start_resolving(const std::string& hostname, const std::string& port); +#ifdef CARES_FOUND + void on_hostname4_resolved(int status, struct hostent* hostent); + void on_hostname6_resolved(int status, struct hostent* hostent); + + void fill_ares_addrinfo4(const struct hostent* hostent); + void fill_ares_addrinfo6(const struct hostent* hostent); + + void on_resolved(); + + bool resolved4; + bool resolved6; + + bool resolving; + + /** + * When using c-ares to resolve the host asynchronously, we need the + * c-ares callbacks to fill a structure (a struct addrinfo, for + * compatibility with getaddrinfo and the rest of the code that works when + * c-ares is not used) with all returned values (for example an IPv6 and + * an IPv4). The pointer is given to the unique_ptr to manage its lifetime. + */ + struct addrinfo* cares_addrinfo; + std::string port; + +#endif + /** + * Tells if we finished the resolution process. It doesn't indicate if it + * was successful (it is true even if the result is an error). + */ + bool resolved; + std::string error_msg; + + + std::unique_ptr addr; + + ErrorCallbackType error_cb; + SuccessCallbackType success_cb; + + Resolver(const Resolver&) = delete; + Resolver(Resolver&&) = delete; + Resolver& operator=(const Resolver&) = delete; + Resolver& operator=(Resolver&&) = delete; +}; + +std::string addr_to_string(const struct addrinfo* rp); + +#endif /* RESOLVER_HPP_INCLUDED */ diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 9424a1a..72abdcc 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -11,13 +11,9 @@ #include #include #include -#include #include #include -#include -#include - #ifdef BOTAN_FOUND # include # include @@ -43,24 +39,9 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): use_tls(false), connected(false), connecting(false), -#ifdef CARES_FOUND - resolving(false), - resolved(false), - resolved4(false), - resolved6(false), - cares_addrinfo(nullptr), - cares_error(), -#endif hostname_resolution_failed(false) {} -TCPSocketHandler::~TCPSocketHandler() -{ -#ifdef CARES_FOUND - this->free_cares_addrinfo(); -#endif -} - void TCPSocketHandler::init_socket(const struct addrinfo* rp) { if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) @@ -91,17 +72,24 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po { // Get the addrinfo from getaddrinfo (or ares_gethostbyname), only if // this is the first call of this function. -#ifdef CARES_FOUND - if (!this->resolved) + if (!this->resolver.is_resolved()) { log_info("Trying to connect to " << address << ":" << port); // Start the asynchronous process of resolving the hostname. Once // the addresses have been found and `resolved` has been set to true // (but connecting will still be false), TCPSocketHandler::connect() // needs to be called, again. - this->resolving = true; - DNSHandler::instance.gethostbyname(address, this, AF_INET6); - DNSHandler::instance.gethostbyname(address, this, AF_INET); + this->resolver.resolve(address, port, + [this](const struct addrinfo*) + { + log_debug("Resolution success, calling connect() again"); + this->connect(); + }, + [this](const char*) + { + log_debug("Resolution failed, calling connect() again"); + this->connect(); + }); return; } else @@ -109,39 +97,16 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po // The c-ares resolved the hostname and the available addresses // where saved in the cares_addrinfo linked list. Now, just use // this list to try to connect. - addr_res = this->cares_addrinfo; + addr_res = this->resolver.get_result().get(); if (!addr_res) { this->hostname_resolution_failed = true; - const auto msg = this->cares_error; + const auto msg = this->resolver.get_error_message(); this->close(); this->on_connection_failed(msg); return ; } } -#else - log_info("Trying to connect to " << address << ":" << port); - struct addrinfo hints; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_flags = 0; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - - const int res = ::getaddrinfo(address.c_str(), port.c_str(), &hints, &addr_res); - - if (res != 0) - { - log_warning("getaddrinfo failed: "s + gai_strerror(res)); - this->hostname_resolution_failed = true; - this->close(); - this->on_connection_failed(gai_strerror(res)); - return ; - } - // Make sure the alloced structure is always freed at the end of the - // function - sg.add_callback([&addr_res](){ freeaddrinfo(addr_res); }); -#endif } else { // This function is called again, use the saved addrinfo structure, @@ -335,30 +300,18 @@ void TCPSocketHandler::close() } this->connected = false; this->connecting = false; -#ifdef CARES_FOUND - this->resolving = false; - this->resolved = false; - this->resolved4 = false; - this->resolved6 = false; - this->free_cares_addrinfo(); - this->cares_error.clear(); -#endif this->in_buf.clear(); this->out_buf.clear(); this->port.clear(); + this->resolver.clear(); } void TCPSocketHandler::display_resolved_ip(struct addrinfo* rp) const { - char buf[INET6_ADDRSTRLEN]; if (rp->ai_family == AF_INET) - log_debug("Connecting to IP address " << ::inet_ntop(rp->ai_family, - &reinterpret_cast(rp->ai_addr)->sin_addr, - buf, sizeof(buf))); + log_debug("Trying IPv4 address " << addr_to_string(rp)); else if (rp->ai_family == AF_INET6) - log_debug("Connecting to IPv6 address " << ::inet_ntop(rp->ai_family, - &reinterpret_cast(rp->ai_addr)->sin6_addr, - buf, sizeof(buf))); + log_debug("Trying IPv6 address " << addr_to_string(rp)); } void TCPSocketHandler::send_data(std::string&& data) @@ -399,11 +352,7 @@ bool TCPSocketHandler::is_connected() const bool TCPSocketHandler::is_connecting() const { -#ifdef CARES_FOUND - return this->connecting || this->resolving; -#else - return this->connecting; -#endif + return this->connecting || this->resolver.is_resolving(); } void* TCPSocketHandler::get_receive_buffer(const size_t) const @@ -506,122 +455,11 @@ void TCPSocketHandler::on_tls_activated() } void Permissive_Credentials_Manager::verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector&) + const std::string& purported_hostname, + const std::vector&) { // TODO: Offer the admin to disallow connection on untrusted // certificates log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); } #endif // BOTAN_FOUND - -#ifdef CARES_FOUND - -void TCPSocketHandler::on_hostname4_resolved(int status, struct hostent* hostent) -{ - this->resolved4 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo4(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->resolving = false; - this->connect(); - } -} - -void TCPSocketHandler::on_hostname6_resolved(int status, struct hostent* hostent) -{ - this->resolved6 = true; - if (status == ARES_SUCCESS) - this->fill_ares_addrinfo6(hostent); - else - this->cares_error = ::ares_strerror(status); - - if (this->resolved4 && this->resolved6) - { - this->resolved = true; - this->resolving = false; - this->connect(); - } -} - -void TCPSocketHandler::fill_ares_addrinfo4(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - 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; - - current->ai_addr = reinterpret_cast(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::fill_ares_addrinfo6(const struct hostent* hostent) -{ - struct addrinfo* prev = this->cares_addrinfo; - struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); - - while (*address) - { - // Create a new addrinfo list element, and fill it - struct addrinfo* current = new struct addrinfo; - current->ai_flags = 0; - current->ai_family = hostent->h_addrtype; - current->ai_socktype = SOCK_STREAM; - 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; - - current->ai_addr = reinterpret_cast(addr); - current->ai_next = nullptr; - current->ai_canonname = nullptr; - - current->ai_next = prev; - this->cares_addrinfo = current; - prev = current; - ++address; - } -} - -void TCPSocketHandler::free_cares_addrinfo() -{ - while (this->cares_addrinfo) - { - delete this->cares_addrinfo->ai_addr; - auto next = this->cares_addrinfo->ai_next; - delete this->cares_addrinfo; - this->cares_addrinfo = next; - } -} - -#endif // CARES_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 835c509..997d575 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -2,6 +2,7 @@ # define SOCKET_HANDLER_INCLUDED #include +#include #include #include @@ -14,10 +15,6 @@ #include "louloulibs.h" -#ifdef CARES_FOUND -# include -#endif - #ifdef BOTAN_FOUND # include # include @@ -43,7 +40,7 @@ public: class TCPSocketHandler: public SocketHandler { protected: - ~TCPSocketHandler(); + ~TCPSocketHandler() = default; public: explicit TCPSocketHandler(std::shared_ptr poller); @@ -109,16 +106,6 @@ public: bool is_connected() const override final; bool is_connecting() const; -#ifdef CARES_FOUND - void on_hostname4_resolved(int status, struct hostent* hostent); - void on_hostname6_resolved(int status, struct hostent* hostent); - - void free_cares_addrinfo(); - - void fill_ares_addrinfo4(const struct hostent* hostent); - void fill_ares_addrinfo6(const struct hostent* hostent); -#endif - private: /** * Initialize the socket with the parameters contained in the given @@ -194,9 +181,13 @@ private: */ std::list out_buf; /** - * Keep the details of the addrinfo that triggered a EINPROGRESS error when - * connect()ing to it, to reuse it directly when connect() is called - * again. + * DNS resolver + */ + Resolver resolver; + /** + * Keep the details of the addrinfo returned by the resolver that + * triggered a EINPROGRESS error when connect()ing to it, to reuse it + * directly when connect() is called again. */ struct addrinfo addrinfo; struct sockaddr_in6 ai_addr; @@ -234,28 +225,6 @@ protected: bool connected; bool connecting; -#ifdef CARES_FOUND - bool resolving; - /** - * Whether or not the DNS resolution was successfully done - */ - bool resolved; - bool resolved4; - bool resolved6; - /** - * When using c-ares to resolve the host asynchronously, we need the - * c-ares callback to fill a structure (a struct addrinfo, for - * compatibility with getaddrinfo and the rest of the code that works when - * c-ares is not used) with all returned values (for example an IPv6 and - * an IPv4). The next call of connect() will then try all these values - * (exactly like we do with the result of getaddrinfo) and save the one - * that worked (or returned EINPROGRESS) in the other struct addrinfo (see - * the members addrinfo, ai_addrlen, and ai_addr). - */ - struct addrinfo* cares_addrinfo; - std::string cares_error; -#endif // CARES_FOUND - bool hostname_resolution_failed; private: diff --git a/src/test.cpp b/src/test.cpp index 1a59041..0a3b4c8 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -3,10 +3,13 @@ */ #include +#include #include #include #include +#include #include +#include #include #include #include @@ -462,7 +465,84 @@ int main() res = xdg_data_path("bonjour.txt"); std::cout << res << std::endl; assert(res == "/datadir/biboumi/bonjour.txt"); + } + + + { + std::cout << color << "Testing the DNS resolver…" << reset << std::endl; + + Resolver resolver; + + /** + * If we are using cares, we need to run a poller loop until each + * resolution is finished. Without cares we get the result before + * resolve() returns because it’s blocking. + */ +#ifdef CARES_FOUND + auto p = std::make_shared(); + + const auto loop = [&p]() + { + do + { + DNSHandler::instance.watch_dns_sockets(p); + } + while (p->poll(utils::no_timeout) != -1); + }; +#else + // We don’t need to do anything if we are not using cares. + const auto loop = [](){}; +#endif + + std::string hostname; + std::string port = "6667"; + + bool success = true; + auto error_cb = [&success, &hostname, &port](const char* msg) + { + std::cout << "Failed to resolve " << hostname << ":" << port << ": " << msg << std::endl; + success = false; + }; + auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) + { + std::cout << "Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr) << std::endl; + success = true; + }; + + hostname = "example.com"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + assert(success); + + hostname = "this.should.fail.because.it.is..misformatted"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + assert(!success); + + hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + assert(!success); + + hostname = "localhost6"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + assert(success); + + hostname = "localhost"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + assert(success); + +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif } return 0; -- cgit v1.2.3 From bf30b4498fc919ecd10d98b4ac551dca227c9ed5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:16:58 +0200 Subject: Add the network module to the test_suite Because we added a network unit test --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e9560cc..b16b906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,7 +158,8 @@ target_link_libraries(test_suite bridge utils config - logger) + logger + network) if(USE_DATABASE) target_link_libraries(test_suite -- cgit v1.2.3 From 74c73799ab83ac1569ddee7a3dddb12df3bc8a6d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:17:53 +0200 Subject: Only compile database.cpp if configured with litesql --- src/database/database.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/database/database.cpp b/src/database/database.cpp index e16465c..8d09788 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -1,3 +1,6 @@ +#include "biboumi.h" +#ifdef USE_DATABASE + #include #include #include @@ -52,3 +55,5 @@ void Database::close() { Database::db.reset(nullptr); } + +#endif -- cgit v1.2.3 From 83bd57b1909a6a0fa8675548d796073bd8054dae Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:19:55 +0200 Subject: Some little fixes in test.cpp --- src/test.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/test.cpp b/src/test.cpp index 0a3b4c8..f6fb629 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -22,17 +21,14 @@ #include #include #include -#include -#include #include -#include - -#include "biboumi.h" #undef NDEBUG #include +using namespace std::chrono_literals; + static const std::string color(""); static const std::string reset(""); @@ -109,7 +105,7 @@ int main() */ std::cout << color << "Testing encoding…" << reset << std::endl; const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; - assert(utils::is_valid_utf8(valid) == true); + assert(utils::is_valid_utf8(valid)); const char* invalid = "\xF0\x0F"; assert(utils::is_valid_utf8(invalid) == false); const char* invalid2 = "\xFE\xFE\xFF\xFF"; @@ -302,7 +298,7 @@ int main() assert(jid2.resource == "coucou@coucou/coucou"); // Jidprep - const std::string& badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); + const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); const std::string correctjid = jidprep(badjid); std::cout << correctjid << std::endl; assert(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); -- cgit v1.2.3 From 2c89b9f1936869a24fb6da2f313d75004b470d4e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:20:29 +0200 Subject: Remove an unused include --- src/xmpp/biboumi_adhoc_commands.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 0a23715..fa3a35c 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include -- cgit v1.2.3 From e7a91badcdd421e04eea8235debc8ae582919744 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:21:05 +0200 Subject: =?UTF-8?q?Use=20=E2=80=9Cusing=E2=80=9D=20instead=20of=20typedef?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bridge/colors.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index b5e6e7b..2ba80ee 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -17,7 +17,7 @@ namespace Xmpp // Contains: // - an XMPP-valid UTF-8 body // - an XML node representing the XHTML-IM body, or null - typedef std::tuple> body; + using body = std::tuple>; } #define IRC_FORMAT_BOLD_CHAR '\x02' // done -- cgit v1.2.3 From e46b9cfc6e1ddef83a3caa9406dee6a6b746f342 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 24 Oct 2015 15:24:40 +0200 Subject: Display a success message at the end of the test suite --- src/test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test.cpp b/src/test.cpp index f6fb629..ae36460 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -30,6 +30,7 @@ using namespace std::chrono_literals; static const std::string color(""); +static const std::string success_color(""); static const std::string reset(""); int main() @@ -541,5 +542,6 @@ int main() #endif } + std::cout << success_color << "All test passed successfully!" << reset << std::endl; return 0; } -- cgit v1.2.3 From ec53fc931533a66872c9dd49b8e11aa084caf75f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 02:34:55 +0100 Subject: Remove some debug thing that should not have been committed --- louloulibs/network/resolver.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index bfd75d7..5324191 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -3,9 +3,6 @@ #include #include -// remove me -#include - using namespace std::string_literals; Resolver::Resolver(): @@ -34,7 +31,6 @@ void Resolver::resolve(const std::string& hostname, const std::string& port, #ifdef CARES_FOUND void Resolver::start_resolving(const std::string& hostname, const std::string& port) { - std::cout << "start_resolving: " << hostname << port << std::endl; this->resolving = true; this->resolved = false; this->resolved4 = false; -- cgit v1.2.3 From 42a743faeb87b5c4d1dd19d828f92f786de96a83 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 02:37:56 +0100 Subject: Introduce a basic .gitlab-ci.yml file --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..855c4a6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,8 @@ +job: + script: + - mkdir build/ + - cd build/ + - cmake .. + - make -j8 + - make test_suite + - ./test_suite -- cgit v1.2.3 From f57a43f33767082e574d6cb74480d09c80e72e64 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 02:43:33 +0100 Subject: Remove some more debug thing --- louloulibs/network/resolver.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 5324191..8028c5a 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -101,7 +101,6 @@ void Resolver::on_resolved() void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) { - std::cout << "fill_ares_addrinfo4" << this->port << std::endl; struct addrinfo* prev = this->cares_addrinfo; struct in_addr** address = reinterpret_cast(hostent->h_addr_list); @@ -133,7 +132,6 @@ void Resolver::fill_ares_addrinfo4(const struct hostent* hostent) void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) { - std::cout << "fill_ares_addrinfo6" << this->port << std::endl; struct addrinfo* prev = this->cares_addrinfo; struct in6_addr** address = reinterpret_cast(hostent->h_addr_list); -- cgit v1.2.3 From c8606e1858363f0f9b8f9cd8ac8b96dfe181fd31 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 02:45:20 +0100 Subject: Remove a unused_parameter warning --- louloulibs/network/resolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 8028c5a..0150fc8 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -29,7 +29,7 @@ void Resolver::resolve(const std::string& hostname, const std::string& port, } #ifdef CARES_FOUND -void Resolver::start_resolving(const std::string& hostname, const std::string& port) +void Resolver::start_resolving(const std::string& hostname, const std::string&) { this->resolving = true; this->resolved = false; -- cgit v1.2.3 From d799dc10b6045837dd58a1945b3227a2b08d40d6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 03:20:22 +0100 Subject: Test a different way to do our CI Introduce a script, which is called with different parameters, to have a number of builds done with a different configuration, without repeating things too much. --- .gitlab-ci.yml | 34 ++++++++++++++++++++++++++-------- scripts/build_and_run_tests.sh | 7 +++++++ 2 files changed, 33 insertions(+), 8 deletions(-) create mode 100755 scripts/build_and_run_tests.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 855c4a6..49bb935 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,8 +1,26 @@ -job: - script: - - mkdir build/ - - cd build/ - - cmake .. - - make -j8 - - make test_suite - - ./test_suite +before_script: + - mkdir build && cd build/ + +botan_cares_systemd_idn: + script: + - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + +botan_cares_systemd: + script: + - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 + +botan_cares_idn: + script: + - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITHOUT_SYSTEMD=1 -DWITH_LIBIDN=1 + +botan_systemd_idn: + script: + - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITHOUT_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + +cares_systemd_idn: + script: + - ../scripts/build_and_run_tests.sh -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + +cares_systemd: + script: + - ../scripts/build_and_run_tests.sh -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 diff --git a/scripts/build_and_run_tests.sh b/scripts/build_and_run_tests.sh new file mode 100755 index 0000000..69a27fd --- /dev/null +++ b/scripts/build_and_run_tests.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e -x + +cmake .. $@ +make -j$(nproc) biboumi test_suite +./test_suite -- cgit v1.2.3 From e43ca29e94342ab8e3cd8be54d6935bb450c00fe Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 19:33:45 +0100 Subject: Fix the jidprep tests when built without libidn --- src/test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/test.cpp b/src/test.cpp index ae36460..14ba929 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -302,8 +302,9 @@ int main() const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); const std::string correctjid = jidprep(badjid); std::cout << correctjid << std::endl; +#ifdef LIBIDN_FOUND assert(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - // Check that the cache do not break things when we prep the same string + // Check that the cache does not break things when we prep the same string // multiple times assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); @@ -317,6 +318,9 @@ int main() const std::string fixed_crappy = jidprep(crappy); std::cout << fixed_crappy << std::endl; assert(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); +#else // Without libidn, jidprep always returns an empty string + assert(jidprep(badjid) == ""); +#endif /** * IID parsing -- cgit v1.2.3 From 0fcba76c793e7c0b1ccc09eaa85016caa11ca3f2 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 19:44:31 +0100 Subject: Fix a build issue when compiling without cares --- louloulibs/network/resolver.cpp | 2 ++ louloulibs/network/resolver.hpp | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index 0150fc8..cb6c445 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -23,7 +23,9 @@ void Resolver::resolve(const std::string& hostname, const std::string& port, { this->error_cb = error_cb; this->success_cb = success_cb; +#ifdef CARES_FOUND this->port = port; +#endif this->start_resolving(hostname, port); } diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index 511afd2..f9e9134 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -66,12 +66,12 @@ public: #ifdef CARES_FOUND this->resolved6 = false; this->resolved4 = false; -#endif - this->resolved = false; this->resolving = false; - this->addr.reset(); this->cares_addrinfo = nullptr; this->port.clear(); +#endif + this->resolved = false; + this->addr.reset(); this->error_msg.clear(); } -- cgit v1.2.3 From 142516a69bb000ce80cbb6509d1f407438a94663 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 20:09:39 +0100 Subject: Fix some trivial issues reported by cppcheck --- louloulibs/network/tcp_socket_handler.cpp | 2 +- src/irc/irc_client.cpp | 1 - src/xmpp/biboumi_component.cpp | 2 +- src/xmpp/biboumi_component.hpp | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 72abdcc..f2a2466 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -247,7 +247,7 @@ void TCPSocketHandler::on_send() struct msghdr msg{nullptr, 0, msg_iov, 0, nullptr, 0, 0}; - for (std::string& s: this->out_buf) + for (const std::string& s: this->out_buf) { // unconsting the content of s is ok, sendmsg will never modify it msg_iov[msg.msg_iovlen].iov_base = const_cast(s.data()); diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0289d72..8f92f9b 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -888,7 +888,6 @@ void IrcClient::on_unknown_message(const IrcMessage& message) if (message.arguments.size() < 2) return ; std::string from = message.prefix; - const std::string to = message.arguments[0]; std::stringstream ss; for (auto it = message.arguments.begin() + 1; it != message.arguments.end(); ++it) { diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 920a2a3..10dce57 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -579,7 +579,7 @@ void BiboumiComponent::send_ping_request(const std::string& from, } void BiboumiComponent::send_iq_room_list_result(const std::string& id, - const std::string to_jid, + const std::string& to_jid, const std::string& from, const std::vector& rooms_list) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index b8fca39..69eebdc 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -66,7 +66,7 @@ public: /** * Send the channels list in one big stanza */ - void send_iq_room_list_result(const std::string& id, const std::string to_jid, + void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from, const std::vector& rooms_list); /** -- cgit v1.2.3 From 450de4c33615fc8cd14bf3a7854ee615ed53df1c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 20:28:36 +0100 Subject: =?UTF-8?q?Style,=20add=20a=20few=20=E2=80=9Cthis->=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/xmpp/xmpp_parser.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index c1fd63a..f44d49a 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -86,7 +86,7 @@ void* XmppParser::get_buffer(const size_t size) const void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) { - level++; + this->level++; auto new_node = std::make_unique(name, this->current_node); auto new_node_ptr = new_node.get(); @@ -101,15 +101,14 @@ void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) this->stream_open_event(*this->current_node); } -void XmppParser::end_element(const XML_Char* name) +void XmppParser::end_element(const XML_Char*) { - (void)name; - level--; - if (level == 1) + this->level--; + if (this->level == 1) { this->stanza_event(*this->current_node); } - if (level == 0) + if (this->level == 0) { this->stream_close_event(*this->current_node); this->current_node = nullptr; @@ -117,7 +116,7 @@ void XmppParser::end_element(const XML_Char* name) } else this->current_node = this->current_node->get_parent(); - if (level == 1) + if (this->level == 1) this->current_node->delete_all_children(); } -- cgit v1.2.3 From 4e32fe213cccdc6cdc1dcba498fd74b8b97703ea Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 26 Oct 2015 20:35:10 +0100 Subject: Refactor XmppParser::end_element to make it clearer Both for me, and apparently for clang static analyzer, who reported a (imo) false positive. --- louloulibs/xmpp/xmpp_parser.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index f44d49a..25f2876 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -104,20 +104,24 @@ void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute) void XmppParser::end_element(const XML_Char*) { this->level--; - if (this->level == 1) - { - this->stanza_event(*this->current_node); - } if (this->level == 0) - { + { // End of the whole stream this->stream_close_event(*this->current_node); this->current_node = nullptr; this->root.reset(); } else - this->current_node = this->current_node->get_parent(); - if (this->level == 1) - this->current_node->delete_all_children(); + { + auto parent = this->current_node->get_parent(); + if (this->level == 1) + { // End of a stanza + this->stanza_event(*this->current_node); + // Note: deleting all the children of our parent deletes ourself, + // so current_node is an invalid pointer after this line + this->current_node->get_parent()->delete_all_children(); + } + this->current_node = parent; + } } void XmppParser::char_data(const XML_Char* data, int len) -- cgit v1.2.3 From 3c1889fbd0d7b96aae16f3479ac8aae70a7e15f7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 Oct 2015 19:13:53 +0100 Subject: Use Catch for our test suite `make check` is also added to compile and run the tests Catch is fetched with cmake automatically into the build directory when needed --- CMakeLists.txt | 22 +- scripts/build_and_run_tests.sh | 2 +- src/test.cpp | 551 ----------------------------------------- tests/colors.cpp | 54 ++++ tests/config.cpp | 25 ++ tests/database.cpp | 30 +++ tests/dns.cpp | 83 +++++++ tests/encoding.cpp | 56 +++++ tests/iid.cpp | 122 +++++++++ tests/jid.cpp | 39 +++ tests/test.cpp | 2 + tests/timed_events.cpp | 62 +++++ tests/utils.cpp | 72 ++++++ tests/uuid.cpp | 13 + tests/xmpp.cpp | 47 ++++ 15 files changed, 625 insertions(+), 555 deletions(-) delete mode 100644 src/test.cpp create mode 100644 tests/colors.cpp create mode 100644 tests/config.cpp create mode 100644 tests/database.cpp create mode 100644 tests/dns.cpp create mode 100644 tests/encoding.cpp create mode 100644 tests/iid.cpp create mode 100644 tests/jid.cpp create mode 100644 tests/test.cpp create mode 100644 tests/timed_events.cpp create mode 100644 tests/utils.cpp create mode 100644 tests/uuid.cpp create mode 100644 tests/xmpp.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b16b906..960b5d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,12 +145,13 @@ if(SYSTEMD_FOUND) target_link_libraries(xmpp ${SYSTEMD_LIBRARIES}) endif() - # ## Tests # +file(GLOB source_tests + tests/*.cpp) add_executable(test_suite EXCLUDE_FROM_ALL - src/test.cpp) + ${source_tests}) target_link_libraries(test_suite xmpplib xmpp @@ -160,11 +161,26 @@ target_link_libraries(test_suite config logger network) - if(USE_DATABASE) target_link_libraries(test_suite database) endif() +include(ExternalProject) +ExternalProject_Add(catch + GIT_REPOSITORY "https://github.com/philsquared/Catch.git" + PREFIX "external" + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) +ExternalProject_Get_Property(catch SOURCE_DIR) +target_include_directories(test_suite + PUBLIC "${SOURCE_DIR}/include/" +) +add_dependencies(test_suite catch) +add_custom_target(check COMMAND "test_suite" + DEPENDS test_suite) # ## Install target diff --git a/scripts/build_and_run_tests.sh b/scripts/build_and_run_tests.sh index 69a27fd..738ef52 100755 --- a/scripts/build_and_run_tests.sh +++ b/scripts/build_and_run_tests.sh @@ -4,4 +4,4 @@ set -e -x cmake .. $@ make -j$(nproc) biboumi test_suite -./test_suite +make -j$(nproc) check diff --git a/src/test.cpp b/src/test.cpp deleted file mode 100644 index 14ba929..0000000 --- a/src/test.cpp +++ /dev/null @@ -1,551 +0,0 @@ -/** - * Just a very simple test suite, by hand, using assert() - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#undef NDEBUG -#include - -using namespace std::chrono_literals; - -static const std::string color(""); -static const std::string success_color(""); -static const std::string reset(""); - -int main() -{ - - /** - * Config - */ - std::cout << color << "Testing config…" << reset << std::endl; - Config::filename = "test.cfg"; - Config::file_must_exist = false; - Config::set("coucou", "bonjour", true); - Config::close(); - - bool error = false; - try - { - Config::file_must_exist = true; - assert(Config::get("coucou", "") == "bonjour"); - assert(Config::get("does not exist", "default") == "default"); - Config::close(); - } - catch (const std::ios::failure& e) - { - error = true; - } - assert(error == false); - - Config::set("log_level", "2"); - Config::set("log_file", ""); - - std::cout << color << "Testing logging…" << reset << std::endl; - log_debug("If you see this, the test FAILED."); - log_info("If you see this, the test FAILED."); - log_warning("You must see this message. And the next one too."); - log_error("It’s not an error, don’t worry, the test passed."); - - - /** - * Timed events - */ - std::cout << color << "Testing timed events…" << reset << std::endl; - // No event. - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - assert(TimedEventsManager::instance().execute_expired_events() == 0); - - // Add a single event - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){ std::cout << "Timeout expired" << std::endl; })); - // The event should not yet be expired - assert(TimedEventsManager::instance().get_timeout() > 0ms); - assert(TimedEventsManager::instance().execute_expired_events() == 0); - std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); - std::cout << "Sleeping for " << timoute.count() << "ms" << std::endl; - std::this_thread::sleep_for(timoute); - - // Event is now expired - assert(TimedEventsManager::instance().execute_expired_events() == 1); - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - - // Test canceling events - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 100ms, [](){ }, "un")); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 200ms, [](){ }, "deux")); - TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 300ms, [](){ }, "deux")); - assert(TimedEventsManager::instance().get_timeout() > 0ms); - assert(TimedEventsManager::instance().size() == 3); - assert(TimedEventsManager::instance().cancel("un") == 1); - assert(TimedEventsManager::instance().size() == 2); - assert(TimedEventsManager::instance().cancel("deux") == 2); - assert(TimedEventsManager::instance().get_timeout() == utils::no_timeout); - - /** - * Encoding - */ - std::cout << color << "Testing encoding…" << reset << std::endl; - const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; - assert(utils::is_valid_utf8(valid)); - const char* invalid = "\xF0\x0F"; - assert(utils::is_valid_utf8(invalid) == false); - const char* invalid2 = "\xFE\xFE\xFF\xFF"; - assert(utils::is_valid_utf8(invalid2) == false); - - std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; - std::cout << in << std::endl; - assert(utils::is_valid_utf8(in.c_str()) == true); - std::string res = utils::convert_to_utf8(in, "UTF-8"); - assert(utils::is_valid_utf8(res.c_str()) == true && res == in); - - std::string original_utf8("couc¥ou"); - std::string original_latin1("couc\xa5ou"); - - // When converting back to utf-8 - std::string from_latin1 = utils::convert_to_utf8(original_latin1.c_str(), "ISO-8859-1"); - assert(from_latin1 == original_utf8); - - // Check the behaviour when the decoding fails (here because we provide a - // wrong charset) - std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); - assert(from_ascii == "couc�ou"); - std::cout << from_ascii << std::endl; - - std::string without_ctrl_char("𤭢€¢$"); - assert(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); - assert(utils::remove_invalid_xml_chars(in) == in); - assert(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); - - /** - * Id generation - */ - std::cout << color << "Testing id generation…" << reset << std::endl; - const std::string first_uuid = XmppComponent::next_id(); - const std::string second_uuid = XmppComponent::next_id(); - std::cout << first_uuid << std::endl; - std::cout << second_uuid << std::endl; - assert(first_uuid.size() == 36); - assert(second_uuid.size() == 36); - assert(first_uuid != second_uuid); - - /** - * Utils - */ - std::cout << color << "Testing utils…" << reset << std::endl; - std::vector splitted = utils::split("a::a", ':', false); - assert(splitted.size() == 2); - splitted = utils::split("a::a", ':', true); - assert(splitted.size() == 3); - assert(splitted[0] == "a"); - assert(splitted[1] == ""); - assert(splitted[2] == "a"); - splitted = utils::split("\na", '\n', true); - assert(splitted.size() == 2); - assert(splitted[0] == ""); - assert(splitted[1] == "a"); - - const std::string lowercase = utils::tolower("CoUcOu LeS CoPaiNs ♥"); - std::cout << lowercase << std::endl; - assert(lowercase == "coucou les copains ♥"); - - const std::string ltr = "coucou"; - assert(utils::revstr(ltr) == "uocuoc"); - - assert(to_bool("true")); - assert(!to_bool("trou")); - assert(to_bool("1")); - assert(!to_bool("0")); - assert(!to_bool("-1")); - assert(!to_bool("false")); - - /** - * XML parsing - */ - std::cout << color << "Testing XML parsing…" << reset << std::endl; - XmppParser xml; - const std::string doc = "innertail"; - auto check_stanza = [](const Stanza& stanza) - { - assert(stanza.get_name() == "stanza"); - assert(stanza.get_tag("xmlns") == "stream_ns"); - assert(stanza.get_tag("b") == "c"); - assert(stanza.get_inner() == "inner"); - assert(stanza.get_tail() == ""); - assert(stanza.get_child("child1", "stream_ns") != nullptr); - assert(stanza.get_child("child2", "stream_ns") == nullptr); - assert(stanza.get_child("child2", "child2_ns") != nullptr); - assert(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); - }; - xml.add_stanza_callback([check_stanza](const Stanza& stanza) - { - std::cout << stanza.to_string() << std::endl; - check_stanza(stanza); - // Do the same checks on a copy of that stanza. - Stanza copy(stanza); - check_stanza(copy); - }); - xml.feed(doc.data(), doc.size(), true); - - const std::string doc2 = "coucou\r\n\a"; - xml.add_stanza_callback([](const Stanza& stanza) - { - std::cout << stanza.to_string() << std::endl; - assert(stanza.get_inner() == "coucou\r\n"); - }); - xml.feed(doc2.data(), doc.size(), true); - - /** - * XML escape/escape - */ - std::cout << color << "Testing XML escaping…" << reset << std::endl; - const std::string unescaped = "'coucou'/&\"gaga\""; - assert(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); - assert(xml_unescape(xml_escape(unescaped)) == unescaped); - - /** - * Irc user parsing - */ - const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; - - IrcUser user1("!nick!~some@host.bla", prefixes); - assert(user1.nick == "nick"); - assert(user1.host == "~some@host.bla"); - assert(user1.modes.size() == 1); - assert(user1.modes.find('a') != user1.modes.end()); - IrcUser user2("coucou!~other@host.bla", prefixes); - assert(user2.nick == "coucou"); - assert(user2.host == "~other@host.bla"); - assert(user2.modes.empty()); - assert(user2.modes.find('a') == user2.modes.end()); - - /** - * Colors conversion - */ - std::cout << color << "Testing IRC colors conversion…" << reset << std::endl; - std::unique_ptr xhtml; - std::string cleaned_up; - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); - std::cout << xhtml->to_string() << std::endl; - assert(xhtml && xhtml->to_string() == "bold"); - - std::tie(cleaned_up, xhtml) = - irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" - "5red,5default-on-red10,2cyan-on-blue"); - assert(xhtml); - assert(xhtml->to_string() == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); - assert(cleaned_up == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normal"); - assert(!xhtml && cleaned_up == "normal"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(""); - assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(",a"); - assert(xhtml && !xhtml->has_children() && cleaned_up == "a"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); - assert(xhtml && !xhtml->has_children() && cleaned_up.empty()); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("[\x1D13dolphin-emu/dolphin\x1D] 03foo commented on #283 (Add support for the guide button to XInput): 02http://example.com"); - assert(xhtml->to_string() == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); - assert(cleaned_up == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("0e46ab by 03Pierre Dindon [090|091|040] 02http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); - assert(cleaned_up == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); - assert(xhtml->to_string() == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); - - std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); - assert(cleaned_up == "test\ncoucou"); - assert(xhtml->to_string() == "test
coucou"); - - /** - * JID parsing - */ - std::cout << color << "Testing JID parsing…" << reset << std::endl; - // Full JID - Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); - std::cout << jid1.local << "@" << jid1.domain << "/" << jid1.resource << std::endl; - assert(jid1.local == "♥"); - assert(jid1.domain == "ツ.coucou"); - assert(jid1.resource == "coucou@coucou/coucou"); - - // Domain and resource - Jid jid2("ツ.coucou/coucou@coucou/coucou"); - std::cout << jid2.local << "@" << jid2.domain << "/" << jid2.resource << std::endl; - assert(jid2.local == ""); - assert(jid2.domain == "ツ.coucou"); - assert(jid2.resource == "coucou@coucou/coucou"); - - // Jidprep - const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); - const std::string correctjid = jidprep(badjid); - std::cout << correctjid << std::endl; -#ifdef LIBIDN_FOUND - assert(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - // Check that the cache does not break things when we prep the same string - // multiple times - assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - assert(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); - - const std::string badjid2("Zigougou@poez.io"); - const std::string correctjid2 = jidprep(badjid2); - std::cout << correctjid2 << std::endl; - assert(correctjid2 == "zigougou@poez.io"); - - const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); - const std::string fixed_crappy = jidprep(crappy); - std::cout << fixed_crappy << std::endl; - assert(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); -#else // Without libidn, jidprep always returns an empty string - assert(jidprep(badjid) == ""); -#endif - - /** - * IID parsing - */ - { - std::cout << color << "Testing IID parsing…" << reset << std::endl; - Iid iid1("foo!irc.example.org"); - std::cout << std::to_string(iid1) << std::endl; - assert(std::to_string(iid1) == "foo!irc.example.org"); - assert(iid1.get_local() == "foo"); - assert(iid1.get_server() == "irc.example.org"); - assert(!iid1.is_channel); - assert(iid1.is_user); - - Iid iid2("#test%irc.example.org"); - std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test%irc.example.org"); - assert(iid2.get_local() == "#test"); - assert(iid2.get_server() == "irc.example.org"); - assert(iid2.is_channel); - assert(!iid2.is_user); - - Iid iid3("%irc.example.org"); - std::cout << std::to_string(iid3) << std::endl; - assert(std::to_string(iid3) == "%irc.example.org"); - assert(iid3.get_local() == ""); - assert(iid3.get_server() == "irc.example.org"); - assert(iid3.is_channel); - assert(!iid3.is_user); - - Iid iid4("irc.example.org"); - std::cout << std::to_string(iid4) << std::endl; - assert(std::to_string(iid4) == "irc.example.org"); - assert(iid4.get_local() == ""); - assert(iid4.get_server() == "irc.example.org"); - assert(!iid4.is_channel); - assert(!iid4.is_user); - - Iid iid5("nick!"); - std::cout << std::to_string(iid5) << std::endl; - assert(std::to_string(iid5) == "nick!"); - assert(iid5.get_local() == "nick"); - assert(iid5.get_server() == ""); - assert(!iid5.is_channel); - assert(iid5.is_user); - - Iid iid6("##channel%"); - std::cout << std::to_string(iid6) << std::endl; - assert(std::to_string(iid6) == "##channel%"); - assert(iid6.get_local() == "##channel"); - assert(iid6.get_server() == ""); - assert(iid6.is_channel); - assert(!iid6.is_user); - } - - { - std::cout << color << "Testing IID parsing with a fixed server configured…" << reset << std::endl; - // Now do the same tests, but with a configured fixed_irc_server - Config::set("fixed_irc_server", "fixed.example.com", false); - - Iid iid1("foo!irc.example.org"); - std::cout << std::to_string(iid1) << std::endl; - assert(std::to_string(iid1) == "foo!"); - assert(iid1.get_local() == "foo"); - assert(iid1.get_server() == "fixed.example.com"); - assert(!iid1.is_channel); - assert(iid1.is_user); - - Iid iid2("#test%irc.example.org"); - std::cout << std::to_string(iid2) << std::endl; - assert(std::to_string(iid2) == "#test%irc.example.org"); - assert(iid2.get_local() == "#test%irc.example.org"); - assert(iid2.get_server() == "fixed.example.com"); - assert(iid2.is_channel); - assert(!iid2.is_user); - - // Note that it is impossible to adress the IRC server directly, or to - // use the virtual channel, in that mode - - // Iid iid3("%irc.example.org"); - // Iid iid4("irc.example.org"); - - Iid iid5("nick!"); - std::cout << std::to_string(iid5) << std::endl; - assert(std::to_string(iid5) == "nick!"); - assert(iid5.get_local() == "nick"); - assert(iid5.get_server() == "fixed.example.com"); - assert(!iid5.is_channel); - assert(iid5.is_user); - - Iid iid6("##channel%"); - std::cout << std::to_string(iid6) << std::endl; - assert(std::to_string(iid6) == "##channel%"); - assert(iid6.get_local() == "##channel%"); - assert(iid6.get_server() == "fixed.example.com"); - assert(iid6.is_channel); - assert(!iid6.is_user); - } -#ifdef USE_DATABASE - { - std::cout << color << "Testing the Database…" << reset << std::endl; - // Remove any potential existing db - unlink("./test.db"); - Config::set("db_name", "test.db"); - Database::set_verbose(true); - auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - o.update(); - auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - - // b does not yet exist in the db, the object is created but not yet - // inserted - assert(1 == Database::count()); - - b.update(); - assert(2 == Database::count()); - - assert(b.pass == ""); - assert(b.pass.value() == ""); - } -#endif - { - std::cout << color << "Testing the xdg_path function…" << reset << std::endl; - std::string res; - - ::unsetenv("XDG_CONFIG_HOME"); - ::unsetenv("HOME"); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "coucou.txt"); - - ::setenv("HOME", "/home/user", 1); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "/home/user/.config/biboumi/coucou.txt"); - - ::setenv("XDG_CONFIG_HOME", "/some_weird_dir", 1); - res = xdg_config_path("coucou.txt"); - std::cout << res << std::endl; - assert(res == "/some_weird_dir/biboumi/coucou.txt"); - - ::setenv("XDG_DATA_HOME", "/datadir", 1); - res = xdg_data_path("bonjour.txt"); - std::cout << res << std::endl; - assert(res == "/datadir/biboumi/bonjour.txt"); - } - - - { - std::cout << color << "Testing the DNS resolver…" << reset << std::endl; - - Resolver resolver; - - /** - * If we are using cares, we need to run a poller loop until each - * resolution is finished. Without cares we get the result before - * resolve() returns because it’s blocking. - */ -#ifdef CARES_FOUND - auto p = std::make_shared(); - - const auto loop = [&p]() - { - do - { - DNSHandler::instance.watch_dns_sockets(p); - } - while (p->poll(utils::no_timeout) != -1); - }; -#else - // We don’t need to do anything if we are not using cares. - const auto loop = [](){}; -#endif - - std::string hostname; - std::string port = "6667"; - - bool success = true; - - auto error_cb = [&success, &hostname, &port](const char* msg) - { - std::cout << "Failed to resolve " << hostname << ":" << port << ": " << msg << std::endl; - success = false; - }; - auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) - { - std::cout << "Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr) << std::endl; - success = true; - }; - - hostname = "example.com"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - - hostname = "this.should.fail.because.it.is..misformatted"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(!success); - - hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(!success); - - hostname = "localhost6"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - - hostname = "localhost"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - assert(success); - -#ifdef CARES_FOUND - DNSHandler::instance.destroy(); -#endif - } - - std::cout << success_color << "All test passed successfully!" << reset << std::endl; - return 0; -} diff --git a/tests/colors.cpp b/tests/colors.cpp new file mode 100644 index 0000000..bf52989 --- /dev/null +++ b/tests/colors.cpp @@ -0,0 +1,54 @@ +#include "catch.hpp" + +#include +#include + +#include + +TEST_CASE("IRC colors parsing") +{ + std::unique_ptr xhtml; + std::string cleaned_up; + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("bold"); + CHECK(xhtml); + CHECK(xhtml->to_string() == "bold"); + + std::tie(cleaned_up, xhtml) = + irc_format_to_xhtmlim("normalboldunder-and-boldbold normal" + "5red,5default-on-red10,2cyan-on-blue"); + CHECK(xhtml); + CHECK(xhtml->to_string() == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + CHECK(cleaned_up == "normalboldunder-and-boldbold normalreddefault-on-redcyan-on-blue"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("normal"); + CHECK_FALSE(xhtml); + CHECK(cleaned_up == "normal"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(""); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up.empty()); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(",a"); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up == "a"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim(","); + CHECK(xhtml); + CHECK(!xhtml->has_children()); + CHECK(cleaned_up.empty()); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("[\x1D13dolphin-emu/dolphin\x1D] 03foo commented on #283 (Add support for the guide button to XInput): 02http://example.com"); + CHECK(xhtml->to_string() == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + CHECK(cleaned_up == "[dolphin-emu/dolphin] foo commented on #283 (Add support for the guide button to XInput): http://example.com"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("0e46ab by 03Pierre Dindon [090|091|040] 02http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + CHECK(cleaned_up == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + CHECK(xhtml->to_string() == "0e46ab by Pierre Dindon [0|1|0] http://example.net/Ojrh4P media: avoid pop-in effect when loading thumbnails by specifying an explicit size"); + + std::tie(cleaned_up, xhtml) = irc_format_to_xhtmlim("test\ncoucou"); + CHECK(cleaned_up == "test\ncoucou"); + CHECK(xhtml->to_string() == "test
coucou"); +} diff --git a/tests/config.cpp b/tests/config.cpp new file mode 100644 index 0000000..1419f0b --- /dev/null +++ b/tests/config.cpp @@ -0,0 +1,25 @@ +#include "catch.hpp" + +#include + +TEST_CASE("Config basic") +{ + Config::filename = "test.cfg"; + Config::file_must_exist = false; + Config::set("coucou", "bonjour", true); + Config::close(); + + bool error = false; + try + { + Config::file_must_exist = true; + CHECK(Config::get("coucou", "") == "bonjour"); + CHECK(Config::get("does not exist", "default") == "default"); + Config::close(); + } + catch (const std::ios::failure& e) + { + error = true; + } + CHECK_FALSE(error); +} diff --git a/tests/database.cpp b/tests/database.cpp new file mode 100644 index 0000000..fd9e873 --- /dev/null +++ b/tests/database.cpp @@ -0,0 +1,30 @@ +#include "catch.hpp" + +#include + +#include +#include + +TEST_CASE("Database") +{ +#ifdef USE_DATABASE + // Remove any potential existing db + ::unlink("./test.db"); + Config::set("db_name", "test.db"); + Database::set_verbose(false); + auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + o.update(); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); + + // b does not yet exist in the db, the object is created but not yet + // inserted + CHECK(1 == Database::count()); + + b.update(); + CHECK(2 == Database::count()); + + CHECK(b.pass == ""); + CHECK(b.pass.value() == ""); +#endif +} diff --git a/tests/dns.cpp b/tests/dns.cpp new file mode 100644 index 0000000..e8cbc7f --- /dev/null +++ b/tests/dns.cpp @@ -0,0 +1,83 @@ +#include "catch.hpp" + +#include +#include +#include + +#include + +TEST_CASE("DNS resolver") +{ + Resolver resolver; + + /** + * If we are using cares, we need to run a poller loop until each + * resolution is finished. Without cares we get the result before + * resolve() returns because it’s blocking. + */ +#ifdef CARES_FOUND + auto p = std::make_shared(); + + const auto loop = [&p]() + { + do + { + DNSHandler::instance.watch_dns_sockets(p); + } + while (p->poll(utils::no_timeout) != -1); + }; +#else + // We don’t need to do anything if we are not using cares. + const auto loop = [](){}; +#endif + + std::string hostname; + std::string port = "6667"; + + bool success = true; + + auto error_cb = [&success, &hostname, &port](const char* msg) + { + INFO("Failed to resolve " << hostname << ":" << port << ": " << msg); + success = false; + }; + auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) + { + INFO("Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr)); + success = true; + }; + + hostname = "example.com"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + + hostname = "this.should.fail.because.it.is..misformatted"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(!success); + + hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(!success); + + hostname = "localhost6"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + + hostname = "localhost"; + resolver.resolve(hostname, port, + success_cb, error_cb); + loop(); + CHECK(success); + +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif +} diff --git a/tests/encoding.cpp b/tests/encoding.cpp new file mode 100644 index 0000000..389cf23 --- /dev/null +++ b/tests/encoding.cpp @@ -0,0 +1,56 @@ +#include "catch.hpp" + +#include + + +TEST_CASE("UTF-8 validation") +{ + const char* valid = "C̡͔͕̩͙̽ͫ̈́ͥ̿̆ͧ̚r̸̩̘͍̻͖̆͆͛͊̉̕͡o͇͈̳̤̱̊̈͢q̻͍̦̮͕ͥͬͬ̽ͭ͌̾ͅǔ͉͕͇͚̙͉̭͉̇̽ȇ͈̮̼͍͔ͣ͊͞͝ͅ ͫ̾ͪ̓ͥ̆̋̔҉̢̦̠͈͔̖̲̯̦ụ̶̯͐̃̋ͮ͆͝n̬̱̭͇̻̱̰̖̤̏͛̏̿̑͟ë́͐҉̸̥̪͕̹̻̙͉̰ ̹̼̱̦̥ͩ͑̈́͑͝ͅt͍̥͈̹̝ͣ̃̔̈̔ͧ̕͝ḙ̸̖̟̙͙ͪ͢ų̯̞̼̲͓̻̞͛̃̀́b̮̰̗̩̰̊̆͗̾̎̆ͯ͌͝.̗̙͎̦ͫ̈́ͥ͌̈̓ͬ"; + CHECK(utils::is_valid_utf8(valid)); + CHECK_FALSE(utils::is_valid_utf8("\xF0\x0F")); + CHECK_FALSE(utils::is_valid_utf8("\xFE\xFE\xFF\xFF")); + + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + INFO(in); + CHECK(utils::is_valid_utf8(in.data())); +} + +TEST_CASE("UTF-8 conversion") +{ + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + REQUIRE(utils::is_valid_utf8(in.data())); + + SECTION("Converting UTF-8 to UTF-8 should return the same string") + { + std::string res = utils::convert_to_utf8(in, "UTF-8"); + CHECK(utils::is_valid_utf8(res.c_str()) == true); + CHECK(res == in); + } + + SECTION("Testing latin-1 conversion") + { + std::string original_utf8("couc¥ou"); + std::string original_latin1("couc\xa5ou"); + + SECTION("Convert proper latin-1 to UTF-8") + { + std::string from_latin1 = utils::convert_to_utf8(original_latin1.c_str(), "ISO-8859-1"); + CHECK(from_latin1 == original_utf8); + } + SECTION("Check the behaviour when the decoding fails (here because we provide a wrong charset)") + { + std::string from_ascii = utils::convert_to_utf8(original_latin1, "US-ASCII"); + CHECK(from_ascii == "couc�ou"); + } + } +} + +TEST_CASE("Remove invalid XML chars") +{ + std::string without_ctrl_char("𤭢€¢$"); + std::string in = "Biboumi ╯°□°)╯︵ ┻━┻"; + INFO(in); + CHECK(utils::remove_invalid_xml_chars(without_ctrl_char) == without_ctrl_char); + CHECK(utils::remove_invalid_xml_chars(in) == in); + CHECK(utils::remove_invalid_xml_chars("\acouco\u0008u\uFFFEt\uFFFFe\r\n♥") == "coucoute\r\n♥"); +} diff --git a/tests/iid.cpp b/tests/iid.cpp new file mode 100644 index 0000000..a90c208 --- /dev/null +++ b/tests/iid.cpp @@ -0,0 +1,122 @@ +#include "catch.hpp" + +#include +#include + +#include + +TEST_CASE("Irc user parsing") +{ + const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; + + IrcUser user1("!nick!~some@host.bla", prefixes); + CHECK(user1.nick == "nick"); + CHECK(user1.host == "~some@host.bla"); + CHECK(user1.modes.size() == 1); + CHECK(user1.modes.find('a') != user1.modes.end()); + + IrcUser user2("coucou!~other@host.bla", prefixes); + CHECK(user2.nick == "coucou"); + CHECK(user2.host == "~other@host.bla"); + CHECK(user2.modes.empty()); + CHECK(user2.modes.find('a') == user2.modes.end()); +} + +/** + * Let Catch know how to display Iid objects + */ +namespace Catch +{ + template<> + struct StringMaker + { + static std::string convert(const Iid& value) + { + return std::to_string(value); + } + }; +} + +TEST_CASE("Iid creation") +{ + Iid iid1("foo!irc.example.org"); + CHECK(std::to_string(iid1) == "foo!irc.example.org"); + CHECK(iid1.get_local() == "foo"); + CHECK(iid1.get_server() == "irc.example.org"); + CHECK(!iid1.is_channel); + CHECK(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + CHECK(std::to_string(iid2) == "#test%irc.example.org"); + CHECK(iid2.get_local() == "#test"); + CHECK(iid2.get_server() == "irc.example.org"); + CHECK(iid2.is_channel); + CHECK(!iid2.is_user); + + Iid iid3("%irc.example.org"); + CHECK(std::to_string(iid3) == "%irc.example.org"); + CHECK(iid3.get_local() == ""); + CHECK(iid3.get_server() == "irc.example.org"); + CHECK(iid3.is_channel); + CHECK(!iid3.is_user); + + Iid iid4("irc.example.org"); + CHECK(std::to_string(iid4) == "irc.example.org"); + CHECK(iid4.get_local() == ""); + CHECK(iid4.get_server() == "irc.example.org"); + CHECK(!iid4.is_channel); + CHECK(!iid4.is_user); + + Iid iid5("nick!"); + CHECK(std::to_string(iid5) == "nick!"); + CHECK(iid5.get_local() == "nick"); + CHECK(iid5.get_server() == ""); + CHECK(!iid5.is_channel); + CHECK(iid5.is_user); + + Iid iid6("##channel%"); + CHECK(std::to_string(iid6) == "##channel%"); + CHECK(iid6.get_local() == "##channel"); + CHECK(iid6.get_server() == ""); + CHECK(iid6.is_channel); + CHECK(!iid6.is_user); +} + +TEST_CASE("Iid creation in fixed_server mode") +{ + Config::set("fixed_irc_server", "fixed.example.com", false); + + Iid iid1("foo!irc.example.org"); + CHECK(std::to_string(iid1) == "foo!"); + CHECK(iid1.get_local() == "foo"); + CHECK(iid1.get_server() == "fixed.example.com"); + CHECK(!iid1.is_channel); + CHECK(iid1.is_user); + + Iid iid2("#test%irc.example.org"); + CHECK(std::to_string(iid2) == "#test%irc.example.org"); + CHECK(iid2.get_local() == "#test%irc.example.org"); + CHECK(iid2.get_server() == "fixed.example.com"); + CHECK(iid2.is_channel); + CHECK(!iid2.is_user); + + // Note that it is impossible to adress the IRC server directly, or to + // use the virtual channel, in that mode + + // Iid iid3("%irc.example.org"); + // Iid iid4("irc.example.org"); + + Iid iid5("nick!"); + CHECK(std::to_string(iid5) == "nick!"); + CHECK(iid5.get_local() == "nick"); + CHECK(iid5.get_server() == "fixed.example.com"); + CHECK(!iid5.is_channel); + CHECK(iid5.is_user); + + Iid iid6("##channel%"); + CHECK(std::to_string(iid6) == "##channel%"); + CHECK(iid6.get_local() == "##channel%"); + CHECK(iid6.get_server() == "fixed.example.com"); + CHECK(iid6.is_channel); + CHECK(!iid6.is_user); +} diff --git a/tests/jid.cpp b/tests/jid.cpp new file mode 100644 index 0000000..9015afd --- /dev/null +++ b/tests/jid.cpp @@ -0,0 +1,39 @@ +#include "catch.hpp" + +#include +#include + +TEST_CASE("Jid") +{ + Jid jid1("♥@ツ.coucou/coucou@coucou/coucou"); + CHECK(jid1.local == "♥"); + CHECK(jid1.domain == "ツ.coucou"); + CHECK(jid1.resource == "coucou@coucou/coucou"); + + // Domain and resource + Jid jid2("ツ.coucou/coucou@coucou/coucou"); + CHECK(jid2.local == ""); + CHECK(jid2.domain == "ツ.coucou"); + CHECK(jid2.resource == "coucou@coucou/coucou"); + + // Jidprep + const std::string badjid("~zigougou™@EpiK-7D9D1FDE.poez.io/Boujour/coucou/slt™"); + const std::string correctjid = jidprep(badjid); +#ifdef LIBIDN_FOUND + CHECK(correctjid == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + // Check that the cache does not break things when we prep the same string + // multiple times + CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + CHECK(jidprep(badjid) == "~zigougoutm@epik-7d9d1fde.poez.io/Boujour/coucou/sltTM"); + + const std::string badjid2("Zigougou@poez.io"); + const std::string correctjid2 = jidprep(badjid2); + CHECK(correctjid2 == "zigougou@poez.io"); + + const std::string crappy("~Bisous@7ea8beb1:c5fd2849:da9a048e:ip"); + const std::string fixed_crappy = jidprep(crappy); + CHECK(fixed_crappy == "~bisous@7ea8beb1-c5fd2849-da9a048e-ip"); +#else // Without libidn, jidprep always returns an empty string + CHECK(jidprep(badjid) == ""); +#endif +} diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 0000000..0c7c351 --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/tests/timed_events.cpp b/tests/timed_events.cpp new file mode 100644 index 0000000..3844f3d --- /dev/null +++ b/tests/timed_events.cpp @@ -0,0 +1,62 @@ +#include "catch.hpp" + +#include + +/** + * Let Catch know how to display std::chrono::duration values + */ +namespace Catch +{ + template struct StringMaker> + { + static std::string convert(const std::chrono::duration& value) + { + return std::to_string(std::chrono::duration_cast(value).count()) + "ms"; + } + }; +} + +/** + * TODO, use a mock clock instead of relying on the real time with a sleep: + * it’s unreliable on heavy load. + */ +#include + +TEST_CASE("Test timed event expiration") +{ + SECTION("Check what happens when there is no events") + { + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); + CHECK(TimedEventsManager::instance().execute_expired_events() == 0); + } + + // Add a single event + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 50ms, [](){})); + + // The event should not yet be expired + CHECK(TimedEventsManager::instance().get_timeout() > 0ms); + CHECK(TimedEventsManager::instance().execute_expired_events() == 0); + + std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); + INFO("Sleeping for " << timoute.count() << "ms"); + std::this_thread::sleep_for(timoute); + + // Event is now expired + CHECK(TimedEventsManager::instance().execute_expired_events() == 1); + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); +} + +TEST_CASE("Test timed event cancellation") +{ + auto now = std::chrono::steady_clock::now(); + TimedEventsManager::instance().add_event(TimedEvent(now + 100ms, [](){ }, "un")); + TimedEventsManager::instance().add_event(TimedEvent(now + 200ms, [](){ }, "deux")); + TimedEventsManager::instance().add_event(TimedEvent(now + 300ms, [](){ }, "deux")); + + CHECK(TimedEventsManager::instance().get_timeout() > 0ms); + CHECK(TimedEventsManager::instance().size() == 3); + CHECK(TimedEventsManager::instance().cancel("un") == 1); + CHECK(TimedEventsManager::instance().size() == 2); + CHECK(TimedEventsManager::instance().cancel("deux") == 2); + CHECK(TimedEventsManager::instance().get_timeout() == utils::no_timeout); +} diff --git a/tests/utils.cpp b/tests/utils.cpp new file mode 100644 index 0000000..6e3c32a --- /dev/null +++ b/tests/utils.cpp @@ -0,0 +1,72 @@ +#include "catch.hpp" + +#include +#include +#include +#include +#include + +TEST_CASE("String split") +{ + std::vector splitted = utils::split("a::a", ':', false); + CHECK(splitted.size() == 2); + splitted = utils::split("a::a", ':', true); + CHECK(splitted.size() == 3); + CHECK(splitted[0] == "a"); + CHECK(splitted[1] == ""); + CHECK(splitted[2] == "a"); + splitted = utils::split("\na", '\n', true); + CHECK(splitted.size() == 2); + CHECK(splitted[0] == ""); + CHECK(splitted[1] == "a"); +} + +TEST_CASE("tolower") +{ + const std::string lowercase = utils::tolower("CoUcOu LeS CoPaiNs ♥"); + CHECK(lowercase == "coucou les copains ♥"); + + const std::string ltr = "coucou"; + CHECK(utils::revstr(ltr) == "uocuoc"); +} + +TEST_CASE("to_bool") +{ + CHECK(to_bool("true")); + CHECK(!to_bool("trou")); + CHECK(to_bool("1")); + CHECK(!to_bool("0")); + CHECK(!to_bool("-1")); + CHECK(!to_bool("false")); +} + +TEST_CASE("xdg_*_path") +{ + ::unsetenv("XDG_CONFIG_HOME"); + ::unsetenv("HOME"); + std::string res; + + SECTION("Without XDG_CONFIG_HOME nor HOME") + { + res = xdg_config_path("coucou.txt"); + CHECK(res == "coucou.txt"); + } + SECTION("With only HOME") + { + ::setenv("HOME", "/home/user", 1); + res = xdg_config_path("coucou.txt"); + CHECK(res == "/home/user/.config/biboumi/coucou.txt"); + } + SECTION("With only XDG_CONFIG_HOME") + { + ::setenv("XDG_CONFIG_HOME", "/some_weird_dir", 1); + res = xdg_config_path("coucou.txt"); + CHECK(res == "/some_weird_dir/biboumi/coucou.txt"); + } + SECTION("With XDG_DATA_HOME") + { + ::setenv("XDG_DATA_HOME", "/datadir", 1); + res = xdg_data_path("bonjour.txt"); + CHECK(res == "/datadir/biboumi/bonjour.txt"); + } +} diff --git a/tests/uuid.cpp b/tests/uuid.cpp new file mode 100644 index 0000000..12c6c32 --- /dev/null +++ b/tests/uuid.cpp @@ -0,0 +1,13 @@ +#include "catch.hpp" + +#include + +TEST_CASE("id generation") +{ + const std::string first_uuid = XmppComponent::next_id(); + const std::string second_uuid = XmppComponent::next_id(); + + CHECK(first_uuid.size() == 36); + CHECK(second_uuid.size() == 36); + CHECK(first_uuid != second_uuid); +} diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp new file mode 100644 index 0000000..46ecd35 --- /dev/null +++ b/tests/xmpp.cpp @@ -0,0 +1,47 @@ +#include "catch.hpp" + +#include + +TEST_CASE("Test basic XML parsing") +{ + XmppParser xml; + + const std::string doc = "innertail"; + + auto check_stanza = [](const Stanza& stanza) + { + CHECK(stanza.get_name() == "stanza"); + CHECK(stanza.get_tag("xmlns") == "stream_ns"); + CHECK(stanza.get_tag("b") == "c"); + CHECK(stanza.get_inner() == "inner"); + CHECK(stanza.get_tail() == ""); + CHECK(stanza.get_child("child1", "stream_ns") != nullptr); + CHECK(stanza.get_child("child2", "stream_ns") == nullptr); + CHECK(stanza.get_child("child2", "child2_ns") != nullptr); + CHECK(stanza.get_child("child2", "child2_ns")->get_tail() == "tail"); + }; + xml.add_stanza_callback([check_stanza](const Stanza& stanza) + { + check_stanza(stanza); + // Do the same checks on a copy of that stanza. + Stanza copy(stanza); + check_stanza(copy); + }); + xml.feed(doc.data(), doc.size(), true); + + const std::string doc2 = "coucou\r\n\a"; + xml.add_stanza_callback([](const Stanza& stanza) + { + CHECK(stanza.get_inner() == "coucou\r\n"); + }); + + xml.feed(doc2.data(), doc.size(), true); +} + +TEST_CASE("XML escape/unescape") +{ + const std::string unescaped = "'coucou'/&\"gaga\""; + CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); + CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); +} + -- cgit v1.2.3 From 6ae7e08c5211fc01155295df4f706dad1836cb80 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 28 Oct 2015 19:22:26 +0100 Subject: Add code coverage support make coverage runs the test_suite and generates a report --- CMakeLists.txt | 12 ++- cmake/Modules/CodeCoverage.cmake | 205 +++++++++++++++++++++++++++++++++++++++ scripts/build_and_run_tests.sh | 3 +- 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 cmake/Modules/CodeCoverage.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 960b5d1..b6411ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(${PROJECT_NAME}_VERSION_MINOR 0) set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") # Define a __FILENAME__ macro to get the filename of each file, instead of # the full path as in __FILE__ @@ -182,6 +183,15 @@ add_dependencies(test_suite catch) add_custom_target(check COMMAND "test_suite" DEPENDS test_suite) +# +## Code coverage +# +include(CodeCoverage) +SETUP_TARGET_FOR_COVERAGE(coverage + test_suite + coverage +) + # ## Install target # @@ -213,4 +223,4 @@ endif() add_custom_target(PrintBuildParameters ALL ${CMAKE_COMMAND} -E cmake_echo_color --cyan "Compiling ${PROJECT_NAME} with ${STR_WITH_BOTAN}, ${STR_WITH_CARES}") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/biboumi.h.cmake ${CMAKE_BINARY_DIR}/src/biboumi.h) \ No newline at end of file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/biboumi.h.cmake ${CMAKE_BINARY_DIR}/src/biboumi.h) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake new file mode 100644 index 0000000..2e2d4a0 --- /dev/null +++ b/cmake/Modules/CodeCoverage.cmake @@ -0,0 +1,205 @@ +# Copyright (c) 2012 - 2015, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# USAGE: + +# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: +# http://stackoverflow.com/a/22404544/80480 +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt: +# INCLUDE(CodeCoverage) +# +# 3. Set compiler flags to turn off optimization and enable coverage: +# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") +# +# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target +# which runs your test executable and produces a lcov code coverage report: +# Example: +# SETUP_TARGET_FOR_COVERAGE( +# my_coverage_target # Name for custom target. +# test_driver # Name of the test driver executable that runs the tests. +# # NOTE! This should always have a ZERO as exit code +# # otherwise the coverage generation will not complete. +# coverage # Name of output directory. +# ) +# +# 4. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# +# + +# Check prereqs +FIND_PROGRAM( GCOV_PATH gcov ) +FIND_PROGRAM( LCOV_PATH lcov ) +FIND_PROGRAM( GENHTML_PATH genhtml ) +FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) + +IF(NOT GCOV_PATH) + MESSAGE(FATAL_ERROR "gcov not found! Aborting...") +ENDIF() # NOT GCOV_PATH + +IF(NOT CMAKE_COMPILER_IS_GNUCXX) + # Clang version 3.0.0 and greater now supports gcov as well. + MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") + + IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + ENDIF() +ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX + +SET(CMAKE_CXX_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +SET(CMAKE_C_FLAGS_COVERAGE + "-g -O0 --coverage -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +MARK_AS_ADVANCED( + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) + MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) +ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" + + +# Param _targetname The name of new the custom make target +# Param _testrunner The name of the target which runs the tests. +# MUST return ZERO always, even on errors. +# If not, no coverage report will be created! +# Param _outputname lcov output is generated as _outputname.info +# HTML report is generated in _outputname/index.html +# Optional fifth parameter is passed as arguments to _testrunner +# Pass them in list form, e.g.: "-j;2" for -j 2 +FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) + + IF(NOT LCOV_PATH) + MESSAGE(FATAL_ERROR "lcov not found! Aborting...") + ENDIF() # NOT LCOV_PATH + + IF(NOT GENHTML_PATH) + MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") + ENDIF() # NOT GENHTML_PATH + + # Setup target + ADD_CUSTOM_TARGET(${_targetname} + + # Cleanup lcov + ${LCOV_PATH} --directory . --zerocounters + + # Create baseline coverage data file + COMMAND ${LCOV_PATH} -c -i -d . -o ${_outputname}.baseline.info -q + + # Run tests + COMMAND ${_testrunner} ${ARGV3} + + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info -q + # Combine the baseline and the test data + COMMAND ${LCOV_PATH} -a ${_outputname}.info -a ${_outputname}.baseline.info -o ${_outputname}.info -q + + # Remove information about source files that are not part of + # the test (system file, external libraries, etc) + COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'tests/*' '/usr/*' 'external/*' --output-file ${_outputname}.info.cleaned -q + + # Generate the report + COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned + + # Clean the temporary files we created + COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." + ) + +ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE + +# Param _targetname The name of new the custom make target +# Param _testrunner The name of the target which runs the tests +# Param _outputname cobertura output is generated as _outputname.xml +# Optional fourth parameter is passed as arguments to _testrunner +# Pass them in list form, e.g.: "-j;2" for -j 2 +FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) + + IF(NOT PYTHON_EXECUTABLE) + MESSAGE(FATAL_ERROR "Python not found! Aborting...") + ENDIF() # NOT PYTHON_EXECUTABLE + + IF(NOT GCOVR_PATH) + MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") + ENDIF() # NOT GCOVR_PATH + + ADD_CUSTOM_TARGET(${_targetname} + + # Run tests + ${_testrunner} ${ARGV3} + + # Running gcovr + COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." + ) + +ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA diff --git a/scripts/build_and_run_tests.sh b/scripts/build_and_run_tests.sh index 738ef52..43d32c1 100755 --- a/scripts/build_and_run_tests.sh +++ b/scripts/build_and_run_tests.sh @@ -2,6 +2,7 @@ set -e -x -cmake .. $@ +cmake .. -DCMAKE_BUILD_TYPE=Debug $@ make -j$(nproc) biboumi test_suite make -j$(nproc) check +make -j$(nproc) coverage -- cgit v1.2.3 From db5e02a0a6c1056038aaddec6b586032ff47b9b6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 02:45:58 +0100 Subject: Document the test and coverage things in the INSTALL file --- INSTALL | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/INSTALL b/INSTALL index 8306978..dc0548b 100644 --- a/INSTALL +++ b/INSTALL @@ -136,6 +136,24 @@ This command will configure the project to build a release, with TLS enabled # make install +============= + Testing +============= + +You can run the test suite with + +% make check + +This project uses the Catch unit test framework, it will be automatically +fetched with cmake, by cloning the github repository. + +You can also check the overall code coverage of this test suite by running + +% make coverage + +This requires gcov and lcov to be installed. + + ============= Run ============= -- cgit v1.2.3 From 3b7bbe147229ee06de8b024e42e86d435cb0f72e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 02:52:24 +0100 Subject: make coverage is only available with a debug build --- CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6411ab..d8633d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,11 +186,13 @@ add_custom_target(check COMMAND "test_suite" # ## Code coverage # -include(CodeCoverage) -SETUP_TARGET_FOR_COVERAGE(coverage - test_suite - coverage -) +if(CMAKE_BUILD_TYPE MATCHES DEBUG) + include(CodeCoverage) + SETUP_TARGET_FOR_COVERAGE(coverage + test_suite + coverage + ) +endif() # ## Install target -- cgit v1.2.3 From 9fe94215daecf21246e19ba59ef83755129a4a8b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 03:20:39 +0100 Subject: Make lcov gcov etc optional make coverage will display an error instead --- CMakeLists.txt | 2 +- cmake/Modules/CodeCoverage.cmake | 42 ++++++++++++++++++++++------------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8633d8..64e21e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,7 @@ add_custom_target(check COMMAND "test_suite" # ## Code coverage # -if(CMAKE_BUILD_TYPE MATCHES DEBUG) +if(CMAKE_BUILD_TYPE MATCHES Debug) include(CodeCoverage) SETUP_TARGET_FOR_COVERAGE(coverage test_suite diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index 2e2d4a0..5954846 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -73,18 +73,30 @@ FIND_PROGRAM( LCOV_PATH lcov ) FIND_PROGRAM( GENHTML_PATH genhtml ) FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) -IF(NOT GCOV_PATH) - MESSAGE(FATAL_ERROR "gcov not found! Aborting...") -ENDIF() # NOT GCOV_PATH +# Display an error when the target is called. If no error is found, this +# function will be overridden by the real one later in this file +FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) + ADD_CUSTOM_TARGET(${_targetname} + COMMAND echo "Coverage is not available: ${ERROR_MSG}" + ) +ENDFUNCTION() +IF(NOT GCOV_PATH) + set(ERROR_MSG "gcov not found") + return() +ENDIF() +IF(NOT LCOV_PATH) + set(ERROR_MSG "lcov not found") + return() +ENDIF() +IF(NOT GENHTML_PATH) + set(ERROR_MSG "genhtml not found") + return() +ENDIF() IF(NOT CMAKE_COMPILER_IS_GNUCXX) - # Clang version 3.0.0 and greater now supports gcov as well. - MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") - - IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") - ENDIF() -ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX + set(ERROR_MSG "Compiler is not gcc") + return() +ENDIF() SET(CMAKE_CXX_FLAGS_COVERAGE "-g -O0 --coverage -fprofile-arcs -ftest-coverage" @@ -123,19 +135,11 @@ ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" # Pass them in list form, e.g.: "-j;2" for -j 2 FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) - IF(NOT LCOV_PATH) - MESSAGE(FATAL_ERROR "lcov not found! Aborting...") - ENDIF() # NOT LCOV_PATH - - IF(NOT GENHTML_PATH) - MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") - ENDIF() # NOT GENHTML_PATH - # Setup target ADD_CUSTOM_TARGET(${_targetname} # Cleanup lcov - ${LCOV_PATH} --directory . --zerocounters + COMMAND ${LCOV_PATH} --directory . --zerocounters # Create baseline coverage data file COMMAND ${LCOV_PATH} -c -i -d . -o ${_outputname}.baseline.info -q -- cgit v1.2.3 From 580b721ba580d27be94b8977e8bbadf359feb2a3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 03:35:25 +0100 Subject: =?UTF-8?q?Remove=20a=20write=20to=20std::cout=20from=20Database?= =?UTF-8?q?=E2=80=99s=20constructor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/database.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/database/database.cpp b/src/database/database.cpp index 8d09788..fd18417 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -17,8 +17,6 @@ db::BibouDB& Database::get_db() { const std::string db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite")); - // log_info("Opening database: " << db_filename); - std::cout << "Opening database: " << db_filename << std::endl; Database::db = std::make_unique("sqlite3", "database="s + db_filename); } -- cgit v1.2.3 From f03d1a16ddca85ccfabc9cad01ed206d75301043 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 03:55:57 +0100 Subject: Only download Catch when the test_suite target is built --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 64e21e0..060fa11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,6 +175,7 @@ ExternalProject_Add(catch BUILD_COMMAND "" INSTALL_COMMAND "" ) +set_target_properties(catch PROPERTIES EXCLUDE_FROM_ALL TRUE) ExternalProject_Get_Property(catch SOURCE_DIR) target_include_directories(test_suite PUBLIC "${SOURCE_DIR}/include/" -- cgit v1.2.3 From cfa754edb8173d6c41fb03c774342df196bca74a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 29 Oct 2015 04:07:46 +0100 Subject: Add a .gitignore file --- .gitignore | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c8dd97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Gcov files +*.gcno +*.gcov +*.gcda + +# Build directories +build/ -- cgit v1.2.3 From b8b2d4f985b8f23bf156afcc32b30f8adc55827e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 30 Oct 2015 17:59:14 +0100 Subject: Remove the build+test script, just specify everything in the CI file --- .gitlab-ci.yml | 18 ++++++++++++------ scripts/build_and_run_tests.sh | 8 -------- 2 files changed, 12 insertions(+), 14 deletions(-) delete mode 100755 scripts/build_and_run_tests.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 49bb935..f653bcf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,24 +3,30 @@ before_script: botan_cares_systemd_idn: script: - - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - make -j$(nproc) biboumi coverage botan_cares_systemd: script: - - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 + - make -j$(nproc) biboumi coverage botan_cares_idn: script: - - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITHOUT_SYSTEMD=1 -DWITH_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITHOUT_SYSTEMD=1 -DWITH_LIBIDN=1 + - make -j$(nproc) biboumi coverage botan_systemd_idn: script: - - ../scripts/build_and_run_tests.sh -DWITH_BOTAN=1 -DWITHOUT_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITHOUT_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - make -j$(nproc) biboumi coverage cares_systemd_idn: script: - - ../scripts/build_and_run_tests.sh -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - make -j$(nproc) biboumi coverage cares_systemd: script: - - ../scripts/build_and_run_tests.sh -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 + - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 + - make -j$(nproc) biboumi coverage diff --git a/scripts/build_and_run_tests.sh b/scripts/build_and_run_tests.sh deleted file mode 100755 index 43d32c1..0000000 --- a/scripts/build_and_run_tests.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -e -x - -cmake .. -DCMAKE_BUILD_TYPE=Debug $@ -make -j$(nproc) biboumi test_suite -make -j$(nproc) check -make -j$(nproc) coverage -- cgit v1.2.3 From 73fd710cfd003022e4d28d98b4d3242e80d7dd52 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 01:48:34 +0100 Subject: =?UTF-8?q?Only=20run=20=E2=80=9Cmake=20coverage=E2=80=9D=20in=20o?= =?UTF-8?q?ne=20CI=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f653bcf..2567721 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,24 +9,24 @@ botan_cares_systemd_idn: botan_cares_systemd: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check botan_cares_idn: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITHOUT_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check botan_systemd_idn: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITHOUT_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check cares_systemd_idn: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check cares_systemd: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check -- cgit v1.2.3 From 66887c225b63cecea62d17bcfae40cddef38c9d1 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:35:46 +0100 Subject: Add a few tests --- tests/config.cpp | 29 +++++++++++++++++++++++++++++ tests/database.cpp | 2 ++ tests/logger.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.cpp | 18 ++++++++++++++++++ tests/xmpp.cpp | 3 ++- 5 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 tests/logger.cpp diff --git a/tests/config.cpp b/tests/config.cpp index 1419f0b..346dea1 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -23,3 +23,32 @@ TEST_CASE("Config basic") } CHECK_FALSE(error); } + +TEST_CASE("Config callbacks") +{ + bool switched = false; + Config::connect([&switched]() + { + switched = !switched; + }); + CHECK_FALSE(switched); + Config::set("un", "deux", true); + CHECK(switched); + Config::set("un", "trois", true); + CHECK_FALSE(switched); + + Config::set("un", "trois", false); + CHECK_FALSE(switched); +} + +TEST_CASE("Config get_int") +{ + auto res = Config::get_int("number", 0); + CHECK(res == 0); + Config::set("number", "88"); + res = Config::get_int("number", 0); + CHECK(res == 88); + Config::set("number", "pouet"); + res = Config::get_int("number", -1); + CHECK(res == 0); +} diff --git a/tests/database.cpp b/tests/database.cpp index fd9e873..c248568 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -26,5 +26,7 @@ TEST_CASE("Database") CHECK(b.pass == ""); CHECK(b.pass.value() == ""); + + Database::close(); #endif } diff --git a/tests/logger.cpp b/tests/logger.cpp new file mode 100644 index 0000000..d4fb055 --- /dev/null +++ b/tests/logger.cpp @@ -0,0 +1,50 @@ +#include "catch.hpp" + +#include +#include + +#include "io_tester.hpp" +#include + +using namespace std::string_literals; + +TEST_CASE("Basic logging") +{ + Logger::instance().reset(); + GIVEN("A logger with log_level 0") + { + Config::set("log_level", "0"); + WHEN("we log some debug text") + { + IoTester out(std::cout); + log_debug("debug"); + THEN("debug logs are written") + CHECK(out.str() == "<7>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); + } + WHEN("we log some errors") + { + IoTester out(std::cout); + log_error("error"); + THEN("error logs are written") + CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + } + } + GIVEN("A logger with log_level 3") + { + Config::set("log_level", "3"); + WHEN("we log some debug text") + { + IoTester out(std::cout); + log_debug("debug"); + THEN("nothing is written") + CHECK(out.str().empty()); + } + WHEN("we log some errors") + { + IoTester out(std::cout); + log_error("error"); + THEN("error logs are still written") + CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + } + } +} diff --git a/tests/utils.cpp b/tests/utils.cpp index 6e3c32a..8691910 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include TEST_CASE("String split") { @@ -70,3 +71,20 @@ TEST_CASE("xdg_*_path") CHECK(res == "/datadir/biboumi/bonjour.txt"); } } + +TEST_CASE("empty if fixed irc server") +{ + GIVEN("A config with fixed_irc_server") + { + Config::set("fixed_irc_server", "irc.localhost"); + THEN("our string is made empty") + CHECK(utils::empty_if_fixed_server("coucou coucou") == ""); + } + GIVEN("A config with NO fixed_irc_server") + { + Config::set("fixed_irc_server", ""); + THEN("our string is returned untouched") + CHECK(utils::empty_if_fixed_server("coucou coucou") == "coucou coucou"); + } + +} diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index 46ecd35..b6b50ed 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -26,6 +26,8 @@ TEST_CASE("Test basic XML parsing") // Do the same checks on a copy of that stanza. Stanza copy(stanza); check_stanza(copy); + // And do the same checks on moved-constructed stanza + Stanza moved(std::move(copy)); }); xml.feed(doc.data(), doc.size(), true); @@ -44,4 +46,3 @@ TEST_CASE("XML escape/unescape") CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); } - -- cgit v1.2.3 From 4dd01a29caff8a1712478cb20e9137453367da07 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:35:59 +0100 Subject: Run the database tests with an in-memory sqlite db --- tests/database.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/database.cpp b/tests/database.cpp index c248568..c0f4e9f 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -2,15 +2,12 @@ #include -#include #include TEST_CASE("Database") { #ifdef USE_DATABASE - // Remove any potential existing db - ::unlink("./test.db"); - Config::set("db_name", "test.db"); + Config::set("db_name", ":memory:"); Database::set_verbose(false); auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); o.update(); -- cgit v1.2.3 From 6c42286109fceddd70eacc467811b102b41a831e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:36:18 +0100 Subject: Remove some useless includes --- src/bridge/bridge.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index ec143f0..18a8d92 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1,9 +1,6 @@ #include -#include #include #include -#include -#include #include #include #include @@ -12,9 +9,6 @@ #include #include #include -#include -#include -#include using namespace std::string_literals; -- cgit v1.2.3 From 2c932cf0f7ca9bc82430c1da5097653f6a4d0bf4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 05:36:33 +0100 Subject: Fix the double sending of the USER command --- src/irc/irc_client.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 8f92f9b..f6b8f9c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -131,9 +131,11 @@ void IrcClient::on_connected() this->realname = options.realname.value(); this->send_user_command(username, realname); } -#endif + else + this->send_user_command(this->username, this->realname); +#else this->send_user_command(this->username, this->realname); - +#endif this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); this->send_pending_data(); } -- cgit v1.2.3 From 34fc1d4010d23be947c00fc956f2bdded2374cee Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 06:17:35 +0100 Subject: Implement a basic webirc support See https://kiwiirc.com/docs/webirc fix #3135 --- doc/biboumi.1.md | 7 +++++++ src/bridge/bridge.cpp | 8 +++++--- src/irc/irc_client.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/irc/irc_client.hpp | 17 ++++++++++++++++- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 763be56..1631ebd 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -96,6 +96,13 @@ The configuration file uses a simple format of the form username of each user will be set to the nick they used to connect to the IRC server. +`webirc_password` + + Configure a password to be communicated to the IRC server, as part of the + WEBIRC message (see https://kiwiirc.com/docs/webirc). If this option is + set, an additional DNS resolution of the hostname of each XMPP server will + be made when connecting to an IRC server. + `log_file` A filename into which logs are written. If none is provided, the logs are diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 18a8d92..5badb18 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -56,7 +56,8 @@ void Bridge::clean() while (it != this->irc_clients.end()) { IrcClient* client = it->second.get(); - if (!client->is_connected() && !client->is_connecting()) + if (!client->is_connected() && !client->is_connecting() && + !client->get_resolver().is_resolving()) it = this->irc_clients.erase(it); else ++it; @@ -94,16 +95,17 @@ IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::strin { auto username = nickname; auto realname = nickname; + Jid jid(this->user_jid); if (Config::get("realname_from_jid", "false") == "true") { - Jid jid(this->user_jid); username = jid.local; realname = this->get_bare_jid(); } this->irc_clients.emplace(hostname, std::make_shared(this->poller, hostname, nickname, username, - realname, this)); + realname, jid.domain, + this)); std::shared_ptr irc = this->irc_clients.at(hostname); return irc.get(); } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index f6b8f9c..3161b85 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -26,9 +26,11 @@ using namespace std::chrono_literals; IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& nickname, const std::string& username, - const std::string& realname, Bridge* bridge): + const std::string& realname, const std::string& user_hostname, + Bridge* bridge): TCPSocketHandler(poller), hostname(hostname), + user_hostname(user_hostname), username(username), realname(realname), current_nick(nickname), @@ -113,6 +115,39 @@ void IrcClient::on_connection_failed(const std::string& reason) void IrcClient::on_connected() { + const auto webirc_password = Config::get("webirc_password", ""); + static std::string resolved_ip; + + if (!webirc_password.empty()) + { + if (!resolved_ip.empty()) + this->send_webirc_command(webirc_password, resolved_ip); + else + { // Start resolving the hostname of the user, and call + // on_connected again when it’s done + this->dns_resolver.resolve(this->user_hostname, "5222", + [this](const struct addrinfo* addr) + { + resolved_ip = addr_to_string(addr); + // Only continue the process if we + // didn’t get connected while we were + // resolving + if (this->is_connected()) + this->on_connected(); + }, + [this](const char* error_msg) + { + if (this->is_connected()) + { + this->on_connection_close("Could not resolve hostname "s + this->user_hostname + + ": " + error_msg); + this->send_quit_command(""); + } + }); + return; + } + } + #ifdef USE_DATABASE auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), this->get_hostname()); @@ -253,7 +288,7 @@ void IrcClient::send_raw(const std::string& txt) void IrcClient::send_user_command(const std::string& username, const std::string& realname) { - this->send_message(IrcMessage("USER", {username, "ignored", "ignored", realname})); + this->send_message(IrcMessage("USER", {username, this->user_hostname, "ignored", realname})); } void IrcClient::send_nick_command(const std::string& nick) @@ -266,6 +301,11 @@ void IrcClient::send_pass_command(const std::string& password) this->send_message(IrcMessage("PASS", {password})); } +void IrcClient::send_webirc_command(const std::string& password, const std::string& user_ip) +{ + this->send_message(IrcMessage("WEBIRC", {password, "biboumi", this->user_hostname, user_ip})); +} + void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason) { this->send_message(IrcMessage("KICK", {chan_name, target, reason})); diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7a04164..885ec84 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -27,7 +28,8 @@ class IrcClient: public TCPSocketHandler public: explicit IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& nickname, const std::string& username, - const std::string& realname, Bridge* bridge); + const std::string& realname, const std::string& user_hostname, + Bridge* bridge); ~IrcClient(); /** * Connect to the IRC server @@ -88,6 +90,7 @@ public: */ void send_nick_command(const std::string& username); void send_pass_command(const std::string& password); + void send_webirc_command(const std::string& password, const std::string& user_ip); /** * Send the JOIN irc command. */ @@ -250,11 +253,18 @@ public: std::string get_nick() const { return this->current_nick; } bool is_welcomed() const { return this->welcomed; } + const Resolver& get_resolver() const; + private: /** * The hostname of the server we are connected to. */ const std::string hostname; + /** + * The hostname of the user. This is used in the USER and the WEBIRC + * commands, but only the one in WEBIRC will be used by the IRC server. + */ + const std::string user_hostname; /** * The username used in the USER irc command */ @@ -330,6 +340,11 @@ private: * A set of (lowercase) nicknames to which we sent a private message. */ std::set nicks_to_treat_as_private; + /** + * DNS resolver, used to resolve the hostname of the user if we are using + * the WebIRC protocole. + */ + Resolver dns_resolver; IrcClient(const IrcClient&) = delete; IrcClient(IrcClient&&) = delete; -- cgit v1.2.3 From ccd185d3f59b545e018bf3c50280e401a3715ab4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 06:18:29 +0100 Subject: Remove a useless assignment --- louloulibs/network/resolver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/louloulibs/network/resolver.cpp b/louloulibs/network/resolver.cpp index cb6c445..9d6de23 100644 --- a/louloulibs/network/resolver.cpp +++ b/louloulibs/network/resolver.cpp @@ -155,7 +155,6 @@ void Resolver::fill_ares_addrinfo6(const struct hostent* hostent) addr->sin6_scope_id = 0; current->ai_addr = reinterpret_cast(addr); - current->ai_next = nullptr; current->ai_canonname = nullptr; current->ai_next = prev; -- cgit v1.2.3 From 6fb4cf5e4db2babad567524df6be3e2798a0fb63 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 06:23:51 +0100 Subject: Do not forget to implement a method --- src/irc/irc_client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 885ec84..cdae0aa 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -253,7 +253,7 @@ public: std::string get_nick() const { return this->current_nick; } bool is_welcomed() const { return this->welcomed; } - const Resolver& get_resolver() const; + const Resolver& get_resolver() const { return this->dns_resolver; } private: /** -- cgit v1.2.3 From 6430479a3a6f15e221f0b9f3e822b44ca37af0f8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 06:25:47 +0100 Subject: Add a IoTester class --- tests/io_tester.cpp | 30 ++++++++++++++++++++++++++++++ tests/io_tester.hpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/io_tester.cpp create mode 100644 tests/io_tester.hpp diff --git a/tests/io_tester.cpp b/tests/io_tester.cpp new file mode 100644 index 0000000..19c97c9 --- /dev/null +++ b/tests/io_tester.cpp @@ -0,0 +1,30 @@ +#include "io_tester.hpp" +#include "catch.hpp" +#include + +/** + * Directly test this class here + */ +TEST_CASE() +{ + { + IoTester out(std::cout); + std::cout << "test"; + CHECK(out.str() == "test"); + } + { + IoTester out(std::cout); + CHECK(out.str().empty()); + } +} + +TEST_CASE() +{ + { + IoTester is(std::cin); + is.set_string("coucou"); + std::string res; + std::cin >> res; + CHECK(res == "coucou"); + } +} diff --git a/tests/io_tester.hpp b/tests/io_tester.hpp new file mode 100644 index 0000000..8afa6f6 --- /dev/null +++ b/tests/io_tester.hpp @@ -0,0 +1,47 @@ +#ifndef BIBOUMI_IO_TESTER_HPP +#define BIBOUMI_IO_TESTER_HPP + +#include +#include + +/** + * Redirects a stream into a streambuf until the object is destroyed. + */ +template +class IoTester +{ +public: + IoTester(StreamType& ios): + stream{}, + ios(ios), + old_buf(ios.rdbuf()) + { + // Redirect the given os into our stringstream’s buf + this->ios.rdbuf(this->stream.rdbuf()); + } + ~IoTester() + { + this->ios.rdbuf(this->old_buf); + } + IoTester& operator=(const IoTester&) = delete; + IoTester& operator=(IoTester&&) = delete; + IoTester(const IoTester&) = delete; + IoTester(IoTester&&) = delete; + + std::string str() const + { + return this->stream.str(); + } + + void set_string(const std::string& s) + { + this->stream.str(s); + } + +private: + std::stringstream stream; + StreamType& ios; + std::streambuf* const old_buf; +}; + +#endif //BIBOUMI_IO_TESTER_HPP -- cgit v1.2.3 From 7e07a17420117758ca319b5beab6440ff1d634f7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 31 Oct 2015 15:41:05 +0100 Subject: Fix the logger test when built without systemd --- tests/logger.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/logger.cpp b/tests/logger.cpp index d4fb055..2a99374 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -10,6 +10,13 @@ using namespace std::string_literals; TEST_CASE("Basic logging") { +#ifdef SYSTEMD_FOUND + const std::string debug_header = "<7>"; + const std::string error_header = "<3>"; +#else + const std::string debug_header = "[DEBUG]: "; + const std::string error_header = "[ERROR]: "; +#endif Logger::instance().reset(); GIVEN("A logger with log_level 0") { @@ -19,14 +26,14 @@ TEST_CASE("Basic logging") IoTester out(std::cout); log_debug("debug"); THEN("debug logs are written") - CHECK(out.str() == "<7>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); + CHECK(out.str() == debug_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); } WHEN("we log some errors") { IoTester out(std::cout); log_error("error"); THEN("error logs are written") - CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); } } GIVEN("A logger with log_level 3") @@ -44,7 +51,7 @@ TEST_CASE("Basic logging") IoTester out(std::cout); log_error("error"); THEN("error logs are still written") - CHECK(out.str() == "<3>tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); } } } -- cgit v1.2.3 From f928f7627247ceaafcf3538066ac17609b652aac Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Nov 2015 03:26:13 +0100 Subject: Verify the remote TLS certificates using the system-wide trusted CAs --- louloulibs/network/credentials_manager.cpp | 33 ++++++++++++++++++++++++++++++ louloulibs/network/credentials_manager.hpp | 22 ++++++++++++++++++++ louloulibs/network/tcp_socket_handler.cpp | 12 ++--------- louloulibs/network/tcp_socket_handler.hpp | 24 ++++++---------------- 4 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 louloulibs/network/credentials_manager.cpp create mode 100644 louloulibs/network/credentials_manager.hpp diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp new file mode 100644 index 0000000..77198a4 --- /dev/null +++ b/louloulibs/network/credentials_manager.cpp @@ -0,0 +1,33 @@ +#include +#include + +Basic_Credentials_Manager::Basic_Credentials_Manager(): + Botan::Credentials_Manager() +{ + this->load_certs(); +} +void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector& certs) +{ + log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + Botan::Credentials_Manager::verify_certificate_chain(type, "louiz.org", certs); + log_debug("Certificate is valid"); +} +void Basic_Credentials_Manager::load_certs() +{ + const std::vector paths = {"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"}; + for (const auto& path: paths) + { + Botan::DataSource_Stream bundle(path); + while (!bundle.end_of_data() && bundle.check_available(27)) + { + const Botan::X509_Certificate cert(bundle); + this->certificate_store.add_certificate(cert); + } + } +} +std::vector Basic_Credentials_Manager::trusted_certificate_authorities(const std::string&, const std::string&) +{ + return {&this->certificate_store}; +} diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp new file mode 100644 index 0000000..ea89eca --- /dev/null +++ b/louloulibs/network/credentials_manager.hpp @@ -0,0 +1,22 @@ +#ifndef BIBOUMI_CREDENTIALS_MANAGER_HPP +#define BIBOUMI_CREDENTIALS_MANAGER_HPP + +#include +#include + +class Basic_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + Basic_Credentials_Manager(); + void verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector&) override final; + std::vector trusted_certificate_authorities(const std::string& type, + const std::string& context) override final; + +private: + void load_certs(); + Botan::Certificate_Store_In_Memory certificate_store; +}; + +#endif //BIBOUMI_CREDENTIALS_MANAGER_HPP diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index f2a2466..81a36ef 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -19,7 +19,7 @@ # include Botan::AutoSeeded_RNG TCPSocketHandler::rng; -Permissive_Credentials_Manager TCPSocketHandler::credential_manager; +Basic_Credentials_Manager TCPSocketHandler::credential_manager; Botan::TLS::Policy TCPSocketHandler::policy; Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); @@ -451,15 +451,7 @@ bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) void TCPSocketHandler::on_tls_activated() { - this->send_data(""); -} - -void Permissive_Credentials_Manager::verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector&) -{ // TODO: Offer the admin to disallow connection on untrusted - // certificates - log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + this->send_data({}); } #endif // BOTAN_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 997d575..d173c1f 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -1,9 +1,13 @@ #ifndef SOCKET_HANDLER_INCLUDED # define SOCKET_HANDLER_INCLUDED +#include "louloulibs.h" + #include #include +#include + #include #include #include @@ -13,23 +17,6 @@ #include #include -#include "louloulibs.h" - -#ifdef BOTAN_FOUND -# include -# include - -/** - * A very simple credential manager that accepts any certificate. - */ -class Permissive_Credentials_Manager: public Botan::Credentials_Manager -{ -public: - void verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector&); -}; -#endif // BOTAN_FOUND /** * An interface, with a series of callbacks that should be implemented in @@ -243,7 +230,7 @@ private: * Botan stuff to manipulate a TLS session. */ static Botan::AutoSeeded_RNG rng; - static Permissive_Credentials_Manager credential_manager; + static Basic_Credentials_Manager credential_manager; static Botan::TLS::Policy policy; static Botan::TLS::Session_Manager_In_Memory session_manager; /** @@ -267,3 +254,4 @@ private: }; #endif // SOCKET_HANDLER_INCLUDED + -- cgit v1.2.3 From 0a4041f7e9d30fae9b2bfcfaaf1dc3efb368f86f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Nov 2015 17:14:21 +0100 Subject: Fix the initial IRC nickname (was using realname, by mistake) --- src/irc/irc_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 3161b85..7caf443 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -155,7 +155,7 @@ void IrcClient::on_connected() this->send_pass_command(options.pass.value()); #endif - this->send_nick_command(this->username); + this->send_nick_command(this->current_nick); #ifdef USE_DATABASE if (Config::get("realname_customization", "true") == "true") -- cgit v1.2.3 From 4a51850173e86f3682e9220704c9b17c09091222 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Nov 2015 17:15:32 +0100 Subject: Verify TLS certificate with the given hostname, instead of a hardcoded one --- louloulibs/network/credentials_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index 77198a4..8608f90 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -11,7 +11,7 @@ void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type const std::vector& certs) { log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); - Botan::Credentials_Manager::verify_certificate_chain(type, "louiz.org", certs); + Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); log_debug("Certificate is valid"); } void Basic_Credentials_Manager::load_certs() -- cgit v1.2.3 From 06db9b366a83121e0c914e527a367f90ec71940a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 2 Nov 2015 17:31:35 +0100 Subject: Fix the build without botan, caused by credentials_manager --- louloulibs/network/credentials_manager.cpp | 6 ++++++ louloulibs/network/credentials_manager.hpp | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index 8608f90..7c13319 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -1,5 +1,9 @@ +#include "louloulibs.h" + +#ifdef BOTAN_FOUND #include #include +#include Basic_Credentials_Manager::Basic_Credentials_Manager(): Botan::Credentials_Manager() @@ -31,3 +35,5 @@ std::vector Basic_Credentials_Manager::trusted_certif { return {&this->certificate_store}; } + +#endif diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index ea89eca..8641f1d 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -1,6 +1,10 @@ #ifndef BIBOUMI_CREDENTIALS_MANAGER_HPP #define BIBOUMI_CREDENTIALS_MANAGER_HPP +#include "louloulibs.h" + +#ifdef BOTAN_FOUND + #include #include @@ -19,4 +23,5 @@ private: Botan::Certificate_Store_In_Memory certificate_store; }; +#endif //BOTAN_FOUND #endif //BIBOUMI_CREDENTIALS_MANAGER_HPP -- cgit v1.2.3 From e8386bd14e9783f0bef39bdf577545522e33e719 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 3 Nov 2015 16:56:38 +0100 Subject: Provide an adhoc option to let user pass the cert verif for some IRC servers --- louloulibs/network/credentials_manager.cpp | 35 +++++++++++++++++++++++++----- louloulibs/network/credentials_manager.hpp | 11 +++++++--- louloulibs/network/tcp_socket_handler.cpp | 6 +++-- louloulibs/network/tcp_socket_handler.hpp | 12 +++++++++- src/irc/irc_client.cpp | 11 ++++++++++ src/irc/irc_client.hpp | 3 +++ src/xmpp/biboumi_adhoc_commands.cpp | 20 +++++++++++++++++ 7 files changed, 87 insertions(+), 11 deletions(-) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index 7c13319..b9a9af8 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -1,25 +1,48 @@ #include "louloulibs.h" #ifdef BOTAN_FOUND +#include #include #include #include -Basic_Credentials_Manager::Basic_Credentials_Manager(): - Botan::Credentials_Manager() +#ifdef USE_DATABASE +# include +#endif + +Botan::Certificate_Store_In_Memory Basic_Credentials_Manager::certificate_store; +bool Basic_Credentials_Manager::certs_loaded = false; + +Basic_Credentials_Manager::Basic_Credentials_Manager(const TCPSocketHandler* const socket_handler): + Botan::Credentials_Manager(), + socket_handler(socket_handler) { this->load_certs(); } + void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector& certs) { log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); - Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); - log_debug("Certificate is valid"); + try + { + Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); + log_debug("Certificate is valid"); + } + catch (const std::exception& tls_exception) + { + log_warning("TLS certificate check failed: " << tls_exception.what()); + if (this->socket_handler->abort_on_invalid_cert()) + throw; + } } + void Basic_Credentials_Manager::load_certs() { + // Only load the certificates the first time + if (Basic_Credentials_Manager::certs_loaded) + return; const std::vector paths = {"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"}; for (const auto& path: paths) { @@ -27,10 +50,12 @@ void Basic_Credentials_Manager::load_certs() while (!bundle.end_of_data() && bundle.check_available(27)) { const Botan::X509_Certificate cert(bundle); - this->certificate_store.add_certificate(cert); + Basic_Credentials_Manager::certificate_store.add_certificate(cert); } } + Basic_Credentials_Manager::certs_loaded = true; } + std::vector Basic_Credentials_Manager::trusted_certificate_authorities(const std::string&, const std::string&) { return {&this->certificate_store}; diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index 8641f1d..e292321 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -8,10 +8,12 @@ #include #include +class TCPSocketHandler; + class Basic_Credentials_Manager: public Botan::Credentials_Manager { public: - Basic_Credentials_Manager(); + Basic_Credentials_Manager(const TCPSocketHandler* const socket_handler); void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) override final; @@ -19,8 +21,11 @@ public: const std::string& context) override final; private: - void load_certs(); - Botan::Certificate_Store_In_Memory certificate_store; + const TCPSocketHandler* const socket_handler; + + static void load_certs(); + static Botan::Certificate_Store_In_Memory certificate_store; + static bool certs_loaded; }; #endif //BOTAN_FOUND diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 81a36ef..0ed74a2 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -19,7 +19,6 @@ # include Botan::AutoSeeded_RNG TCPSocketHandler::rng; -Basic_Credentials_Manager TCPSocketHandler::credential_manager; Botan::TLS::Policy TCPSocketHandler::policy; Botan::TLS::Session_Manager_In_Memory TCPSocketHandler::session_manager(TCPSocketHandler::rng); @@ -40,6 +39,9 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): connected(false), connecting(false), hostname_resolution_failed(false) +#ifdef BOTAN_FOUND + ,credential_manager(this) +#endif {} void TCPSocketHandler::init_socket(const struct addrinfo* rp) @@ -369,7 +371,7 @@ void TCPSocketHandler::start_tls() std::bind(&TCPSocketHandler::tls_data_cb, this, ph::_1, ph::_2), std::bind(&TCPSocketHandler::tls_alert_cb, this, ph::_1, ph::_2, ph::_3), std::bind(&TCPSocketHandler::tls_handshake_cb, this, ph::_1), - session_manager, credential_manager, policy, + session_manager, this->credential_manager, policy, rng, server_info, Botan::TLS::Protocol_Version::latest_tls_version()); } diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index d173c1f..213e286 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -90,6 +90,16 @@ public: * The size argument is the size of the last chunk of data that was added to the buffer. */ virtual void parse_in_buffer(const size_t size) = 0; +#ifdef BOTAN_FOUND + /** + * Tell whether the credential manager should cancel the connection when the + * certificate is invalid. + */ + virtual bool abort_on_invalid_cert() const + { + return true; + } +#endif bool is_connected() const override final; bool is_connecting() const; @@ -230,9 +240,9 @@ private: * Botan stuff to manipulate a TLS session. */ static Botan::AutoSeeded_RNG rng; - static Basic_Credentials_Manager credential_manager; static Botan::TLS::Policy policy; static Botan::TLS::Session_Manager_In_Memory session_manager; + Basic_Credentials_Manager credential_manager; /** * We use a unique_ptr because we may not want to create the object at * all. The Botan::TLS::Client object generates a handshake message and diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 7caf443..93ea2ae 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -962,3 +962,14 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message) this->dummy_channel.remove_all_users(); this->bridge->send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true); } + +#ifdef BOTAN_FOUND +bool IrcClient::abort_on_invalid_cert() const +{ +#ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), this->hostname); + return options.verifyCert.value(); +#endif + return true; +} +#endif diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index cdae0aa..733fc92 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -52,6 +52,9 @@ public: * complete messages from it. */ void parse_in_buffer(const size_t) override final; +#ifdef BOTAN_FOUND + virtual bool abort_on_invalid_cert() const override final; +#endif /** * Return the channel with this name, create it if it does not yet exist */ diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index fa3a35c..ff0c8d4 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -162,6 +162,19 @@ void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& com } tls_ports.add_child(required); x.add_child(std::move(tls_ports)); + + XmlNode verify_cert("field"); + verify_cert["var"] = "verify_cert"; + verify_cert["type"] = "boolean"; + verify_cert["label"] = "Verify certificate"; + verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid"; + XmlNode verify_cert_value("value"); + if (options.verifyCert.value()) + verify_cert_value.set_inner("true"); + else + verify_cert_value.set_inner("false"); + verify_cert.add_child(std::move(verify_cert_value)); + x.add_child(std::move(verify_cert)); #endif XmlNode pass("field"); @@ -252,6 +265,13 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com ports += val->get_inner() + ";"; options.tlsPorts = ports; } + + else if (field->get_tag("var") == "verify_cert" && value + && !value->get_inner().empty()) + { + auto val = to_bool(value->get_inner()); + options.verifyCert = val; + } #endif // BOTAN_FOUND else if (field->get_tag("var") == "pass" && -- cgit v1.2.3 From 5ce9d3f1429228746fcee724a44860f16ad166f5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Nov 2015 02:16:21 +0100 Subject: Make the CA file configurable --- doc/biboumi.1.md | 6 +++++ louloulibs/network/credentials_manager.cpp | 39 ++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 1631ebd..8a234fc 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -114,6 +114,12 @@ The configuration file uses a simple format of the form from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The default is 0, but a more practical value for production use is 1. +`ca_file` + + Specifies which file should be use as the list of trusted CA when + negociating a TLS session. By default this value is unset and biboumi + tries a list of well-known paths. + The configuration can be re-read at runtime (you can for example change the log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 (see kill(1)) to the process. diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index b9a9af8..57100ee 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -5,11 +5,22 @@ #include #include #include +#include #ifdef USE_DATABASE # include #endif +/** + * TODO find a standard way to find that out. + */ +static const std::vector default_cert_files = { + "/etc/ssl/certs/ca-bundle.crt", + "/etc/pki/tls/certs/ca-bundle.crt", + "/etc/ssl/certs/ca-certificates.crt", + "/etc/ca-certificates/extracted/tls-ca-bundle.pem" +}; + Botan::Certificate_Store_In_Memory Basic_Credentials_Manager::certificate_store; bool Basic_Credentials_Manager::certs_loaded = false; @@ -43,16 +54,34 @@ void Basic_Credentials_Manager::load_certs() // Only load the certificates the first time if (Basic_Credentials_Manager::certs_loaded) return; - const std::vector paths = {"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"}; + const std::string conf_path = Config::get("ca_file", ""); + std::vector paths; + if (conf_path.empty()) + paths = default_cert_files; + else + paths.push_back(conf_path); for (const auto& path: paths) { - Botan::DataSource_Stream bundle(path); - while (!bundle.end_of_data() && bundle.check_available(27)) + try + { + Botan::DataSource_Stream bundle(path); + log_debug("Using ca bundle: " << path); + while (!bundle.end_of_data() && bundle.check_available(27)) + { + const Botan::X509_Certificate cert(bundle); + Basic_Credentials_Manager::certificate_store.add_certificate(cert); + } + // Only use the first file that can successfully be read. + goto success; + } + catch (Botan::Stream_IO_Error& e) { - const Botan::X509_Certificate cert(bundle); - Basic_Credentials_Manager::certificate_store.add_certificate(cert); + 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: Basic_Credentials_Manager::certs_loaded = true; } -- cgit v1.2.3 From a44372e1b108719504d1e60e0bd3dc91df022f6a Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Nov 2015 02:22:00 +0100 Subject: Add a CI build with clang --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2567721..a8bdad7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,11 @@ botan_cares_systemd_idn: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - make -j$(nproc) biboumi coverage +clang_botan_cares_systemd_idn: + script: + - cmake .. -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 + - make -j$(nproc) biboumi coverage + botan_cares_systemd: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 -- cgit v1.2.3 From de6335b9944190ec5454ff4052710d05d8b02b43 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Nov 2015 02:37:46 +0100 Subject: Fix a clang warning --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 10dce57..f4de302 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -141,7 +141,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) else if (type == "unavailable") { const XmlNode* status = stanza.get_child("status", COMPONENT_NS); - bridge->leave_irc_channel(std::move(iid), status ? std::move(status->get_inner()) : ""); + bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : ""); } } else -- cgit v1.2.3 From 73dffca24e6926d2bd573d9223b2b7f3efd8f31d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Nov 2015 16:40:09 +0100 Subject: Only include the coverage flags when compiling with gcc --- CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 060fa11..7c27477 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,9 @@ set(${PROJECT_NAME}_VERSION_MINOR 0) set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage --coverage") +endif() # Define a __FILENAME__ macro to get the filename of each file, instead of # the full path as in __FILE__ -- cgit v1.2.3 From 5027b21fd1fe0dec6a82a2b0f428f30f5cc98a42 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 5 Nov 2015 19:08:19 +0100 Subject: Add verifyCert field into the database --- database/database.xml | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/database/database.xml b/database/database.xml index 1292e71..1750b61 100644 --- a/database/database.xml +++ b/database/database.xml @@ -2,21 +2,26 @@ - - - + + + - - - - - - + + + + + + + - - - - + + + + + - + + + + -- cgit v1.2.3 From 0620c8e589c713fa0f0d0f66a4c4d203e8f3f87f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 7 Nov 2015 23:41:06 +0100 Subject: Avoid leaking socket filedescriptors When trying the various results of getaddrinfo, we forgot to close the socket when one fails, before trying the next one. Also use the destructor to make sure we do not have some other unrelated leak. --- louloulibs/network/tcp_socket_handler.cpp | 8 ++++++++ louloulibs/network/tcp_socket_handler.hpp | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 0ed74a2..d5c0dfa 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -44,8 +44,16 @@ TCPSocketHandler::TCPSocketHandler(std::shared_ptr poller): #endif {} +TCPSocketHandler::~TCPSocketHandler() +{ + this->close(); +} + + void TCPSocketHandler::init_socket(const struct addrinfo* rp) { + if (this->socket != -1) + ::close(this->socket); if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) throw std::runtime_error("Could not create socket: "s + strerror(errno)); int optval = 1; diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 213e286..d33b919 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -27,7 +27,7 @@ class TCPSocketHandler: public SocketHandler { protected: - ~TCPSocketHandler() = default; + ~TCPSocketHandler(); public: explicit TCPSocketHandler(std::shared_ptr poller); -- cgit v1.2.3 From 296a16fa75da5900395a7cf70f63a6ff266ef0cc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Nov 2015 03:20:51 +0100 Subject: Do not segfault when trying to send TLS data over a not-yet connected socket --- louloulibs/network/tcp_socket_handler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index d5c0dfa..78efdce 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -410,7 +410,9 @@ void TCPSocketHandler::tls_recv() void TCPSocketHandler::tls_send(std::string&& data) { - if (this->tls->is_active()) + // We may not be connected yet, or the tls session has + // not yet been negociated + if (this->tls && this->tls->is_active()) { const bool was_active = this->tls->is_active(); if (!this->pre_buf.empty()) -- cgit v1.2.3 From 87d00ebe2d4e7a006359770ac9e551c2bb2ea392 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Nov 2015 05:06:23 +0100 Subject: Coverage cannot run with clang++ --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8bdad7..9da0fb8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,7 +9,7 @@ botan_cares_systemd_idn: clang_botan_cares_systemd_idn: script: - cmake .. -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - make -j$(nproc) biboumi check botan_cares_systemd: script: -- cgit v1.2.3 From a2fb3d0520e517e211202d8b67b362fc2a63c47d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Nov 2015 05:07:01 +0100 Subject: Rename the CI build where we run the coverage --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9da0fb8..b26c73d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ before_script: - mkdir build && cd build/ -botan_cares_systemd_idn: +coverage_botan_cares_systemd_idn: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - make -j$(nproc) biboumi coverage -- cgit v1.2.3 From 6dcb21a032d47cd3a1f6f2d5d795a1c0f67b382e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 30 Nov 2015 21:28:35 +0100 Subject: Document the fact that the joining-nick is ignored fix #2695 --- doc/biboumi.1.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 8a234fc..4aded1e 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -275,6 +275,11 @@ given IRC server will be your nickname in all other channels that you join on that same IRC server. If you explicitely change your nickname on one channel, your nickname will be changed on all channels on the same server as well. +Joining a new channel with a different nick, however, will not change your +nick. The provided nick will be ignored, in order to avoid changing your +nick on the whole server by mistake. If you want to have a different +nickname in the channel you’re going to join, you need to do it explicitly +with the NICK command before joining the channel. ### Private messages -- cgit v1.2.3 From 241768836ddfb9e3609f987224cd821058fcc948 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Dec 2015 16:08:40 +0100 Subject: Add the outgoing_bind option Lets the admin choose a local address to bind each outgoing (IRC) socket. --- doc/biboumi.1.md | 9 +++++++++ louloulibs/network/tcp_socket_handler.cpp | 28 ++++++++++++++++++++++++++++ louloulibs/network/tcp_socket_handler.hpp | 6 ++++++ src/irc/irc_client.cpp | 3 +++ 4 files changed, 46 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 4aded1e..7a33f4f 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -120,6 +120,15 @@ The configuration file uses a simple format of the form negociating a TLS session. By default this value is unset and biboumi tries a list of well-known paths. +`outgoing_bind` + + An address (IPv4 or IPv6) to bind the outgoing sockets to. If no value is + specified, it will use the one assigned by the operating system. You can + for example use outgoing_bind=192.168.1.11 to force biboumi to use the + interface with this address. Note that this is only used for connections + to IRC servers, the connection to the XMPP server is always done locally + on 127.0.0.1. + The configuration can be re-read at runtime (you can for example change the log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 (see kill(1)) to the process. diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 78efdce..e6901c8 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -56,6 +56,34 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) ::close(this->socket); if ((this->socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) throw std::runtime_error("Could not create socket: "s + strerror(errno)); + // Bind the socket to a specific address, if specified + if (!this->bind_addr.empty()) + { + // Convert the address from string format to a sockaddr that can be + // used in bind() + struct addrinfo* result; + int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result); + if (err != 0) + log_error("Failed to bind socket to " << this->bind_addr << ": " + << gai_strerror(err)); + else + { + struct addrinfo* rp; + int bind_error; + for (rp = result; rp; rp = rp->ai_next) + { + if ((bind_error = ::bind(this->socket, + reinterpret_cast(rp->ai_addr), + rp->ai_addrlen)) == 0) + break; + } + if (!rp) + log_error("Failed to bind socket to " << this->bind_addr << ": " + << strerror(bind_error)); + else + log_info("Socket successfully bound to " << this->bind_addr); + } + } int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index d33b919..9f5caa3 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -224,6 +224,12 @@ protected: bool hostname_resolution_failed; + /** + * Address to bind the socket to, before calling connect(). + * If empty, it’s equivalent to binding to INADDR_ANY. + */ + std::string bind_addr; + private: TCPSocketHandler(const TCPSocketHandler&) = delete; TCPSocketHandler(TCPSocketHandler&&) = delete; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 93ea2ae..e53c540 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -86,6 +86,9 @@ void IrcClient::start() this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s + this->hostname + ":" + port + " (" + (tls ? "encrypted" : "not encrypted") + ")"); + + this->bind_addr = Config::get("outgoing_bind", ""); + this->connect(this->hostname, port, tls); } -- cgit v1.2.3 From 05a82b810f09d5cf3a0388fc4eeb523d33662c9c Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 1 Dec 2015 16:09:21 +0100 Subject: Display a better error when connection fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The error should not tell “while reading” when we are just connecting. --- louloulibs/network/tcp_socket_handler.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index e6901c8..83863b0 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -266,7 +266,10 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) } else if (-1 == size) { - log_warning("Error while reading from socket: " << strerror(errno)); + if (this->connecting) + log_warning("Error connecting: " << strerror(errno)); + else + log_warning("Error while reading from socket: " << strerror(errno)); // Remember if we were connecting, or already connected when this // happened, because close() sets this->connecting to false const auto were_connecting = this->connecting; -- cgit v1.2.3 From 6a7cd5eecfa4df0552969e5d5d57452d290b7fc5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 20:41:48 +0100 Subject: Do not forget to call freeaddrinfo, fix a memleak For the getaddrinfo call we added in the previous commit, to convert the IP provided in the conf. --- louloulibs/network/tcp_socket_handler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 83863b0..6ed981c 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -68,6 +68,7 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) << gai_strerror(err)); else { + utils::ScopeGuard sg([result](){ freeaddrinfo(result); }); struct addrinfo* rp; int bind_error; for (rp = result; rp; rp = rp->ai_next) -- cgit v1.2.3 From 284af79128c4bae9fa76f2dd5916ebfd81ecf718 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 21:08:04 +0100 Subject: =?UTF-8?q?Provide=20a=20=E2=80=9Cvar=E2=80=9D=20map=20in=20AdhocS?= =?UTF-8?q?ession=20objects,=20to=20save=20values=20between=20each=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/xmpp/adhoc_session.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index da7913f..05afc91 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -5,6 +5,7 @@ #include #include +#include class XmppComponent; @@ -75,6 +76,15 @@ private: size_t current_step; bool terminated; +public: + /** + * A map to store various things that we may want to remember between two + * steps of the same session. A step can insert any value associated to + * any key in there. + */ + std::map vars; + +private: AdhocSession(const AdhocSession&) = delete; AdhocSession(AdhocSession&&) = delete; AdhocSession& operator=(const AdhocSession&) = delete; -- cgit v1.2.3 From 756b297045c5ec3ce051b48379a692d80398ef47 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 21:09:03 +0100 Subject: Small cleanup --- louloulibs/xmpp/adhoc_session.cpp | 4 ---- louloulibs/xmpp/adhoc_session.hpp | 7 ++----- louloulibs/xmpp/jid.cpp | 2 -- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/louloulibs/xmpp/adhoc_session.cpp b/louloulibs/xmpp/adhoc_session.cpp index bf8d292..dda4bea 100644 --- a/louloulibs/xmpp/adhoc_session.cpp +++ b/louloulibs/xmpp/adhoc_session.cpp @@ -13,10 +13,6 @@ AdhocSession::AdhocSession(const AdhocCommand& command, const std::string& owner { } -AdhocSession::~AdhocSession() -{ -} - const AdhocStep& AdhocSession::get_next_step() { assert(this->current_step < this->command.callbacks.size()); diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index 05afc91..7f07dc1 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -16,17 +16,15 @@ class AdhocSession; * A function executed as an ad-hoc command step. It takes a * XmlNode and modifies it accordingly (inserting for example an * node, or a data form…). - * TODO fix this: - * It also must call one of step_passed(), cancel() etc on the AdhocSession object. */ -typedef std::function AdhocStep; +using AdhocStep = std::function; class AdhocSession { public: explicit AdhocSession(const AdhocCommand& command, const std::string& owner_jid, const std::string& to_jid); - ~AdhocSession(); + ~AdhocSession() = default; /** * Return the function to be executed, found in our AdhocCommand, for the * current_step. And increment the current_step. @@ -52,7 +50,6 @@ public: return this->owner_jid; } - private: /** * A reference of the command concerned by this session. Used for example diff --git a/louloulibs/xmpp/jid.cpp b/louloulibs/xmpp/jid.cpp index e6fee45..dcd7012 100644 --- a/louloulibs/xmpp/jid.cpp +++ b/louloulibs/xmpp/jid.cpp @@ -30,8 +30,6 @@ Jid::Jid(const std::string& jid) this->domain = jid.substr(at, slash - at); } -#include - static constexpr size_t max_jid_part_len = 1023; std::string jidprep(const std::string& original) -- cgit v1.2.3 From 700a6c7bc39aa48af5037ecbb9778b20fb6f87df Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 21:09:26 +0100 Subject: JID class provides bare() and full() methods --- louloulibs/xmpp/jid.hpp | 9 +++++++++ src/xmpp/biboumi_component.cpp | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp index b6975a2..e2ee586 100644 --- a/louloulibs/xmpp/jid.hpp +++ b/louloulibs/xmpp/jid.hpp @@ -15,6 +15,15 @@ public: std::string local; std::string resource; + std::string bare() const + { + return this->local + "@" + this->domain; + } + std::string full() const + { + return this->local + "@" + this->domain + "/" + this->resource; + } + private: Jid(const Jid&) = delete; Jid(Jid&&) = delete; diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f4de302..6f1e585 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -394,7 +394,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) { // Get biboumi's adhoc commands this->send_adhoc_commands_list(id, from, this->served_hostname, (Config::get("admin", "") == - from_jid.local + "@" + from_jid.domain), + from_jid.bare()), this->adhoc_commands_handler); stanza_error.disable(); } @@ -402,7 +402,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) { // Get the server's adhoc commands this->send_adhoc_commands_list(id, from, to_str, (Config::get("admin", "") == - from_jid.local + "@" + from_jid.domain), + from_jid.bare()), this->irc_server_adhoc_commands_handler); stanza_error.disable(); } -- cgit v1.2.3 From 1f6eea62f46789c0d3ebe7da133ccad2e59c89c8 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 3 Dec 2015 21:14:24 +0100 Subject: Add an ad-hoc command to disconnect a user from one or more IRC server fix #3077 --- doc/biboumi.1.md | 6 ++ louloulibs/xmpp/xmpp_stanza.cpp | 5 ++ louloulibs/xmpp/xmpp_stanza.hpp | 2 + src/bridge/bridge.cpp | 5 ++ src/bridge/bridge.hpp | 1 + src/irc/irc_message.hpp | 6 +- src/xmpp/biboumi_adhoc_commands.cpp | 166 ++++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 4 + src/xmpp/biboumi_component.cpp | 3 +- 9 files changed, 194 insertions(+), 4 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 7a33f4f..cf7b893 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -410,6 +410,12 @@ Biboumi supports a few ad-hoc commands, as described in the XEP 0050. “Gateway shutdown” quit message, except that biboumi does not exit when using this ad-hoc command. + - disconnect-from-irc-servers: Disconnect a single user from one or more + IRC server. The user is immediately disconnected by closing the socket, + no message is sent to the IRC server, but the user is of course notified + with an XMPP message. The administrator can disconnect any user, while + the other users can only disconnect themselves. + ### Raw IRC messages Biboumi tries to support as many IRC features as possible, but doesn’t diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index 4c0f5c7..d1c2e0f 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -260,3 +260,8 @@ std::string sanitize(const std::string& data) else return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); } + +std::ostream& operator<<(std::ostream& os, const XmlNode& node) +{ + return os << node.to_string(); +} diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 9cf7d8d..b1ba54a 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -140,4 +140,6 @@ private: */ typedef XmlNode Stanza; +std::ostream& operator<<(std::ostream& os, const XmlNode& node); + #endif // XMPP_STANZA_INCLUDED diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 5badb18..a1ba71a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -690,3 +690,8 @@ void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMe ++it; } } + +std::unordered_map>& Bridge::get_irc_clients() +{ + return this->irc_clients; +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f1ecf25..7b8df8f 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -187,6 +187,7 @@ public: * iq_responder_callback_t and remove the callback from the list. */ void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); + std::unordered_map>& get_irc_clients(); private: /** diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp index a0bd772..01e78cf 100644 --- a/src/irc/irc_message.hpp +++ b/src/irc/irc_message.hpp @@ -8,9 +8,9 @@ class IrcMessage { public: - explicit IrcMessage(std::string&& line); - explicit IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); - explicit IrcMessage(std::string&& command, std::vector&& args); + IrcMessage(std::string&& line); + IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); + IrcMessage(std::string&& command, std::vector&& args); ~IrcMessage(); std::string prefix; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index ff0c8d4..84df2b3 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -313,3 +313,169 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com session.terminate(); } #endif // USE_DATABASE + +void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + const Jid owner(session.get_owner_jid()); + if (owner.bare() != Config::get("admin", "")) + { // A non-admin is not allowed to disconnect other users, only + // him/herself, so we just skip this step + auto next_step = session.get_next_step(); + next_step(xmpp_component, session, command_node); + } + else + { // Send a form to select the user to disconnect + auto biboumi_component = static_cast(xmpp_component); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from selected IRC servers"); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose a user JID"); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "jid"; + jids_field["type"] = "list-single"; + jids_field["label"] = "The JID to disconnect"; + XmlNode required("required"); + jids_field.add_child(std::move(required)); + for (Bridge* bridge: biboumi_component->get_bridges()) + { + XmlNode option("option"); + option["label"] = bridge->get_jid(); + XmlNode value("value"); + value.set_inner(bridge->get_jid()); + option.add_child(std::move(value)); + jids_field.add_child(std::move(option)); + } + x.add_child(std::move(jids_field)); + command_node.add_child(std::move(x)); + } +} + +void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + // If no JID is contained in the command node, it means we skipped the + // previous stage, and the jid to disconnect is the executor's jid + std::string jid_to_disconnect = session.get_owner_jid(); + + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) + { + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + if (field->get_tag("var") == "jid") + { + if (const XmlNode* value = field->get_child("value", "jabber:x:data")) + jid_to_disconnect = value->get_inner(); + } + } + + // Save that JID for the last step + session.vars["jid"] = jid_to_disconnect; + + // Send a data form to let the user choose which server to disconnect the + // user from + command_node.delete_all_children(); + auto biboumi_component = static_cast(xmpp_component); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Disconnect a user from selected IRC servers"); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Choose one or more servers to disconnect this JID from"); + x.add_child(std::move(instructions)); + XmlNode jids_field("field"); + jids_field["var"] = "irc-servers"; + jids_field["type"] = "list-multi"; + jids_field["label"] = "The servers to disconnect from"; + XmlNode required("required"); + jids_field.add_child(std::move(required)); + Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + + if (!bridge || bridge->get_irc_clients().empty()) + { + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("User "s + jid_to_disconnect + " is not connected to any IRC server."); + command_node.add_child(std::move(note)); + session.terminate(); + return ; + } + + for (const auto& pair: bridge->get_irc_clients()) + { + XmlNode option("option"); + option["label"] = pair.first; + XmlNode value("value"); + value.set_inner(pair.first); + option.add_child(std::move(value)); + jids_field.add_child(std::move(option)); + } + x.add_child(std::move(jids_field)); + + XmlNode message_field("field"); + message_field["var"] = "quit-message"; + message_field["type"] = "text-single"; + message_field["label"] = "Quit message"; + XmlNode message_value("value"); + message_value.set_inner("Killed by admin"); + message_field.add_child(std::move(message_value)); + x.add_child(std::move(message_field)); + + command_node.add_child(std::move(x)); +} + +void DisconnectUserFromServerStep3(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +{ + const auto it = session.vars.find("jid"); + if (it == session.vars.end()) + return ; + const auto jid_to_disconnect = it->second; + + std::vector servers; + std::string quit_message; + + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) + { + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + if (field->get_tag("var") == "irc-servers") + { + for (const XmlNode* value: field->get_children("value", "jabber:x:data")) + servers.push_back(value->get_inner()); + } + else if (field->get_tag("var") == "quit-message") + if (const XmlNode* value = field->get_child("value", "jabber:x:data")) + quit_message = value->get_inner(); + } + } + + auto biboumi_component = static_cast(xmpp_component); + Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + auto& clients = bridge->get_irc_clients(); + + std::size_t number = 0; + + for (const auto& hostname: servers) + { + auto it = clients.find(hostname); + if (it != clients.end()) + { + it->second->on_error({"ERROR", {quit_message}}); + clients.erase(it); + number++; + } + } + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + std::string msg = jid_to_disconnect + " was disconnected from " + std::to_string(number) + " IRC server"; + if (number > 1) + msg += "s"; + msg += "."; + note.set_inner(msg); + command_node.add_child(std::move(note)); +} diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index e530fa7..9377d13 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -13,4 +13,8 @@ void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep3(XmppComponent*, AdhocSession& session, XmlNode& command_node); + #endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6f1e585..72b767f 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -55,7 +55,8 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st this->adhoc_commands_handler.get_commands() = { {"ping", AdhocCommand({&PingStep1}, "Do a ping", false)}, {"hello", AdhocCommand({&HelloStep1, &HelloStep2}, "Receive a custom greeting", false)}, - {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect a user from the gateway", true)}, + {"disconnect-user", AdhocCommand({&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true)}, + {"disconnect-from-irc-servers", AdhocCommand({&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false)}, {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} }; -- cgit v1.2.3 From 8ddbe8d3e6a5a5001537379aa4f1a418c6cb6d23 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 11 Dec 2015 15:19:40 +0000 Subject: Make the XMPP server address configurable. fix #3145 --- doc/biboumi.1.md | 6 ++++++ louloulibs/xmpp/xmpp_component.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index cf7b893..bb18475 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -47,6 +47,12 @@ The configuration file uses a simple format of the form This password must be configured in the XMPP server, associated with the external component on *hostname*. +`xmpp_server_ip` + + The IP address to connect to the XMPP server on. The connection to the XMPP + server is unencrypted, so the biboumi instance and the server should normally + be on the same host. The default value is 127.0.0.1. + `port` The TCP port to use to connect to the local XMPP component. The default diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 3017c0b..ce9552f 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -61,7 +61,7 @@ XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& void XmppComponent::start() { - this->connect("127.0.0.1", Config::get("port", "5347"), false); + this->connect(Config::get("xmpp_server_ip", "127.0.0.1"), Config::get("port", "5347"), false); } bool XmppComponent::is_document_open() const -- cgit v1.2.3 From 7e2427148e9023483f266cd3ac4e167d50320796 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Dec 2015 20:45:40 +0100 Subject: =?UTF-8?q?Use=20references=20instead=20of=20raw=20pointer,=20to?= =?UTF-8?q?=20store=20the=20=E2=80=9Cparent=E2=80=9D=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Bridge and IrcClient --- src/bridge/bridge.cpp | 62 +++++++++++++++++------------------ src/bridge/bridge.hpp | 8 ++--- src/irc/irc_client.cpp | 74 +++++++++++++++++++++--------------------- src/irc/irc_client.hpp | 6 ++-- src/xmpp/biboumi_component.cpp | 2 +- 5 files changed, 75 insertions(+), 77 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index a1ba71a..55f4c51 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -14,7 +14,7 @@ using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; -Bridge::Bridge(const std::string& user_jid, BiboumiComponent* xmpp, std::shared_ptr poller): +Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr poller): user_jid(user_jid), xmpp(xmpp), poller(poller) @@ -105,7 +105,7 @@ IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::strin std::make_shared(this->poller, hostname, nickname, username, realname, jid.domain, - this)); + *this)); std::shared_ptr irc = this->irc_clients.at(hostname); return irc.get(); } @@ -172,7 +172,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) { if (iid.get_server().empty()) { - this->xmpp->send_stanza_error("message", this->user_jid, std::to_string(iid), "", + this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "", "cancel", "remote-server-not-found", std::to_string(iid) + " is not a valid channel name. " "A correct room jid is of the form: #%", @@ -205,7 +205,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else irc->send_channel_message(iid.get_local(), line); - this->xmpp->send_muc_message(std::to_string(iid), irc->get_own_nick(), + this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid); } } @@ -278,7 +278,7 @@ void Bridge::send_private_message(const Iid& iid, const std::string& body, const { if (iid.get_local().empty() || iid.get_server().empty()) { - this->xmpp->send_stanza_error("message", this->user_jid, std::to_string(iid), "", + this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "", "cancel", "remote-server-not-found", std::to_string(iid) + " is not a valid channel name. " "A correct room jid is of the form: #%", @@ -336,7 +336,7 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq std::string text; if (message.arguments.size() >= 2) text = message.arguments[1]; - this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, + this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false); return true; } @@ -349,7 +349,7 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq } else if (message.command == "323" || message.command == "RPL_LISTEND") { // Send the iq response with the content of the list - this->xmpp->send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list); + this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list); return true; } return false; @@ -374,7 +374,7 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: const std::string chan_name_later = utils::tolower(message.arguments[0]); if (target_later != target || chan_name_later != iid.get_local()) return false; - this->xmpp->send_iq_result(iq_id, to_jid, std::to_string(iid)); + this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid)); } else if (message.command == "401" && message.arguments.size() >= 2) { @@ -384,7 +384,7 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: std::string error_message = "No such nick"; if (message.arguments.size() >= 3) error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found", + this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found", error_message, false); } else if (message.command == "482" && message.arguments.size() >= 2) @@ -395,7 +395,7 @@ void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std: std::string error_message = "You're not channel operator"; if (message.arguments.size() >= 3) error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed", + this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed", error_message, false); } return true; @@ -441,7 +441,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s if (id != iq_id) return false; Jid jid(from_jid); - this->xmpp->send_iq_result(iq_id, to_jid, jid.local); + this->xmpp.send_iq_result(iq_id, to_jid, jid.local); return true; } if (message.command == "401" && message.arguments[1] == nick) @@ -449,7 +449,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s std::string error_message = "No such nick"; if (message.arguments.size() >= 3) error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", + this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", error_message, true); return true; } @@ -467,13 +467,13 @@ void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string IrcChannel* chan = irc->get_channel(iid.get_local()); if (!chan->joined) { - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed", + this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-allowed", "", true); return; } if (chan->get_self()->nick != nick && !chan->find_user(nick)) { - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", + this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", "Recipient not in room", true); return; } @@ -487,9 +487,9 @@ void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& { Jid jid(from_jid); if (irc_hostname.empty() || this->find_irc_client(irc_hostname)) - this->xmpp->send_iq_result(iq_id, to_jid, jid.local); + this->xmpp.send_iq_result(iq_id, to_jid, jid.local); else - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", + this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable", "", true); } @@ -511,7 +511,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std { // remove the "\01VERSION " and the "\01" parts from the string const std::string version = message.arguments[1].substr(9, message.arguments[1].size() - 10); - this->xmpp->send_version(iq_id, to_jid, from_jid, version); + this->xmpp.send_version(iq_id, to_jid, from_jid, version); return true; } if (message.command == "401" && message.arguments.size() >= 2 @@ -520,7 +520,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std std::string error_message = "No such nick"; if (message.arguments.size() >= 3) error_message = message.arguments[2]; - this->xmpp->send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", + this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found", error_message, true); return true; } @@ -532,7 +532,7 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { if (muc) - this->xmpp->send_muc_message(std::to_string(iid), nick, + this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body), this->user_jid); else { @@ -544,7 +544,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st target = it->second; fulljid = true; } - this->xmpp->send_message(target, this->make_xmpp_body(body), + this->xmpp.send_message(target, this->make_xmpp_body(body), this->user_jid, "chat", fulljid); } } @@ -553,12 +553,12 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, const std::string& type, const std::string& condition, const std::string& error_code, const std::string& text) { - this->xmpp->send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); + this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { - this->xmpp->send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -574,7 +574,7 @@ void Bridge::send_nick_change(Iid&& iid, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp->send_nick_change(std::to_string(iid), + this->xmpp.send_nick_change(std::to_string(iid), old_nick, new_nick, affiliation, role, this->user_jid, self); } @@ -590,7 +590,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho } else body = msg; - this->xmpp->send_message(from, this->make_xmpp_body(body), this->user_jid, "chat"); + this->xmpp.send_message(from, this->make_xmpp_body(body), this->user_jid, "chat"); } void Bridge::send_user_join(const std::string& hostname, @@ -603,13 +603,13 @@ void Bridge::send_user_join(const std::string& hostname, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp->send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, + this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, affiliation, role, this->user_jid, self); } void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic) { - this->xmpp->send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic), this->user_jid); + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic), this->user_jid); } std::string Bridge::get_own_nick(const Iid& iid) @@ -627,12 +627,12 @@ size_t Bridge::active_clients() const void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { - this->xmpp->kick_user(std::to_string(iid), target, reason, author, this->user_jid); + this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp->send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); + this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) @@ -641,12 +641,12 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::string affiliation; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); - this->xmpp->send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid); + this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid); } void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { - this->xmpp->send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid); + this->xmpp.send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, @@ -655,7 +655,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Use revstr because the forwarded ping to target XMPP user must not be // the same that the request iq, but we also need to get it back easily // (revstr again) - this->xmpp->send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id)); + this->xmpp.send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id)); } void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 7b8df8f..e73bd19 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -33,7 +33,7 @@ using irc_responder_callback_t = std::function poller); + explicit Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr poller); ~Bridge(); /** * QUIT all connected IRC servers. @@ -216,11 +216,9 @@ private: */ std::unordered_map> irc_clients; /** - * A raw pointer, because we do not own it, the XMPP component owns us, - * but we still need to communicate with it, when sending messages from - * IRC to XMPP. + * To communicate back with the XMPP component */ - BiboumiComponent* xmpp; + BiboumiComponent& xmpp; /** * Poller, to give it the IrcClients that we spawn, to make it manage * their sockets. diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e53c540..4aa45ac 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -27,7 +27,7 @@ using namespace std::chrono_literals; IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& nickname, const std::string& username, const std::string& realname, const std::string& user_hostname, - Bridge* bridge): + Bridge& bridge): TCPSocketHandler(poller), hostname(hostname), user_hostname(user_hostname), @@ -48,7 +48,7 @@ IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname "To disconnect from the IRC server, leave this room and all " "other IRC channels of that server."; #ifdef USE_DATABASE - auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->get_hostname()); std::vector ports = utils::split(options.ports, ';', false); for (auto it = ports.rbegin(); it != ports.rend(); ++it) @@ -72,7 +72,7 @@ IrcClient::~IrcClient() { // This event may or may not exist (if we never got connected, it // doesn't), but it's ok - TimedEventsManager::instance().cancel("PING"s + this->hostname + this->bridge->get_jid()); + TimedEventsManager::instance().cancel("PING"s + this->hostname + this->bridge.get_jid()); } void IrcClient::start() @@ -83,7 +83,7 @@ void IrcClient::start() bool tls; std::tie(port, tls) = this->ports_to_try.top(); this->ports_to_try.pop(); - this->bridge->send_xmpp_message(this->hostname, "", "Connecting to "s + + this->bridge.send_xmpp_message(this->hostname, "", "Connecting to "s + this->hostname + ":" + port + " (" + (tls ? "encrypted" : "not encrypted") + ")"); @@ -94,7 +94,7 @@ void IrcClient::start() void IrcClient::on_connection_failed(const std::string& reason) { - this->bridge->send_xmpp_message(this->hostname, "", + this->bridge.send_xmpp_message(this->hostname, "", "Connection failed: "s + reason); if (this->hostname_resolution_failed) @@ -107,7 +107,7 @@ void IrcClient::on_connection_failed(const std::string& reason) for (const auto& tuple: this->channels_to_join) { Iid iid(std::get<0>(tuple) + "%" + this->hostname); - this->bridge->send_presence_error(iid, this->current_nick, + this->bridge.send_presence_error(iid, this->current_nick, "cancel", "item-not-found", "", reason); } @@ -152,7 +152,7 @@ void IrcClient::on_connected() } #ifdef USE_DATABASE - auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->get_hostname()); if (!options.pass.value().empty()) this->send_pass_command(options.pass.value()); @@ -258,7 +258,7 @@ void IrcClient::parse_in_buffer(const size_t) this->on_unknown_message(message); } // Try to find a waiting_iq, which response will be triggered by this IrcMessage - this->bridge->trigger_on_irc_message(this->hostname, message); + this->bridge.trigger_on_irc_message(this->hostname, message); } } @@ -413,7 +413,7 @@ void IrcClient::forward_server_message(const IrcMessage& message) const std::string from = message.prefix; const std::string body = message.arguments[1]; - this->bridge->send_xmpp_message(this->hostname, from, body); + this->bridge.send_xmpp_message(this->hostname, from, body); } void IrcClient::on_notice(const IrcMessage& message) @@ -438,11 +438,11 @@ void IrcClient::on_notice(const IrcMessage& message) if (this->nicks_to_treat_as_private.find(nick) != this->nicks_to_treat_as_private.end()) { // We previously sent a message to that nick) - this->bridge->send_message({nick + "!" + this->hostname}, nick, body, + this->bridge.send_message({nick + "!" + this->hostname}, nick, body, false); } else - this->bridge->send_xmpp_message(this->hostname, from, body); + this->bridge.send_xmpp_message(this->hostname, from, body); } else { @@ -494,7 +494,7 @@ void IrcClient::on_isupport_message(const IrcMessage& message) void IrcClient::send_gateway_message(const std::string& message, const std::string& from) { - this->bridge->send_xmpp_message(this->hostname, from, message); + this->bridge.send_xmpp_message(this->hostname, from, message); } void IrcClient::set_and_forward_user_list(const IrcMessage& message) @@ -507,7 +507,7 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); if (user->nick != channel->get_self()->nick) { - this->bridge->send_user_join(this->hostname, chan_name, user, + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } @@ -533,7 +533,7 @@ void IrcClient::on_channel_join(const IrcMessage& message) else { const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); - this->bridge->send_user_join(this->hostname, chan_name, user, + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } @@ -559,16 +559,16 @@ void IrcClient::on_channel_message(const IrcMessage& message) if (!body.empty() && body[0] == '\01') { if (body.substr(1, 6) == "ACTION") - this->bridge->send_message(iid, nick, + this->bridge.send_message(iid, nick, "/me"s + body.substr(7, body.size() - 8), muc); else if (body.substr(1, 8) == "VERSION\01") - this->bridge->send_iq_version_request(nick, this->hostname); + this->bridge.send_iq_version_request(nick, this->hostname); else if (body.substr(1, 5) == "PING ") - this->bridge->send_xmpp_ping_request(utils::tolower(nick), this->hostname, + this->bridge.send_xmpp_ping_request(utils::tolower(nick), this->hostname, body.substr(6, body.size() - 7)); } else - this->bridge->send_message(iid, nick, body, muc); + this->bridge.send_message(iid, nick, body, muc); } void IrcClient::on_rpl_liststart(const IrcMessage&) @@ -599,7 +599,7 @@ void IrcClient::on_motd_line(const IrcMessage& message) void IrcClient::send_motd(const IrcMessage&) { - this->bridge->send_xmpp_message(this->hostname, "", this->motd); + this->bridge.send_xmpp_message(this->hostname, "", this->motd); } void IrcClient::on_topic_received(const IrcMessage& message) @@ -608,7 +608,7 @@ void IrcClient::on_topic_received(const IrcMessage& message) IrcChannel* channel = this->get_channel(chan_name); channel->topic = message.arguments[message.arguments.size() - 1]; if (channel->joined) - this->bridge->send_topic(this->hostname, chan_name, channel->topic); + this->bridge.send_topic(this->hostname, chan_name, channel->topic); } void IrcClient::on_channel_completely_joined(const IrcMessage& message) @@ -616,10 +616,10 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->joined = true; - this->bridge->send_user_join(this->hostname, chan_name, channel->get_self(), + this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); - this->bridge->send_topic(this->hostname, chan_name, channel->topic); + this->bridge.send_topic(this->hostname, chan_name, channel->topic); } void IrcClient::on_erroneous_nickname(const IrcMessage& message) @@ -639,7 +639,7 @@ void IrcClient::on_nickname_conflict(const IrcMessage& message) iid.set_local(it->first); iid.set_server(this->hostname); iid.is_channel = true; - this->bridge->send_nickname_conflict_error(iid, nickname); + this->bridge.send_nickname_conflict_error(iid, nickname); } } @@ -656,7 +656,7 @@ void IrcClient::on_nickname_change_too_fast(const IrcMessage& message) iid.set_local(it->first); iid.set_server(this->hostname); iid.is_channel = true; - this->bridge->send_presence_error(iid, nickname, + this->bridge.send_presence_error(iid, nickname, "cancel", "not-acceptable", "", txt); } @@ -674,14 +674,14 @@ void IrcClient::on_welcome_message(const IrcMessage& message) this->current_nick = message.arguments[0]; this->welcomed = true; #ifdef USE_DATABASE - auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), + auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->get_hostname()); if (!options.afterConnectionCommand.value().empty()) this->send_raw(options.afterConnectionCommand.value()); #endif // Install a repeated events to regularly send a PING TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this), - "PING"s + this->hostname + this->bridge->get_jid())); + "PING"s + this->hostname + this->bridge.get_jid())); for (const auto& tuple: this->channels_to_join) this->send_join_command(std::get<0>(tuple), std::get<1>(tuple)); this->channels_to_join.clear(); @@ -725,7 +725,7 @@ void IrcClient::on_part(const IrcMessage& message) // channel pointer is now invalid channel = nullptr; } - this->bridge->send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); + this->bridge.send_muc_leave(std::move(iid), std::move(nick), std::move(txt), self); } } @@ -743,7 +743,7 @@ void IrcClient::on_error(const IrcMessage& message) if (!channel->joined) continue; std::string own_nick = channel->get_self()->nick; - this->bridge->send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); + this->bridge.send_muc_leave(std::move(iid), std::move(own_nick), leave_message, true); } this->channels.clear(); this->send_gateway_message("ERROR: "s + leave_message); @@ -767,7 +767,7 @@ void IrcClient::on_quit(const IrcMessage& message) iid.set_local(chan_name); iid.set_server(this->hostname); iid.is_channel = true; - this->bridge->send_muc_leave(std::move(iid), std::move(nick), txt, false); + this->bridge.send_muc_leave(std::move(iid), std::move(nick), txt, false); } } } @@ -789,7 +789,7 @@ void IrcClient::on_nick(const IrcMessage& message) iid.is_channel = true; const bool self = channel->get_self()->nick == old_nick; const char user_mode = user->get_most_significant_mode(this->sorted_user_modes); - this->bridge->send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); + this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self); user->nick = new_nick; if (self) { @@ -815,7 +815,7 @@ void IrcClient::on_kick(const IrcMessage& message) iid.set_local(chan_name); iid.set_server(this->hostname); iid.is_channel = true; - this->bridge->kick_muc_user(std::move(iid), target, reason, author.nick); + this->bridge.kick_muc_user(std::move(iid), target, reason, author.nick); } void IrcClient::on_mode(const IrcMessage& message) @@ -846,7 +846,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) mode_arguments += message.arguments[i]; } } - this->bridge->send_message(iid, "", "Mode "s + iid.get_local() + + this->bridge.send_message(iid, "", "Mode "s + iid.get_local() + " [" + mode_arguments + "] by " + user.nick, true); const IrcChannel* channel = this->get_channel(iid.get_local()); @@ -917,13 +917,13 @@ void IrcClient::on_channel_mode(const IrcMessage& message) for (const IrcUser* u: modified_users) { char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes); - this->bridge->send_affiliation_role_change(iid, u->nick, most_significant_mode); + this->bridge.send_affiliation_role_change(iid, u->nick, most_significant_mode); } } void IrcClient::on_user_mode(const IrcMessage& message) { - this->bridge->send_xmpp_message(this->hostname, "", + this->bridge.send_xmpp_message(this->hostname, "", "User mode for "s + message.arguments[0] + " is [" + message.arguments[1] + "]"); } @@ -940,7 +940,7 @@ void IrcClient::on_unknown_message(const IrcMessage& message) if (it + 1 != message.arguments.end()) ss << " "; } - this->bridge->send_xmpp_message(this->hostname, from, ss.str()); + this->bridge.send_xmpp_message(this->hostname, from, ss.str()); } size_t IrcClient::number_of_joined_channels() const @@ -963,14 +963,14 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message) this->dummy_channel.joined = false; this->dummy_channel.joining = false; this->dummy_channel.remove_all_users(); - this->bridge->send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true); + this->bridge.send_muc_leave(Iid("%"s + this->hostname), std::string(this->current_nick), exit_message, true); } #ifdef BOTAN_FOUND bool IrcClient::abort_on_invalid_cert() const { #ifdef USE_DATABASE - auto options = Database::get_irc_server_options(this->bridge->get_bare_jid(), this->hostname); + auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname); return options.verifyCert.value(); #endif return true; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 733fc92..7c2a43f 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -29,7 +29,7 @@ public: explicit IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& nickname, const std::string& username, const std::string& realname, const std::string& user_hostname, - Bridge* bridge); + Bridge& bridge); ~IrcClient(); /** * Connect to the IRC server @@ -281,9 +281,9 @@ private: */ std::string current_nick; /** - * Raw pointer because the bridge owns us. + * To communicate back with the bridge */ - Bridge* bridge; + Bridge& bridge; /** * The list of joined channels, indexed by name */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 72b767f..9682dcb 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -488,7 +488,7 @@ Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) } catch (const std::out_of_range& exception) { - this->bridges.emplace(user_jid, std::make_unique(user_jid, this, this->poller)); + this->bridges.emplace(user_jid, std::make_unique(user_jid, *this, this->poller)); return this->bridges.at(user_jid).get(); } } -- cgit v1.2.3 From 9714d02018904b0c207a3f4d7de0be1a6a22774b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 21 Dec 2015 21:15:03 +0100 Subject: Also store a reference instead of a pointer, in AdhocCommandsHandler --- louloulibs/xmpp/adhoc_command.cpp | 14 ++++++------ louloulibs/xmpp/adhoc_command.hpp | 8 +++---- louloulibs/xmpp/adhoc_commands_handler.hpp | 7 +++--- louloulibs/xmpp/adhoc_session.hpp | 2 +- louloulibs/xmpp/xmpp_component.cpp | 2 +- src/xmpp/biboumi_adhoc_commands.cpp | 34 +++++++++++++++--------------- src/xmpp/biboumi_adhoc_commands.hpp | 14 ++++++------ src/xmpp/biboumi_component.cpp | 2 +- 8 files changed, 40 insertions(+), 43 deletions(-) diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp index b8b07c6..8a8bcff 100644 --- a/louloulibs/xmpp/adhoc_command.cpp +++ b/louloulibs/xmpp/adhoc_command.cpp @@ -20,7 +20,7 @@ bool AdhocCommand::is_admin_only() const return this->admin_only; } -void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) +void PingStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) { XmlNode note("note"); note["type"] = "info"; @@ -28,7 +28,7 @@ void PingStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) command_node.add_child(std::move(note)); } -void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) +void HelloStep1(XmppComponent&, AdhocSession&, XmlNode& command_node) { XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -48,11 +48,10 @@ void HelloStep1(XmppComponent*, AdhocSession&, XmlNode& command_node) command_node.add_child(std::move(x)); } -void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) +void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) { // Find out if the name was provided in the form. - const XmlNode* x = command_node.get_child("x", "jabber:x:data"); - if (x) + if (const XmlNode* x = command_node.get_child("x", "jabber:x:data")) { const XmlNode* name_field = nullptr; for (const XmlNode* field: x->get_children("field", "jabber:x:data")) @@ -63,8 +62,7 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) } if (name_field) { - const XmlNode* value = name_field->get_child("value", "jabber:x:data"); - if (value) + if (const XmlNode* value = name_field->get_child("value", "jabber:x:data")) { XmlNode note("note"); note["type"] = "info"; @@ -84,7 +82,7 @@ void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) session.terminate(); } -void Reload(XmppComponent*, AdhocSession&, XmlNode& command_node) +void Reload(XmppComponent&, AdhocSession&, XmlNode& command_node) { ::reload_process(); command_node.delete_all_children(); diff --git a/louloulibs/xmpp/adhoc_command.hpp b/louloulibs/xmpp/adhoc_command.hpp index 1ff2bcf..a2e033a 100644 --- a/louloulibs/xmpp/adhoc_command.hpp +++ b/louloulibs/xmpp/adhoc_command.hpp @@ -35,9 +35,9 @@ private: const bool admin_only; }; -void PingStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void HelloStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void HelloStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void Reload(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void PingStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void HelloStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void Reload(XmppComponent&, AdhocSession& session, XmlNode& command_node); #endif // ADHOC_COMMAND_HPP diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index cf9ca17..0614b2f 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -16,7 +16,7 @@ class AdhocCommandsHandler { public: - explicit AdhocCommandsHandler(XmppComponent* xmpp_component): + explicit AdhocCommandsHandler(XmppComponent& xmpp_component): xmpp_component(xmpp_component), commands{} { } @@ -50,10 +50,9 @@ public: void remove_session(const std::string& session_id, const std::string& initiator_jid); private: /** - * A pointer to the XmppComponent, to access to basically anything in the - * gateway. + * To access basically anything in the gateway. */ - XmppComponent* xmpp_component; + XmppComponent& xmpp_component; /** * The list of all available commands. */ diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index 7f07dc1..e98b6a8 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -17,7 +17,7 @@ class AdhocSession; * XmlNode and modifies it accordingly (inserting for example an * node, or a data form…). */ -using AdhocStep = std::function; +using AdhocStep = std::function; class AdhocSession { diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index ce9552f..a82892d 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -45,7 +45,7 @@ XmppComponent::XmppComponent(std::shared_ptr poller, const std::string& doc_open(false), served_hostname(hostname), stanza_handlers{}, - adhoc_commands_handler(this) + adhoc_commands_handler(*this) { this->parser.add_stream_open_callback(std::bind(&XmppComponent::on_remote_stream_open, this, std::placeholders::_1)); diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 84df2b3..f4a6bc8 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -17,9 +17,9 @@ using namespace std::string_literals; -void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node) +void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& command_node) { - auto biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = static_cast(xmpp_component); XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -35,7 +35,7 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& jids_field["label"] = "The JIDs to disconnect"; XmlNode required("required"); jids_field.add_child(std::move(required)); - for (Bridge* bridge: biboumi_component->get_bridges()) + for (Bridge* bridge: biboumi_component.get_bridges()) { XmlNode option("option"); option["label"] = bridge->get_jid(); @@ -57,9 +57,9 @@ void DisconnectUserStep1(XmppComponent* xmpp_component, AdhocSession&, XmlNode& command_node.add_child(std::move(x)); } -void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { - auto biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = static_cast(xmpp_component); // Find out if the jids, and the quit message are provided in the form. std::string quit_message; @@ -84,7 +84,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X std::size_t num = 0; for (const XmlNode* value: jids_field->get_children("value", "jabber:x:data")) { - Bridge* bridge = biboumi_component->find_user_bridge(value->get_inner()); + Bridge* bridge = biboumi_component.find_user_bridge(value->get_inner()); if (bridge) { bridge->shutdown(quit_message); @@ -114,7 +114,7 @@ void DisconnectUserStep2(XmppComponent* xmpp_component, AdhocSession& session, X } #ifdef USE_DATABASE -void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node) +void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); @@ -236,7 +236,7 @@ void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& com command_node.add_child(std::move(x)); } -void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node) +void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) { const XmlNode* x = command_node.get_child("x", "jabber:x:data"); if (x) @@ -314,7 +314,7 @@ void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& com } #endif // USE_DATABASE -void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { const Jid owner(session.get_owner_jid()); if (owner.bare() != Config::get("admin", "")) @@ -325,7 +325,7 @@ void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& } else { // Send a form to select the user to disconnect - auto biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = static_cast(xmpp_component); XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -341,7 +341,7 @@ void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& jids_field["label"] = "The JID to disconnect"; XmlNode required("required"); jids_field.add_child(std::move(required)); - for (Bridge* bridge: biboumi_component->get_bridges()) + for (Bridge* bridge: biboumi_component.get_bridges()) { XmlNode option("option"); option["label"] = bridge->get_jid(); @@ -355,7 +355,7 @@ void DisconnectUserFromServerStep1(XmppComponent* xmpp_component, AdhocSession& } } -void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { // If no JID is contained in the command node, it means we skipped the // previous stage, and the jid to disconnect is the executor's jid @@ -377,7 +377,7 @@ void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& // Send a data form to let the user choose which server to disconnect the // user from command_node.delete_all_children(); - auto biboumi_component = static_cast(xmpp_component); + auto& biboumi_component = static_cast(xmpp_component); XmlNode x("jabber:x:data:x"); x["type"] = "form"; @@ -393,7 +393,7 @@ void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& jids_field["label"] = "The servers to disconnect from"; XmlNode required("required"); jids_field.add_child(std::move(required)); - Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect); if (!bridge || bridge->get_irc_clients().empty()) { @@ -428,7 +428,7 @@ void DisconnectUserFromServerStep2(XmppComponent* xmpp_component, AdhocSession& command_node.add_child(std::move(x)); } -void DisconnectUserFromServerStep3(XmppComponent* xmpp_component, AdhocSession& session, XmlNode& command_node) +void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) { const auto it = session.vars.find("jid"); if (it == session.vars.end()) @@ -453,8 +453,8 @@ void DisconnectUserFromServerStep3(XmppComponent* xmpp_component, AdhocSession& } } - auto biboumi_component = static_cast(xmpp_component); - Bridge* bridge = biboumi_component->find_user_bridge(jid_to_disconnect); + auto& biboumi_component = static_cast(xmpp_component); + Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect); auto& clients = bridge->get_irc_clients(); std::size_t number = 0; diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 9377d13..1d30a27 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -7,14 +7,14 @@ class XmppComponent; -void DisconnectUserStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void DisconnectUserStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); -void ConfigureIrcServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void ConfigureIrcServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); -void DisconnectUserFromServerStep1(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void DisconnectUserFromServerStep2(XmppComponent*, AdhocSession& session, XmlNode& command_node); -void DisconnectUserFromServerStep3(XmppComponent*, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); #endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 9682dcb..e697fcd 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -43,7 +43,7 @@ static std::set kickable_errors{ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): XmppComponent(poller, hostname, secret), - irc_server_adhoc_commands_handler(this) + irc_server_adhoc_commands_handler(*this) { this->stanza_handlers.emplace("presence", std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1)); -- cgit v1.2.3 From 9167cdf1269c1956b72db1e8dfdbfd61cbf66bb9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Dec 2015 17:34:33 +0100 Subject: Notify systemd when the process is stopping --- src/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index a160df9..226b400 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,10 @@ # include #endif +#ifdef SYSTEMD_FOUND +# include +#endif + // A flag set by the SIGINT signal handler. static volatile std::atomic stop(false); // Flag set by the SIGUSR1/2 signal handler. @@ -136,6 +140,9 @@ int main(int ac, char** av) if (stop) { log_info("Signal received, exiting..."); +#ifdef SYSTEMD_FOUND + sd_notify(0, "STOPPING=1"); +#endif exiting = true; stop.store(false); xmpp_component->shutdown(); -- cgit v1.2.3 From 9ac0d3a5766494c9c0c2074c4a21542eea195a29 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 22 Dec 2015 21:37:29 +0100 Subject: A few cleanups, and make a few things more modern --- louloulibs/config/config.hpp | 18 ++++++------------ louloulibs/xmpp/xmpp_parser.cpp | 6 +++--- louloulibs/xmpp/xmpp_parser.hpp | 2 +- louloulibs/xmpp/xmpp_stanza.cpp | 10 ++++++++-- louloulibs/xmpp/xmpp_stanza.hpp | 9 +++++---- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/louloulibs/config/config.hpp b/louloulibs/config/config.hpp index e070816..688c081 100644 --- a/louloulibs/config/config.hpp +++ b/louloulibs/config/config.hpp @@ -7,7 +7,7 @@ * If you want to exit if the file does not exist when it is open for * reading, set Config::file_must_exist = true. * - * Config::get() can the be used to access the values in the conf. + * Config::get() can then be used to access the values in the conf. * * Use Config::close() when you're done getting/setting value. This will * save the config into the file. @@ -33,35 +33,29 @@ public: /** * returns a value from the config. If it doesn’t exist, use * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist */ static std::string get(const std::string&, const std::string&); /** * returns a value from the config. If it doesn’t exist, use * the second argument as the default. - * @param option The option we want - * @param def The default value in case the option does not exist */ static int get_int(const std::string&, const int&); /** * Set a value for the given option. And write all the config - * in the file from which it was read if boolean is set. - * @param option The option to set - * @param value The value to use - * @param save if true, save the config file + * in the file from which it was read if save is true. */ static void set(const std::string&, const std::string&, bool save = false); /** * Adds a function to a list. This function will be called whenever a - * configuration change occurs. + * configuration change occurs (when set() is called, or when the initial + * conf is read) */ static void connect(t_config_changed_callback); /** - * Close the config file, saving it to the file is save == true. + * Destroy the instance, forcing it to be recreated (with potentially + * different parameters) the next time it’s needed. */ static void close(); - /** * Set the value of the filename to use, before calling any method. */ diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index 25f2876..69de145 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -124,12 +124,12 @@ void XmppParser::end_element(const XML_Char*) } } -void XmppParser::char_data(const XML_Char* data, int len) +void XmppParser::char_data(const XML_Char* data, const size_t len) { if (this->current_node->has_children()) - this->current_node->get_last_child()->add_to_tail(std::string(data, len)); + this->current_node->get_last_child()->add_to_tail({data, len}); else - this->current_node->add_to_inner(std::string(data, len)); + this->current_node->add_to_inner({data, len}); } void XmppParser::stanza_event(const Stanza& stanza) const diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp index 4de639d..3474b13 100644 --- a/louloulibs/xmpp/xmpp_parser.hpp +++ b/louloulibs/xmpp/xmpp_parser.hpp @@ -82,7 +82,7 @@ public: /** * Some inner or tail data has been parsed */ - void char_data(const XML_Char* data, int len); + void char_data(const XML_Char* data, const size_t len); /** * Calls all the stanza_callbacks one by one. */ diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index d1c2e0f..f247436 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -199,6 +199,11 @@ void XmlNode::set_name(const std::string& name) this->name = name; } +void XmlNode::set_name(std::string&& name) +{ + this->name = std::move(name); +} + const std::string XmlNode::get_name() const { return this->name; @@ -228,7 +233,7 @@ bool XmlNode::has_children() const return !this->children.empty(); } -const std::string XmlNode::get_tag(const std::string& name) const +const std::string& XmlNode::get_tag(const std::string& name) const { try { @@ -237,7 +242,8 @@ const std::string XmlNode::get_tag(const std::string& name) const } catch (const std::out_of_range& e) { - return ""; + static const std::string def{}; + return def; } } diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index b1ba54a..bdc937f 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -96,6 +96,7 @@ public: XmlNode* get_last_child() const; XmlNode* get_parent() const; void set_name(const std::string& name); + void set_name(std::string&& name); const std::string get_name() const; /** * Serialize the stanza into a string @@ -110,7 +111,7 @@ public: * Gets the value for the given attribute, returns an empty string if the * node as no such attribute. */ - const std::string get_tag(const std::string& name) const; + const std::string& get_tag(const std::string& name) const; /** * Remove the attribute of the node. Does nothing if that attribute is not * present. Returns true if the tag was removed, false if it was absent. @@ -133,13 +134,13 @@ private: XmlNode& operator=(XmlNode&&) = delete; }; +std::ostream& operator<<(std::ostream& os, const XmlNode& node); + /** * An XMPP stanza is just an XML node of level 2 in the XMPP document (the * level 1 ones are the , and the ones above 2 are just the * content of the stanzas) */ -typedef XmlNode Stanza; - -std::ostream& operator<<(std::ostream& os, const XmlNode& node); +using Stanza = XmlNode; #endif // XMPP_STANZA_INCLUDED -- cgit v1.2.3 From 421c960df501b40e836a783400ab00dc60c3fdae Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 24 Dec 2015 21:39:53 +0100 Subject: Add a ChannelOptions table in the DB And a way to retrieve its values, defaulting on the ServerOptions for unset values. --- database/database.xml | 18 ++++++++++ src/database/database.cpp | 33 +++++++++++++++++++ src/database/database.hpp | 6 ++++ tests/database.cpp | 84 ++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 130 insertions(+), 11 deletions(-) diff --git a/database/database.xml b/database/database.xml index 1750b61..0688984 100644 --- a/database/database.xml +++ b/database/database.xml @@ -14,9 +14,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/database/database.cpp b/src/database/database.cpp index fd18417..0c7f425 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -49,6 +49,39 @@ db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, } } +db::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner, + const std::string& server, + const std::string& channel) +{ + try { + auto options = litesql::select(Database::get_db(), + db::IrcChannelOptions::Owner == owner && + db::IrcChannelOptions::Server == server && + db::IrcChannelOptions::Channel == channel).one(); + return options; + } catch (const litesql::NotFound& e) { + db::IrcChannelOptions options(Database::get_db()); + options.owner = owner; + options.server = server; + options.channel = channel; + return options; + } +} + +db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner, + const std::string& server, + const std::string& channel) +{ + auto coptions = Database::get_irc_channel_options(owner, server, channel); + auto soptions = Database::get_irc_server_options(owner, server); + if (coptions.encodingIn.value().empty()) + coptions.encodingIn = soptions.encodingIn; + if (coptions.encodingOut.value().empty()) + coptions.encodingOut = soptions.encodingOut; + + return coptions; +} + void Database::close() { Database::db.reset(nullptr); diff --git a/src/database/database.hpp b/src/database/database.hpp index d8dc735..fc957bd 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -29,6 +29,12 @@ public: */ static db::IrcServerOptions get_irc_server_options(const std::string& owner, const std::string& server); + static db::IrcChannelOptions get_irc_channel_options(const std::string& owner, + const std::string& server, + const std::string& channel); + static db::IrcChannelOptions get_irc_channel_options_with_server_default(const std::string& owner, + const std::string& server, + const std::string& channel); static void close(); diff --git a/tests/database.cpp b/tests/database.cpp index c0f4e9f..5d557fd 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -9,20 +9,82 @@ TEST_CASE("Database") #ifdef USE_DATABASE Config::set("db_name", ":memory:"); Database::set_verbose(false); - auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - o.update(); - auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); - auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - // b does not yet exist in the db, the object is created but not yet - // inserted - CHECK(1 == Database::count()); + SECTION("Basic retrieve and update") + { + auto o = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + o.update(); + auto a = Database::get_irc_server_options("zouzou@example.com", "irc.example.com"); + auto b = Database::get_irc_server_options("moumou@example.com", "irc.example.com"); - b.update(); - CHECK(2 == Database::count()); + // b does not yet exist in the db, the object is created but not yet + // inserted + CHECK(1 == Database::count()); - CHECK(b.pass == ""); - CHECK(b.pass.value() == ""); + b.update(); + CHECK(2 == Database::count()); + + CHECK(b.pass == ""); + CHECK(b.pass.value() == ""); + } + + SECTION("channel options") + { + Config::set("db_name", ":memory:"); + auto o = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); + + CHECK(o.encodingIn == ""); + o.encodingIn = "ISO-8859-1"; + o.update(); + auto b = Database::get_irc_channel_options("zouzou@example.com", "irc.example.com", "#foo"); + CHECK(o.encodingIn == "ISO-8859-1"); + } + + SECTION("Channel options with server default") + { + const std::string owner{"zouzou@example.com"}; + const std::string server{"irc.example.com"}; + const std::string chan1{"#foo"}; + + auto c = Database::get_irc_channel_options(owner, server, chan1); + auto s = Database::get_irc_server_options(owner, server); + + GIVEN("An option defined for the channel but not the server") + { + c.encodingIn = "channelEncoding"; + c.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the channel option") + CHECK(r.encodingIn == "channelEncoding"); + } + } + GIVEN("An option defined for the server but not the channel") + { + s.encodingIn = "serverEncoding"; + s.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the server option") + CHECK(r.encodingIn == "serverEncoding"); + } + } + GIVEN("An option defined for both the server and the channel") + { + s.encodingIn = "serverEncoding"; + s.update(); + c.encodingIn = "channelEncoding"; + c.update(); + WHEN("we fetch that option") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, chan1); + THEN("we get the channel option") + CHECK(r.encodingIn == "channelEncoding"); + } + } + } Database::close(); #endif -- cgit v1.2.3 From 1fc3fa1bd8d98d45d18e8af76202cbf6b757b369 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 24 Dec 2015 15:50:29 +0100 Subject: Add an ad-hoc configure command on IRC channels Include encodingIn and encodingOut options, unused at the moment --- src/xmpp/biboumi_adhoc_commands.cpp | 90 +++++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 3 ++ src/xmpp/biboumi_component.cpp | 16 ++++++- src/xmpp/biboumi_component.hpp | 1 + 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index f4a6bc8..d115ae7 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -312,6 +312,96 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com command_node.add_child(std::move(error)); session.terminate(); } + +void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + const Iid iid(target.local); + auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain, + iid.get_server(), iid.get_local()); + + XmlNode x("jabber:x:data:x"); + x["type"] = "form"; + XmlNode title("title"); + title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server()); + x.add_child(std::move(title)); + XmlNode instructions("instructions"); + instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local()); + x.add_child(std::move(instructions)); + + XmlNode required("required"); + + XmlNode encoding_out("field"); + encoding_out["var"] = "encoding_out"; + encoding_out["type"] = "text-single"; + encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel"; + encoding_out["label"] = "Out encoding"; + if (!options.encodingOut.value().empty()) + { + XmlNode encoding_out_value("value"); + encoding_out_value.set_inner(options.encodingOut.value()); + encoding_out.add_child(std::move(encoding_out_value)); + } + encoding_out.add_child(required); + x.add_child(std::move(encoding_out)); + + XmlNode encoding_in("field"); + encoding_in["var"] = "encoding_in"; + encoding_in["type"] = "text-single"; + encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel"; + encoding_in["label"] = "In encoding"; + if (!options.encodingIn.value().empty()) + { + XmlNode encoding_in_value("value"); + encoding_in_value.set_inner(options.encodingIn.value()); + encoding_in.add_child(std::move(encoding_in_value)); + } + encoding_in.add_child(required); + x.add_child(std::move(encoding_in)); + + command_node.add_child(std::move(x)); +} + +void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) +{ + const XmlNode* x = command_node.get_child("x", "jabber:x:data"); + if (x) + { + const Jid owner(session.get_owner_jid()); + const Jid target(session.get_target_jid()); + const Iid iid(target.local); + auto options = Database::get_irc_channel_options(owner.local + "@" + owner.domain, + iid.get_server(), iid.get_local()); + for (const XmlNode* field: x->get_children("field", "jabber:x:data")) + { + const XmlNode* value = field->get_child("value", "jabber:x:data"); + + if (field->get_tag("var") == "encoding_out" && + value && !value->get_inner().empty()) + options.encodingOut = value->get_inner(); + + else if (field->get_tag("var") == "encoding_in" && + value && !value->get_inner().empty()) + options.encodingIn = value->get_inner(); + } + + options.update(); + + command_node.delete_all_children(); + XmlNode note("note"); + note["type"] = "info"; + note.set_inner("Configuration successfully applied."); + command_node.add_child(std::move(note)); + return; + } + XmlNode error(ADHOC_NS":error"); + error["type"] = "modify"; + XmlNode condition(STANZA_NS":bad-request"); + error.add_child(std::move(condition)); + command_node.add_child(std::move(error)); + session.terminate(); +} #endif // USE_DATABASE void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node) diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index 1d30a27..ffa8be4 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -13,6 +13,9 @@ void DisconnectUserStep2(XmppComponent&, AdhocSession& session, XmlNode& command void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); +void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); + void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e697fcd..e30bdd3 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -43,7 +43,8 @@ static std::set kickable_errors{ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret): XmppComponent(poller, hostname, secret), - irc_server_adhoc_commands_handler(*this) + irc_server_adhoc_commands_handler(*this), + irc_channel_adhoc_commands_handler(*this) { this->stanza_handlers.emplace("presence", std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1)); @@ -65,6 +66,9 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st {"configure", AdhocCommand({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false)}, #endif }; + this->irc_channel_adhoc_commands_handler.get_commands() = { + {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)}, + }; } void BiboumiComponent::shutdown() @@ -331,6 +335,8 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) AdhocCommandsHandler* adhoc_handler; if (!to.local.empty() && !iid.is_user && !iid.is_channel) adhoc_handler = &this->irc_server_adhoc_commands_handler; + else if (!to.local.empty() && iid.is_channel) + adhoc_handler = &this->irc_channel_adhoc_commands_handler; else adhoc_handler = &this->adhoc_commands_handler; @@ -407,6 +413,14 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) this->irc_server_adhoc_commands_handler); stanza_error.disable(); } + else if (!iid.is_user && iid.is_channel) + { // Get the channel's adhoc commands + this->send_adhoc_commands_list(id, from, to_str, + (Config::get("admin", "") == + from_jid.bare()), + this->irc_channel_adhoc_commands_handler); + stanza_error.disable(); + } } else if (node.empty() && !iid.is_user && !iid.is_channel) { // Disco on an IRC server: get the list of channels diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 69eebdc..7602332 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -98,6 +98,7 @@ private: std::unordered_map> bridges; AdhocCommandsHandler irc_server_adhoc_commands_handler; + AdhocCommandsHandler irc_channel_adhoc_commands_handler; BiboumiComponent(const BiboumiComponent&) = delete; BiboumiComponent(BiboumiComponent&&) = delete; -- cgit v1.2.3 From cb0209bbbe4391436326350d6e176c90b8e8e88b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Dec 2015 16:23:04 +0100 Subject: Also provide in and out encoding configure options for IRC servers --- src/xmpp/biboumi_adhoc_commands.cpp | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index d115ae7..be755e9 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -233,6 +233,35 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com x.add_child(std::move(realname)); } + XmlNode encoding_out("field"); + encoding_out["var"] = "encoding_out"; + encoding_out["type"] = "text-single"; + encoding_out["desc"] = "The encoding used when sending messages to the IRC server."; + encoding_out["label"] = "Out encoding"; + if (!options.encodingOut.value().empty()) + { + XmlNode encoding_out_value("value"); + encoding_out_value.set_inner(options.encodingOut.value()); + encoding_out.add_child(std::move(encoding_out_value)); + } + encoding_out.add_child(required); + x.add_child(std::move(encoding_out)); + + XmlNode encoding_in("field"); + encoding_in["var"] = "encoding_in"; + encoding_in["type"] = "text-single"; + encoding_in["desc"] = "The encoding used to decode message received from the IRC server."; + encoding_in["label"] = "In encoding"; + if (!options.encodingIn.value().empty()) + { + XmlNode encoding_in_value("value"); + encoding_in_value.set_inner(options.encodingIn.value()); + encoding_in.add_child(std::move(encoding_in_value)); + } + encoding_in.add_child(required); + x.add_child(std::move(encoding_in)); + + command_node.add_child(std::move(x)); } @@ -294,6 +323,15 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com else if (field->get_tag("var") == "realname" && value && !value->get_inner().empty()) options.realname = value->get_inner(); + + else if (field->get_tag("var") == "encoding_out" && + value && !value->get_inner().empty()) + options.encodingOut = value->get_inner(); + + else if (field->get_tag("var") == "encoding_in" && + value && !value->get_inner().empty()) + options.encodingIn = value->get_inner(); + } options.update(); -- cgit v1.2.3 From b0c3aa0be960443b3959d0026f29f1dba18a08c3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Dec 2015 16:23:38 +0100 Subject: Defaults the encoding in the DB to latin-1 --- database/database.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/database.xml b/database/database.xml index 0688984..a22b49d 100644 --- a/database/database.xml +++ b/database/database.xml @@ -14,8 +14,8 @@ - - + + -- cgit v1.2.3 From 79cdf170d2ab6c5378cfbf61d5c8888a4c666190 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 28 Dec 2015 21:25:48 +0100 Subject: Use the configured encoding value when decoding received messages --- louloulibs/xmpp/xmpp_stanza.cpp | 16 ++++++++-------- louloulibs/xmpp/xmpp_stanza.hpp | 2 +- src/bridge/bridge.cpp | 40 ++++++++++++++++++++++++++++++++++------ src/bridge/bridge.hpp | 4 ++-- tests/database.cpp | 6 ++++++ 5 files changed, 51 insertions(+), 17 deletions(-) diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index f247436..407e631 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -84,6 +84,14 @@ std::string xml_unescape(const std::string& data) return res; } +std::string sanitize(const std::string& data, const std::string& encoding) +{ + if (utils::is_valid_utf8(data.data())) + return xml_escape(utils::remove_invalid_xml_chars(data)); + else + return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, encoding.data()))); +} + XmlNode::XmlNode(const std::string& name, XmlNode* parent): parent(parent) { @@ -259,14 +267,6 @@ std::string& XmlNode::operator[](const std::string& name) return this->attributes[name]; } -std::string sanitize(const std::string& data) -{ - if (utils::is_valid_utf8(data.data())) - return xml_escape(utils::remove_invalid_xml_chars(data)); - else - return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1"))); -} - std::ostream& operator<<(std::ostream& os, const XmlNode& node) { return os << node.to_string(); diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index bdc937f..77ab206 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -8,7 +8,7 @@ std::string xml_escape(const std::string& data); std::string xml_unescape(const std::string& data); -std::string sanitize(const std::string& data); +std::string sanitize(const std::string& data, const std::string& encoding = "ISO-8859-1"); /** * Represent an XML node. It has diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 55f4c51..4c97a91 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -9,11 +9,35 @@ #include #include #include +#include using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; + +static std::string out_encoding_for(const Bridge& bridge, const Iid& iid) +{ +#ifdef USE_DATABASE + const auto jid = bridge.get_bare_jid(); + auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); + return options.encodingOut.value(); +#else + return {"ISO-8859-1"}; +#endif +} + +static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) +{ +#ifdef USE_DATABASE + const auto jid = bridge.get_bare_jid(); + auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); + return options.encodingIn.value(); +#else + return {"ISO-8859-1"}; +#endif +} + Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr poller): user_jid(user_jid), xmpp(xmpp), @@ -75,13 +99,13 @@ std::string Bridge::get_bare_jid() const return jid.local + "@" + jid.domain; } -Xmpp::body Bridge::make_xmpp_body(const std::string& str) +Xmpp::body Bridge::make_xmpp_body(const std::string& str, const std::string& encoding) { std::string res; if (utils::is_valid_utf8(str.c_str())) res = str; else - res = utils::convert_to_utf8(str, "ISO-8859-1"); + res = utils::convert_to_utf8(str, encoding.data()); return irc_format_to_xhtmlim(res); } @@ -531,9 +555,10 @@ void Bridge::send_irc_version_request(const std::string& irc_hostname, const std void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc) { + const auto encoding = in_encoding_for(*this, iid); if (muc) this->xmpp.send_muc_message(std::to_string(iid), nick, - this->make_xmpp_body(body), this->user_jid); + this->make_xmpp_body(body, encoding), this->user_jid); else { std::string target = std::to_string(iid); @@ -544,7 +569,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st target = it->second; fulljid = true; } - this->xmpp.send_message(target, this->make_xmpp_body(body), + this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), this->user_jid, "chat", fulljid); } } @@ -590,7 +615,9 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho } else body = msg; - this->xmpp.send_message(from, this->make_xmpp_body(body), this->user_jid, "chat"); + + const auto encoding = in_encoding_for(*this, {from}); + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid, "chat"); } void Bridge::send_user_join(const std::string& hostname, @@ -609,7 +636,8 @@ void Bridge::send_user_join(const std::string& hostname, void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic) { - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic), this->user_jid); + const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid); } std::string Bridge::get_own_nick(const Iid& iid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index e73bd19..c030ed8 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -49,7 +49,7 @@ public: const std::string& get_jid() const; std::string get_bare_jid() const; - static Xmpp::body make_xmpp_body(const std::string& str); + static Xmpp::body make_xmpp_body(const std::string& str, const std::string& encodin = "ISO-8859-1"); /*** ** ** From XMPP to IRC. @@ -108,7 +108,7 @@ public: /** * Send a message corresponding to a server NOTICE, the from attribute - * should be juste the server hostname@irc.component. + * should be juste the server hostname. */ void send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg); /** diff --git a/tests/database.cpp b/tests/database.cpp index 5d557fd..b059d0d 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -83,6 +83,12 @@ TEST_CASE("Database") THEN("we get the channel option") CHECK(r.encodingIn == "channelEncoding"); } + WHEN("we fetch that option, with no channel specified") + { + auto r = Database::get_irc_channel_options_with_server_default(owner, server, ""); + THEN("we get the server option") + CHECK(r.encodingIn == "serverEncoding"); + } } } -- cgit v1.2.3 From dfcb0a6e0e975b7a4fd3b9ca3cb340fc1859d7f9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Dec 2015 10:05:40 +0100 Subject: This is no longer true, with the recent xmpp_server_ip configuration option --- doc/biboumi.1.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index bb18475..085a754 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -132,8 +132,7 @@ The configuration file uses a simple format of the form specified, it will use the one assigned by the operating system. You can for example use outgoing_bind=192.168.1.11 to force biboumi to use the interface with this address. Note that this is only used for connections - to IRC servers, the connection to the XMPP server is always done locally - on 127.0.0.1. + to IRC servers. The configuration can be re-read at runtime (you can for example change the log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 -- cgit v1.2.3 From a38b17692e0297cbd5d719f059bd0a1b6ef39fe4 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 29 Dec 2015 11:19:39 +0100 Subject: Support multi-prefix See http://ircv3.net/specs/extensions/multi-prefix-3.1.html ref #3103 --- src/irc/irc_client.cpp | 3 +++ src/irc/irc_user.cpp | 18 ++++++++++++++---- tests/iid.cpp | 10 +++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4aa45ac..e71d38c 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -151,6 +151,9 @@ void IrcClient::on_connected() } } + this->send_message({"CAP", {"REQ", "multi-prefix"}}); + this->send_message({"CAP", {"END"}}); + #ifdef USE_DATABASE auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->get_hostname()); diff --git a/src/irc/irc_user.cpp b/src/irc/irc_user.cpp index 8785270..9fa3612 100644 --- a/src/irc/irc_user.cpp +++ b/src/irc/irc_user.cpp @@ -7,8 +7,20 @@ IrcUser::IrcUser(const std::string& name, { if (name.empty()) return ; - const std::map::const_iterator prefix = prefix_to_mode.find(name[0]); - const std::string::size_type name_begin = prefix == prefix_to_mode.end()? 0: 1; + + // One or more prefix (with multi-prefix support) may come before the + // actual nick + std::string::size_type name_begin = 0; + while (name_begin != name.size()) + { + const auto prefix = prefix_to_mode.find(name[name_begin]); + // This is not a prefix + if (prefix == prefix_to_mode.end()) + break; + this->modes.insert(prefix->second); + name_begin++; + } + const std::string::size_type sep = name.find("!", name_begin); if (sep == std::string::npos) this->nick = name.substr(name_begin); @@ -17,8 +29,6 @@ IrcUser::IrcUser(const std::string& name, this->nick = name.substr(name_begin, sep-name_begin); this->host = name.substr(sep+1); } - if (prefix != prefix_to_mode.end()) - this->modes.insert(prefix->second); } IrcUser::IrcUser(const std::string& name): diff --git a/tests/iid.cpp b/tests/iid.cpp index a90c208..74d010d 100644 --- a/tests/iid.cpp +++ b/tests/iid.cpp @@ -8,7 +8,6 @@ TEST_CASE("Irc user parsing") { const std::map prefixes{{'!', 'a'}, {'@', 'o'}}; - IrcUser user1("!nick!~some@host.bla", prefixes); CHECK(user1.nick == "nick"); CHECK(user1.host == "~some@host.bla"); @@ -22,6 +21,15 @@ TEST_CASE("Irc user parsing") CHECK(user2.modes.find('a') == user2.modes.end()); } +TEST_CASE("multi-prefix") +{ + const std::map prefixes{{'!', 'a'}, {'@', 'o'}, {'~', 'f'}}; + IrcUser user("!@~nick", prefixes); + CHECK(user.nick == "nick"); + CHECK(user.modes.size() == 3); + CHECK(user.modes.find('f') != user.modes.end()); +} + /** * Let Catch know how to display Iid objects */ -- cgit v1.2.3 From 1f8333f23f060750673d0b7c573f2e2d12142adf Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 30 Dec 2015 21:34:11 +0100 Subject: Support a trusted SHA1 fingerprint to be configured for each IRC server --- database/database.xml | 1 + louloulibs/network/credentials_manager.cpp | 15 ++++++++++++++- louloulibs/network/credentials_manager.hpp | 2 ++ louloulibs/network/tcp_socket_handler.hpp | 2 ++ src/irc/irc_client.cpp | 7 +++++++ src/xmpp/biboumi_adhoc_commands.cpp | 22 +++++++++++++++++++++- 6 files changed, 47 insertions(+), 2 deletions(-) diff --git a/database/database.xml b/database/database.xml index a22b49d..f102db0 100644 --- a/database/database.xml +++ b/database/database.xml @@ -13,6 +13,7 @@ + diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index 57100ee..f92aef8 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -26,11 +26,17 @@ bool Basic_Credentials_Manager::certs_loaded = false; Basic_Credentials_Manager::Basic_Credentials_Manager(const TCPSocketHandler* const socket_handler): Botan::Credentials_Manager(), - socket_handler(socket_handler) + socket_handler(socket_handler), + trusted_fingerprint{} { this->load_certs(); } +void Basic_Credentials_Manager::set_trusted_fingerprint(const std::string& fingerprint) +{ + this->trusted_fingerprint = fingerprint; +} + void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector& certs) @@ -44,6 +50,13 @@ void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type catch (const std::exception& tls_exception) { log_warning("TLS certificate check failed: " << tls_exception.what()); + if (!this->trusted_fingerprint.empty() && !certs.empty() && + this->trusted_fingerprint == certs[0].fingerprint() && + certs[0].matches_dns_name(purported_hostname)) + // We trust the certificate, based on the trusted fingerprint and + // the fact that the hostname matches + return; + if (this->socket_handler->abort_on_invalid_cert()) throw; } diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index e292321..dbd1d95 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -19,6 +19,7 @@ public: const std::vector&) override final; std::vector trusted_certificate_authorities(const std::string& type, const std::string& context) override final; + void set_trusted_fingerprint(const std::string& fingerprint); private: const TCPSocketHandler* const socket_handler; @@ -26,6 +27,7 @@ private: static void load_certs(); static Botan::Certificate_Store_In_Memory certificate_store; static bool certs_loaded; + std::string trusted_fingerprint; }; #endif //BOTAN_FOUND diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 9f5caa3..fb4195d 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -248,7 +248,9 @@ private: static Botan::AutoSeeded_RNG rng; static Botan::TLS::Policy policy; static Botan::TLS::Session_Manager_In_Memory session_manager; +protected: Basic_Credentials_Manager credential_manager; +private: /** * We use a unique_ptr because we may not want to create the object at * all. The Botan::TLS::Client object generates a handshake message and diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e71d38c..1a83446 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -89,6 +89,13 @@ void IrcClient::start() this->bind_addr = Config::get("outgoing_bind", ""); +#ifdef BOTAN_FOUND +# ifdef USE_DATABASE + auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), + this->get_hostname()); + this->credential_manager.set_trusted_fingerprint(options.trustedFingerprint); +# endif +#endif this->connect(this->hostname, port, tls); } diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index be755e9..7c157cb 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -175,6 +175,19 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com verify_cert_value.set_inner("false"); verify_cert.add_child(std::move(verify_cert_value)); x.add_child(std::move(verify_cert)); + + XmlNode fingerprint("field"); + fingerprint["var"] = "fingerprint"; + fingerprint["type"] = "text-single"; + fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust."; + if (!options.trustedFingerprint.value().empty()) + { + XmlNode fingerprint_value("value"); + fingerprint_value.set_inner(options.trustedFingerprint.value()); + fingerprint.add_child(std::move(fingerprint_value)); + } + fingerprint.add_child(required); + x.add_child(std::move(fingerprint)); #endif XmlNode pass("field"); @@ -295,12 +308,19 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com options.tlsPorts = ports; } - else if (field->get_tag("var") == "verify_cert" && value + else if (field->get_tag("var") == "verify_cert" && value && !value->get_inner().empty()) { auto val = to_bool(value->get_inner()); options.verifyCert = val; } + + else if (field->get_tag("var") == "fingerprint" && value && + !value->get_inner().empty()) + { + options.trustedFingerprint = value->get_inner(); + } + #endif // BOTAN_FOUND else if (field->get_tag("var") == "pass" && -- cgit v1.2.3 From 8ca818f8abab5fba668dfd54268de72cdfa3d95d Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 30 Dec 2015 20:04:21 +0100 Subject: Document the Configure ad-hoc commands --- doc/biboumi.1.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md index 085a754..3a1fb28 100644 --- a/doc/biboumi.1.md +++ b/doc/biboumi.1.md @@ -401,6 +401,9 @@ Affiliation set to `owner` ### Ad-hoc commands Biboumi supports a few ad-hoc commands, as described in the XEP 0050. +Different ad-hoc commands are available for each JID type. + +#### On the gateway itself (e.g on the JID biboumi.example.com): - ping: Just respond “pong” @@ -421,6 +424,20 @@ Biboumi supports a few ad-hoc commands, as described in the XEP 0050. with an XMPP message. The administrator can disconnect any user, while the other users can only disconnect themselves. +#### On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) + + - Configure: Lets each user configure some options that applies to the + concerned IRC server. + +#### On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) + + - Configure: Lets each user configure some options that applies to the + concerned IRC channel. Some of these options, if not configured for a + specific channel, defaults to the value configured at the IRC server + level. For example the encoding can be specified for both the channel + and the server. If an encoding is not specified for a channel, the + encoding configured in the server applies. + ### Raw IRC messages Biboumi tries to support as many IRC features as possible, but doesn’t -- cgit v1.2.3 From 4233e1f6538351e3bf3c15c88454455998b718e5 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sat, 9 Jan 2016 17:00:52 +0100 Subject: Do not fail to build when litesql is not used fix #3151 --- src/xmpp/biboumi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e30bdd3..564c79a 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -67,7 +67,9 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st #endif }; this->irc_channel_adhoc_commands_handler.get_commands() = { +#ifdef USE_DATABASE {"configure", AdhocCommand({&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false)}, +#endif }; } -- cgit v1.2.3 From 25475b60ab363299ddbe5e81ded8968b76838b44 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 19:44:58 +0100 Subject: Generate systemd file from template and make installs it and the conf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unit file Type=, as well as the executable path should now be correct with a simple “make install”, whatever the install prefix and the DESTDIR values are. ref #3152 --- CMakeLists.txt | 18 ++++++++++++------ packaging/biboumi.spec | 12 ++++-------- unit/biboumi.service | 15 --------------- unit/biboumi.service.cmake | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 29 deletions(-) delete mode 100644 unit/biboumi.service create mode 100644 unit/biboumi.service.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c27477..3ff7be6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,11 +200,10 @@ endif() # ## Install target # -install(TARGETS ${PROJECT_NAME} - RUNTIME DESTINATION bin) -if(WITH_DOC) - install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL) -endif() +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) +install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL COMPONENT documentation) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION /etc/systemd/system COMPONENT init) +install(FILES conf/biboumi.cfg DESTINATION /etc/biboumi COMPONENT configuration) # ## Dist target @@ -228,4 +227,11 @@ endif() add_custom_target(PrintBuildParameters ALL ${CMAKE_COMMAND} -E cmake_echo_color --cyan "Compiling ${PROJECT_NAME} with ${STR_WITH_BOTAN}, ${STR_WITH_CARES}") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/biboumi.h.cmake ${CMAKE_BINARY_DIR}/src/biboumi.h) +configure_file(biboumi.h.cmake src/biboumi.h) + +if(SYSTEMD_FOUND) + set(SYSTEMD_SERVICE_TYPE "notify") +else() + set(SYSTEMD_SERVICE_TYPE "simple") +endif() +configure_file(unit/biboumi.service.cmake biboumi.service) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index fb9a30f..e375504 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -49,14 +49,6 @@ LC_ALL=en_GB.utf-8 make doc %install make install DESTDIR=%{buildroot} -# Default config file -install -D -p -m 644 conf/biboumi.cfg \ - %{buildroot}%{biboumi_confdir}/biboumi.cfg - -# Systemd unit file -install -D -p -m 644 unit/%{name}.service \ - %{buildroot}%{_unitdir}/%{name}.service - %check make test_suite/fast VERBOSE=1 @@ -73,6 +65,10 @@ make test_suite/fast VERBOSE=1 %changelog +* Wed Jan 13 2016 Le Coz Florent - 2.0-2 +- Do not install the systemd unit and configuration files, because + “make install” does it itself now + * Fri May 29 2015 Le Coz Florent - 2.0-1 - Update to 2.0 sources diff --git a/unit/biboumi.service b/unit/biboumi.service deleted file mode 100644 index 59001a6..0000000 --- a/unit/biboumi.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Biboumi, XMPP to IRC gateway -After=network.target - -[Service] -Type=notify -ExecStart=/usr/bin/biboumi /etc/biboumi/biboumi.cfg -ExecReload=/bin/kill -s USR1 $MAINPID -WatchdogSec=10 -Restart=always -User=nobody -Group=nobody - -[Install] -WantedBy=multi-user.target diff --git a/unit/biboumi.service.cmake b/unit/biboumi.service.cmake new file mode 100644 index 0000000..0a9b76b --- /dev/null +++ b/unit/biboumi.service.cmake @@ -0,0 +1,15 @@ +[Unit] +Description=Biboumi, XMPP to IRC gateway +After=network.target + +[Service] +Type=${SYSTEMD_SERVICE_TYPE} +ExecStart=${CMAKE_INSTALL_PREFIX}/bin/biboumi /etc/biboumi/biboumi.cfg +ExecReload=/bin/kill -s USR1 $MAINPID +WatchdogSec=10 +Restart=always +User=nobody +Group=nobody + +[Install] +WantedBy=multi-user.target -- cgit v1.2.3 From 1e79705628af79b2cf6f87a67268f8202774bb9b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 13:50:34 +0100 Subject: Build the doc with the default make target --- CMakeLists.txt | 3 ++- packaging/biboumi.spec | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ff7be6..3683c39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ if(RONN_EXECUTABLE) add_custom_command(OUTPUT ${MAN_PAGE} COMMAND ${RONN_EXECUTABLE} --roff < ${DOC_PAGE} > ${MAN_PAGE} DEPENDS ${DOC_PAGE}) - add_custom_target(doc DEPENDS ${MAN_PAGE}) + add_custom_target(doc ALL DEPENDS ${MAN_PAGE}) endif() # Look for litesql and enable the database if found @@ -124,6 +124,7 @@ target_link_libraries(xmpp xmpplib bridge network utils logger) if(USE_DATABASE) target_link_libraries(xmpp database) + target_link_libraries(irc database) endif() # diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index e375504..ce57800 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -39,11 +39,10 @@ cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ -DWITH_SYSTEMD=1 \ -DWITH_LIBIDN=1 -make %{?_smp_mflags} - # The documentation is in utf-8, ronn fails to build it if that locale is # not specified -LC_ALL=en_GB.utf-8 make doc +export LC_ALL=en_GB.utf-8 +make %{?_smp_mflags} %install -- cgit v1.2.3 From 6bebb2f67b73219c455579d5d414418e317e06b9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 13:51:34 +0100 Subject: Remove the second '-' from the package when ~dev is there --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3683c39..f38d5d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo # set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES ".+") - set(ARCHIVE_NAME ${ARCHIVE_NAME}-${${PROJECT_NAME}_VERSION_SUFFIX}) + set(ARCHIVE_NAME ${ARCHIVE_NAME}${${PROJECT_NAME}_VERSION_SUFFIX}) endif() if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") @@ -38,7 +38,7 @@ if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") OUTPUT_STRIP_TRAILING_WHITESPACE) if(GIT_REVISION) set(${PROJECT_NAME}_VERSION_SUFFIX "${${PROJECT_NAME}_VERSION_SUFFIX} (${GIT_REVISION})") - set(ARCHIVE_NAME ${ARCHIVE_NAME}-${GIT_REVISION}) + set(ARCHIVE_NAME ${ARCHIVE_NAME}${GIT_REVISION}) endif() endif() endif() -- cgit v1.2.3 From 5da0158fdb4a77512555a82ead49b7e794824e3b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 21:52:57 +0100 Subject: Use make check in the spec file --- packaging/biboumi.spec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index ce57800..6961874 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -50,9 +50,7 @@ make install DESTDIR=%{buildroot} %check -make test_suite/fast VERBOSE=1 - -./test_suite || exit 1 +make check %files -- cgit v1.2.3 From 3497b330dc4aa848582da085e94ac20e6871731e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 23:24:44 +0100 Subject: If catch.hpp is found in tests/, use it without cloning the git repo --- CMakeLists.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f38d5d6..562b21f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ if(USE_DATABASE) target_link_libraries(test_suite database) endif() + include(ExternalProject) ExternalProject_Add(catch GIT_REPOSITORY "https://github.com/philsquared/Catch.git" @@ -177,13 +178,15 @@ ExternalProject_Add(catch CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" -) + ) set_target_properties(catch PROPERTIES EXCLUDE_FROM_ALL TRUE) ExternalProject_Get_Property(catch SOURCE_DIR) -target_include_directories(test_suite - PUBLIC "${SOURCE_DIR}/include/" -) -add_dependencies(test_suite catch) +if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) + target_include_directories(test_suite + PUBLIC "${SOURCE_DIR}/include/" + ) + add_dependencies(test_suite catch) +endif() add_custom_target(check COMMAND "test_suite" DEPENDS test_suite) -- cgit v1.2.3 From 85dfe066655654eca18d180e5b165485bcbc20ab Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 13 Jan 2016 16:13:45 +0100 Subject: Include catch.hpp in the dist tarball --- CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 562b21f..1e8f7dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,7 +215,13 @@ install(FILES conf/biboumi.cfg DESTINATION /etc/bib # add_custom_target(dist COMMAND git archive --prefix=${ARCHIVE_NAME}/ --format=tar HEAD - | xz > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz + > ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar + # Append this specific file that is not part of the git repo + COMMAND tar -rf ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar -P ${SOURCE_DIR}/single_include/catch.hpp --xform 's|/.*/|${ARCHIVE_NAME}/tests/|g' + # Remove a potential existing archive + COMMAND rm -f ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz + # Compress the archive + COMMAND xz ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) if(BOTAN_FOUND) -- cgit v1.2.3 From 3fdc4cb334d04dd6688299271bc30f9b9ccb6c02 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Jan 2016 00:25:17 +0100 Subject: Install the unit file in lib/, not etc/ --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e8f7dd..f2f521f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,7 +206,7 @@ endif() # install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) install(FILES ${MAN_PAGE} DESTINATION share/man/man1 OPTIONAL COMPONENT documentation) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION /etc/systemd/system COMPONENT init) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/biboumi.service DESTINATION lib/systemd/system COMPONENT init) install(FILES conf/biboumi.cfg DESTINATION /etc/biboumi COMPONENT configuration) # -- cgit v1.2.3 From 1f8e2431647169f4b377dd16520fba7b247378c9 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Jan 2016 00:28:53 +0100 Subject: Display an info message on make dist --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2f521f..b51444a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -222,6 +222,7 @@ add_custom_target(dist COMMAND rm -f ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz # Compress the archive COMMAND xz ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "${ARCHIVE_NAME}.tar.xz created." WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) if(BOTAN_FOUND) -- cgit v1.2.3 From ed49e067a0d2fe25f4e3cd653258ad24a2772893 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Jan 2016 00:32:04 +0100 Subject: Compile with smp_mflags for tests as well --- packaging/biboumi.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec index 6961874..5c3afb4 100644 --- a/packaging/biboumi.spec +++ b/packaging/biboumi.spec @@ -50,7 +50,7 @@ make install DESTDIR=%{buildroot} %check -make check +make check %{?_smp_mflags} %files -- cgit v1.2.3 From 0864f2ab99073b62e4173da694a13aa4ccc8b554 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Jan 2016 00:39:08 +0100 Subject: Quiet a warning --- louloulibs/network/tcp_socket_handler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 6ed981c..81369dd 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -63,14 +63,14 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) // used in bind() struct addrinfo* result; int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result); - if (err != 0) + if (err != 0 || !result) log_error("Failed to bind socket to " << this->bind_addr << ": " << gai_strerror(err)); else { utils::ScopeGuard sg([result](){ freeaddrinfo(result); }); struct addrinfo* rp; - int bind_error; + int bind_error = 0; for (rp = result; rp; rp = rp->ai_next) { if ((bind_error = ::bind(this->socket, -- cgit v1.2.3 From d58dcde856aa1885e0eb4a1286eeaa6e4861557b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 14 Jan 2016 14:33:49 +0100 Subject: =?UTF-8?q?Only=20activate=20systemd=E2=80=99s=20watchdog=20if=20w?= =?UTF-8?q?e=20are=20compiling=20with=20systemd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ref #3152 --- CMakeLists.txt | 2 ++ unit/biboumi.service.cmake | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b51444a..ba3213b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,7 +242,9 @@ configure_file(biboumi.h.cmake src/biboumi.h) if(SYSTEMD_FOUND) set(SYSTEMD_SERVICE_TYPE "notify") + set(WATCHDOG_SEC "20") else() set(SYSTEMD_SERVICE_TYPE "simple") + set(WATCHDOG_SEC "") endif() configure_file(unit/biboumi.service.cmake biboumi.service) diff --git a/unit/biboumi.service.cmake b/unit/biboumi.service.cmake index 0a9b76b..4685f43 100644 --- a/unit/biboumi.service.cmake +++ b/unit/biboumi.service.cmake @@ -1,12 +1,13 @@ [Unit] Description=Biboumi, XMPP to IRC gateway +Documentation=man:biboumi(1) http://biboumi.louiz.org After=network.target [Service] Type=${SYSTEMD_SERVICE_TYPE} ExecStart=${CMAKE_INSTALL_PREFIX}/bin/biboumi /etc/biboumi/biboumi.cfg ExecReload=/bin/kill -s USR1 $MAINPID -WatchdogSec=10 +WatchdogSec=${WATCHDOG_SEC} Restart=always User=nobody Group=nobody -- cgit v1.2.3 From ccfdb60cc1b63cec2a97eaaaec23ca547b7f2a09 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 15 Jan 2016 11:54:33 +0100 Subject: Fix a build when litesql is found but not botan fix #3157 --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ba3213b..7e356c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,7 +84,10 @@ if(LITESQL_FOUND) add_library(database STATIC src/database/database.cpp ${LITESQL_GENERATED_SOURCES}) - target_link_libraries(database ${LITESQL_LIBRARIES} ${BOTAN_LIBRARIES}) + target_link_libraries(database ${LITESQL_LIBRARIES}) + if(BOTAN_FOUND) + target_link_libraries(database ${BOTAN_LIBRARIES}) + endif() set(USE_DATABASE TRUE) endif() -- cgit v1.2.3 From 0c85ed128a650aa4dde7d919b4165162225a818f Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 15 Jan 2016 12:14:49 +0100 Subject: =?UTF-8?q?Remove=20the=20localhost6=20test,=20that=E2=80=99s=20no?= =?UTF-8?q?t=20portable=20at=20all?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/dns.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/dns.cpp b/tests/dns.cpp index e8cbc7f..4ec1b96 100644 --- a/tests/dns.cpp +++ b/tests/dns.cpp @@ -65,12 +65,6 @@ TEST_CASE("DNS resolver") loop(); CHECK(!success); - hostname = "localhost6"; - resolver.resolve(hostname, port, - success_cb, error_cb); - loop(); - CHECK(success); - hostname = "localhost"; resolver.resolve(hostname, port, success_cb, error_cb); -- cgit v1.2.3 From ae7c54f3737b3646edfa5c4d4d7fd553aa296da6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 22 Jan 2016 00:47:24 +0100 Subject: Spec file becomes a template, auto filled with the date and version --- CMakeLists.txt | 9 +++++ packaging/biboumi.spec | 81 ------------------------------------------ packaging/biboumi.spec.cmake | 84 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 81 deletions(-) delete mode 100644 packaging/biboumi.spec create mode 100644 packaging/biboumi.spec.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e356c9..167415e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,11 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo ## Get the software version # set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) +set(RPM_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}) + if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES ".+") set(ARCHIVE_NAME ${ARCHIVE_NAME}${${PROJECT_NAME}_VERSION_SUFFIX}) + set(RPM_VERSION ${RPM_VERSION}${${PROJECT_NAME}_VERSION_SUFFIX}) endif() if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") @@ -39,6 +42,7 @@ if(${PROJECT_NAME}_VERSION_SUFFIX MATCHES "^~dev$") if(GIT_REVISION) set(${PROJECT_NAME}_VERSION_SUFFIX "${${PROJECT_NAME}_VERSION_SUFFIX} (${GIT_REVISION})") set(ARCHIVE_NAME ${ARCHIVE_NAME}${GIT_REVISION}) + set(RPM_VERSION ${RPM_VERSION}${GIT_REVISION}) endif() endif() endif() @@ -251,3 +255,8 @@ else() set(WATCHDOG_SEC "") endif() configure_file(unit/biboumi.service.cmake biboumi.service) + +execute_process(COMMAND "date" "+%a %b %d %Y" OUTPUT_VARIABLE RPM_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +configure_file(packaging/biboumi.spec.cmake biboumi.spec) diff --git a/packaging/biboumi.spec b/packaging/biboumi.spec deleted file mode 100644 index 5c3afb4..0000000 --- a/packaging/biboumi.spec +++ /dev/null @@ -1,81 +0,0 @@ -Name: biboumi -Version: 2.0 -Release: 1%{?dist} -Summary: Lightweight XMPP to IRC gateway - -License: zlib -URL: http://biboumi.louiz.org -Source0: http://git.louiz.org/biboumi/snapshot/biboumi-%{version}.tar.xz - -BuildRequires: libidn-devel -BuildRequires: expat-devel -BuildRequires: libuuid-devel -BuildRequires: systemd-devel -BuildRequires: cmake -BuildRequires: systemd -BuildRequires: rubygem-ronn - -%global _hardened_build 1 - -%global biboumi_confdir %{_sysconfdir}/%{name} - - -%description -An XMPP gateway that connects to IRC servers and translates between the two -protocols. It can be used to access IRC channels using any XMPP client as if -these channels were XMPP MUCs. - - -%prep -%setup -q - - -%build -cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ - -DCMAKE_BUILD_TYPE=release \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DPOLLER=EPOLL \ - -DWITHOUT_BOTAN=1 \ - -DWITH_SYSTEMD=1 \ - -DWITH_LIBIDN=1 - -# The documentation is in utf-8, ronn fails to build it if that locale is -# not specified -export LC_ALL=en_GB.utf-8 -make %{?_smp_mflags} - - -%install -make install DESTDIR=%{buildroot} - - -%check -make check %{?_smp_mflags} - - -%files -%{_bindir}/%{name} -%{_mandir}/man1/%{name}.1* -%doc README COPYING doc/biboumi.1.md -%{_unitdir}/%{name}.service -%config(noreplace) %{biboumi_confdir}/biboumi.cfg - - -%changelog -* Wed Jan 13 2016 Le Coz Florent - 2.0-2 -- Do not install the systemd unit and configuration files, because - “make install” does it itself now - -* Fri May 29 2015 Le Coz Florent - 2.0-1 -- Update to 2.0 sources - -* Wed Nov 13 2014 Le Coz Florent - 1.1-2 -- Use the -DWITH(OUT) cmake flags for all optional dependencies -- Build with the correct optflags -- Use hardened_build - -* Wed Aug 18 2014 Le Coz Florent - 1.1-1 -- Update to 1.1 release - -* Wed Jun 25 2014 Le Coz Florent - 1.0-1 -- Spec file written from scratch diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake new file mode 100644 index 0000000..633a168 --- /dev/null +++ b/packaging/biboumi.spec.cmake @@ -0,0 +1,84 @@ +Name: biboumi +Version: ${RPM_VERSION} +Release: 1%{?dist} +Summary: Lightweight XMPP to IRC gateway + +License: zlib +URL: http://biboumi.louiz.org +Source0: http://git.louiz.org/biboumi/snapshot/biboumi-%{version}.tar.xz + +BuildRequires: libidn-devel +BuildRequires: expat-devel +BuildRequires: libuuid-devel +BuildRequires: systemd-devel +BuildRequires: cmake +BuildRequires: systemd +BuildRequires: rubygem-ronn + +%global _hardened_build 1 + +%global biboumi_confdir %{_sysconfdir}/%{name} + + +%description +An XMPP gateway that connects to IRC servers and translates between the two +protocols. It can be used to access IRC channels using any XMPP client as if +these channels were XMPP MUCs. + + +%prep +%setup -q + + +%build +cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ + -DCMAKE_BUILD_TYPE=release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DPOLLER=EPOLL \ + -DWITHOUT_BOTAN=1 \ + -DWITH_SYSTEMD=1 \ + -DWITH_LIBIDN=1 + +# The documentation is in utf-8, ronn fails to build it if that locale is +# not specified +export LC_ALL=en_GB.utf-8 +make %{?_smp_mflags} + + +%install +make install DESTDIR=%{buildroot} + + +%check +make check %{?_smp_mflags} + + +%files +%{_bindir}/%{name} +%{_mandir}/man1/%{name}.1* +%doc README COPYING doc/biboumi.1.md +%{_unitdir}/%{name}.service +%config(noreplace) %{biboumi_confdir}/biboumi.cfg + + +%changelog +* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 +- Build latest git revision + +* Wed Jan 13 2016 Le Coz Florent - 2.0-2 +- Do not install the systemd unit and configuration files, because + “make install” does it itself now + +* Fri May 29 2015 Le Coz Florent - 2.0-1 +- Update to 2.0 sources + +* Thu Nov 13 2014 Le Coz Florent - 1.1-2 +- Use the -DWITH(OUT) cmake flags for all optional dependencies +- Build with the correct optflags +- Use hardened_build + +* Mon Aug 18 2014 Le Coz Florent - 1.1-1 +- Update to 1.1 release + +* Wed Jun 25 2014 Le Coz Florent - 1.0-1 +- Spec file written from scratch -- cgit v1.2.3 From 85867976dcf27427708927ebebd3d8a4866e706b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Jan 2016 16:48:42 +0100 Subject: =?UTF-8?q?Add=20a=20=E2=80=9Crpm=E2=80=9D=20target=20in=20the=20m?= =?UTF-8?q?akefile,=20building=20a=20RPM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 167415e..e371bf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,7 +230,15 @@ add_custom_target(dist # Compress the archive COMMAND xz ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}.tar COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "${ARCHIVE_NAME}.tar.xz created." - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +add_dependencies(dist catch) + +add_custom_target(rpm + COMMAND mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + COMMAND rpmbuild --define "_topdir `pwd`/rpmbuild/" --define "_sourcedir `pwd`" -ba biboumi.spec + ) +add_dependencies(rpm dist) if(BOTAN_FOUND) set(STR_WITH_BOTAN "Botan: yes") -- cgit v1.2.3 From 6770629bd7db1bf9f371a1d8a2376fad71cbd0f7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Jan 2016 16:52:44 +0100 Subject: Fix a clang-check warning I think it was a UB --- src/xmpp/biboumi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 564c79a..65fbfc5 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -543,7 +543,7 @@ void BiboumiComponent::send_self_disco_info(const std::string& id, const std::st identity["type"] = "irc"; identity["name"] = "Biboumi XMPP-IRC gateway"; query.add_child(std::move(identity)); - for (const std::string& ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) + for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS}) { XmlNode feature("feature"); feature["var"] = ns; -- cgit v1.2.3 From 8c249f1447b475619033b978d166acf91e6bb4d7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Thu, 21 Jan 2016 16:54:42 +0100 Subject: Build the rpm with gitlab-ci --- .gitlab-ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b26c73d..4e385fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,3 +35,8 @@ cares_systemd: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 - make -j$(nproc) biboumi check + +rpm: + script: + - cmake .. + - make rpm \ No newline at end of file -- cgit v1.2.3 From 0a352e557d2a0760601847a97c562759fb04d529 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 2 Feb 2016 17:41:38 +0100 Subject: Move the irc callbacks into the cpp file --- src/irc/irc_client.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ src/irc/irc_client.hpp | 93 -------------------------------------------------- 2 files changed, 92 insertions(+), 93 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1a83446..0efae04 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -23,6 +23,98 @@ using namespace std::string_literals; using namespace std::chrono_literals; +/** + * Define a map of functions to be called for each IRC command we can + * handle. + */ +typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); + +static const std::unordered_map>> irc_callbacks = { + {"NOTICE", {&IrcClient::on_notice, {2, 0}}}, + {"002", {&IrcClient::forward_server_message, {2, 0}}}, + {"003", {&IrcClient::forward_server_message, {2, 0}}}, + {"005", {&IrcClient::on_isupport_message, {0, 0}}}, + {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}}, + {"321", {&IrcClient::on_rpl_liststart, {0, 0}}}, + {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}}, + {"322", {&IrcClient::on_rpl_list, {0, 0}}}, + {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}}, + {"323", {&IrcClient::on_rpl_listend, {0, 0}}}, + {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}}, + {"375", {&IrcClient::empty_motd, {0, 0}}}, + {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}}, + {"372", {&IrcClient::on_motd_line, {2, 0}}}, + {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}}, + {"376", {&IrcClient::send_motd, {0, 0}}}, + {"JOIN", {&IrcClient::on_channel_join, {1, 0}}}, + {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}}, + {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}}, + {"332", {&IrcClient::on_topic_received, {2, 0}}}, + {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}}, + {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}}, + {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}}, + {"433", {&IrcClient::on_nickname_conflict, {2, 0}}}, + {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}}, + {"001", {&IrcClient::on_welcome_message, {1, 0}}}, + {"PART", {&IrcClient::on_part, {1, 0}}}, + {"ERROR", {&IrcClient::on_error, {1, 0}}}, + {"QUIT", {&IrcClient::on_quit, {0, 0}}}, + {"NICK", {&IrcClient::on_nick, {1, 0}}}, + {"MODE", {&IrcClient::on_mode, {1, 0}}}, + {"PING", {&IrcClient::send_pong_command, {1, 0}}}, + {"PONG", {&IrcClient::on_pong, {0, 0}}}, + {"KICK", {&IrcClient::on_kick, {3, 0}}}, + + {"401", {&IrcClient::on_generic_error, {2, 0}}}, + {"402", {&IrcClient::on_generic_error, {2, 0}}}, + {"403", {&IrcClient::on_generic_error, {2, 0}}}, + {"404", {&IrcClient::on_generic_error, {2, 0}}}, + {"405", {&IrcClient::on_generic_error, {2, 0}}}, + {"406", {&IrcClient::on_generic_error, {2, 0}}}, + {"407", {&IrcClient::on_generic_error, {2, 0}}}, + {"408", {&IrcClient::on_generic_error, {2, 0}}}, + {"409", {&IrcClient::on_generic_error, {2, 0}}}, + {"410", {&IrcClient::on_generic_error, {2, 0}}}, + {"411", {&IrcClient::on_generic_error, {2, 0}}}, + {"412", {&IrcClient::on_generic_error, {2, 0}}}, + {"414", {&IrcClient::on_generic_error, {2, 0}}}, + {"421", {&IrcClient::on_generic_error, {2, 0}}}, + {"422", {&IrcClient::on_generic_error, {2, 0}}}, + {"423", {&IrcClient::on_generic_error, {2, 0}}}, + {"424", {&IrcClient::on_generic_error, {2, 0}}}, + {"431", {&IrcClient::on_generic_error, {2, 0}}}, + {"436", {&IrcClient::on_generic_error, {2, 0}}}, + {"441", {&IrcClient::on_generic_error, {2, 0}}}, + {"442", {&IrcClient::on_generic_error, {2, 0}}}, + {"443", {&IrcClient::on_generic_error, {2, 0}}}, + {"444", {&IrcClient::on_generic_error, {2, 0}}}, + {"446", {&IrcClient::on_generic_error, {2, 0}}}, + {"451", {&IrcClient::on_generic_error, {2, 0}}}, + {"461", {&IrcClient::on_generic_error, {2, 0}}}, + {"462", {&IrcClient::on_generic_error, {2, 0}}}, + {"463", {&IrcClient::on_generic_error, {2, 0}}}, + {"464", {&IrcClient::on_generic_error, {2, 0}}}, + {"465", {&IrcClient::on_generic_error, {2, 0}}}, + {"467", {&IrcClient::on_generic_error, {2, 0}}}, + {"470", {&IrcClient::on_generic_error, {2, 0}}}, + {"471", {&IrcClient::on_generic_error, {2, 0}}}, + {"472", {&IrcClient::on_generic_error, {2, 0}}}, + {"473", {&IrcClient::on_generic_error, {2, 0}}}, + {"474", {&IrcClient::on_generic_error, {2, 0}}}, + {"475", {&IrcClient::on_generic_error, {2, 0}}}, + {"476", {&IrcClient::on_generic_error, {2, 0}}}, + {"477", {&IrcClient::on_generic_error, {2, 0}}}, + {"481", {&IrcClient::on_generic_error, {2, 0}}}, + {"482", {&IrcClient::on_generic_error, {2, 0}}}, + {"483", {&IrcClient::on_generic_error, {2, 0}}}, + {"484", {&IrcClient::on_generic_error, {2, 0}}}, + {"485", {&IrcClient::on_generic_error, {2, 0}}}, + {"487", {&IrcClient::on_generic_error, {2, 0}}}, + {"491", {&IrcClient::on_generic_error, {2, 0}}}, + {"501", {&IrcClient::on_generic_error, {2, 0}}}, + {"502", {&IrcClient::on_generic_error, {2, 0}}}, +}; IrcClient::IrcClient(std::shared_ptr poller, const std::string& hostname, const std::string& nickname, const std::string& username, diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7c2a43f..628f547 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -355,97 +355,4 @@ private: IrcClient& operator=(IrcClient&&) = delete; }; -/** - * Define a map of functions to be called for each IRC command we can - * handle. - */ -typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); - -static const std::unordered_map>> irc_callbacks = { - {"NOTICE", {&IrcClient::on_notice, {2, 0}}}, - {"002", {&IrcClient::forward_server_message, {2, 0}}}, - {"003", {&IrcClient::forward_server_message, {2, 0}}}, - {"005", {&IrcClient::on_isupport_message, {0, 0}}}, - {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}}, - {"321", {&IrcClient::on_rpl_liststart, {0, 0}}}, - {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}}, - {"322", {&IrcClient::on_rpl_list, {0, 0}}}, - {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}}, - {"323", {&IrcClient::on_rpl_listend, {0, 0}}}, - {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}}, - {"375", {&IrcClient::empty_motd, {0, 0}}}, - {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}}, - {"372", {&IrcClient::on_motd_line, {2, 0}}}, - {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}}, - {"376", {&IrcClient::send_motd, {0, 0}}}, - {"JOIN", {&IrcClient::on_channel_join, {1, 0}}}, - {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}}, - {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}}, - {"332", {&IrcClient::on_topic_received, {2, 0}}}, - {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}}, - {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}}, - {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}}, - {"433", {&IrcClient::on_nickname_conflict, {2, 0}}}, - {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}}, - {"001", {&IrcClient::on_welcome_message, {1, 0}}}, - {"PART", {&IrcClient::on_part, {1, 0}}}, - {"ERROR", {&IrcClient::on_error, {1, 0}}}, - {"QUIT", {&IrcClient::on_quit, {0, 0}}}, - {"NICK", {&IrcClient::on_nick, {1, 0}}}, - {"MODE", {&IrcClient::on_mode, {1, 0}}}, - {"PING", {&IrcClient::send_pong_command, {1, 0}}}, - {"PONG", {&IrcClient::on_pong, {0, 0}}}, - {"KICK", {&IrcClient::on_kick, {3, 0}}}, - - {"401", {&IrcClient::on_generic_error, {2, 0}}}, - {"402", {&IrcClient::on_generic_error, {2, 0}}}, - {"403", {&IrcClient::on_generic_error, {2, 0}}}, - {"404", {&IrcClient::on_generic_error, {2, 0}}}, - {"405", {&IrcClient::on_generic_error, {2, 0}}}, - {"406", {&IrcClient::on_generic_error, {2, 0}}}, - {"407", {&IrcClient::on_generic_error, {2, 0}}}, - {"408", {&IrcClient::on_generic_error, {2, 0}}}, - {"409", {&IrcClient::on_generic_error, {2, 0}}}, - {"410", {&IrcClient::on_generic_error, {2, 0}}}, - {"411", {&IrcClient::on_generic_error, {2, 0}}}, - {"412", {&IrcClient::on_generic_error, {2, 0}}}, - {"414", {&IrcClient::on_generic_error, {2, 0}}}, - {"421", {&IrcClient::on_generic_error, {2, 0}}}, - {"422", {&IrcClient::on_generic_error, {2, 0}}}, - {"423", {&IrcClient::on_generic_error, {2, 0}}}, - {"424", {&IrcClient::on_generic_error, {2, 0}}}, - {"431", {&IrcClient::on_generic_error, {2, 0}}}, - {"436", {&IrcClient::on_generic_error, {2, 0}}}, - {"441", {&IrcClient::on_generic_error, {2, 0}}}, - {"442", {&IrcClient::on_generic_error, {2, 0}}}, - {"443", {&IrcClient::on_generic_error, {2, 0}}}, - {"444", {&IrcClient::on_generic_error, {2, 0}}}, - {"446", {&IrcClient::on_generic_error, {2, 0}}}, - {"451", {&IrcClient::on_generic_error, {2, 0}}}, - {"461", {&IrcClient::on_generic_error, {2, 0}}}, - {"462", {&IrcClient::on_generic_error, {2, 0}}}, - {"463", {&IrcClient::on_generic_error, {2, 0}}}, - {"464", {&IrcClient::on_generic_error, {2, 0}}}, - {"465", {&IrcClient::on_generic_error, {2, 0}}}, - {"467", {&IrcClient::on_generic_error, {2, 0}}}, - {"470", {&IrcClient::on_generic_error, {2, 0}}}, - {"471", {&IrcClient::on_generic_error, {2, 0}}}, - {"472", {&IrcClient::on_generic_error, {2, 0}}}, - {"473", {&IrcClient::on_generic_error, {2, 0}}}, - {"474", {&IrcClient::on_generic_error, {2, 0}}}, - {"475", {&IrcClient::on_generic_error, {2, 0}}}, - {"476", {&IrcClient::on_generic_error, {2, 0}}}, - {"477", {&IrcClient::on_generic_error, {2, 0}}}, - {"481", {&IrcClient::on_generic_error, {2, 0}}}, - {"482", {&IrcClient::on_generic_error, {2, 0}}}, - {"483", {&IrcClient::on_generic_error, {2, 0}}}, - {"484", {&IrcClient::on_generic_error, {2, 0}}}, - {"485", {&IrcClient::on_generic_error, {2, 0}}}, - {"487", {&IrcClient::on_generic_error, {2, 0}}}, - {"491", {&IrcClient::on_generic_error, {2, 0}}}, - {"501", {&IrcClient::on_generic_error, {2, 0}}}, - {"502", {&IrcClient::on_generic_error, {2, 0}}}, -}; - #endif // IRC_CLIENT_INCLUDED -- cgit v1.2.3 From a13285d0ff360d0a83e007a776d9efbcfc347c76 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 10 Feb 2016 20:10:40 +0100 Subject: Rename BasicCredentialManager --- louloulibs/network/credentials_manager.cpp | 24 ++++++++++++------------ louloulibs/network/credentials_manager.hpp | 4 ++-- louloulibs/network/tcp_socket_handler.hpp | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index f92aef8..d5b0eb3 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -21,10 +21,10 @@ static const std::vector default_cert_files = { "/etc/ca-certificates/extracted/tls-ca-bundle.pem" }; -Botan::Certificate_Store_In_Memory Basic_Credentials_Manager::certificate_store; -bool Basic_Credentials_Manager::certs_loaded = false; +Botan::Certificate_Store_In_Memory BasicCredentialsManager::certificate_store; +bool BasicCredentialsManager::certs_loaded = false; -Basic_Credentials_Manager::Basic_Credentials_Manager(const TCPSocketHandler* const socket_handler): +BasicCredentialsManager::BasicCredentialsManager(const TCPSocketHandler* const socket_handler): Botan::Credentials_Manager(), socket_handler(socket_handler), trusted_fingerprint{} @@ -32,14 +32,14 @@ Basic_Credentials_Manager::Basic_Credentials_Manager(const TCPSocketHandler* con this->load_certs(); } -void Basic_Credentials_Manager::set_trusted_fingerprint(const std::string& fingerprint) +void BasicCredentialsManager::set_trusted_fingerprint(const std::string& fingerprint) { this->trusted_fingerprint = fingerprint; } -void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector& certs) +void BasicCredentialsManager::verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector& certs) { log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); try @@ -62,10 +62,10 @@ void Basic_Credentials_Manager::verify_certificate_chain(const std::string& type } } -void Basic_Credentials_Manager::load_certs() +void BasicCredentialsManager::load_certs() { // Only load the certificates the first time - if (Basic_Credentials_Manager::certs_loaded) + if (BasicCredentialsManager::certs_loaded) return; const std::string conf_path = Config::get("ca_file", ""); std::vector paths; @@ -82,7 +82,7 @@ void Basic_Credentials_Manager::load_certs() while (!bundle.end_of_data() && bundle.check_available(27)) { const Botan::X509_Certificate cert(bundle); - Basic_Credentials_Manager::certificate_store.add_certificate(cert); + BasicCredentialsManager::certificate_store.add_certificate(cert); } // Only use the first file that can successfully be read. goto success; @@ -95,10 +95,10 @@ void Basic_Credentials_Manager::load_certs() // 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: - Basic_Credentials_Manager::certs_loaded = true; + BasicCredentialsManager::certs_loaded = true; } -std::vector Basic_Credentials_Manager::trusted_certificate_authorities(const std::string&, const std::string&) +std::vector BasicCredentialsManager::trusted_certificate_authorities(const std::string&, const std::string&) { return {&this->certificate_store}; } diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index dbd1d95..153c4d6 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -10,10 +10,10 @@ class TCPSocketHandler; -class Basic_Credentials_Manager: public Botan::Credentials_Manager +class BasicCredentialsManager: public Botan::Credentials_Manager { public: - Basic_Credentials_Manager(const TCPSocketHandler* const socket_handler); + BasicCredentialsManager(const TCPSocketHandler* const socket_handler); void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) override final; diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index fb4195d..62b2ce7 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -249,7 +249,7 @@ private: static Botan::TLS::Policy policy; static Botan::TLS::Session_Manager_In_Memory session_manager; protected: - Basic_Credentials_Manager credential_manager; + BasicCredentialsManager credential_manager; private: /** * We use a unique_ptr because we may not want to create the object at -- cgit v1.2.3 From 218260362f0ccb4fd5b51765d4bd331389f39baa Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 10 Feb 2016 20:22:51 +0100 Subject: Remove unused xml_unescape() function --- louloulibs/xmpp/xmpp_stanza.cpp | 44 ----------------------------------------- tests/xmpp.cpp | 3 +-- 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/louloulibs/xmpp/xmpp_stanza.cpp b/louloulibs/xmpp/xmpp_stanza.cpp index 407e631..ac6ce9b 100644 --- a/louloulibs/xmpp/xmpp_stanza.cpp +++ b/louloulibs/xmpp/xmpp_stanza.cpp @@ -40,50 +40,6 @@ std::string xml_escape(const std::string& data) return res; } -std::string xml_unescape(const std::string& data) -{ - std::string res; - res.reserve(data.size()); - const char* str = data.c_str(); - while (str && *str && static_cast(str - data.c_str()) < data.size()) - { - if (*str == '&') - { - if (strncmp(str+1, "amp;", 4) == 0) - { - res += "&"; - str += 4; - } - else if (strncmp(str+1, "lt;", 3) == 0) - { - res += "<"; - str += 3; - } - else if (strncmp(str+1, "gt;", 3) == 0) - { - res += ">"; - str += 3; - } - else if (strncmp(str+1, "quot;", 5) == 0) - { - res += "\""; - str += 5; - } - else if (strncmp(str+1, "apos;", 5) == 0) - { - res += "'"; - str += 5; - } - else - res += "&"; - } - else - res += *str; - str++; - } - return res; -} - std::string sanitize(const std::string& data, const std::string& encoding) { if (utils::is_valid_utf8(data.data())) diff --git a/tests/xmpp.cpp b/tests/xmpp.cpp index b6b50ed..6aab8c4 100644 --- a/tests/xmpp.cpp +++ b/tests/xmpp.cpp @@ -40,9 +40,8 @@ TEST_CASE("Test basic XML parsing") xml.feed(doc2.data(), doc.size(), true); } -TEST_CASE("XML escape/unescape") +TEST_CASE("XML escape") { const std::string unescaped = "'coucou'/&\"gaga\""; CHECK(xml_escape(unescaped) == "'coucou'<cc>/&"gaga""); - CHECK(xml_unescape(xml_escape(unescaped)) == unescaped); } -- cgit v1.2.3 From 339176449f8fe4c5258d65e8a02095617e99b6a6 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 12 Feb 2016 10:09:41 +0100 Subject: =?UTF-8?q?Remove=20the=20clang=20ci=20test=20that=20won=E2=80=99t?= =?UTF-8?q?=20work=20for=20a=20mysterious=20reason=20in=20my=20docker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e385fd..2ecbbc6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,11 +6,6 @@ coverage_botan_cares_systemd_idn: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - make -j$(nproc) biboumi coverage -clang_botan_cares_systemd_idn: - script: - - cmake .. -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi check - botan_cares_systemd: script: - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 -- cgit v1.2.3 From 4cd97f7e88e31103f224d28cee84e78b7a35c2e7 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 17 Feb 2016 19:43:54 +0100 Subject: Simplify the ci file --- .gitlab-ci.yml | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2ecbbc6..b543d08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,37 +1,24 @@ +stages: +- build + before_script: - - mkdir build && cd build/ + - g++ --version + - clang++ --version + - uname -a -coverage_botan_cares_systemd_idn: +build: + stage: build script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi coverage + - mkdir build + - cd build + - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 + - make biboumi -j$(nproc) + - make rpm + - make check -j$(nproc) + artifacts: + paths: + - build/rpmbuild/RPMS/ -botan_cares_systemd: - script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 - - make -j$(nproc) biboumi check -botan_cares_idn: - script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITHOUT_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi check -botan_systemd_idn: - script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITHOUT_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi check -cares_systemd_idn: - script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 - - make -j$(nproc) biboumi check - -cares_systemd: - script: - - cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITHOUT_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITHOUT_LIBIDN=1 - - make -j$(nproc) biboumi check - -rpm: - script: - - cmake .. - - make rpm \ No newline at end of file -- cgit v1.2.3 From 6ee80ad26858eeee9a99b448320025ca439eee55 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 22 Feb 2016 16:56:13 +0100 Subject: Fix the ordering of poll callbacks (recv, connect, send) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because if we have a send event to signal the connection sucess + a recv event to signal something to read on the socket, we need to first finish the connect process before reading the available data. That’s what we do now. --- louloulibs/network/poller.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 329e1c8..50e9806 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -205,15 +205,12 @@ int Poller::poll(const std::chrono::milliseconds& timeout) for (int i = 0; i < nb_events; ++i) { auto socket_handler = static_cast(revents[i].data.ptr); - if (revents[i].events & EPOLLIN) + if (revents[i].events & EPOLLIN && socket_handler->is_connected()) socket_handler->on_recv(); + else if (revents[i].events & EPOLLOUT && socket_handler->is_connected()) + socket_handler->on_send(); else if (revents[i].events & EPOLLOUT) - { - if (socket_handler->is_connected()) - socket_handler->on_send(); - else - socket_handler->connect(); - } + socket_handler->connect(); } return nb_events; #endif -- cgit v1.2.3 From 37fd6ff18a6e51ee5c4127b7e1269e3944d71b44 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 22 Feb 2016 17:01:10 +0100 Subject: log the handshake + stream opening/close in a more consistent way --- louloulibs/xmpp/xmpp_component.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index a82892d..c5906e5 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -89,9 +89,10 @@ void XmppComponent::on_connected() { log_info("connected to XMPP server"); this->first_connection_try = true; - this->send_data(""); + auto data = ""; + log_debug("XMPP SENDING: " << data); + this->send_data(std::move(data)); this->doc_open = true; // We may have some pending data to send: this happens when we try to send // some data before we are actually connected. We send that data right now, if any @@ -124,7 +125,7 @@ void XmppComponent::parse_in_buffer(const size_t size) void XmppComponent::on_remote_stream_open(const XmlNode& node) { - log_debug("XMPP DOCUMENT OPEN: " << node.to_string()); + log_debug("XMPP RECEIVING: " << node.to_string()); this->stream_id = node.get_tag("id"); if (this->stream_id.empty()) { @@ -145,14 +146,14 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) sprintf(digest + (i*2), "%02x", result[i]); digest[HASH_LENGTH * 2] = '\0'; - this->send_data(""); - this->send_data(digest); - this->send_data(""); + auto data = ""s + digest + ""; + log_debug("XMPP SENDING: " << data); + this->send_data(std::move(data)); } void XmppComponent::on_remote_stream_close(const XmlNode& node) { - log_debug("XMPP DOCUMENT CLOSE " << node.to_string()); + log_debug("XMPP RECEIVING: " << node.to_string()); this->doc_open = false; } -- cgit v1.2.3 From 51a34f83f9cae36f65b021e379e411cacf84c054 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 23 Feb 2016 19:20:00 +0100 Subject: Add a basic integration test in python --- CMakeLists.txt | 3 +- tests/end_to_end/__main__.py | 217 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/end_to_end/__main__.py diff --git a/CMakeLists.txt b/CMakeLists.txt index e371bf6..2f1bdd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,7 +195,8 @@ if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) add_dependencies(test_suite catch) endif() add_custom_target(check COMMAND "test_suite" - DEPENDS test_suite) + COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" + DEPENDS test_suite biboumi) # ## Code coverage diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py new file mode 100644 index 0000000..96bbbf4 --- /dev/null +++ b/tests/end_to_end/__main__.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 + +import slixmpp +import asyncio +import logging +import signal +import atexit +import sys +from functools import partial + + +class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): + """match everything""" + + def match(self, xml): + return True + + +class XMPPComponent(slixmpp.BaseXMPP): + """ + XMPPComponent sending a “scenario” of stanzas, checking that the responses + match the expected results. + """ + + def __init__(self, scenario, biboumi): + super().__init__(jid="biboumi.localhost", default_ns="jabber:component:accept") + self.is_component = True + self.stream_header = '' % ( + 'xmlns="jabber:component:accept"', + 'xmlns:stream="%s"' % self.stream_ns, + self.boundjid, self.get_id()) + self.stream_footer = "" + + self.register_handler(slixmpp.Callback('Match All', + MatchAll(None), + self.handle_incoming_stanza)) + + self.add_event_handler("session_end", self.on_end_session) + + asyncio.async(self.accept_routine()) + + self.scenario = scenario + self.biboumi = biboumi + self.expected_xpath = None + self.failed = False + self.accepting_server = None + + def error(self, message): + print("Failure: %s" % (message,)) + self.scenario.steps = [] + self.failed = True + + def on_end_session(self, event): + self.loop.stop() + + def handle_incoming_stanza(self, stanza): + if self.expected_xpath: + matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(self.expected_xpath).match(stanza) + if not matched: + self.error("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) + self.expected_xpath = None + self.run_scenario() + + def run_scenario(self): + if scenario.steps: + step = scenario.steps.pop(0) + step(self, self.biboumi) + else: + self.biboumi.stop() + + @asyncio.coroutine + def accept_routine(self): + self.accepting_server = yield from self.loop.create_server(lambda: self, + "127.0.0.1", "8811", reuse_address=True) + + +class Scenario: + """Defines a list of actions that are executed in sequence, until one of + them throws an exception, or until the end. An action can be something + like “send a stanza”, “receive the next stanza and check that it matches + the given XPath”, “send a signal”, “wait for the end of the process”, + etc + """ + + def __init__(self, name, steps): + """ + Steps is a list of 2-tuple: + [(action, answer), (action, answer)] + """ + self.name = name + self.steps = steps + + +class BiboumiRunner: + def __init__(self, name, with_valgrind): + self.name = name + self.fd = open("biboumi_%s_output.txt" % (name,), "w") + if with_valgrind: + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + else: + self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + self.process = None + + self.signal_sent = False + + @asyncio.coroutine + def start(self): + self.process = yield from self.create + + @asyncio.coroutine + def wait(self): + code = yield from self.process.wait() + return code + + def stop(self): + if not self.signal_sent: + self.signal_sent = True + if self.process: + self.process.send_signal(signal.SIGINT) + + +def send_stanza(stanza, xmpp, biboumi): + xmpp.send_raw(stanza) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + +def expect_stanza(xpath, xmpp, biboumi): + xmpp.expected_xpath = xpath + + +class BiboumiTest: + """ + Spawns a biboumi process and a fake XMPP Component that will run a + Scenario. It redirects the outputs of the subprocess into separated + files, and detects any failure in the running of the scenario. + """ + + def __init__(self, scenario, expected_code=0): + self.scenario = scenario + self.expected_code = 0 + + def run(self, with_valgrind=True): + print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else '')) + # Redirect the slixmpp logging into a specific file + output_filename = "slixmpp_%s_output.txt" % (self.scenario.name,) + with open(output_filename, "w"): + pass + logging.basicConfig(level=logging.DEBUG, + format='%(levelname)-8s %(message)s', + filename=output_filename) + + # Start the XMPP component and biboumi + biboumi = BiboumiRunner(scenario.name, with_valgrind) + xmpp = XMPPComponent(self.scenario, biboumi) + asyncio.get_event_loop().run_until_complete(biboumi.start()) + + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + xmpp.process() + + code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) + failed = False + if not xmpp.failed: + if code != self.expected_code: + xmpp.error("Wrong return code from biboumi's process: %d" % (code,)) + failed = True + else: + print("Success!") + else: + failed = True + + if xmpp.server: + xmpp.accepting_server.close() + + return not failed + + +if __name__ == '__main__': + + atexit.register(asyncio.get_event_loop().close) + + # Start the test component, accepting connections on the configured + # port. + scenarios = ( + Scenario("basic_handshake_success", + [ + partial(expect_stanza, "{jabber:component:accept}handshake"), + partial(send_stanza, ""), + ]), + Scenario("channel_join", + [ + partial(expect_stanza, "{jabber:component:accept}handshake"), + partial(send_stanza, ""), + partial(send_stanza, ""), + partial(expect_stanza, "{jabber:component:accept}message/body"), + ]), + ) + + failures = 0 + + print("Running %s checks for biboumi." % (len(scenarios))) + + for scenario in scenarios: + test = BiboumiTest(scenario) + if not test.run(False): + print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % + (scenario.name, scenario.name)) + failures += 1 + + if failures: + print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', + 'them' if failures > 1 else 'it')) + sys.exit(1) + else: + print("All tests passed successfully") -- cgit v1.2.3 From 1ee5f8e01a932b73628ed3f89e8c77c5fa25f1b0 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 23 Feb 2016 19:40:17 +0100 Subject: end_to_end creates a config file before running biboumi --- tests/end_to_end/__main__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 96bbbf4..8e9c46e 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -151,6 +151,9 @@ class BiboumiTest: format='%(levelname)-8s %(message)s', filename=output_filename) + with open("test.conf", "w") as fd: + fd.write(confs['basic']) + # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) xmpp = XMPPComponent(self.scenario, biboumi) @@ -176,6 +179,11 @@ class BiboumiTest: return not failed +confs = {'basic': +"""hostname=biboumi.localhost +password=coucou +db_name=biboumi.sqlite +port=8811"""} if __name__ == '__main__': -- cgit v1.2.3 From 1f1a27c9307285fabcd8d1fe51e3bc5cb47a88b3 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Tue, 23 Feb 2016 19:43:46 +0100 Subject: Fix a few ci things --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b543d08..8a0d1a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,11 +13,11 @@ build: - cd build - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 - make biboumi -j$(nproc) - - make rpm - - make check -j$(nproc) + - make coverage -j$(nproc) + - make check artifacts: paths: - - build/rpmbuild/RPMS/ + - build/ -- cgit v1.2.3 From b29d94444d176604f0847985f3ecf57091d4a47b Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 24 Feb 2016 09:28:59 +0100 Subject: Close the epoll fd --- louloulibs/network/poller.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 50e9806..0559644 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,8 @@ Poller::Poller() Poller::~Poller() { + if (this->epfd > 0) + ::close(this->epfd); } void Poller::add_socket_handler(SocketHandler* socket_handler) -- cgit v1.2.3 From b47179f1256f2a3fe6ce0d8f30eceb0f91b33b2e Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 24 Feb 2016 09:29:28 +0100 Subject: Fix the ordering of poll callbacks, with ppoll too --- louloulibs/network/poller.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 0559644..26e5a8f 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -171,21 +171,22 @@ int Poller::poll(const std::chrono::milliseconds& timeout) assert(static_cast(nb_events) <= this->nfds); 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) continue; - else if (this->fds[i].revents & POLLIN) + else if (this->fds[i].revents & POLLIN && socket_handler->is_connected()) { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); socket_handler->on_recv(); nb_events--; } - else if (this->fds[i].revents & POLLOUT) + else if (this->fds[i].revents & POLLOUT && socket_handler->is_connected()) { - auto socket_handler = this->socket_handlers.at(this->fds[i].fd); - if (socket_handler->is_connected()) socket_handler->on_send(); - else - socket_handler->connect(); + nb_events--; + } + else if (this->fds[i].revents & POLLOUT) + { + socket_handler->connect(); nb_events--; } } -- cgit v1.2.3 From 13eceeeecf6eb700bc04c0470b089d960ca0becc Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 28 Feb 2016 21:55:28 +0100 Subject: Include a note on own to contribute changes --- README | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README b/README index 32636aa..015ada7 100644 --- a/README +++ b/README @@ -37,9 +37,19 @@ Florent Le Coz (louiz’) ================= Contact/Support ================= -Jabber ChatRoom: biboumi@muc.poez.io +XMPP ChatRoom: biboumi@muc.poez.io Report a bug: https://dev.louiz.org/projects/biboumi/issues/new +To contribute, the preferred way is to commit your changes on some +publicly-available git repository (your own, or github +(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and +to notify the developers with a ticket on the bug tracker +(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on +github or a merge request on gitlab. + +Optionally you can come discuss your changes on the XMPP chat room, +beforehand. + ================= Licence ================= -- cgit v1.2.3 From 55d1d817719646d6be3cac200e4ff8b7b113e136 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 29 Feb 2016 17:59:47 +0100 Subject: Provide a better way to check stanzas at each step of the end_to_end test --- tests/end_to_end/__main__.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 8e9c46e..dbc7c66 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -16,6 +16,10 @@ class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): return True +class StanzaError(Exception): + pass + + class XMPPComponent(slixmpp.BaseXMPP): """ XMPPComponent sending a “scenario” of stanzas, checking that the responses @@ -41,7 +45,9 @@ class XMPPComponent(slixmpp.BaseXMPP): self.scenario = scenario self.biboumi = biboumi - self.expected_xpath = None + # A callable, taking a stanza as argument and raising a StanzaError + # exception if the test should fail. + self.stanza_checker = None self.failed = False self.accepting_server = None @@ -54,11 +60,12 @@ class XMPPComponent(slixmpp.BaseXMPP): self.loop.stop() def handle_incoming_stanza(self, stanza): - if self.expected_xpath: - matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(self.expected_xpath).match(stanza) - if not matched: - self.error("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) - self.expected_xpath = None + if self.stanza_checker: + try: + self.stanza_checker(stanza) + except StanzaError as e: + self.error(e) + self.stanza_checker = None self.run_scenario() def run_scenario(self): @@ -73,6 +80,10 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) +def check_xpath(xpath, stanza): + matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) + if not matched: + raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) class Scenario: """Defines a list of actions that are executed in sequence, until one of @@ -127,7 +138,7 @@ def send_stanza(stanza, xmpp, biboumi): def expect_stanza(xpath, xmpp, biboumi): - xmpp.expected_xpath = xpath + xmpp.stanza_checker = partial(check_xpath, xpath) class BiboumiTest: -- cgit v1.2.3 From 14930284a71772a5a31c263b6f4bbdac1a036876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 24 Mar 2016 10:06:02 +0100 Subject: Fetch Catch from our own clone, instead of the github one. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoids: - Relying on github’s services - Fetching a new (malicious) version, automatically, without any “check” --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f1bdd8..8bfc2e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ endif() include(ExternalProject) ExternalProject_Add(catch - GIT_REPOSITORY "https://github.com/philsquared/Catch.git" + GIT_REPOSITORY "https://lab.louiz.org/louiz/Catch.git" PREFIX "external" UPDATE_COMMAND "" CONFIGURE_COMMAND "" -- cgit v1.2.3 From b32729fb8a5ff5cdbd6c94ad327a09aa19396389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 8 Mar 2016 20:29:31 +0100 Subject: Convert README, INSTALL etc to markdown --- INSTALL | 163 ------------------------------------------- INSTALL.md | 156 +++++++++++++++++++++++++++++++++++++++++ README | 60 ---------------- README.md | 61 ++++++++++++++++ packaging/biboumi.spec.cmake | 2 +- 5 files changed, 218 insertions(+), 224 deletions(-) delete mode 100644 INSTALL create mode 100644 INSTALL.md delete mode 100644 README create mode 100644 README.md diff --git a/INSTALL b/INSTALL deleted file mode 100644 index dc0548b..0000000 --- a/INSTALL +++ /dev/null @@ -1,163 +0,0 @@ -============== - tl;dr: -============== - -$ cmake . && make && ./biboumi - -If that didn’t work, read on. - -============== - Dependencies -============== - -Build and runtime dependencies: - -Tools: - -- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) -- CMake -- ronn (optional) to build the man page - -Libraries: - -- expat - Used to parse XML from the XMPP server. - http://expat.sourceforge.net/ - -- libiconv - Encoding from anything into UTF-8 - http://www.gnu.org/software/libiconv/ - -- libuuid - Generate unique IDs - http://sourceforge.net/projects/libuuid/ - -- libidn (optional, but recommended) - Provides the stringprep functionality. Without it, JIDs for IRC users are - not provided. - http://www.gnu.org/software/libidn/ - -- c-ares (optional, but recommended) - Asynchronously resolve domain names. This offers better reactivity and - performances when connecting to a big number of IRC servers at the same - time. - http://c-ares.haxx.se/ - -- libbotan 1.11 (optional) - Provides TLS support. Without it, IRC connections are all made in - plain-text mode. - Other branches than the 1.11 are not supported. - http://botan.randombit.net/ - -- litesql (optional) - Provides a way to store various options in a (sqlite3) database. Each user - of the gateway can store their own values (for example their prefered port, - or their IRC password). - http://git.louiz.org/litesql - -- systemd (optional) - Provides the support for a systemd service of Type=notify. This is useful only - if you are packaging biboumi in a distribution with Systemd. - - -============== - Configure -============== - -Configure the build system using cmake, there are many solutions to do that, -the simplest is to just run - -% cmake . - -in the current directory. - -The default build type is "Debug", if you want to build a release version, -set the CMAKE_BUILD_TYPE variable to "release", by running this command -instead: - -% cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr - -You can also configure many parameters of the build (like customize CFLAGS, -the install path, choose the compiler, or enabling some options like the -POLLER to use), using the ncurses interface of ccmake: - -% ccmake . - -In ccmake, first use 'c' to configure the build system, edit the values you -need and finaly use 'g' to generate the Makefiles to build the system and -quit ccmake. - -You can also configure these options using a -D command line flag. - -The list of available options: - -- POLLER: lets you select the poller used by biboumi, at - compile-time. Possible values are: - EPOLL: use the Linux-specific epoll(7). This is the default on Linux. - POLL: use the standard poll(2). This is the default value on all non-Linux - platforms. - -- WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, - if it is not found, the configuration process will fail. The second will - make the build process ignore the Botan library, it will not be used even - if it's available on the system. If none of these option is specified, the - library will be used if available and will be ignored otherwise. - -- WITH_LIBIDN and WITHOUT_LIBIDN: Just like the WITH(OUT)_BOTAN options, but - for the IDN library - -- WITH_SYSTEMD and WITHOUT_SYSTEMD: Just like the other WITH(OUT)_* options, - but for the Systemd library - -Example: - -% cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX \ - -DWITH_BOTAN=1 -DWITHOUT_SYSTEMD=1 - -This command will configure the project to build a release, with TLS enabled -(using Botan) but without using Systemd (even if available on the system). - - -============== - Build -============== - -- Once you’ve configured everything using cmake, build the project - -% make - - -============= - Install -============= - -- And then, optionaly, Install the software system-wide - -# make install - - -============= - Testing -============= - -You can run the test suite with - -% make check - -This project uses the Catch unit test framework, it will be automatically -fetched with cmake, by cloning the github repository. - -You can also check the overall code coverage of this test suite by running - -% make coverage - -This requires gcov and lcov to be installed. - - -============= - Run -============= - -Run the software using the `biboumi` binary. Read the documentation (the -man page biboumi(1) or the “biboumi.1.md” file) for more information on how -to use biboumi. diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..7709fe9 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,156 @@ +tl;dr +===== + + cmake . && make && ./biboumi + +If that didn’t work, read on. + +Dependencies +============ + +Build and runtime dependencies: + +Tools: + +- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) +- CMake +- ronn (optional) to build the man page + +Libraries: + +- expat + Used to parse XML from the XMPP server. + http://expat.sourceforge.net/ + +- libiconv + Encoding from anything into UTF-8 + http://www.gnu.org/software/libiconv/ + +- libuuid + Generate unique IDs + http://sourceforge.net/projects/libuuid/ + +- libidn (optional, but recommended) + Provides the stringprep functionality. Without it, JIDs for IRC users are + not provided. + http://www.gnu.org/software/libidn/ + +- c-ares (optional, but recommended) + Asynchronously resolve domain names. This offers better reactivity and + performances when connecting to a big number of IRC servers at the same + time. + http://c-ares.haxx.se/ + +- libbotan 1.11 (optional) + Provides TLS support. Without it, IRC connections are all made in + plain-text mode. + Other branches than the 1.11 are not supported. + http://botan.randombit.net/ + +- litesql (optional) + Provides a way to store various options in a (sqlite3) database. Each user + of the gateway can store their own values (for example their prefered port, + or their IRC password). + http://git.louiz.org/litesql + +- systemd (optional) + Provides the support for a systemd service of Type=notify. This is useful only + if you are packaging biboumi in a distribution with Systemd. + + +Configure +========= + +Configure the build system using cmake, there are many solutions to do that, +the simplest is to just run + + cmake . + +in the current directory. + +The default build type is "Debug", if you want to build a release version, +set the CMAKE_BUILD_TYPE variable to "release", by running this command +instead: + + cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr + +You can also configure many parameters of the build (like customize CFLAGS, +the install path, choose the compiler, or enabling some options like the +POLLER to use), using the ncurses interface of ccmake: + + ccmake . + +In ccmake, first use 'c' to configure the build system, edit the values you +need and finaly use 'g' to generate the Makefiles to build the system and +quit ccmake. + +You can also configure these options using a -D command line flag. + +The list of available options: + +- POLLER: lets you select the poller used by biboumi, at + compile-time. Possible values are: + EPOLL: use the Linux-specific epoll(7). This is the default on Linux. + POLL: use the standard poll(2). This is the default value on all non-Linux + platforms. + +- WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, + if it is not found, the configuration process will fail. The second will + make the build process ignore the Botan library, it will not be used even + if it's available on the system. If none of these option is specified, the + library will be used if available and will be ignored otherwise. + +- WITH_LIBIDN and WITHOUT_LIBIDN: Just like the WITH(OUT)_BOTAN options, but + for the IDN library + +- WITH_SYSTEMD and WITHOUT_SYSTEMD: Just like the other WITH(OUT)_* options, + but for the Systemd library + +Example: + + cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX \ + -DWITH_BOTAN=1 -DWITHOUT_SYSTEMD=1 + +This command will configure the project to build a release, with TLS enabled +(using Botan) but without using Systemd (even if available on the system). + + +Build +===== + +- Once you’ve configured everything using cmake, build the project + + make + + +Install +======= + +- And then, optionaly, Install the software system-wide + + make install + + +Testing +======= + +You can run the test suite with + + make check + +This project uses the Catch unit test framework, it will be automatically +fetched with cmake, by cloning the github repository. + +You can also check the overall code coverage of this test suite by running + + make coverage + +This requires gcov and lcov to be installed. + + +Run +=== + +Run the software using the `biboumi` binary. Read the documentation (the +man page biboumi(1) or the `biboumi.1.md` file) for more information on how +to use biboumi. diff --git a/README b/README deleted file mode 100644 index 015ada7..0000000 --- a/README +++ /dev/null @@ -1,60 +0,0 @@ - _ _ _ _ -| |__ (_) |__ ___ _ _ _ __ ___ (_) -| '_ \| | '_ \ / _ \| | | | '_ ` _ \| | -| |_) | | |_) | (_) | |_| | | | | | | | -|_.__/|_|_.__/ \___/ \__,_|_| |_| |_|_| - -Homepage: http://biboumi.louiz.org - -Biboumi is an XMPP gateway that connects to IRC servers and translates -between the two protocols. It can be used to access IRC channels using any -XMPP client as if these channels were XMPP MUCs. - -It is written in modern C++14 and makes great efforts to have as little -dependencies and to be as simple as possible. - -The goal is to provide a way to access most of IRC features using any XMPP -client. It doesn’t however try to provide a complete mapping of the -features of both worlds simply because this is not useful and most probably -impossible. For example all IRC modes are not all translatable into an XMPP -features. Some of them are (like +m (mute) or +o (operator) modes), but -some others are IRC-specific. If IRC is the limiting factor (for example -you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to -work around this issue: it just enforces the rules of the IRC server by -telling the user that he/she must choose an ASCII-only nickname. An -important goal is to keep the software (and its code) light and simple. - -================ - Install -================ -Refer to the INSTALL file. - -================ - Authors -================ -Florent Le Coz (louiz’) - -================= - Contact/Support -================= -XMPP ChatRoom: biboumi@muc.poez.io -Report a bug: https://dev.louiz.org/projects/biboumi/issues/new - -To contribute, the preferred way is to commit your changes on some -publicly-available git repository (your own, or github -(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and -to notify the developers with a ticket on the bug tracker -(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on -github or a merge request on gitlab. - -Optionally you can come discuss your changes on the XMPP chat room, -beforehand. - -================= - Licence -================= -Biboumi is Free Software. -(learn more: http://www.gnu.org/philosophy/free-sw.html) - -Biboumi is released under the zlib license. -Please read the COPYING file for details. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b8b5124 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +Biboumi +======= + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +It is written in modern C++14 and makes great efforts to have as little +dependencies and to be as simple as possible. + +The goal is to provide a way to access most of IRC features using any XMPP +client. It doesn’t however try to provide a complete mapping of the +features of both worlds simply because this is not useful and most probably +impossible. For example all IRC modes are not all translatable into an XMPP +features. Some of them are (like +m (mute) or +o (operator) modes), but +some others are IRC-specific. If IRC is the limiting factor (for example +you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to +work around this issue: it just enforces the rules of the IRC server by +telling the user that he/she must choose an ASCII-only nickname. An +important goal is to keep the software (and its code) light and simple. + + +Install +======= +Refer to the [INSTALL][] file. + +Usage +===== + +Read [the documentation](doc/biboumi.1.md). + +Authors +======= +Florent Le Coz (louiz’) + + +Contact/Support +=============== +* XMPP ChatRoom: biboumi@muc.poez.io +* Report a bug: https://dev.louiz.org/projects/biboumi/issues/new + +To contribute, the preferred way is to commit your changes on some +publicly-available git repository (your own, or github +(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and +to notify the developers with a ticket on the bug tracker +(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on +github or a merge request on gitlab. + +Optionally you can come discuss your changes on the XMPP chat room, +beforehand. + + +Licence +======= +Biboumi is Free Software. +(learn more: http://www.gnu.org/philosophy/free-sw.html) + +Biboumi is released under the zlib license. +Please read the COPYING file for details. + +[INSTALL]: INSTALL diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 633a168..08b5c6d 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -56,7 +56,7 @@ make check %{?_smp_mflags} %files %{_bindir}/%{name} %{_mandir}/man1/%{name}.1* -%doc README COPYING doc/biboumi.1.md +%doc README.md COPYING doc/biboumi.1.md %{_unitdir}/%{name}.service %config(noreplace) %{biboumi_confdir}/biboumi.cfg -- cgit v1.2.3 From d57b8bb8c3edc4271a9be3d052a220db09250a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 17 Mar 2016 19:18:24 +0100 Subject: Trivial formatting --- tests/end_to_end/__main__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index dbc7c66..7b01a39 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -7,9 +7,10 @@ import signal import atexit import sys from functools import partial +from slixmpp.xmlstream.matcher.base import MatcherBase -class MatchAll(slixmpp.xmlstream.matcher.base.MatcherBase): +class MatchAll(MatcherBase): """match everything""" def match(self, xml): @@ -80,11 +81,13 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) + def check_xpath(xpath, stanza): matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) + class Scenario: """Defines a list of actions that are executed in sequence, until one of them throws an exception, or until the end. An action can be something @@ -107,7 +110,9 @@ class BiboumiRunner: self.name = name self.fd = open("biboumi_%s_output.txt" % (name,), "w") if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + "--errors-for-leak-kinds=all", "--error-exitcode=16", + "./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) else: self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, @@ -190,6 +195,7 @@ class BiboumiTest: return not failed + confs = {'basic': """hostname=biboumi.localhost password=coucou @@ -212,7 +218,8 @@ if __name__ == '__main__': [ partial(expect_stanza, "{jabber:component:accept}handshake"), partial(send_stanza, ""), - partial(send_stanza, ""), + partial(send_stanza, + ""), partial(expect_stanza, "{jabber:component:accept}message/body"), ]), ) -- cgit v1.2.3 From 73ad709ddf5db77e721b7080da64603aab10de17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:17:48 +0200 Subject: Add a TEMPORARY work-around for botan 1.11.29 --- louloulibs/network/credentials_manager.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index d5b0eb3..c5b8493 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -81,8 +81,18 @@ void BasicCredentialsManager::load_certs() log_debug("Using ca bundle: " << path); while (!bundle.end_of_data() && bundle.check_available(27)) { - const Botan::X509_Certificate cert(bundle); - BasicCredentialsManager::certificate_store.add_certificate(cert); + // TODO: remove this work-around for Botan 1.11.29 + // https://github.com/randombit/botan/issues/438#issuecomment-192866796 + // Note that every certificate that fails to be transcoded into latin-1 + // 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) + { + continue; + } } // Only use the first file that can successfully be read. goto success; -- cgit v1.2.3 From 606700f4c5c2e76762b6c5d1ba360e99932b3309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:18:07 +0200 Subject: Improve e2e test, start mammond ourself, etc --- tests/end_to_end/__main__.py | 182 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 152 insertions(+), 30 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7b01a39..da9e80c 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -1,11 +1,14 @@ #!/usr/bin/env python3 +import collections import slixmpp import asyncio import logging import signal import atexit +import lxml.etree import sys +import io from functools import partial from slixmpp.xmlstream.matcher.base import MatcherBase @@ -81,11 +84,16 @@ class XMPPComponent(slixmpp.BaseXMPP): self.accepting_server = yield from self.loop.create_server(lambda: self, "127.0.0.1", "8811", reuse_address=True) + def check_stanza_against_all_expected_xpaths(self): + pass -def check_xpath(xpath, stanza): - matched = slixmpp.xmlstream.matcher.xpath.MatchXPath(xpath).match(stanza) - if not matched: - raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, self.expected_xpath)) +def check_xpath(xpaths, stanza): + for xpath in xpaths: + tree = lxml.etree.parse(io.StringIO(str(stanza))) + matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', + 'muc_user': 'http://jabber.org/protocol/muc#user'}) + if not matched: + raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) class Scenario: @@ -102,23 +110,17 @@ class Scenario: [(action, answer), (action, answer)] """ self.name = name - self.steps = steps - + self.steps = [] + for elem in steps: + if isinstance(elem, collections.Iterable): + for step in elem: + self.steps.append(step) + else: + self.steps.append(elem) -class BiboumiRunner: - def __init__(self, name, with_valgrind): - self.name = name - self.fd = open("biboumi_%s_output.txt" % (name,), "w") - if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", - "--errors-for-leak-kinds=all", "--error-exitcode=16", - "./biboumi", "test.conf", stdin=None, stdout=self.fd, - stderr=self.fd, loop=None, limit=None) - else: - self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, - stderr=self.fd, loop=None, limit=None) +class ProcessRunner: + def __init__(self): self.process = None - self.signal_sent = False @asyncio.coroutine @@ -136,15 +138,42 @@ class BiboumiRunner: if self.process: self.process.send_signal(signal.SIGINT) + def __del__(self): + self.stop() + +class BiboumiRunner(ProcessRunner): + def __init__(self, name, with_valgrind): + super().__init__() + self.name = name + self.fd = open("biboumi_%s_output.txt" % (name,), "w") + if with_valgrind: + self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + "--errors-for-leak-kinds=all", "--error-exitcode=16", + "./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + else: + self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, + stderr=self.fd, loop=None, limit=None) + +class IrcServerRunner(ProcessRunner): + def __init__(self): + super().__init__() + self.create = asyncio.create_subprocess_exec("mammond", "--debug", "--nofork", + "--config", "../tests/end_to_end/mammond.conf", + stderr=asyncio.subprocess.PIPE) def send_stanza(stanza, xmpp, biboumi): - xmpp.send_raw(stanza) + xmpp.send_raw(stanza.format_map(common_replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpath, xmpp, biboumi): - xmpp.stanza_checker = partial(check_xpath, xpath) - +def expect_stanza(xpaths, xmpp, biboumi): + if isinstance(xpaths, str): + xmpp.stanza_checker = partial(check_xpath, [xpaths.format_map(common_replacements)]) + elif isinstance(xpaths, tuple): + xmpp.stanza_checker = partial(check_xpath, [xpath.format_map(common_replacements) for xpath in xpaths]) + else: + print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) class BiboumiTest: """ @@ -202,6 +231,59 @@ password=coucou db_name=biboumi.sqlite port=8811"""} +common_replacements = { + 'irc_server_one': 'irc.localhost@biboumi.localhost', + 'irc_host_one': 'irc.localhost', + 'resource_one': 'resource1', + 'nick_one': 'Nick', + 'jid_one': 'first@example.com', + 'jid_two': 'second@example.com', + 'nick_two': 'Bobby', +} + + +def handshake_sequence(): + return (partial(expect_stanza, "//handshake"), + partial(send_stanza, "")) + + +def connection_sequence(irc_host, jid): + jid = jid.format_map(common_replacements) + xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + return ( + partial(expect_stanza, + xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connection failed: Connection refused')), + partial(expect_stanza, + xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connection failed: Connection refused')), + partial(expect_stanza, + xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), + partial(expect_stanza, + xpath % ('Connected to IRC server.')), + partial(expect_stanza, + xpath % ('%s: *** Looking up your hostname...' % irc_host)), + partial(expect_stanza, + xpath % ('%s: *** Checking Ident' % irc_host)), + # These three messages can be received in any order + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: Your host is .*$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: This server was started at .*$' % irc_host)), + partial(expect_stanza, + xpath % ("- Default MOTD\n")), + ) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) @@ -211,21 +293,57 @@ if __name__ == '__main__': scenarios = ( Scenario("basic_handshake_success", [ - partial(expect_stanza, "{jabber:component:accept}handshake"), - partial(send_stanza, ""), + handshake_sequence() + ]), + Scenario("irc_server_connection", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + ]), + Scenario("simple_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), - Scenario("channel_join", + Scenario("channel_join_with_two_users", [ - partial(expect_stanza, "{jabber:component:accept}handshake"), - partial(send_stanza, ""), + handshake_sequence(), + # First user joins partial(send_stanza, - ""), - partial(expect_stanza, "{jabber:component:accept}message/body"), + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Second user joins + partial(send_stanza, + ""), + # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), ]), ) failures = 0 + irc = IrcServerRunner() + print("Starting mammond server…") + asyncio.get_event_loop().run_until_complete(irc.start()) + while True: + res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) + if b"init finished..." in res: + break + print("mammond server started.") print("Running %s checks for biboumi." % (len(scenarios))) for scenario in scenarios: @@ -235,6 +353,10 @@ if __name__ == '__main__': (scenario.name, scenario.name)) failures += 1 + print("Waiting for mammond to exit…") + irc.stop() + code = asyncio.get_event_loop().run_until_complete(irc.wait()) + if failures: print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', 'them' if failures > 1 else 'it')) -- cgit v1.2.3 From 464261d4ed462f5d74507fe4d17bc5f76b5f726d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:14:39 +0200 Subject: Support RPL_NOTPIC and 005 --- src/irc/irc_client.cpp | 16 ++++++++++++++++ src/irc/irc_client.hpp | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0efae04..1150155 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -34,6 +34,7 @@ static const std::unordered_mapbridge.send_xmpp_message(this->hostname, from, message); @@ -690,6 +697,15 @@ void IrcClient::empty_motd(const IrcMessage&) this->motd.erase(); } +void IrcClient::on_empty_topic(const IrcMessage& message) +{ + const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 1]); + log_debug("empty topic for " << chan_name); + IrcChannel* channel = this->get_channel(chan_name); + if (channel) + channel->topic.clear(); +} + void IrcClient::on_motd_line(const IrcMessage& message) { const std::string body = message.arguments[1]; diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 628f547..5efecc6 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -143,6 +143,10 @@ public: * http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt */ void on_isupport_message(const IrcMessage& message); + /** + * Does nothing yet. Isn’t that duplicating features from 005? + */ + void on_server_myinfo(const IrcMessage& message); /** * Just empty the motd we kept as a string */ @@ -194,6 +198,10 @@ public: * Save the topic in the IrcChannel */ void on_topic_received(const IrcMessage& message); + /** + * Empty the topic + */ + void on_empty_topic(const IrcMessage& message); /** * The channel has been completely joined (self presence, topic, all names * received etc), send the self presence and topic to the XMPP user. -- cgit v1.2.3 From 065f52a0b32d5355da58e200a7e9a301693dcb55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:29:54 +0200 Subject: Add mammond conf --- tests/end_to_end/mammond.conf | 269 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 tests/end_to_end/mammond.conf diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf new file mode 100644 index 0000000..a9c7d43 --- /dev/null +++ b/tests/end_to_end/mammond.conf @@ -0,0 +1,269 @@ +# The server object defines the server information parameters. +server: + # name - the server name + name: "irc.localhost" + + # description - the description of the server on the network + description: "Test server for biboumi" + + # network - the NETWORK= name in 005 for rfc1459 clients + network: "irc.localhost" + + # recvq_len - the maximum number of lines that can be in a client's recvq + recvq_len: 20 + + # motd - the motd content (will later be file) + motd: + - "Default MOTD" + + +# The clients object defines client parameters +clients: + # ping_frequency - client ping frequency + ping_frequency: + minutes: 1 + + # ping_timeout - ping timeout length + ping_timeout: + minutes: 2 + + +# The data object defines the data store parameters +data: + + ## JSON should only be considered for testing + # format - data store type + format: "json" + + # filename - data store filename + filename: ".mammon.data.json" + + # save_frequency - save the database every this amount of time + save_frequency: + minutes: 5 + + +# The listeners object is a list of listeners. +listeners: +- {"host": "0.0.0.0", "port": 6667, "ssl": false, "certfile": "~/workspace/biboumi/cert.pem", "keyfile": "~/workspace/biboumi/key.pem", } + + +# The logs section is a list of logs. +logs: +- { + # path - the path of the logfile + "path": "mammond.log", + + # level - the log level of the file + "level": "debug" + } + + +# Limits define maximum lengths for various commands and objects +# to remove a limit, simply comment it out +limits: + # user - maximum length of usernames + user: 10 + + # nick - maximum length of nicknames + nick: 50 + + # channel - maximum length of channel names + channel: 200 + + # topic - maximum length of channel topics + topic: 400 + + # line - maximum length of lines in and out + line: 2048 + + +# The register object defines registration information +register: + + # verify_timeout - length of time a user has to verify their newly-created + # account before it can be re-registered + verify_timeout: + days: 5 + + # enabled_callbacks - callbacks that we allow + enabled_callbacks: + # - mailto + # - none # no verification required, will instantly register successfully + + # callbacks - types and details for various callback methods + callbacks: + + # mailto - email using sendmail + mailto: + # from - address our messages get sent from + from: mammon@example.com + + # sendmail - location of the sendmail binary + sendmail: /usr/sbin/sendmail + + # verify_message_subject - subject of the verify message + verify_message_subject: "{network_name} Account Registration" + + # verify_message - message sent to users to verify their account + verify_message: | + Hi, + + You have requested to register the account {account}. + + Your verification code is {auth_code} + + Please type "/quote REG VERIFY {account} {auth_code}" to complete registration + + Thank you, + {network_name} +# Roles define the capabilities an oper may have, as well as role-specific +# metadata. + + # mammon capability names: + # oper:local_kill allows /KILLing local users + # oper:global_kill allows /KILLing local and remote users + # oper:routing allows remote SQUIT and CONNECT + # oper:kline allows KLINE and DLINE + # oper:unkline allows UNKLINE and UNDLINE + # oper:remote_ban allows remote klines + # oper:rehash allows REHASH of server config + # oper:die allows DIE and RESTART + +roles: + # name - the name of the privilege set + "local_op": + # capabilities - a list of qualified capability names + capabilities: + - oper:local_kill + - oper:kline + - oper:unkline + - metadata:set_local + + # metakeys_get - metadata keys this role can view + metakeys_get: + # - spammer_probability + + # title - metadata identifying the specific role + title: "IRC Operator" + + # name - the name of the privilege set + "global_op": + # extends - inherets this role's capabilities + extends: "local_op" + + # capabilities - a list of qualified capability names + capabilities: + - oper:global_kill + - oper:remote_ban + - metadata:set_global + + # title - metadata identifying the specific role + title: "IRC Operator" + + # name - the name of the privilege set + "network_admin": + # capabilities - a list of qualified capability names + capabilities: + - oper:global_kill + - oper:routing + + # title - metadata identifying the specific role + title: "Network Administrator" + + # name - the name of the privilege set + "server_admin": + # extends - inherets this role's capabilities + extends: "local_op" + + # capabilities - a list of qualified capability names + capabilities: + - oper:rehash + - oper:die + + # title - metadata identifying the specific role + title: "Server Administrator" + + # example metadata-specific roles + # name - the name of the privilege set + "spam_detection_bot": + # capabilities - a list of qualified capability names + capabilities: + - metadata:set_global + + # metakeys_access - metadata keys this role can view and set + metakeys_access: + - spammer_probability + + # title - metadata identifying the specific role + title: "Spam Detection Bot" + + +# Metadata defines the metadata users are allowed to set for themselves +metadata: + # limit - max number of metadata each target is allowed to have + # comment out to remove limit + limit: 20 + + # whitelist - if defined, a list of lowercase keys that are checked when users set data + whitelist: + - avatar + - info + - source + - url + - version + + # restricted_keys - keys that unprived users cannot see / edit, and require a + # special entry in a ROLE to see / edit + restricted_keys: + - away + # - spammer_probability + + +# Monitor defines the monitoring users are allowed to do on other users +monitor: + # limit - max number of 'monitors' each target is allowed to have + # comment out to remove limit + limit: 20 + + +# Operator credentials allow a user to transition from a typical user role +# to a privileged role. +opers: + # name - the name of the operator + "nobody": + # password - the plaintext oper password + password: "nothing" + + # hostmask - if defined, the hostmask the oper must connect from + hostmask: "*@localhost" + + # role - the role that the credentials allow transition to + role: "local_op" + + # name - the name of the operator + "somebody": + # password - the hashed oper password + # created by: mammond --mkpasswd + password: "$6$rounds=100000$KkEHFBhWHV3BHCCS$YuOdlikJHdeIv2YpwvyLAtYCslDlsnUUnrfeKJiBh4SeVhkSU6pQqHWWDjr6lnalkkf1KLDD1wgSQH5AObILQ1" + + # hash - the hashing algorithm to use + hash: "sha512_crypt" + + # hostmask - if defined, the hostmask the oper must connect from + hostmask: "somebody!*@localhost" + + # role - the role that the credentials allow transition to + role: "local_op" + + +# The extensions section is a list of extension modules to load. +extensions: +- mammon.ext.rfc1459.42 +- mammon.ext.rfc1459.ident +- mammon.ext.ircv3.account_notify +- mammon.ext.ircv3.server_time +- mammon.ext.ircv3.echo_message +- mammon.ext.ircv3.register +- mammon.ext.ircv3.sasl +- mammon.ext.misc.nopost \ No newline at end of file -- cgit v1.2.3 From e0ee881f10ddc29d2e633df62c4dc1879a39b342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 11 Apr 2016 17:33:54 +0200 Subject: Remove localdomain --- tests/end_to_end/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index da9e80c..3d907ca 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -309,7 +309,7 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -322,7 +322,7 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost.localdomain'][@role='participant']", + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), -- cgit v1.2.3 From 45f442b67d0331e82e25d8cafe3b6f2c87d1d611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 17 Apr 2016 17:01:28 +0200 Subject: More tests --- tests/end_to_end/__main__.py | 65 +++++++++++++++++++++++++++++++++++++++---- tests/end_to_end/mammond.conf | 2 +- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3d907ca..f498585 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -87,6 +87,7 @@ class XMPPComponent(slixmpp.BaseXMPP): def check_stanza_against_all_expected_xpaths(self): pass + def check_xpath(xpaths, stanza): for xpath in xpaths: tree = lxml.etree.parse(io.StringIO(str(stanza))) @@ -118,6 +119,7 @@ class Scenario: else: self.steps.append(elem) + class ProcessRunner: def __init__(self): self.process = None @@ -155,13 +157,14 @@ class BiboumiRunner(ProcessRunner): self.create = asyncio.create_subprocess_exec("./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) + class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("mammond", "--debug", "--nofork", - "--config", "../tests/end_to_end/mammond.conf", + self.create = asyncio.create_subprocess_exec("/home/louiz/sources/charybdis/ircd/charybdis", "-foreground", stderr=asyncio.subprocess.PIPE) + def send_stanza(stanza, xmpp, biboumi): xmpp.send_raw(stanza.format_map(common_replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) @@ -175,6 +178,7 @@ def expect_stanza(xpaths, xmpp, biboumi): else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) + class BiboumiTest: """ Spawns a biboumi process and a fake XMPP Component that will run a @@ -330,20 +334,69 @@ if __name__ == '__main__': # Second user joins partial(send_stanza, ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") + ), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + Scenario("channel_custom_topic", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # First user sets the topic + partial(send_stanza, + "TOPIC\nTEST"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC\nTEST']"), + + # # Second user joins + # partial(send_stanza, + # ""), # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # # Our presence, sent to the other user + # partial(expect_stanza, + # ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # # The other user presence + # partial(expect_stanza, + # ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") + # ), + # # Our own presence + # partial(expect_stanza, + # ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + # "/presence/muc_user:x/muc_user:status[@code='110']") + # ), + # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), ) failures = 0 irc = IrcServerRunner() - print("Starting mammond server…") + print("Starting irc server…") asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) - if b"init finished..." in res: + if b"now running in foreground mode" in res: break - print("mammond server started.") + print("irc server started.") print("Running %s checks for biboumi." % (len(scenarios))) for scenario in scenarios: @@ -353,7 +406,7 @@ if __name__ == '__main__': (scenario.name, scenario.name)) failures += 1 - print("Waiting for mammond to exit…") + print("Waiting for irc server to exit…") irc.stop() code = asyncio.get_event_loop().run_until_complete(irc.wait()) diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf index a9c7d43..99db2bc 100644 --- a/tests/end_to_end/mammond.conf +++ b/tests/end_to_end/mammond.conf @@ -233,7 +233,7 @@ opers: # name - the name of the operator "nobody": # password - the plaintext oper password - password: "nothing" + # password: "nothing" # hostmask - if defined, the hostmask the oper must connect from hostmask: "*@localhost" -- cgit v1.2.3 From bd625aa59bcd5194dd5ee5eea03e7f465d555ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:36:45 +0200 Subject: e2etests: we are able to receive optional stanzas --- tests/end_to_end/__main__.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f498585..c45e8b9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -21,6 +21,17 @@ class MatchAll(MatcherBase): class StanzaError(Exception): + """ + Raised when a step fails. + """ + pass + + +class SkipStepError(Exception): + """ + Raised by a step when it needs to be skiped, by running + the next available step immediately. + """ pass @@ -69,6 +80,10 @@ class XMPPComponent(slixmpp.BaseXMPP): self.stanza_checker(stanza) except StanzaError as e: self.error(e) + except SkipStepError: + # Run the next step and then re-handle this same stanza + self.run_scenario() + return self.handle_incoming_stanza(stanza) self.stanza_checker = None self.run_scenario() @@ -97,6 +112,13 @@ def check_xpath(xpaths, stanza): raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) +def check_xpath_optional(xpaths, stanza): + try: + check_xpath(xpaths, stanza) + except StanzaError: + raise SkipStepError() + + class Scenario: """Defines a list of actions that are executed in sequence, until one of them throws an exception, or until the end. An action can be something @@ -170,11 +192,12 @@ def send_stanza(stanza, xmpp, biboumi): asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpaths, xmpp, biboumi): +def expect_stanza(xpaths, xmpp, biboumi, optional=False): + check_func = check_xpath if not optional else check_xpath_optional if isinstance(xpaths, str): - xmpp.stanza_checker = partial(check_xpath, [xpaths.format_map(common_replacements)]) + xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)]) elif isinstance(xpaths, tuple): - xmpp.stanza_checker = partial(check_xpath, [xpath.format_map(common_replacements) for xpath in xpaths]) + xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths]) else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) -- cgit v1.2.3 From 64f341ee80b0d53f0a6e128a1ccc38205361c3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:39:27 +0200 Subject: e2etests: Use charybdis and update the scenarios accordingly --- tests/end_to_end/__main__.py | 96 +++++++++------ tests/end_to_end/mammond.conf | 269 ------------------------------------------ 2 files changed, 58 insertions(+), 307 deletions(-) delete mode 100644 tests/end_to_end/mammond.conf diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c45e8b9..b027bd9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -165,6 +165,7 @@ class ProcessRunner: def __del__(self): self.stop() + class BiboumiRunner(ProcessRunner): def __init__(self, name, with_valgrind): super().__init__() @@ -183,7 +184,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("/home/louiz/sources/charybdis/ircd/charybdis", "-foreground", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", stderr=asyncio.subprocess.PIPE) @@ -271,7 +272,7 @@ common_replacements = { def handshake_sequence(): return (partial(expect_stanza, "//handshake"), - partial(send_stanza, "")) + partial(send_stanza, "")) def connection_sequence(irc_host, jid): @@ -282,32 +283,47 @@ def connection_sequence(irc_host, jid): partial(expect_stanza, xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connection failed: Connection refused')), + xpath % 'Connection failed: Connection refused'), partial(expect_stanza, xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connection failed: Connection refused')), + xpath % 'Connection failed: Connection refused'), partial(expect_stanza, xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, - xpath % ('Connected to IRC server.')), + xpath % 'Connected to IRC server.'), + # These two messages can be receive in any order partial(expect_stanza, - xpath % ('%s: *** Looking up your hostname...' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), partial(expect_stanza, - xpath % ('%s: *** Checking Ident' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), # These three messages can be received in any order partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|NAK multi-prefix |\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, xpath_re % (r'^%s: Your host is .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: This server was started at .*$' % irc_host)), + xpath_re % (r'^%s: This server was created .*$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), + partial(expect_stanza, + xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), + partial(expect_stanza, + xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"), partial(expect_stanza, - xpath % ("- Default MOTD\n")), + xpath_re % r'^User mode for \w+ is \[\+i\]$'), ) @@ -336,7 +352,9 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -349,7 +367,9 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), @@ -363,8 +383,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), # The other user presence partial(expect_stanza, - ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") - ), + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), # Our own presence partial(expect_stanza, ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", @@ -380,33 +399,34 @@ if __name__ == '__main__': ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, - ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']", - "/presence/muc_user:x/muc_user:status[@code='110']") + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # First user sets the topic partial(send_stanza, - "TOPIC\nTEST"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC\nTEST']"), - - # # Second user joins - # partial(send_stanza, - # ""), - # connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), - # # Our presence, sent to the other user - # partial(expect_stanza, - # ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), - # # The other user presence - # partial(expect_stanza, - # ("/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']") - # ), - # # Our own presence - # partial(expect_stanza, - # ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", - # "/presence/muc_user:x/muc_user:status[@code='110']") - # ), - # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + "TOPIC TEST"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']"), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), ) diff --git a/tests/end_to_end/mammond.conf b/tests/end_to_end/mammond.conf deleted file mode 100644 index 99db2bc..0000000 --- a/tests/end_to_end/mammond.conf +++ /dev/null @@ -1,269 +0,0 @@ -# The server object defines the server information parameters. -server: - # name - the server name - name: "irc.localhost" - - # description - the description of the server on the network - description: "Test server for biboumi" - - # network - the NETWORK= name in 005 for rfc1459 clients - network: "irc.localhost" - - # recvq_len - the maximum number of lines that can be in a client's recvq - recvq_len: 20 - - # motd - the motd content (will later be file) - motd: - - "Default MOTD" - - -# The clients object defines client parameters -clients: - # ping_frequency - client ping frequency - ping_frequency: - minutes: 1 - - # ping_timeout - ping timeout length - ping_timeout: - minutes: 2 - - -# The data object defines the data store parameters -data: - - ## JSON should only be considered for testing - # format - data store type - format: "json" - - # filename - data store filename - filename: ".mammon.data.json" - - # save_frequency - save the database every this amount of time - save_frequency: - minutes: 5 - - -# The listeners object is a list of listeners. -listeners: -- {"host": "0.0.0.0", "port": 6667, "ssl": false, "certfile": "~/workspace/biboumi/cert.pem", "keyfile": "~/workspace/biboumi/key.pem", } - - -# The logs section is a list of logs. -logs: -- { - # path - the path of the logfile - "path": "mammond.log", - - # level - the log level of the file - "level": "debug" - } - - -# Limits define maximum lengths for various commands and objects -# to remove a limit, simply comment it out -limits: - # user - maximum length of usernames - user: 10 - - # nick - maximum length of nicknames - nick: 50 - - # channel - maximum length of channel names - channel: 200 - - # topic - maximum length of channel topics - topic: 400 - - # line - maximum length of lines in and out - line: 2048 - - -# The register object defines registration information -register: - - # verify_timeout - length of time a user has to verify their newly-created - # account before it can be re-registered - verify_timeout: - days: 5 - - # enabled_callbacks - callbacks that we allow - enabled_callbacks: - # - mailto - # - none # no verification required, will instantly register successfully - - # callbacks - types and details for various callback methods - callbacks: - - # mailto - email using sendmail - mailto: - # from - address our messages get sent from - from: mammon@example.com - - # sendmail - location of the sendmail binary - sendmail: /usr/sbin/sendmail - - # verify_message_subject - subject of the verify message - verify_message_subject: "{network_name} Account Registration" - - # verify_message - message sent to users to verify their account - verify_message: | - Hi, - - You have requested to register the account {account}. - - Your verification code is {auth_code} - - Please type "/quote REG VERIFY {account} {auth_code}" to complete registration - - Thank you, - {network_name} -# Roles define the capabilities an oper may have, as well as role-specific -# metadata. - - # mammon capability names: - # oper:local_kill allows /KILLing local users - # oper:global_kill allows /KILLing local and remote users - # oper:routing allows remote SQUIT and CONNECT - # oper:kline allows KLINE and DLINE - # oper:unkline allows UNKLINE and UNDLINE - # oper:remote_ban allows remote klines - # oper:rehash allows REHASH of server config - # oper:die allows DIE and RESTART - -roles: - # name - the name of the privilege set - "local_op": - # capabilities - a list of qualified capability names - capabilities: - - oper:local_kill - - oper:kline - - oper:unkline - - metadata:set_local - - # metakeys_get - metadata keys this role can view - metakeys_get: - # - spammer_probability - - # title - metadata identifying the specific role - title: "IRC Operator" - - # name - the name of the privilege set - "global_op": - # extends - inherets this role's capabilities - extends: "local_op" - - # capabilities - a list of qualified capability names - capabilities: - - oper:global_kill - - oper:remote_ban - - metadata:set_global - - # title - metadata identifying the specific role - title: "IRC Operator" - - # name - the name of the privilege set - "network_admin": - # capabilities - a list of qualified capability names - capabilities: - - oper:global_kill - - oper:routing - - # title - metadata identifying the specific role - title: "Network Administrator" - - # name - the name of the privilege set - "server_admin": - # extends - inherets this role's capabilities - extends: "local_op" - - # capabilities - a list of qualified capability names - capabilities: - - oper:rehash - - oper:die - - # title - metadata identifying the specific role - title: "Server Administrator" - - # example metadata-specific roles - # name - the name of the privilege set - "spam_detection_bot": - # capabilities - a list of qualified capability names - capabilities: - - metadata:set_global - - # metakeys_access - metadata keys this role can view and set - metakeys_access: - - spammer_probability - - # title - metadata identifying the specific role - title: "Spam Detection Bot" - - -# Metadata defines the metadata users are allowed to set for themselves -metadata: - # limit - max number of metadata each target is allowed to have - # comment out to remove limit - limit: 20 - - # whitelist - if defined, a list of lowercase keys that are checked when users set data - whitelist: - - avatar - - info - - source - - url - - version - - # restricted_keys - keys that unprived users cannot see / edit, and require a - # special entry in a ROLE to see / edit - restricted_keys: - - away - # - spammer_probability - - -# Monitor defines the monitoring users are allowed to do on other users -monitor: - # limit - max number of 'monitors' each target is allowed to have - # comment out to remove limit - limit: 20 - - -# Operator credentials allow a user to transition from a typical user role -# to a privileged role. -opers: - # name - the name of the operator - "nobody": - # password - the plaintext oper password - # password: "nothing" - - # hostmask - if defined, the hostmask the oper must connect from - hostmask: "*@localhost" - - # role - the role that the credentials allow transition to - role: "local_op" - - # name - the name of the operator - "somebody": - # password - the hashed oper password - # created by: mammond --mkpasswd - password: "$6$rounds=100000$KkEHFBhWHV3BHCCS$YuOdlikJHdeIv2YpwvyLAtYCslDlsnUUnrfeKJiBh4SeVhkSU6pQqHWWDjr6lnalkkf1KLDD1wgSQH5AObILQ1" - - # hash - the hashing algorithm to use - hash: "sha512_crypt" - - # hostmask - if defined, the hostmask the oper must connect from - hostmask: "somebody!*@localhost" - - # role - the role that the credentials allow transition to - role: "local_op" - - -# The extensions section is a list of extension modules to load. -extensions: -- mammon.ext.rfc1459.42 -- mammon.ext.rfc1459.ident -- mammon.ext.ircv3.account_notify -- mammon.ext.ircv3.server_time -- mammon.ext.ircv3.echo_message -- mammon.ext.ircv3.register -- mammon.ext.ircv3.sasl -- mammon.ext.misc.nopost \ No newline at end of file -- cgit v1.2.3 From 04d28f968b227067e77e365d317fc251d3c965f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 19 Apr 2016 02:43:26 +0200 Subject: Forward the topic authors, handle the author from 333 messages fix #2 --- louloulibs/xmpp/xmpp_component.cpp | 7 +++++-- louloulibs/xmpp/xmpp_component.hpp | 2 +- src/bridge/bridge.cpp | 5 +++-- src/bridge/bridge.hpp | 2 +- src/irc/irc_channel.hpp | 1 + src/irc/irc_client.cpp | 16 ++++++++++++++-- src/irc/irc_client.hpp | 4 ++++ tests/end_to_end/__main__.py | 4 ++-- 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index c5906e5..8a0ca52 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -388,11 +388,14 @@ void XmppComponent::send_invalid_user_error(const std::string& user_name, const this->send_stanza(message); } -void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to) +void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) { XmlNode message("message"); message["to"] = to; - message["from"] = from + "@" + this->served_hostname; + if (who.empty()) + message["from"] = from + "@" + this->served_hostname; + else + message["from"] = from + "@" + this->served_hostname + "/" + who; message["type"] = "groupchat"; XmlNode subject("subject"); subject.set_inner(std::get<0>(topic)); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 06236fe..07322dd 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -124,7 +124,7 @@ public: /** * Send the MUC topic to the user */ - void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to); + void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to, const std::string& who); /** * Send a (non-private) message to the MUC */ diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4c97a91..2815da9 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -634,10 +634,11 @@ void Bridge::send_user_join(const std::string& hostname, affiliation, role, this->user_jid, self); } -void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic) +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who) { const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server("%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid); + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( + "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid, who); } std::string Bridge::get_own_nick(const Iid& iid) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index c030ed8..6222ef0 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -122,7 +122,7 @@ public: /** * Send the topic of the MUC to the user */ - void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic); + void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who); /** * Send a MUC message from some participant */ diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 568de0a..651b8ed 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -18,6 +18,7 @@ public: bool joined; std::string topic; + std::string topic_author; void set_self(const std::string& name); IrcUser* get_self() const; IrcUser* add_user(const std::string& name, diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1150155..327b170 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -55,6 +55,8 @@ static const std::unordered_mapget_channel(chan_name); channel->topic = message.arguments[message.arguments.size() - 1]; + channel->topic_author = author.nick; if (channel->joined) - this->bridge.send_topic(this->hostname, chan_name, channel->topic); + this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); +} + +void IrcClient::on_topic_who_time_received(const IrcMessage& message) +{ + IrcUser author(message.arguments[2]); + const std::string chan_name = utils::tolower(message.arguments[1]); + IrcChannel* channel = this->get_channel(chan_name); + channel->topic_author = author.nick; } void IrcClient::on_channel_completely_joined(const IrcMessage& message) @@ -737,7 +749,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); - this->bridge.send_topic(this->hostname, chan_name, channel->topic); + this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } void IrcClient::on_erroneous_nickname(const IrcMessage& message) diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 5efecc6..ceaa860 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -198,6 +198,10 @@ public: * Save the topic in the IrcChannel */ void on_topic_received(const IrcMessage& message); + /** + * Save the topic author in the IrcChannel + */ + void on_topic_who_time_received(const IrcMessage& message); /** * Empty the topic */ diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index b027bd9..f9d0447 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -409,7 +409,7 @@ if __name__ == '__main__': # First user sets the topic partial(send_stanza, "TOPIC TEST"), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[text()='TOPIC TEST']"), # Second user joins partial(send_stanza, @@ -426,7 +426,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), ) -- cgit v1.2.3 From 04facc515ce2bc32249fa4cf2cdadb75a8bcb3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 20 Apr 2016 22:57:27 +0200 Subject: Provide a conf for charybdis --- tests/end_to_end/__main__.py | 2 +- tests/end_to_end/ircd.conf | 510 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 tests/end_to_end/ircd.conf diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index f9d0447..58165d9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -184,7 +184,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", "../tests/end_to_end/ircd.conf", stderr=asyncio.subprocess.PIPE) diff --git a/tests/end_to_end/ircd.conf b/tests/end_to_end/ircd.conf new file mode 100644 index 0000000..7edb3a8 --- /dev/null +++ b/tests/end_to_end/ircd.conf @@ -0,0 +1,510 @@ +/* doc/ircd.conf.example - brief example configuration file + * + * Copyright (C) 2000-2002 Hybrid Development Team + * Copyright (C) 2002-2005 ircd-ratbox development team + * Copyright (C) 2005-2006 charybdis development team + * + * See reference.conf for more information. + */ + +/* Extensions */ +#loadmodule "extensions/chm_operonly_compat"; +#loadmodule "extensions/chm_quietunreg_compat"; +#loadmodule "extensions/chm_sslonly_compat"; +#loadmodule "extensions/chm_operpeace"; +#loadmodule "extensions/createauthonly"; +#loadmodule "extensions/extb_account"; +#loadmodule "extensions/extb_canjoin"; +#loadmodule "extensions/extb_channel"; +#loadmodule "extensions/extb_combi"; +#loadmodule "extensions/extb_extgecos"; +#loadmodule "extensions/extb_hostmask"; +#loadmodule "extensions/extb_oper"; +#loadmodule "extensions/extb_realname"; +#loadmodule "extensions/extb_server"; +#loadmodule "extensions/extb_ssl"; +#loadmodule "extensions/extb_usermode"; +#loadmodule "extensions/hurt"; +#loadmodule "extensions/m_extendchans"; +#loadmodule "extensions/m_findforwards"; +#loadmodule "extensions/m_identify"; +#loadmodule "extensions/m_locops"; +#loadmodule "extensions/no_oper_invis"; +#loadmodule "extensions/sno_farconnect"; +#loadmodule "extensions/sno_globalkline"; +#loadmodule "extensions/sno_globalnickchange"; +#loadmodule "extensions/sno_globaloper"; +#loadmodule "extensions/sno_whois"; +#loadmodule "extensions/override"; +#loadmodule "extensions/no_kill_services"; + +/* + * IP cloaking extensions: use ip_cloaking_4.0 + * if you're linking 3.2 and later, otherwise use + * ip_cloaking, for compatibility with older 3.x + * releases. + */ + +#loadmodule "extensions/ip_cloaking_4.0"; +#loadmodule "extensions/ip_cloaking"; + +serverinfo { + name = "irc.localhost"; + sid = "42X"; + description = "charybdis test server"; + network_name = "StaticBox"; + + /* On multi-homed hosts you may need the following. These define + * the addresses we connect from to other servers. */ + /* for IPv4 */ + #vhost = "192.0.2.6"; + /* for IPv6 */ + #vhost6 = "2001:db8:2::6"; + + /* ssl_private_key: our ssl private key */ + ssl_private_key = "etc/ssl.key"; + + /* ssl_cert: certificate for our ssl server */ + ssl_cert = "etc/ssl.pem"; + + /* ssl_dh_params: DH parameters, generate with openssl dhparam -out dh.pem 2048 + * In general, the DH parameters size should be the same as your key's size. + * However it has been reported that some clients have broken TLS implementations which may + * choke on keysizes larger than 2048-bit, so we would recommend using 2048-bit DH parameters + * for now if your keys are larger than 2048-bit. + */ + ssl_dh_params = "etc/dh.pem"; + + /* ssld_count: number of ssld processes you want to start, if you + * have a really busy server, using N-1 where N is the number of + * cpu/cpu cores you have might be useful. A number greater than one + * can also be useful in case of bugs in ssld and because ssld needs + * two file descriptors per SSL connection. + */ + ssld_count = 1; + + /* default max clients: the default maximum number of clients + * allowed to connect. This can be changed once ircd has started by + * issuing: + * /quote set maxclients + */ + default_max_clients = 1024; + + /* nicklen: enforced nickname length (for this server only; must not + * be longer than the maximum length set while building). + */ + nicklen = 30; +}; + +admin { + name = "Lazy admin (lazya)"; + description = "StaticBox client server"; + email = "nobody@127.0.0.1"; +}; + +log { + fname_userlog = "logs/userlog"; + #fname_fuserlog = "logs/fuserlog"; + fname_operlog = "logs/operlog"; + #fname_foperlog = "logs/foperlog"; + fname_serverlog = "logs/serverlog"; + #fname_klinelog = "logs/klinelog"; + fname_killlog = "logs/killlog"; + fname_operspylog = "logs/operspylog"; + #fname_ioerrorlog = "logs/ioerror"; +}; + +/* class {} blocks MUST be specified before anything that uses them. That + * means they must be defined before auth {} and before connect {}. + */ +class "users" { + ping_time = 2 minutes; + number_per_ident = 10; + number_per_ip = 10; + number_per_ip_global = 50; + cidr_ipv4_bitlen = 24; + cidr_ipv6_bitlen = 64; + number_per_cidr = 200; + max_number = 3000; + sendq = 400 kbytes; +}; + +class "opers" { + ping_time = 5 minutes; + number_per_ip = 10; + max_number = 1000; + sendq = 1 megabyte; +}; + +class "server" { + ping_time = 5 minutes; + connectfreq = 5 minutes; + max_number = 1; + sendq = 4 megabytes; +}; + +listen { + /* defer_accept: wait for clients to send IRC handshake data before + * accepting them. if you intend to use software which depends on the + * server replying first, such as BOPM, you should disable this feature. + * otherwise, you probably want to leave it on. + */ + defer_accept = yes; + + /* If you want to listen on a specific IP only, specify host. + * host definitions apply only to the following port line. + */ + #host = "192.0.2.6"; + port = 5000, 6665 .. 6669; + # sslport = 6697; + + /* Listen on IPv6 (if you used host= above). */ + #host = "2001:db8:2::6"; + #port = 5000, 6665 .. 6669; + #sslport = 9999; +}; + +/* auth {}: allow users to connect to the ircd (OLD I:) + * auth {} blocks MUST be specified in order of precedence. The first one + * that matches a user will be used. So place spoofs first, then specials, + * then general access, then restricted. + */ +auth { + /* user: the user@host allowed to connect. Multiple IPv4/IPv6 user + * lines are permitted per auth block. This is matched against the + * hostname and IP address (using :: shortening for IPv6 and + * prepending a 0 if it starts with a colon) and can also use CIDR + * masks. + */ + user = "*@198.51.100.0/24"; + user = "*test@2001:db8:1:*"; + + /* password: an optional password that is required to use this block. + * By default this is not encrypted, specify the flag "encrypted" in + * flags = ...; below if it is. + */ + password = "letmein"; + + /* spoof: fake the users user@host to be be this. You may either + * specify a host or a user@host to spoof to. This is free-form, + * just do everyone a favour and dont abuse it. (OLD I: = flag) + */ + spoof = "I.still.hate.packets"; + + /* Possible flags in auth: + * + * encrypted | password is encrypted with mkpasswd + * spoof_notice | give a notice when spoofing hosts + * exceed_limit (old > flag) | allow user to exceed class user limits + * kline_exempt (old ^ flag) | exempt this user from k/g/xlines, + * | dnsbls, and proxies + * proxy_exempt | exempt this user from proxies + * dnsbl_exempt | exempt this user from dnsbls + * spambot_exempt | exempt this user from spambot checks + * shide_exempt | exempt this user from serverhiding + * jupe_exempt | exempt this user from generating + * warnings joining juped channels + * resv_exempt | exempt this user from resvs + * flood_exempt | exempt this user from flood limits + * USE WITH CAUTION. + * no_tilde (old - flag) | don't prefix ~ to username if no ident + * need_ident (old + flag) | require ident for user in this class + * need_ssl | require SSL/TLS for user in this class + * need_sasl | require SASL id for user in this class + */ + flags = kline_exempt, exceed_limit; + + /* class: the class the user is placed in */ + class = "opers"; +}; + +auth { + user = "*@*"; + class = "users"; +}; + +/* privset {} blocks MUST be specified before anything that uses them. That + * means they must be defined before operator {}. + */ +privset "local_op" { + privs = oper:local_kill, oper:operwall; +}; + +privset "server_bot" { + extends = "local_op"; + privs = oper:kline, oper:remoteban, snomask:nick_changes; +}; + +privset "global_op" { + extends = "local_op"; + privs = oper:global_kill, oper:routing, oper:kline, oper:unkline, oper:xline, + oper:resv, oper:mass_notice, oper:remoteban; +}; + +privset "admin" { + extends = "global_op"; + privs = oper:admin, oper:die, oper:rehash, oper:spy, oper:grant; +}; + +operator "god" { + /* name: the name of the oper must go above */ + + /* user: the user@host required for this operator. CIDR *is* + * supported now. auth{} spoofs work here, other spoofs do not. + * multiple user="" lines are supported. + */ + user = "*god@127.0.0.1"; + + /* password: the password required to oper. Unless ~encrypted is + * contained in flags = ...; this will need to be encrypted using + * mkpasswd, MD5 is supported + */ + password = "etcnjl8juSU1E"; + + /* rsa key: the public key for this oper when using Challenge. + * A password should not be defined when this is used, see + * doc/challenge.txt for more information. + */ + #rsa_public_key_file = "/usr/local/ircd/etc/oper.pub"; + + /* umodes: the specific umodes this oper gets when they oper. + * If this is specified an oper will not be given oper_umodes + * These are described above oper_only_umodes in general {}; + */ + #umodes = locops, servnotice, operwall, wallop; + + /* fingerprint: if specified, the oper's client certificate + * fingerprint will be checked against the specified fingerprint + * below. + */ + #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; + + /* snomask: specific server notice mask on oper up. + * If this is specified an oper will not be given oper_snomask. + */ + snomask = "+Zbfkrsuy"; + + /* flags: misc options for the operator. You may prefix an option + * with ~ to disable it, e.g. ~encrypted. + * + * Default flags are encrypted. + * + * Available options: + * + * encrypted: the password above is encrypted [DEFAULT] + * need_ssl: must be using SSL/TLS to oper up + */ + flags = encrypted; + + /* privset: privileges set to grant */ + privset = "admin"; +}; + +connect "irc.uplink.com" { + host = "203.0.113.3"; + send_password = "password"; + accept_password = "anotherpassword"; + port = 6666; + hub_mask = "*"; + class = "server"; + flags = compressed, topicburst; + + #fingerprint = "c77106576abf7f9f90cca0f63874a60f2e40a64b"; + + /* If the connection is IPv6, uncomment below. + * Use 0::1, not ::1, for IPv6 localhost. */ + #aftype = ipv6; +}; + +connect "ssl.uplink.com" { + host = "203.0.113.129"; + send_password = "password"; + accept_password = "anotherpassword"; + port = 9999; + hub_mask = "*"; + class = "server"; + flags = ssl, topicburst; +}; + +service { + name = "services.int"; +}; + +cluster { + name = "*"; + flags = kline, tkline, unkline, xline, txline, unxline, resv, tresv, unresv; +}; + +shared { + oper = "*@*", "*"; + flags = all, rehash; +}; + +/* exempt {}: IPs that are exempt from Dlines and rejectcache. (OLD d:) */ +exempt { + ip = "127.0.0.1"; +}; + +channel { + use_invex = yes; + use_except = yes; + use_forward = yes; + use_knock = yes; + knock_delay = 5 minutes; + knock_delay_channel = 1 minute; + max_chans_per_user = 15; + max_chans_per_user_large = 60; + max_bans = 100; + max_bans_large = 500; + default_split_user_count = 0; + default_split_server_count = 0; + no_create_on_split = no; + no_join_on_split = no; + burst_topicwho = yes; + kick_on_split_riding = no; + only_ascii_channels = no; + resv_forcepart = yes; + channel_target_change = yes; + disable_local_channels = no; + autochanmodes = "+nt"; + displayed_usercount = 3; + strip_topic_colors = no; +}; + +serverhide { + flatten_links = yes; + links_delay = 5 minutes; + hidden = no; + disable_hidden = no; +}; + +alias "NickServ" { + target = "NickServ"; +}; + +alias "ChanServ" { + target = "ChanServ"; +}; + +alias "OperServ" { + target = "OperServ"; +}; + +alias "MemoServ" { + target = "MemoServ"; +}; + +alias "NS" { + target = "NickServ"; +}; + +alias "CS" { + target = "ChanServ"; +}; + +alias "OS" { + target = "OperServ"; +}; + +alias "MS" { + target = "MemoServ"; +}; + +general { + hide_error_messages = opers; + hide_spoof_ips = yes; + + /* + * default_umodes: umodes to enable on connect. + * If you have enabled the new ip_cloaking_4.0 module, and you want + * to make use of it, add +x to this option, i.e.: + * default_umodes = "+ix"; + * + * If you have enabled the old ip_cloaking module, and you want + * to make use of it, add +h to this option, i.e.: + * default_umodes = "+ih"; + */ + default_umodes = "+i"; + + default_operstring = "is an IRC Operator"; + default_adminstring = "is a Server Administrator"; + servicestring = "is a Network Service"; + + /* + * Nick of the network's SASL agent. Used to check whether services are here, + * SASL credentials are only sent to its server. Needs to be a service. + * + * Defaults to SaslServ if unspecified. + */ + sasl_service = "SaslServ"; + disable_fake_channels = no; + tkline_expire_notices = no; + default_floodcount = 10; + failed_oper_notice = yes; + dots_in_ident=2; + min_nonwildcard = 4; + min_nonwildcard_simple = 3; + max_accept = 100; + max_monitor = 100; + anti_nick_flood = yes; + max_nick_time = 20 seconds; + max_nick_changes = 5; + anti_spam_exit_message_time = 5 minutes; + ts_warn_delta = 30 seconds; + ts_max_delta = 5 minutes; + client_exit = yes; + collision_fnc = yes; + resv_fnc = yes; + global_snotices = yes; + dline_with_reason = yes; + kline_delay = 0 seconds; + kline_with_reason = yes; + kline_reason = "K-Lined"; + identify_service = "NickServ@services.int"; + identify_command = "IDENTIFY"; + non_redundant_klines = yes; + warn_no_nline = yes; + use_propagated_bans = yes; + stats_e_disabled = yes; + stats_c_oper_only=no; + stats_h_oper_only=no; + stats_y_oper_only=no; + stats_o_oper_only=yes; + stats_P_oper_only=no; + stats_i_oper_only=masked; + stats_k_oper_only=masked; + map_oper_only = no; + operspy_admin_only = no; + operspy_dont_care_user_info = no; + caller_id_wait = 1 minute; + pace_wait_simple = 1 second; + pace_wait = 10 seconds; + short_motd = no; + ping_cookie = no; + connect_timeout = 30 seconds; + default_ident_timeout = 5; + disable_auth = no; + no_oper_flood = yes; + max_targets = 4; + client_flood_max_lines = 20; + use_whois_actually = no; + oper_only_umodes = operwall, locops, servnotice; + oper_umodes = locops, servnotice, operwall, wallop; + oper_snomask = "+s"; + burst_away = yes; + nick_delay = 0 seconds; # 15 minutes if you want to enable this + reject_ban_time = 1 minute; + reject_after_count = 3; + reject_duration = 5 minutes; + throttle_duration = 60; + throttle_count = 4; + max_ratelimit_tokens = 30; + away_interval = 30; + certfp_method = sha1; + hide_opers_in_whois = no; +}; + +modules { + path = "modules"; + path = "modules/autoload"; +}; -- cgit v1.2.3 From 71b3f8f50b2831142ec50c725f07333ab948c169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Apr 2016 10:14:17 +0200 Subject: Correct charybdis config file path --- tests/end_to_end/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 58165d9..2efbf24 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -9,6 +9,7 @@ import atexit import lxml.etree import sys import io +import os from functools import partial from slixmpp.xmlstream.matcher.base import MatcherBase @@ -184,7 +185,7 @@ class BiboumiRunner(ProcessRunner): class IrcServerRunner(ProcessRunner): def __init__(self): super().__init__() - self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", "../tests/end_to_end/ircd.conf", + self.create = asyncio.create_subprocess_exec("charybdis", "-foreground", "-configfile", os.getcwd() + "/../tests/end_to_end/ircd.conf", stderr=asyncio.subprocess.PIPE) -- cgit v1.2.3 From 3ddca86851786b5d2bb24a29ba0f73567e0c2bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 21 Apr 2016 17:48:39 +0200 Subject: Debug and exit if IRC server startup fails --- tests/end_to_end/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2efbf24..0eba231 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -438,6 +438,10 @@ if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) + print(res) + if not res: + print("IRC server failed to start, exiting") + sys.exit(1) if b"now running in foreground mode" in res: break print("irc server started.") -- cgit v1.2.3 From ee9b7f55bb84b00a3e33a63dd3b2d3878a905b77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 10:18:05 +0200 Subject: Run make rpm, in the ci --- .gitlab-ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a0d1a0..e8435d2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,19 +2,25 @@ stages: - build before_script: + - uname -a + - whoami + - echo $LANG - g++ --version - clang++ --version - - uname -a build: stage: build script: + - whoami + - echo $LANG + - echo $PATH - mkdir build - cd build - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 - make biboumi -j$(nproc) - make coverage -j$(nproc) - - make check + - make check -j$(nproc) + - make rpm -j$(nproc) artifacts: paths: - build/ -- cgit v1.2.3 From 327c821f4630b283e08a0b0875a6e7073af2423d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 10:23:25 +0200 Subject: Write irc output into a file --- tests/end_to_end/__main__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0eba231..1043a95 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -433,14 +433,15 @@ if __name__ == '__main__': failures = 0 + irc_output = open("irc_output.txt", "w") irc = IrcServerRunner() print("Starting irc server…") asyncio.get_event_loop().run_until_complete(irc.start()) while True: res = asyncio.get_event_loop().run_until_complete(irc.process.stderr.readline()) - print(res) + irc_output.write(res.decode()) if not res: - print("IRC server failed to start, exiting") + print("IRC server failed to start, see irc_output.txt for more details. Exiting…") sys.exit(1) if b"now running in foreground mode" in res: break -- cgit v1.2.3 From 3119ec72f32abf0dbd1edf7fc2f302fc9dd60ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 10:36:14 +0200 Subject: Force LANG to en_US when generating biboumi.spec (fixes the date format) --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bfc2e2..ec6ae73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,7 +265,10 @@ else() endif() configure_file(unit/biboumi.service.cmake biboumi.service) +# The date MUST be in english format +set(ENV{LANG} "en_US.utf-8") execute_process(COMMAND "date" "+%a %b %d %Y" OUTPUT_VARIABLE RPM_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) +unset(ENV{LANG}) configure_file(packaging/biboumi.spec.cmake biboumi.spec) -- cgit v1.2.3 From 747f04ee6d104f466014a0b0e810ac9f855fa547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 10:44:29 +0200 Subject: Move the e2e tests out of make check --- .gitlab-ci.yml | 1 + CMakeLists.txt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8435d2..68704c0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,7 @@ build: - make biboumi -j$(nproc) - make coverage -j$(nproc) - make check -j$(nproc) + - make e2e -j$(nproc) - make rpm -j$(nproc) artifacts: paths: diff --git a/CMakeLists.txt b/CMakeLists.txt index ec6ae73..b3321cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,8 +195,10 @@ if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) add_dependencies(test_suite catch) endif() add_custom_target(check COMMAND "test_suite" - COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS test_suite biboumi) +add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" + DEPENDS biboumi) + # ## Code coverage -- cgit v1.2.3 From ea35a12d5b20c50a1405a6eed5149f44aee59a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:57:32 +0200 Subject: Each e2e scenario can provide its own config name --- tests/end_to_end/__main__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 1043a95..c0c8542 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -128,13 +128,14 @@ class Scenario: etc """ - def __init__(self, name, steps): + def __init__(self, name, steps, conf="basic"): """ Steps is a list of 2-tuple: [(action, answer), (action, answer)] """ self.name = name self.steps = [] + self.conf = conf for elem in steps: if isinstance(elem, collections.Iterable): for step in elem: @@ -226,7 +227,7 @@ class BiboumiTest: filename=output_filename) with open("test.conf", "w") as fd: - fd.write(confs['basic']) + fd.write(confs[scenario.conf]) # Start the XMPP component and biboumi biboumi = BiboumiRunner(scenario.name, with_valgrind) @@ -254,7 +255,8 @@ class BiboumiTest: return not failed -confs = {'basic': +confs = { +'basic': """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -- cgit v1.2.3 From 439ea262237f6c33342bf62c05de7f305a63ff2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:55:08 +0200 Subject: Add a basic fixed_server e2e scenario --- tests/end_to_end/__main__.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index c0c8542..ef8c7aa 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -260,11 +260,20 @@ confs = { """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -port=8811"""} +port=8811""", + +'fixed_server': +"""hostname=biboumi.localhost +password=coucou +db_name=biboumi.sqlite +port=8811 +fixed_irc_server=irc.localhost +"""} common_replacements = { 'irc_server_one': 'irc.localhost@biboumi.localhost', 'irc_host_one': 'irc.localhost', + 'biboumi_host': 'biboumi.localhost', 'resource_one': 'resource1', 'nick_one': 'Nick', 'jid_one': 'first@example.com', @@ -431,6 +440,21 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@type='groupchat']/subject[text()='TOPIC TEST']"), ]), + Scenario("channel_basic_join_on_fixed_irc_server", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #zgeg [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#zgeg@{biboumi_host}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), + ], conf='fixed_server' + ), ) failures = 0 -- cgit v1.2.3 From 3fe55ab0b3935339e77c86c55cd81434c29edec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:55:30 +0200 Subject: Remove an unused variable --- tests/end_to_end/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index ef8c7aa..3811d2b 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -483,7 +483,7 @@ if __name__ == '__main__': print("Waiting for irc server to exit…") irc.stop() - code = asyncio.get_event_loop().run_until_complete(irc.wait()) + asyncio.get_event_loop().run_until_complete(irc.wait()) if failures: print("%d test%s failed, please fix %s." % (failures, 's' if failures > 1 else '', -- cgit v1.2.3 From 38a59c38ffbebb59fafadace297361a0dbf519de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 11:58:18 +0200 Subject: Add a Dockerfile to build a biboumi-test image --- docker/biboumi-test/Dockerfile | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docker/biboumi-test/Dockerfile diff --git a/docker/biboumi-test/Dockerfile b/docker/biboumi-test/Dockerfile new file mode 100644 index 0000000..704b413 --- /dev/null +++ b/docker/biboumi-test/Dockerfile @@ -0,0 +1,64 @@ +# This Dockerfile creates a docker image suitable to run biboumi’s build and +# tests. For example, it can be used on with gitlab-ci. + +FROM docker.io/fedora:latest + +RUN dnf update -y + +# Needed to build biboumi +RUN dnf install -y gcc-c++ +RUN dnf install -y clang +RUN dnf install -y valgrind +RUN dnf install -y c-ares-devel +RUN dnf install -y sqlite-devel +RUN dnf install -y libuuid-devel +RUN dnf install -y cmake +RUN dnf install -y make +RUN dnf install -y expat-devel +RUN dnf install -y libidn-devel +RUN dnf install -y uuid-devel +RUN dnf install -y systemd-devel +RUN dnf install -y rubygem-ronn + +# Needed to run tests +RUN dnf install -y git +RUN dnf install -y fedora-packager python3-lxml +RUN dnf install -y lcov + +# To be able to create the RPM +RUN dnf install -y rpmdevtools + +# Install botan +RUN git clone https://github.com/randombit/botan.git +RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install +RUN rm -rf /botan + +# Install litesql +RUN git clone git://git.louiz.org/litesql +RUN mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 +RUN cd /litesql/build && make install +RUN rm -rf /litesql + +RUN ldconfig + +# Install slixmpp, for e2e tests +RUN git clone git://git.louiz.org/slixmpp +RUN pip3 install pyasn1 +RUN dnf install -y python3-devel +RUN cd slixmpp && python3 setup.py build && python3 setup.py install + +RUN useradd tester + +# Install charybdis, for e2e tests +RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel +RUN dnf install -y libtool +RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis +RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install +RUN chown -R tester:tester /home/tester/ircd +RUN rm -rf /charybdis + +RUN su - tester -c "echo export LANG=fr_FR.utf-8 >> /home/tester/.bashrc" + +WORKDIR /home/tester +USER tester + -- cgit v1.2.3 From 376b28172b94d3950d4a20d447a9b9e49dad9ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:02:28 +0200 Subject: tests: Split the connection sequence into two We can now insert steps between the two parts --- tests/end_to_end/__main__.py | 46 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3811d2b..d1fbb29 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -287,51 +287,59 @@ def handshake_sequence(): partial(send_stanza, "")) -def connection_sequence(irc_host, jid): +def connection_begin_sequence(irc_host, jid): jid = jid.format_map(common_replacements) xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" return ( partial(expect_stanza, - xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), + xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connection failed: Connection refused'), partial(expect_stanza, - xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), + xpath % ('Connecting to %s:6670 (encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connection failed: Connection refused'), partial(expect_stanza, - xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), + xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, xpath % 'Connected to IRC server.'), # These two messages can be receive in any order partial(expect_stanza, - xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), + xpath_re % (r'^%s: \*\*\* (Checking Ident|Looking up your hostname...)$' % irc_host)), # These three messages can be received in any order partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + xpath_re % (r'^%s: (\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* No Ident response)$' % irc_host)), + ) + + +def connection_end_sequence(irc_host, jid): + jid = jid.format_map(common_replacements) + xpath = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[text()='%s']" + xpath_re = "/message[@to='" + jid + "'][@from='irc.localhost@biboumi.localhost']/body[re:test(text(), '%s')]" + return ( partial(expect_stanza, - xpath_re % (r'^%s: Your host is .*$' % irc_host)), + xpath_re % (r'^%s: Your host is .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: This server was created .*$' % irc_host)), + xpath_re % (r'^%s: This server was created .*$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), + xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), partial(expect_stanza, - xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), + xpath_re % (r'^%s: I have \d+ clients and \d+ servers$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), + xpath_re % (r'^%s: \d+ \d+ Current local users \d+, max \d+$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), + xpath_re % (r'^%s: \d+ \d+ Current global users \d+, max \d+$' % irc_host)), partial(expect_stanza, - xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), + xpath_re % (r'^%s: Highest connection count: \d+ \(\d+ clients\) \(\d+ connections received\)$' % irc_host)), partial(expect_stanza, xpath % "- This is charybdis MOTD you might replace it, but if not your friends will\n- laugh at you.\n"), partial(expect_stanza, @@ -339,6 +347,10 @@ def connection_sequence(irc_host, jid): ) +def connection_sequence(irc_host, jid): + return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) -- cgit v1.2.3 From 254249dd7848020fea48dab5dd3405ae56c85b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:03:29 +0200 Subject: Test the the virtual channel --- tests/end_to_end/__main__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d1fbb29..4c07661 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -383,6 +383,22 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), + Scenario("virtual_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), + # partial(expect_stanza, + # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), + # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), + ]), Scenario("channel_join_with_two_users", [ handshake_sequence(), -- cgit v1.2.3 From b98402c972e1d27656af2303754d233a23868fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 22 Apr 2016 15:06:45 +0200 Subject: Remove a forgotten comment [ci skip] --- tests/end_to_end/__main__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4c07661..3b2e9ee 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -396,7 +396,6 @@ if __name__ == '__main__': "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject[re:test(text(), '^This is a virtual channel.*$')]"), - # partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), connection_end_sequence("irc.localhost", '{jid_one}/{resource_one}'), ]), Scenario("channel_join_with_two_users", -- cgit v1.2.3 From d452c2ff1897d5f5c519d6a821598864d8207933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 10:28:53 +0200 Subject: Add e2e tests for the ad-hoc listing on the server jid --- tests/end_to_end/__main__.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 3b2e9ee..4f8e35b 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -108,7 +108,8 @@ def check_xpath(xpaths, stanza): for xpath in xpaths: tree = lxml.etree.parse(io.StringIO(str(stanza))) matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', - 'muc_user': 'http://jabber.org/protocol/muc#user'}) + 'muc_user': 'http://jabber.org/protocol/muc#user', + 'disco_items': 'http://jabber.org/protocol/disco#items'}) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) @@ -260,7 +261,8 @@ confs = { """hostname=biboumi.localhost password=coucou db_name=biboumi.sqlite -port=8811""", +port=8811 +admin=admin@example.com""", 'fixed_server': """hostname=biboumi.localhost @@ -268,6 +270,7 @@ password=coucou db_name=biboumi.sqlite port=8811 fixed_irc_server=irc.localhost +admin=admin@example.com """} common_replacements = { @@ -278,6 +281,7 @@ common_replacements = { 'nick_one': 'Nick', 'jid_one': 'first@example.com', 'jid_two': 'second@example.com', + 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', } @@ -482,6 +486,34 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#zgeg@{biboumi_host}'][@type='groupchat']/subject[not(text())]"), ], conf='fixed_server' ), + Scenario("list_adhoc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[3]")), + ]), + Scenario("list_admin_adhoc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[5]")), + ]), + Scenario("list_adhoc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[3]")), + ], conf='fixed_server'), + Scenario("list_admin_adhoc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[5]")), + ], conf='fixed_server'), ) failures = 0 -- cgit v1.2.3 From 82e0cf99b0dd0ba48f31060656d03a9586b939c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 10:29:55 +0200 Subject: Trivial cleanup --- tests/end_to_end/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 4f8e35b..786c4e9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -240,6 +240,8 @@ class BiboumiTest: xmpp.process() code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) + xmpp.biboumi = None + scenario.steps.clear() failed = False if not xmpp.failed: if code != self.expected_code: @@ -393,8 +395,6 @@ if __name__ == '__main__': partial(send_stanza, ""), connection_begin_sequence("irc.localhost", '{jid_one}/{resource_one}'), - # partial(expect_stanza, - # "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") -- cgit v1.2.3 From 1e56c59e8241dbfc6a2526c371cc2e894f9d0f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Apr 2016 11:29:52 +0200 Subject: Include the Configure ad-hoc command on biboumi's JID for fixed_irc_server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because a jid like “freenode.example.org” is both the JID for the configured IRC server, and biboumi’s JID. fix #3175 --- louloulibs/xmpp/adhoc_command.cpp | 4 ---- louloulibs/xmpp/adhoc_command.hpp | 6 +++++- louloulibs/xmpp/xmpp_component.cpp | 4 +++- src/xmpp/biboumi_adhoc_commands.cpp | 14 ++++++++++---- src/xmpp/biboumi_component.cpp | 11 ++++++++++- tests/end_to_end/__main__.py | 25 +++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/louloulibs/xmpp/adhoc_command.cpp b/louloulibs/xmpp/adhoc_command.cpp index 8a8bcff..99701d7 100644 --- a/louloulibs/xmpp/adhoc_command.cpp +++ b/louloulibs/xmpp/adhoc_command.cpp @@ -11,10 +11,6 @@ AdhocCommand::AdhocCommand(std::vector&& callbacks, const std::string { } -AdhocCommand::~AdhocCommand() -{ -} - bool AdhocCommand::is_admin_only() const { return this->admin_only; diff --git a/louloulibs/xmpp/adhoc_command.hpp b/louloulibs/xmpp/adhoc_command.hpp index a2e033a..1c4e4de 100644 --- a/louloulibs/xmpp/adhoc_command.hpp +++ b/louloulibs/xmpp/adhoc_command.hpp @@ -19,7 +19,11 @@ class AdhocCommand friend class AdhocSession; public: AdhocCommand(std::vector&& callback, const std::string& name, const bool admin_only); - ~AdhocCommand(); + ~AdhocCommand() = default; + AdhocCommand(const AdhocCommand&) = default; + AdhocCommand(AdhocCommand&&) = default; + AdhocCommand& operator=(AdhocCommand&&) = delete; + AdhocCommand& operator=(const AdhocCommand&) = delete; const std::string name; diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 8a0ca52..b92d9a3 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -591,7 +591,9 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t this->send_stanza(iq); } -void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, const std::string& from_jid, const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler) +void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::string& requester_jid, + const std::string& from_jid, + const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler) { Stanza iq("iq"); iq["type"] = "result"; diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index 7c157cb..eec930d 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -118,16 +118,19 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); + std::string server_domain; + if ((server_domain = Config::get("fixed_irc_server", "")).empty()) + server_domain = target.local; auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, - target.local); + server_domain); XmlNode x("jabber:x:data:x"); x["type"] = "form"; XmlNode title("title"); - title.set_inner("Configure the IRC server "s + target.local); + title.set_inner("Configure the IRC server "s + server_domain); x.add_child(std::move(title)); XmlNode instructions("instructions"); - instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + target.local); + instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain); x.add_child(std::move(instructions)); XmlNode required("required"); @@ -285,8 +288,11 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com { const Jid owner(session.get_owner_jid()); const Jid target(session.get_target_jid()); + std::string server_domain; + if ((server_domain = Config::get("fixed_irc_server", "")).empty()) + server_domain = target.local; auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain, - target.local); + server_domain); for (const XmlNode* field: x->get_children("field", "jabber:x:data")) { const XmlNode* value = field->get_child("value", "jabber:x:data"); diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 65fbfc5..5b1d0e0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -61,9 +61,18 @@ BiboumiComponent::BiboumiComponent(std::shared_ptr poller, const std::st {"reload", AdhocCommand({&Reload}, "Reload biboumi’s configuration", true)} }; +#ifdef USE_DATABASE + AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false); + if (!Config::get("fixed_irc_server", "").empty()) + { + this->adhoc_commands_handler.get_commands().emplace(std::make_pair("configure", + configure_server_command)); + } +#endif + this->irc_server_adhoc_commands_handler.get_commands() = { #ifdef USE_DATABASE - {"configure", AdhocCommand({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false)}, + {"configure", configure_server_command}, #endif }; this->irc_channel_adhoc_commands_handler.get_commands() = { diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 786c4e9..fbfd3ce 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -514,6 +514,31 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[5]")), ], conf='fixed_server'), + + + Scenario("list_adhoc_irc", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[1]")), + ]), + Scenario("list_adhoc_irc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[4]")), + ], conf='fixed_server'), + Scenario("list_admin_adhoc_irc_fixed_server", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", + "/iq/disco_items:query/disco_items:item[6]")), + ], conf='fixed_server'), + + ) failures = 0 -- cgit v1.2.3 From 98517e9a7b7873c15a5bde081a29be92c3dcfac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Apr 2016 10:22:43 +0200 Subject: Change the name of the DB in the e2e tests --- tests/end_to_end/__main__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index fbfd3ce..147e132 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -262,14 +262,14 @@ confs = { 'basic': """hostname=biboumi.localhost password=coucou -db_name=biboumi.sqlite +db_name=e2e_test.sqlite port=8811 admin=admin@example.com""", 'fixed_server': """hostname=biboumi.localhost password=coucou -db_name=biboumi.sqlite +db_name=e2e_test.sqlite port=8811 fixed_irc_server=irc.localhost admin=admin@example.com @@ -537,8 +537,6 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[6]")), ], conf='fixed_server'), - - ) failures = 0 -- cgit v1.2.3 From 305e01c0b58ec5cfee276841488f9c24835ce923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Apr 2016 15:52:30 +0200 Subject: Update .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 1c8dd97..83cd31d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,12 @@ *.gcov *.gcda +# Python files +*.pyc +*.pyo + # Build directories build/ + +# Clion directory +.idea/ \ No newline at end of file -- cgit v1.2.3 From af42073830087d97385e507f27f601e8769541b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 May 2016 14:16:40 +0200 Subject: Style fix Move all constructors at the top of classes --- louloulibs/config/config.hpp | 13 +++++++------ louloulibs/logger/logger.hpp | 8 +++++--- louloulibs/network/credentials_manager.hpp | 6 ++++++ louloulibs/network/dns_handler.cpp | 4 +++- louloulibs/network/dns_handler.hpp | 10 +++++----- louloulibs/network/dns_socket_handler.cpp | 4 ---- louloulibs/network/dns_socket_handler.hpp | 11 ++++++----- louloulibs/network/poller.hpp | 9 ++++----- louloulibs/network/resolver.hpp | 9 ++++----- louloulibs/network/socket_handler.hpp | 11 +++++------ louloulibs/network/tcp_socket_handler.hpp | 11 +++++------ louloulibs/utils/scopeguard.hpp | 10 ++++++---- louloulibs/utils/timed_events.cpp | 4 ---- louloulibs/utils/timed_events.hpp | 25 ++++++++++++++----------- louloulibs/utils/timed_events_manager.cpp | 8 -------- louloulibs/xmpp/adhoc_commands_handler.hpp | 11 ++++++----- louloulibs/xmpp/adhoc_session.hpp | 12 ++++++------ louloulibs/xmpp/jid.hpp | 11 +++++------ louloulibs/xmpp/xmpp_component.hpp | 10 +++++----- louloulibs/xmpp/xmpp_parser.cpp | 4 ++-- louloulibs/xmpp/xmpp_parser.hpp | 16 +++++++++------- louloulibs/xmpp/xmpp_stanza.hpp | 8 ++++---- src/bridge/bridge.cpp | 4 ---- src/bridge/bridge.hpp | 12 ++++++------ src/database/database.hpp | 10 +++++----- src/irc/iid.cpp | 8 -------- src/irc/iid.hpp | 12 ++++++------ src/irc/irc_channel.hpp | 20 +++++++++----------- src/irc/irc_client.hpp | 11 ++++++----- src/irc/irc_message.cpp | 4 ---- src/irc/irc_message.hpp | 10 +++++----- src/irc/irc_user.hpp | 13 +++++++------ src/xmpp/biboumi_component.hpp | 10 +++++----- 33 files changed, 156 insertions(+), 173 deletions(-) diff --git a/louloulibs/config/config.hpp b/louloulibs/config/config.hpp index 688c081..72620c0 100644 --- a/louloulibs/config/config.hpp +++ b/louloulibs/config/config.hpp @@ -28,8 +28,13 @@ typedef std::function t_config_changed_callback; class Config { public: - Config(){}; - ~Config(){}; + Config() = default; + ~Config() = default; + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; + /** * returns a value from the config. If it doesn’t exist, use * the second argument as the default. @@ -88,10 +93,6 @@ private: std::map values; std::vector callbacks; - Config(const Config&) = delete; - Config& operator=(const Config&) = delete; - Config(Config&&) = delete; - Config& operator=(Config&&) = delete; }; #endif // CONFIG_INCLUDED diff --git a/louloulibs/logger/logger.hpp b/louloulibs/logger/logger.hpp index ab8bdf1..3547513 100644 --- a/louloulibs/logger/logger.hpp +++ b/louloulibs/logger/logger.hpp @@ -67,10 +67,12 @@ public: Logger(const int log_level, const std::string& log_file); Logger(const int log_level); -private: - Logger(const Logger&); - Logger& operator=(const Logger&); + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + Logger(Logger&&) = delete; + Logger& operator=(Logger&&) = delete; +private: const int log_level; std::ofstream ofstream; nullstream null_stream; diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index 153c4d6..c0d23ae 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -14,6 +14,12 @@ class BasicCredentialsManager: public Botan::Credentials_Manager { public: BasicCredentialsManager(const TCPSocketHandler* const socket_handler); + + BasicCredentialsManager(BasicCredentialsManager&&) = delete; + BasicCredentialsManager(const BasicCredentialsManager&) = delete; + BasicCredentialsManager& operator=(const BasicCredentialsManager&) = delete; + BasicCredentialsManager& operator=(BasicCredentialsManager&&) = delete; + void verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector&) override final; diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index a8d0f9c..5e19f8c 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -13,7 +13,9 @@ DNSHandler DNSHandler::instance; using namespace std::string_literals; -DNSHandler::DNSHandler() +DNSHandler::DNSHandler(): + socket_handlers{}, + channel{nullptr} { int ares_error; if ((ares_error = ::ares_library_init(ARES_LIB_INIT_ALL)) != 0) diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index 61e2302..d2b48d2 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -24,6 +24,11 @@ class DNSHandler public: DNSHandler(); ~DNSHandler() = default; + DNSHandler(const DNSHandler&) = delete; + DNSHandler(DNSHandler&&) = delete; + DNSHandler& operator=(const DNSHandler&) = delete; + DNSHandler& operator=(DNSHandler&&) = delete; + void gethostbyname(const std::string& name, ares_host_callback callback, void* socket_handler, int family); /** @@ -48,11 +53,6 @@ private: */ std::vector> socket_handlers; ares_channel channel; - - DNSHandler(const DNSHandler&) = delete; - DNSHandler(DNSHandler&&) = delete; - DNSHandler& operator=(const DNSHandler&) = delete; - DNSHandler& operator=(DNSHandler&&) = delete; }; #endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp index 124c9b2..faaabdb 100644 --- a/louloulibs/network/dns_socket_handler.cpp +++ b/louloulibs/network/dns_socket_handler.cpp @@ -13,10 +13,6 @@ DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, { } -DNSSocketHandler::~DNSSocketHandler() -{ -} - void DNSSocketHandler::connect() { } diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index ad119e1..5ea9846 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -18,7 +18,12 @@ class DNSSocketHandler: public SocketHandler { public: explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); - ~DNSSocketHandler(); + ~DNSSocketHandler() = default; + DNSSocketHandler(const DNSSocketHandler&) = delete; + DNSSocketHandler(DNSSocketHandler&&) = delete; + DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; + DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; + /** * Just call dns_process_fd, c-ares will do its work of send()ing or * recv()ing the data it wants on that socket. @@ -36,10 +41,6 @@ public: bool is_connected() const override final; private: - DNSSocketHandler(const DNSSocketHandler&) = delete; - DNSSocketHandler(DNSSocketHandler&&) = delete; - DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; - DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; }; #endif // CARES_FOUND diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp index de0cb48..4bf7432 100644 --- a/louloulibs/network/poller.hpp +++ b/louloulibs/network/poller.hpp @@ -38,6 +38,10 @@ class Poller public: explicit Poller(); ~Poller(); + Poller(const Poller&) = delete; + Poller(Poller&&) = delete; + Poller& operator=(const Poller&) = delete; + Poller& operator=(Poller&&) = delete; /** * Add a SocketHandler to be monitored by this Poller. All receive events * are always automatically watched. @@ -85,11 +89,6 @@ private: #elif POLLER == EPOLL int epfd; #endif - - Poller(const Poller&) = delete; - Poller(Poller&&) = delete; - Poller& operator=(const Poller&) = delete; - Poller& operator=(Poller&&) = delete; }; #endif // POLLER_INCLUDED diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index f9e9134..fd57e25 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -37,6 +37,10 @@ public: Resolver(); ~Resolver() = default; + Resolver(const Resolver&) = delete; + Resolver(Resolver&&) = delete; + Resolver& operator=(const Resolver&) = delete; + Resolver& operator=(Resolver&&) = delete; bool is_resolving() const { @@ -117,11 +121,6 @@ private: ErrorCallbackType error_cb; SuccessCallbackType success_cb; - - Resolver(const Resolver&) = delete; - Resolver(Resolver&&) = delete; - Resolver& operator=(const Resolver&) = delete; - Resolver& operator=(Resolver&&) = delete; }; std::string addr_to_string(const struct addrinfo* rp); diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index d01ac5d..daa4413 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -16,6 +16,11 @@ public: socket(socket) {} virtual ~SocketHandler() {} + SocketHandler(const SocketHandler&) = delete; + SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; + virtual void on_recv() = 0; virtual void on_send() = 0; virtual void connect() = 0; @@ -34,12 +39,6 @@ protected: * The handled socket. */ socket_t socket; - -private: - SocketHandler(const SocketHandler&) = delete; - SocketHandler(SocketHandler&&) = delete; - SocketHandler& operator=(const SocketHandler&) = delete; - SocketHandler& operator=(SocketHandler&&) = delete; }; #endif // SOCKET_HANDLER_HPP diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 62b2ce7..dd4e82e 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -28,9 +28,13 @@ class TCPSocketHandler: public SocketHandler { protected: ~TCPSocketHandler(); - public: explicit TCPSocketHandler(std::shared_ptr poller); + TCPSocketHandler(const TCPSocketHandler&) = delete; + TCPSocketHandler(TCPSocketHandler&&) = delete; + TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; + TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + /** * Connect to the remote server, and call on_connected() if this * succeeds. If tls is true, we set use_tls to true and will also call @@ -231,11 +235,6 @@ protected: std::string bind_addr; private: - TCPSocketHandler(const TCPSocketHandler&) = delete; - TCPSocketHandler(TCPSocketHandler&&) = delete; - TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; - TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; - /** * Display the resolved IP, just for information purpose. */ diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp index df78831..fed40ff 100644 --- a/louloulibs/utils/scopeguard.hpp +++ b/louloulibs/utils/scopeguard.hpp @@ -40,6 +40,12 @@ public: { this->add_callback(std::move(func)); } + + ScopeGuard(const ScopeGuard&) = delete; + ScopeGuard& operator=(ScopeGuard&&) = delete; + ScopeGuard(ScopeGuard&&) = delete; + ScopeGuard& operator=(const ScopeGuard&) = delete; + /** * default constructor, the scope guard is enabled but empty, use * add_callback() @@ -78,10 +84,6 @@ private: bool enabled; std::vector> callbacks; - ScopeGuard(const ScopeGuard&) = delete; - ScopeGuard& operator=(ScopeGuard&&) = delete; - ScopeGuard(ScopeGuard&&) = delete; - ScopeGuard& operator=(const ScopeGuard&) = delete; }; } diff --git a/louloulibs/utils/timed_events.cpp b/louloulibs/utils/timed_events.cpp index 5010a3f..930380b 100644 --- a/louloulibs/utils/timed_events.cpp +++ b/louloulibs/utils/timed_events.cpp @@ -29,10 +29,6 @@ TimedEvent::TimedEvent(TimedEvent&& other): { } -TimedEvent::~TimedEvent() -{ -} - bool TimedEvent::is_after(const TimedEvent& other) const { return this->is_after(other.time_point); diff --git a/louloulibs/utils/timed_events.hpp b/louloulibs/utils/timed_events.hpp index 4e2800c..c3dfc40 100644 --- a/louloulibs/utils/timed_events.hpp +++ b/louloulibs/utils/timed_events.hpp @@ -31,7 +31,12 @@ public: std::function callback, const std::string& name=""); explicit TimedEvent(TimedEvent&&); - ~TimedEvent(); + ~TimedEvent() = default; + + TimedEvent(const TimedEvent&) = delete; + TimedEvent& operator=(const TimedEvent&) = delete; + TimedEvent& operator=(TimedEvent&&) = delete; + /** * Whether or not this event happens after the other one. */ @@ -70,10 +75,6 @@ private: * unique. */ const std::string name; - - TimedEvent(const TimedEvent&) = delete; - TimedEvent& operator=(const TimedEvent&) = delete; - TimedEvent& operator=(TimedEvent&&) = delete; }; /** @@ -84,7 +85,13 @@ private: class TimedEventsManager { public: - ~TimedEventsManager(); + ~TimedEventsManager() = default; + + TimedEventsManager(const TimedEventsManager&) = delete; + TimedEventsManager(TimedEventsManager&&) = delete; + TimedEventsManager& operator=(const TimedEventsManager&) = delete; + TimedEventsManager& operator=(TimedEventsManager&&) = delete; + /** * Return the unique instance of this class */ @@ -121,12 +128,8 @@ public: std::size_t size() const; private: - explicit TimedEventsManager(); std::list events; - TimedEventsManager(const TimedEventsManager&) = delete; - TimedEventsManager(TimedEventsManager&&) = delete; - TimedEventsManager& operator=(const TimedEventsManager&) = delete; - TimedEventsManager& operator=(TimedEventsManager&&) = delete; + explicit TimedEventsManager() = default; }; #endif // TIMED_EVENTS_HPP diff --git a/louloulibs/utils/timed_events_manager.cpp b/louloulibs/utils/timed_events_manager.cpp index 2c75e48..b90a237 100644 --- a/louloulibs/utils/timed_events_manager.cpp +++ b/louloulibs/utils/timed_events_manager.cpp @@ -6,14 +6,6 @@ TimedEventsManager& TimedEventsManager::instance() return inst; } -TimedEventsManager::TimedEventsManager() -{ -} - -TimedEventsManager::~TimedEventsManager() -{ -} - void TimedEventsManager::add_event(TimedEvent&& event) { for (auto it = this->events.begin(); it != this->events.end(); ++it) diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index 0614b2f..65b094d 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -21,6 +21,12 @@ public: commands{} { } ~AdhocCommandsHandler() = default; + + AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; + AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; + AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; + /** * Returns the list of available commands. */ @@ -63,11 +69,6 @@ private: * Of the form: {{session_id, owner_jid}, session}. */ std::map, AdhocSession> sessions; - - AdhocCommandsHandler(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler(AdhocCommandsHandler&&) = delete; - AdhocCommandsHandler& operator=(const AdhocCommandsHandler&) = delete; - AdhocCommandsHandler& operator=(AdhocCommandsHandler&&) = delete; }; #endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index e98b6a8..0966c4e 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -25,6 +25,12 @@ public: explicit AdhocSession(const AdhocCommand& command, const std::string& owner_jid, const std::string& to_jid); ~AdhocSession() = default; + + AdhocSession(const AdhocSession&) = delete; + AdhocSession(AdhocSession&&) = delete; + AdhocSession& operator=(const AdhocSession&) = delete; + AdhocSession& operator=(AdhocSession&&) = delete; + /** * Return the function to be executed, found in our AdhocCommand, for the * current_step. And increment the current_step. @@ -80,12 +86,6 @@ public: * any key in there. */ std::map vars; - -private: - AdhocSession(const AdhocSession&) = delete; - AdhocSession(AdhocSession&&) = delete; - AdhocSession& operator=(const AdhocSession&) = delete; - AdhocSession& operator=(AdhocSession&&) = delete; }; #endif // ADHOC_SESSION_HPP diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp index e2ee586..5ff5a4f 100644 --- a/louloulibs/xmpp/jid.hpp +++ b/louloulibs/xmpp/jid.hpp @@ -11,6 +11,11 @@ class Jid public: explicit Jid(const std::string& jid); + Jid(const Jid&) = delete; + Jid(Jid&&) = delete; + Jid& operator=(const Jid&) = delete; + Jid& operator=(Jid&&) = delete; + std::string domain; std::string local; std::string resource; @@ -23,12 +28,6 @@ public: { return this->local + "@" + this->domain + "/" + this->resource; } - -private: - Jid(const Jid&) = delete; - Jid(Jid&&) = delete; - Jid& operator=(const Jid&) = delete; - Jid& operator=(Jid&&) = delete; }; /** diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 07322dd..913e337 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -38,6 +38,11 @@ public: explicit XmppComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); virtual ~XmppComponent() = default; + XmppComponent(const XmppComponent&) = delete; + XmppComponent(XmppComponent&&) = delete; + XmppComponent& operator=(const XmppComponent&) = delete; + XmppComponent& operator=(XmppComponent&&) = delete; + void on_connection_failed(const std::string& reason) override final; void on_connected() override final; void on_connection_close(const std::string& error) override final; @@ -231,11 +236,6 @@ protected: std::unordered_map> stanza_handlers; AdhocCommandsHandler adhoc_commands_handler; - - XmppComponent(const XmppComponent&) = delete; - XmppComponent(XmppComponent&&) = delete; - XmppComponent& operator=(const XmppComponent&) = delete; - XmppComponent& operator=(XmppComponent&&) = delete; }; #endif // XMPP_COMPONENT_INCLUDED diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index 69de145..dc12000 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -118,7 +118,7 @@ void XmppParser::end_element(const XML_Char*) this->stanza_event(*this->current_node); // Note: deleting all the children of our parent deletes ourself, // so current_node is an invalid pointer after this line - this->current_node->get_parent()->delete_all_children(); + parent->delete_all_children(); } this->current_node = parent; } @@ -139,7 +139,7 @@ void XmppParser::stanza_event(const Stanza& stanza) const try { callback(stanza); } catch (const std::exception& e) { - log_debug("Unhandled exception: " << e.what()); + log_error("Unhandled exception: " << e.what()); } } } diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp index 3474b13..93c6c53 100644 --- a/louloulibs/xmpp/xmpp_parser.hpp +++ b/louloulibs/xmpp/xmpp_parser.hpp @@ -32,12 +32,12 @@ class XmppParser public: explicit XmppParser(); ~XmppParser(); + XmppParser(const XmppParser&) = delete; + XmppParser& operator=(const XmppParser&) = delete; + XmppParser(XmppParser&&) = delete; + XmppParser& operator=(XmppParser&&) = delete; public: - /** - * Init the XML parser and install the callbacks - */ - void init_xml_parser(); /** * Feed the parser with some XML data */ @@ -98,6 +98,11 @@ public: void stream_close_event(const XmlNode& node) const; private: + /** + * Init the XML parser and install the callbacks + */ + void init_xml_parser(); + /** * Expat structure. */ @@ -123,9 +128,6 @@ private: std::vector> stanza_callbacks; std::vector> stream_open_callbacks; std::vector> stream_close_callbacks; - - XmppParser(const XmppParser&) = delete; - XmppParser& operator=(const XmppParser&) = delete; }; #endif // XMPP_PARSER_INCLUDED diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 77ab206..28b8414 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -25,7 +25,6 @@ class XmlNode public: explicit XmlNode(const std::string& name, XmlNode* parent); explicit XmlNode(const std::string& name); - XmlNode(XmlNode&& node) = default; /** * The copy constructor does not copy the parent attribute. The children * nodes are all copied recursively. @@ -42,6 +41,10 @@ public: this->add_child(std::make_unique(*child)); } + XmlNode(XmlNode&& node) = default; + XmlNode& operator=(const XmlNode&) = delete; + XmlNode& operator=(XmlNode&&) = delete; + ~XmlNode() = default; void delete_all_children(); @@ -129,9 +132,6 @@ private: std::vector> children; std::string inner; std::string tail; - - XmlNode& operator=(const XmlNode&) = delete; - XmlNode& operator=(XmlNode&&) = delete; }; std::ostream& operator<<(std::ostream& os, const XmlNode& node); diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 2815da9..6008dfc 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -45,10 +45,6 @@ Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ { } -Bridge::~Bridge() -{ -} - /** * Return the role and affiliation, corresponding to the given irc mode */ static std::tuple get_role_affiliation_from_irc_mode(const char mode) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 6222ef0..2676d1d 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -34,7 +34,12 @@ class Bridge { public: explicit Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ptr poller); - ~Bridge(); + ~Bridge() = default; + + Bridge(const Bridge&) = delete; + Bridge(Bridge&& other) = delete; + Bridge& operator=(const Bridge&) = delete; + Bridge& operator=(Bridge&&) = delete; /** * QUIT all connected IRC servers. */ @@ -239,11 +244,6 @@ private: * response iq. */ std::list waiting_irc; - - Bridge(const Bridge&) = delete; - Bridge(Bridge&& other) = delete; - Bridge& operator=(const Bridge&) = delete; - Bridge& operator=(Bridge&&) = delete; }; struct IRCNotConnected: public std::exception diff --git a/src/database/database.hpp b/src/database/database.hpp index fc957bd..7bd41a3 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -16,6 +16,11 @@ public: Database() = default; ~Database() = default; + Database(const Database&) = delete; + Database(Database&&) = delete; + Database& operator=(const Database&) = delete; + Database& operator=(Database&&) = delete; + static void set_verbose(const bool val); template @@ -42,11 +47,6 @@ private: static std::unique_ptr db; static db::BibouDB& get_db(); - - Database(const Database&) = delete; - Database(Database&&) = delete; - Database& operator=(const Database&) = delete; - Database& operator=(Database&&) = delete; }; #endif /* USE_DATABASE */ diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 9d39129..212fb8f 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -51,14 +51,6 @@ void Iid::init_with_fixed_server(const std::string& iid, const std::string& host } } -Iid::Iid(const Iid& other): - is_channel(other.is_channel), - is_user(other.is_user), - local(other.local), - server(other.server) -{ -} - Iid::Iid(): is_channel(false), is_user(false) diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 91779b2..1c026e8 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -43,8 +43,12 @@ class Iid { public: Iid(const std::string& iid); - explicit Iid(const Iid&); - explicit Iid(); + Iid(); + Iid(const Iid&) = default; + + Iid(Iid&&) = delete; + Iid& operator=(const Iid&) = delete; + Iid& operator=(Iid&&) = delete; void set_local(const std::string& loc); void set_server(const std::string& serv); @@ -63,10 +67,6 @@ private: std::string local; std::string server; - - Iid(Iid&&) = delete; - Iid& operator=(const Iid&) = delete; - Iid& operator=(Iid&&) = delete; }; namespace std { diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 651b8ed..e22947b 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -16,6 +16,11 @@ class IrcChannel public: explicit IrcChannel(); + IrcChannel(const IrcChannel&) = delete; + IrcChannel(IrcChannel&&) = delete; + IrcChannel& operator=(const IrcChannel&) = delete; + IrcChannel& operator=(IrcChannel&&) = delete; + bool joined; std::string topic; std::string topic_author; @@ -30,12 +35,6 @@ public: protected: std::unique_ptr self; std::vector> users; - -private: - IrcChannel(const IrcChannel&) = delete; - IrcChannel(IrcChannel&&) = delete; - IrcChannel& operator=(const IrcChannel&) = delete; - IrcChannel& operator=(IrcChannel&&) = delete; }; /** @@ -50,6 +49,10 @@ class DummyIrcChannel: public IrcChannel { public: explicit DummyIrcChannel(); + DummyIrcChannel(const DummyIrcChannel&) = delete; + DummyIrcChannel(DummyIrcChannel&&) = delete; + DummyIrcChannel& operator=(const DummyIrcChannel&) = delete; + DummyIrcChannel& operator=(DummyIrcChannel&&) = delete; /** * This flag is at true whenever the user wants to join this channel, but @@ -60,11 +63,6 @@ public: * the channel, we don’t use that flag, we just join it immediately. */ bool joining; -private: - DummyIrcChannel(const DummyIrcChannel&) = delete; - DummyIrcChannel(DummyIrcChannel&&) = delete; - DummyIrcChannel& operator=(const DummyIrcChannel&) = delete; - DummyIrcChannel& operator=(DummyIrcChannel&&) = delete; }; #endif // IRC_CHANNEL_INCLUDED diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index ceaa860..7af097c 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -31,6 +31,12 @@ public: const std::string& realname, const std::string& user_hostname, Bridge& bridge); ~IrcClient(); + + IrcClient(const IrcClient&) = delete; + IrcClient(IrcClient&&) = delete; + IrcClient& operator=(const IrcClient&) = delete; + IrcClient& operator=(IrcClient&&) = delete; + /** * Connect to the IRC server */ @@ -360,11 +366,6 @@ private: * the WebIRC protocole. */ Resolver dns_resolver; - - IrcClient(const IrcClient&) = delete; - IrcClient(IrcClient&&) = delete; - IrcClient& operator=(const IrcClient&) = delete; - IrcClient& operator=(IrcClient&&) = delete; }; #endif // IRC_CLIENT_INCLUDED diff --git a/src/irc/irc_message.cpp b/src/irc/irc_message.cpp index eb3eb47..966a47c 100644 --- a/src/irc/irc_message.cpp +++ b/src/irc/irc_message.cpp @@ -47,10 +47,6 @@ IrcMessage::IrcMessage(std::string&& command, { } -IrcMessage::~IrcMessage() -{ -} - std::ostream& operator<<(std::ostream& os, const IrcMessage& message) { os << "IrcMessage"; diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp index 01e78cf..29ed946 100644 --- a/src/irc/irc_message.hpp +++ b/src/irc/irc_message.hpp @@ -11,16 +11,16 @@ public: IrcMessage(std::string&& line); IrcMessage(std::string&& prefix, std::string&& command, std::vector&& args); IrcMessage(std::string&& command, std::vector&& args); - ~IrcMessage(); - - std::string prefix; - std::string command; - std::vector arguments; + ~IrcMessage() = default; IrcMessage(const IrcMessage&) = delete; IrcMessage(IrcMessage&&) = delete; IrcMessage& operator=(const IrcMessage&) = delete; IrcMessage& operator=(IrcMessage&&) = delete; + + std::string prefix; + std::string command; + std::vector arguments; }; std::ostream& operator<<(std::ostream& os, const IrcMessage& message); diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp index d3397ca..4b6cd7a 100644 --- a/src/irc/irc_user.hpp +++ b/src/irc/irc_user.hpp @@ -15,18 +15,19 @@ public: explicit IrcUser(const std::string& name, const std::map& prefix_to_mode); explicit IrcUser(const std::string& name); + + IrcUser(const IrcUser&) = delete; + IrcUser(IrcUser&&) = delete; + IrcUser& operator=(const IrcUser&) = delete; + IrcUser& operator=(IrcUser&&) = delete; + void add_mode(const char mode); void remove_mode(const char mode); char get_most_significant_mode(const std::vector& sorted_user_modes) const; + std::string nick; std::string host; std::set modes; - -private: - IrcUser(const IrcUser&) = delete; - IrcUser(IrcUser&&) = delete; - IrcUser& operator=(const IrcUser&) = delete; - IrcUser& operator=(IrcUser&&) = delete; }; #endif // IRC_USER_INCLUDED diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 7602332..f1817e2 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -26,6 +26,11 @@ public: explicit BiboumiComponent(std::shared_ptr poller, const std::string& hostname, const std::string& secret); ~BiboumiComponent() = default; + BiboumiComponent(const BiboumiComponent&) = delete; + BiboumiComponent(BiboumiComponent&&) = delete; + BiboumiComponent& operator=(const BiboumiComponent&) = delete; + BiboumiComponent& operator=(BiboumiComponent&&) = delete; + /** * Returns the bridge for the given user. If it does not exist, return * nullptr. @@ -99,11 +104,6 @@ private: AdhocCommandsHandler irc_server_adhoc_commands_handler; AdhocCommandsHandler irc_channel_adhoc_commands_handler; - - BiboumiComponent(const BiboumiComponent&) = delete; - BiboumiComponent(BiboumiComponent&&) = delete; - BiboumiComponent& operator=(const BiboumiComponent&) = delete; - BiboumiComponent& operator=(BiboumiComponent&&) = delete; }; #endif // BIBOUMI_COMPONENT_INCLUDED -- cgit v1.2.3 From 06439fa2fc181d4ca1212cab530bee7e481df6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 4 May 2016 14:25:30 +0200 Subject: Run e2e in a subdirectory and lighten the build artifacts --- .gitlab-ci.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 68704c0..65b70e6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,12 +20,10 @@ build: - make biboumi -j$(nproc) - make coverage -j$(nproc) - make check -j$(nproc) - - make e2e -j$(nproc) + - mkdir tests_outputs && pushd tests_outputs && make e2e -j$(nproc) -C .. && popd - make rpm -j$(nproc) artifacts: paths: - - build/ - - - - + - build/coverage/ + - build/rpmbuild/ + - build/tests_outputs/ -- cgit v1.2.3 From ce798b1e8cfce7df69e0e91400c96090447a5ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 10 May 2016 12:48:39 +0200 Subject: Fix build with POLLER=POLL --- louloulibs/network/poller.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 26e5a8f..959567e 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -28,8 +28,10 @@ Poller::Poller() Poller::~Poller() { +#if POLLER == EPOLL if (this->epfd > 0) ::close(this->epfd); +#endif } void Poller::add_socket_handler(SocketHandler* socket_handler) -- cgit v1.2.3 From cef238e7002874939cc5e93cd3c29c130623344b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 11 May 2016 00:56:49 +0200 Subject: Do not use std::endl for each line when saving the conf file --- louloulibs/config/config.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/config/config.cpp b/louloulibs/config/config.cpp index 3e016f4..c785632 100644 --- a/louloulibs/config/config.cpp +++ b/louloulibs/config/config.cpp @@ -117,6 +117,6 @@ void Config::save_to_file() const return ; } for (auto& it: this->values) - file << it.first << "=" << it.second << std::endl; + file << it.first << "=" << it.second << '\n'; file.close(); } -- cgit v1.2.3 From e8e60b715c574388fc9df3f7a73801de38233679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 12 May 2016 11:39:47 +0200 Subject: Add a Dockerfile for biboumi-test-debian --- docker/biboumi-test/debian/Dockerfile | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docker/biboumi-test/debian/Dockerfile diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile new file mode 100644 index 0000000..32f294f --- /dev/null +++ b/docker/biboumi-test/debian/Dockerfile @@ -0,0 +1,72 @@ +# This Dockerfile creates a docker image suitable to run biboumi’s build and +# tests. For example, it can be used on with gitlab-ci. + +FROM docker.io/debian:latest + +RUN apt update + +# Needed to build biboumi +RUN apt install -y g++ +RUN apt install -y clang +RUN apt install -y valgrind +RUN apt install -y libc-ares-dev +RUN apt install -y libsqlite3-dev +RUN apt install -y libuuid1 +RUN apt install -y cmake +RUN apt install -y make +RUN apt install -y libexpat1-dev +RUN apt install -y libidn11-dev +RUN apt install -y uuid-dev +RUN apt install -y libsystemd-dev +RUN apt install -y ruby-ronn + +# Needed to run tests +RUN apt install -y git +RUN apt install -y python3-lxml +RUN apt install -y lcov + +# Install botan +RUN git clone https://github.com/randombit/botan.git +RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install +RUN rm -rf /botan + +# Install litesql +RUN git clone git://git.louiz.org/litesql +RUN mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 +RUN cd /litesql/build && make install +RUN rm -rf /litesql + +RUN ldconfig + +# Install slixmpp, for e2e tests +RUN apt install -y python3-pip +RUN git clone git://git.louiz.org/slixmpp +RUN pip3 install pyasn1 +RUN apt install -y python3-dev +RUN cd slixmpp && python3 setup.py build && python3 setup.py install + +RUN useradd tester -m + +# Install charybdis, for e2e tests +RUN apt install -y automake autoconf flex bison libltdl-dev openssl +RUN apt install -y libtool +RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis +RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install +RUN chown -R tester:tester /home/tester/ircd +RUN rm -rf /charybdis + +RUN apt install -y locales +RUN export LANGUAGE=en_US.UTF-8 +RUN export LANG=en_US.UTF-8 +RUN export LC_ALL=en_US.UTF-8 +RUN locale-gen +RUN dpkg-reconfigure locales + +RUN dpkg-reconfigure locales && \ + locale-gen C.UTF-8 && \ + /usr/sbin/update-locale LANG=C.UTF-8 + +ENV LC_ALL C.UTF-8 + +WORKDIR /home/tester +USER tester -- cgit v1.2.3 From ce2b8a15f17151c9ecfeebdec205ff2bb9248078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 12 May 2016 10:04:01 +0200 Subject: ci: Have two builds: on fedora and on debian --- .gitlab-ci.yml | 24 +++++++++---- docker/biboumi-test/Dockerfile | 64 ----------------------------------- docker/biboumi-test/fedora/Dockerfile | 64 +++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 70 deletions(-) delete mode 100644 docker/biboumi-test/Dockerfile create mode 100644 docker/biboumi-test/fedora/Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 65b70e6..93c206b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,15 +7,13 @@ before_script: - echo $LANG - g++ --version - clang++ --version + - mkdir build + - cd build -build: +build:fedora: stage: build + image: biboumi-test-fedora:latest script: - - whoami - - echo $LANG - - echo $PATH - - mkdir build - - cd build - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 - make biboumi -j$(nproc) - make coverage -j$(nproc) @@ -27,3 +25,17 @@ build: - build/coverage/ - build/rpmbuild/ - build/tests_outputs/ + +build:debian: + stage: build + image: biboumi-test-debian:latest + script: + - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 + - make biboumi -j$(nproc) + - make coverage -j$(nproc) + - make check -j$(nproc) + - mkdir tests_outputs && pushd tests_outputs && make e2e -j$(nproc) -C .. && popd + artifacts: + paths: + - build/coverage/ + - build/tests_outputs/ \ No newline at end of file diff --git a/docker/biboumi-test/Dockerfile b/docker/biboumi-test/Dockerfile deleted file mode 100644 index 704b413..0000000 --- a/docker/biboumi-test/Dockerfile +++ /dev/null @@ -1,64 +0,0 @@ -# This Dockerfile creates a docker image suitable to run biboumi’s build and -# tests. For example, it can be used on with gitlab-ci. - -FROM docker.io/fedora:latest - -RUN dnf update -y - -# Needed to build biboumi -RUN dnf install -y gcc-c++ -RUN dnf install -y clang -RUN dnf install -y valgrind -RUN dnf install -y c-ares-devel -RUN dnf install -y sqlite-devel -RUN dnf install -y libuuid-devel -RUN dnf install -y cmake -RUN dnf install -y make -RUN dnf install -y expat-devel -RUN dnf install -y libidn-devel -RUN dnf install -y uuid-devel -RUN dnf install -y systemd-devel -RUN dnf install -y rubygem-ronn - -# Needed to run tests -RUN dnf install -y git -RUN dnf install -y fedora-packager python3-lxml -RUN dnf install -y lcov - -# To be able to create the RPM -RUN dnf install -y rpmdevtools - -# Install botan -RUN git clone https://github.com/randombit/botan.git -RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install -RUN rm -rf /botan - -# Install litesql -RUN git clone git://git.louiz.org/litesql -RUN mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 -RUN cd /litesql/build && make install -RUN rm -rf /litesql - -RUN ldconfig - -# Install slixmpp, for e2e tests -RUN git clone git://git.louiz.org/slixmpp -RUN pip3 install pyasn1 -RUN dnf install -y python3-devel -RUN cd slixmpp && python3 setup.py build && python3 setup.py install - -RUN useradd tester - -# Install charybdis, for e2e tests -RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel -RUN dnf install -y libtool -RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis -RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install -RUN chown -R tester:tester /home/tester/ircd -RUN rm -rf /charybdis - -RUN su - tester -c "echo export LANG=fr_FR.utf-8 >> /home/tester/.bashrc" - -WORKDIR /home/tester -USER tester - diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile new file mode 100644 index 0000000..704b413 --- /dev/null +++ b/docker/biboumi-test/fedora/Dockerfile @@ -0,0 +1,64 @@ +# This Dockerfile creates a docker image suitable to run biboumi’s build and +# tests. For example, it can be used on with gitlab-ci. + +FROM docker.io/fedora:latest + +RUN dnf update -y + +# Needed to build biboumi +RUN dnf install -y gcc-c++ +RUN dnf install -y clang +RUN dnf install -y valgrind +RUN dnf install -y c-ares-devel +RUN dnf install -y sqlite-devel +RUN dnf install -y libuuid-devel +RUN dnf install -y cmake +RUN dnf install -y make +RUN dnf install -y expat-devel +RUN dnf install -y libidn-devel +RUN dnf install -y uuid-devel +RUN dnf install -y systemd-devel +RUN dnf install -y rubygem-ronn + +# Needed to run tests +RUN dnf install -y git +RUN dnf install -y fedora-packager python3-lxml +RUN dnf install -y lcov + +# To be able to create the RPM +RUN dnf install -y rpmdevtools + +# Install botan +RUN git clone https://github.com/randombit/botan.git +RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install +RUN rm -rf /botan + +# Install litesql +RUN git clone git://git.louiz.org/litesql +RUN mkdir /litesql/build && cd /litesql/build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr && make -j8 +RUN cd /litesql/build && make install +RUN rm -rf /litesql + +RUN ldconfig + +# Install slixmpp, for e2e tests +RUN git clone git://git.louiz.org/slixmpp +RUN pip3 install pyasn1 +RUN dnf install -y python3-devel +RUN cd slixmpp && python3 setup.py build && python3 setup.py install + +RUN useradd tester + +# Install charybdis, for e2e tests +RUN dnf install -y automake autoconf flex flex-devel bison libtool-ltdl-devel openssl-devel +RUN dnf install -y libtool +RUN git clone https://github.com/charybdis-ircd/charybdis.git && cd charybdis +RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bindir=/usr/bin && make -j8 && make install +RUN chown -R tester:tester /home/tester/ircd +RUN rm -rf /charybdis + +RUN su - tester -c "echo export LANG=fr_FR.utf-8 >> /home/tester/.bashrc" + +WORKDIR /home/tester +USER tester + -- cgit v1.2.3 From fdddd447b3976f26a4146ebf91abfc04736a006b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 01:22:49 +0200 Subject: =?UTF-8?q?Use=20=E2=80=9Cusing=E2=80=9D=20instead=20of=20typedef?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/network/socket_handler.hpp | 2 +- louloulibs/xmpp/body.hpp | 2 +- src/irc/irc_client.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index daa4413..feabcf1 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -6,7 +6,7 @@ class Poller; -typedef int socket_t; +using socket_t = int; class SocketHandler { diff --git a/louloulibs/xmpp/body.hpp b/louloulibs/xmpp/body.hpp index 6ac678e..df86feb 100644 --- a/louloulibs/xmpp/body.hpp +++ b/louloulibs/xmpp/body.hpp @@ -6,7 +6,7 @@ namespace Xmpp // Contains: // - an XMPP-valid UTF-8 body // - an XML node representing the XHTML-IM body, or null - typedef std::tuple> body; + using body = std::tuple>; } #endif /* XMPP_BODY_HPP_INCLUDED */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 327b170..0b1b079 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -27,10 +27,10 @@ using namespace std::chrono_literals; * Define a map of functions to be called for each IRC command we can * handle. */ -typedef void (IrcClient::*irc_callback_t)(const IrcMessage&); +using IrcCallback = void (IrcClient::*)(const IrcMessage&); static const std::unordered_map>> irc_callbacks = { + std::pair>> irc_callbacks = { {"NOTICE", {&IrcClient::on_notice, {2, 0}}}, {"002", {&IrcClient::forward_server_message, {2, 0}}}, {"003", {&IrcClient::forward_server_message, {2, 0}}}, -- cgit v1.2.3 From 0b1bf92479f7086f04404538b6813106ce7733bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 15:14:29 +0200 Subject: e2e: possibility to extract a value from a stanza and reuse it in send_stanza --- tests/end_to_end/__main__.py | 48 +++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 147e132..815093e 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -67,6 +67,8 @@ class XMPPComponent(slixmpp.BaseXMPP): self.failed = False self.accepting_server = None + self.saved_values = {} + def error(self, message): print("Failure: %s" % (message,)) self.scenario.steps = [] @@ -104,19 +106,27 @@ class XMPPComponent(slixmpp.BaseXMPP): pass -def check_xpath(xpaths, stanza): - for xpath in xpaths: - tree = lxml.etree.parse(io.StringIO(str(stanza))) - matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', - 'muc_user': 'http://jabber.org/protocol/muc#user', - 'disco_items': 'http://jabber.org/protocol/disco#items'}) +def match(stanza, xpath): + tree = lxml.etree.parse(io.StringIO(str(stanza))) + matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', + 'muc_user': 'http://jabber.org/protocol/muc#user', + 'disco_items': 'http://jabber.org/protocol/disco#items', + 'dataform': 'jabber:x:data'}) + return matched + + +def check_xpath(xpaths, xmpp, after, stanza): + for i, xpath in enumerate(xpaths): + matched = match(stanza, xpath) if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) + if after: + after(stanza, xmpp) -def check_xpath_optional(xpaths, stanza): +def check_xpath_optional(xpaths, xmpp, after, stanza): try: - check_xpath(xpaths, stanza) + check_xpath(xpaths, xmpp, after, stanza) except StanzaError: raise SkipStepError() @@ -149,6 +159,7 @@ class ProcessRunner: def __init__(self): self.process = None self.signal_sent = False + self.create = None @asyncio.coroutine def start(self): @@ -192,16 +203,18 @@ class IrcServerRunner(ProcessRunner): def send_stanza(stanza, xmpp, biboumi): - xmpp.send_raw(stanza.format_map(common_replacements)) + replacements = common_replacements + replacements.update(xmpp.saved_values) + xmpp.send_raw(stanza.format_map(replacements)) asyncio.get_event_loop().call_soon(xmpp.run_scenario) -def expect_stanza(xpaths, xmpp, biboumi, optional=False): +def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): check_func = check_xpath if not optional else check_xpath_optional if isinstance(xpaths, str): - xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)]) + xmpp.stanza_checker = partial(check_func, [xpaths.format_map(common_replacements)], xmpp, after) elif isinstance(xpaths, tuple): - xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths]) + xmpp.stanza_checker = partial(check_func, [xpath.format_map(common_replacements) for xpath in xpaths], xmpp, after) else: print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) @@ -252,6 +265,8 @@ class BiboumiTest: else: failed = True + xmpp.saved_values.clear() + if xmpp.server: xmpp.accepting_server.close() @@ -357,6 +372,15 @@ def connection_sequence(irc_host, jid): return connection_begin_sequence(irc_host, jid) + connection_end_sequence(irc_host, jid) +def extract_attribute(xpath, name, stanza): + matched = match(stanza, xpath) + return matched[0].get(name) + + +def save_value(name, func, stanza, xmpp): + xmpp.saved_values[name] = func(stanza) + + if __name__ == '__main__': atexit.register(asyncio.get_event_loop().close) -- cgit v1.2.3 From 8f80b9c53011561f8eee9875504e526c174317a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 15:15:10 +0200 Subject: Test the execution of the hello command --- tests/end_to_end/__main__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 815093e..706d177 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -111,6 +111,7 @@ def match(stanza, xpath): matched = tree.xpath(xpath, namespaces={'re': 'http://exslt.org/regular-expressions', 'muc_user': 'http://jabber.org/protocol/muc#user', 'disco_items': 'http://jabber.org/protocol/disco#items', + 'commands': 'http://jabber.org/protocol/commands', 'dataform': 'jabber:x:data'}) return matched @@ -561,6 +562,23 @@ if __name__ == '__main__': partial(expect_stanza, ("/iq[@type='result']/disco_items:query[@node='http://jabber.org/protocol/commands']", "/iq/disco_items:query/disco_items:item[6]")), ], conf='fixed_server'), + + Scenario("execute_hello_adhoc_command", + [ + handshake_sequence(), + partial(send_stanza, ""), + partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='hello'][@sessionid][@status='executing']", + "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Please provide your name.']", + "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single']/dataform:required", + "/iq/commands:command/commands:actions/commands:next", + ), + after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='hello']", "sessionid")) + + ), + partial(send_stanza, "COUCOU"), + partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") + ]), ) failures = 0 -- cgit v1.2.3 From 6f3dacc40d0f218348329dafa03d3088b768e98c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 13 May 2016 01:55:03 +0200 Subject: First rst attempt --- INSTALL.md | 156 ----------------- INSTALL.rst | 161 +++++++++++++++++ README.md | 61 ------- README.rst | 62 +++++++ doc/biboumi.1.md | 493 ---------------------------------------------------- doc/biboumi.1.rst | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 730 insertions(+), 710 deletions(-) delete mode 100644 INSTALL.md create mode 100644 INSTALL.rst delete mode 100644 README.md create mode 100644 README.rst delete mode 100644 doc/biboumi.1.md create mode 100644 doc/biboumi.1.rst diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index 7709fe9..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,156 +0,0 @@ -tl;dr -===== - - cmake . && make && ./biboumi - -If that didn’t work, read on. - -Dependencies -============ - -Build and runtime dependencies: - -Tools: - -- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) -- CMake -- ronn (optional) to build the man page - -Libraries: - -- expat - Used to parse XML from the XMPP server. - http://expat.sourceforge.net/ - -- libiconv - Encoding from anything into UTF-8 - http://www.gnu.org/software/libiconv/ - -- libuuid - Generate unique IDs - http://sourceforge.net/projects/libuuid/ - -- libidn (optional, but recommended) - Provides the stringprep functionality. Without it, JIDs for IRC users are - not provided. - http://www.gnu.org/software/libidn/ - -- c-ares (optional, but recommended) - Asynchronously resolve domain names. This offers better reactivity and - performances when connecting to a big number of IRC servers at the same - time. - http://c-ares.haxx.se/ - -- libbotan 1.11 (optional) - Provides TLS support. Without it, IRC connections are all made in - plain-text mode. - Other branches than the 1.11 are not supported. - http://botan.randombit.net/ - -- litesql (optional) - Provides a way to store various options in a (sqlite3) database. Each user - of the gateway can store their own values (for example their prefered port, - or their IRC password). - http://git.louiz.org/litesql - -- systemd (optional) - Provides the support for a systemd service of Type=notify. This is useful only - if you are packaging biboumi in a distribution with Systemd. - - -Configure -========= - -Configure the build system using cmake, there are many solutions to do that, -the simplest is to just run - - cmake . - -in the current directory. - -The default build type is "Debug", if you want to build a release version, -set the CMAKE_BUILD_TYPE variable to "release", by running this command -instead: - - cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr - -You can also configure many parameters of the build (like customize CFLAGS, -the install path, choose the compiler, or enabling some options like the -POLLER to use), using the ncurses interface of ccmake: - - ccmake . - -In ccmake, first use 'c' to configure the build system, edit the values you -need and finaly use 'g' to generate the Makefiles to build the system and -quit ccmake. - -You can also configure these options using a -D command line flag. - -The list of available options: - -- POLLER: lets you select the poller used by biboumi, at - compile-time. Possible values are: - EPOLL: use the Linux-specific epoll(7). This is the default on Linux. - POLL: use the standard poll(2). This is the default value on all non-Linux - platforms. - -- WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, - if it is not found, the configuration process will fail. The second will - make the build process ignore the Botan library, it will not be used even - if it's available on the system. If none of these option is specified, the - library will be used if available and will be ignored otherwise. - -- WITH_LIBIDN and WITHOUT_LIBIDN: Just like the WITH(OUT)_BOTAN options, but - for the IDN library - -- WITH_SYSTEMD and WITHOUT_SYSTEMD: Just like the other WITH(OUT)_* options, - but for the Systemd library - -Example: - - cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX \ - -DWITH_BOTAN=1 -DWITHOUT_SYSTEMD=1 - -This command will configure the project to build a release, with TLS enabled -(using Botan) but without using Systemd (even if available on the system). - - -Build -===== - -- Once you’ve configured everything using cmake, build the project - - make - - -Install -======= - -- And then, optionaly, Install the software system-wide - - make install - - -Testing -======= - -You can run the test suite with - - make check - -This project uses the Catch unit test framework, it will be automatically -fetched with cmake, by cloning the github repository. - -You can also check the overall code coverage of this test suite by running - - make coverage - -This requires gcov and lcov to be installed. - - -Run -=== - -Run the software using the `biboumi` binary. Read the documentation (the -man page biboumi(1) or the `biboumi.1.md` file) for more information on how -to use biboumi. diff --git a/INSTALL.rst b/INSTALL.rst new file mode 100644 index 0000000..1e5d0f5 --- /dev/null +++ b/INSTALL.rst @@ -0,0 +1,161 @@ +INSTALL +======= + +tl;dr +----- + + cmake . && make && ./biboumi + +If that didn’t work, read on. + +Dependencies +------------ + +Build and runtime dependencies: + +Tools: +~~~~~~ + +- A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) +- CMake +- ronn (optional) to build the man page + +Libraries: +~~~~~~~~~~ + +expat_ + Used to parse XML from the XMPP server. + +libiconv_ + Encoding from anything into UTF-8 + +libuuid_ + Generate unique IDs + +libidn_ (optional, but recommended) + Provides the stringprep functionality. Without it, JIDs for IRC users are + not provided. + +c-ares_ (optional, but recommended) + Asynchronously resolve domain names. This offers better reactivity and + performances when connecting to a big number of IRC servers at the same + time. + +libbotan_ 1.11 (optional) + Provides TLS support. Without it, IRC connections are all made in + plain-text mode. + Other branches than the 1.11 are not supported. + +litesql_ (optional) + Provides a way to store various options in a (sqlite3) database. Each user + of the gateway can store their own values (for example their prefered port, + or their IRC password). + +systemd_ (optional) + Provides the support for a systemd service of Type=notify. This is useful only + if you are packaging biboumi in a distribution with Systemd. + + +Configure +--------- + +Configure the build system using cmake, there are many solutions to do that, +the simplest is to just run + + cmake . + +in the current directory. + +The default build type is "Debug", if you want to build a release version, +set the CMAKE_BUILD_TYPE variable to "release", by running this command +instead: + + cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr + +You can also configure many parameters of the build (like customize CFLAGS, +the install path, choose the compiler, or enabling some options like the +POLLER to use), using the ncurses interface of ccmake: + + ccmake . + +In ccmake, first use 'c' to configure the build system, edit the values you +need and finaly use 'g' to generate the Makefiles to build the system and +quit ccmake. + +You can also configure these options using a -D command line flag. + +The list of available options: + +- POLLER: lets you select the poller used by biboumi, at + compile-time. Possible values are: + + - EPOLL: use the Linux-specific epoll(7). This is the default on Linux. + - POLL: use the standard poll(2). This is the default value on all non-Linux + platforms. + +- WITH_BOTAN and WITHOUT_BOTAN: The first force the usage of the Botan library, + if it is not found, the configuration process will fail. The second will + make the build process ignore the Botan library, it will not be used even + if it's available on the system. If none of these option is specified, the + library will be used if available and will be ignored otherwise. + +- WITH_LIBIDN and WITHOUT_LIBIDN: Just like the WITH(OUT)_BOTAN options, but + for the IDN library + +- WITH_SYSTEMD and WITHOUT_SYSTEMD: Just like the other WITH(OUT)_* options, + but for the Systemd library + +Example: + + cmake . -DCMAKE_BUILD_TYPE=release -DCMAKE_INSTALL_PREFIX=/usr + -DWITH_BOTAN=1 -DWITHOUT_SYSTEMD=1 + +This command will configure the project to build a release, with TLS enabled +(using Botan) but without using Systemd (even if available on the system). + + +Build +----- +Once you’ve configured everything using cmake, build the project + + make + + +Install +------- +And then, optionaly, Install the software system-wide + + make install + + +Testing +------- +You can run the test suite with + + make check + +This project uses the Catch unit test framework, it will be automatically +fetched with cmake, by cloning the github repository. + +You can also check the overall code coverage of this test suite by running + + make coverage + +This requires gcov and lcov to be installed. + + +Run +--- +Run the software using the `biboumi` binary. Read the documentation (the +man page biboumi(1) or the `biboumi.1.rst`_ file) for more information on how +to use biboumi. + +.. _expat: http://expat.sourceforge.net/ +.. _libiconv: http://www.gnu.org/software/libiconv/ +.. _libuuid: http://sourceforge.net/projects/libuuid/ +.. _libidn: http://www.gnu.org/software/libidn/ +.. _libbotan: http://botan.randombit.net/ +.. _c-ares: http://c-ares.haxx.se/ +.. _litesql: http://git.louiz.org/litesql +.. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ +.. _biboumi.1.rst: doc/biboumi.1.rst diff --git a/README.md b/README.md deleted file mode 100644 index b8b5124..0000000 --- a/README.md +++ /dev/null @@ -1,61 +0,0 @@ -Biboumi -======= - -Biboumi is an XMPP gateway that connects to IRC servers and translates -between the two protocols. It can be used to access IRC channels using any -XMPP client as if these channels were XMPP MUCs. - -It is written in modern C++14 and makes great efforts to have as little -dependencies and to be as simple as possible. - -The goal is to provide a way to access most of IRC features using any XMPP -client. It doesn’t however try to provide a complete mapping of the -features of both worlds simply because this is not useful and most probably -impossible. For example all IRC modes are not all translatable into an XMPP -features. Some of them are (like +m (mute) or +o (operator) modes), but -some others are IRC-specific. If IRC is the limiting factor (for example -you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to -work around this issue: it just enforces the rules of the IRC server by -telling the user that he/she must choose an ASCII-only nickname. An -important goal is to keep the software (and its code) light and simple. - - -Install -======= -Refer to the [INSTALL][] file. - -Usage -===== - -Read [the documentation](doc/biboumi.1.md). - -Authors -======= -Florent Le Coz (louiz’) - - -Contact/Support -=============== -* XMPP ChatRoom: biboumi@muc.poez.io -* Report a bug: https://dev.louiz.org/projects/biboumi/issues/new - -To contribute, the preferred way is to commit your changes on some -publicly-available git repository (your own, or github -(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and -to notify the developers with a ticket on the bug tracker -(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on -github or a merge request on gitlab. - -Optionally you can come discuss your changes on the XMPP chat room, -beforehand. - - -Licence -======= -Biboumi is Free Software. -(learn more: http://www.gnu.org/philosophy/free-sw.html) - -Biboumi is released under the zlib license. -Please read the COPYING file for details. - -[INSTALL]: INSTALL diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a7f9348 --- /dev/null +++ b/README.rst @@ -0,0 +1,62 @@ +Biboumi +======= + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +It is written in modern C++14 and makes great efforts to have as little +dependencies and to be as simple as possible. + +The goal is to provide a way to access most of IRC features using any XMPP +client. It doesn’t however try to provide a complete mapping of the +features of both worlds simply because this is not useful and most probably +impossible. For example all IRC modes are not all translatable into an XMPP +features. Some of them are (like +m (mute) or +o (operator) modes), but +some others are IRC-specific. If IRC is the limiting factor (for example +you cannot have a non-ASCII nickname on IRC) then biboumi doesn’t try to +work around this issue: it just enforces the rules of the IRC server by +telling the user that he/she must choose an ASCII-only nickname. An +important goal is to keep the software (and its code) light and simple. + + +Install +------- +Refer to the INSTALL_ file. + + +Usage +----- +Read `the documentation`_. + +Authors +------- +Florent Le Coz (louiz’) + + +Contact/Support +--------------- +* XMPP ChatRoom: biboumi@muc.poez.io +* Report a bug: https://dev.louiz.org/projects/biboumi/issues/new + +To contribute, the preferred way is to commit your changes on some +publicly-available git repository (your own, or github +(https://github.com/louiz/biboumi), or a fork on https://lab.louiz.org) and +to notify the developers with a ticket on the bug tracker +(https://dev.louiz.org/projects/biboumi/issues/new), a pull request on +github or a merge request on gitlab. + +Optionally you can come discuss your changes on the XMPP chat room, +beforehand. + + +Licence +------- +Biboumi is Free Software. +(learn more: http://www.gnu.org/philosophy/free-sw.html) + +Biboumi is released under the zlib license. +Please read the COPYING file for details. + +.. _INSTALL: INSTALL.rst +.. _the documentation: doc/biboumi.1.rst diff --git a/doc/biboumi.1.md b/doc/biboumi.1.md deleted file mode 100644 index 3a1fb28..0000000 --- a/doc/biboumi.1.md +++ /dev/null @@ -1,493 +0,0 @@ -BIBOUMI 1 "2014-06-02" -====================== - -NAME ----- - -biboumi - XMPP gateway to IRC - -DESCRIPTION ------------ - -Biboumi is an XMPP gateway that connects to IRC servers and translates -between the two protocols. It can be used to access IRC channels using any -XMPP client as if these channels were XMPP MUCs. - -SYNOPSIS --------- - -`biboumi` [`config_filename`] - -OPTIONS -------- - -Available command line options: - -`config_filename` - - Specify the file to read for configuration. See *CONFIG* section for more - details on its content. - -CONFIG ------- - -The configuration file uses a simple format of the form -`"option=value"`. Here is a description of each possible option: - -`hostname` (mandatory) - - The hostname served by the XMPP gateway. This domain must be configured in - the XMPP server as an external component. See the manual for your XMPP - server for more information. - For prosody, see http://prosody.im/doc/components#adding_an_external_component - -`password` (mandatory) - - The password used to authenticate the XMPP component to your XMPP server. - This password must be configured in the XMPP server, associated with the - external component on *hostname*. - -`xmpp_server_ip` - - The IP address to connect to the XMPP server on. The connection to the XMPP - server is unencrypted, so the biboumi instance and the server should normally - be on the same host. The default value is 127.0.0.1. - -`port` - - The TCP port to use to connect to the local XMPP component. The default - value is 5347. - -`admin` - - The bare JID of the gateway administrator. This JID will have more - privileges than other standard users (the admin thus needs to check their - privileges), for example some administration ad-hoc commands will only be - available to that JID. - -`fixed_irc_server` - - If this option contains the hostname of an IRC server (for example - irc.example.org), then biboumi will enforce the connexion to that IRC - server only. This means that a JID like "#chan@biboumi.example.com" must - be used instead of "#chan%irc.example.org@biboumi.example.com". In that - mode, the virtual channel (see *Connect to an IRC server*) is not - available and you still need to use the ! separator to send message to an - IRC user (for example "foo!@biboumi.example.com" to send a message to - foo), although the in-room JID still work as expected - ("#channel@biboumi.example.com/Nick"). On the other hand, the '%' lose - any meaning. It can appear in the JID but will not be interpreted as a - separator (thus the JID "#channel%hello@biboumi.example.com" points to the - channel named "#channel%hello" on the configured IRC server) This option - can for example be used by an administrator that just wants to let their - users join their own IRC server using an XMPP client, while forbidding - access to any other IRC server. - -`realname_customization` - - If this option is set to “false” (default is “true”), the users will not be - able to use the ad-hoc commands that lets them configure their realname and - username. - -`realname_from_jid` - - If this option is set to “true”, the realname and username of each biboumi - user will be extracted from their JID. The realname is their bare JID, and - the username is the node-part of their JID. Note that if - `realname_customization` is “true”, each user will still be able to - customize their realname and username, this option just decides the default - realname and username. - - If this option is set to “false” (the default value), the realname and - username of each user will be set to the nick they used to connect to the - IRC server. - -`webirc_password` - - Configure a password to be communicated to the IRC server, as part of the - WEBIRC message (see https://kiwiirc.com/docs/webirc). If this option is - set, an additional DNS resolution of the hostname of each XMPP server will - be made when connecting to an IRC server. - -`log_file` - - A filename into which logs are written. If none is provided, the logs are - written on standard output. - -`log_level` - - Indicate what type of log messages to write in the logs. Value can be - from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The - default is 0, but a more practical value for production use is 1. - -`ca_file` - - Specifies which file should be use as the list of trusted CA when - negociating a TLS session. By default this value is unset and biboumi - tries a list of well-known paths. - -`outgoing_bind` - - An address (IPv4 or IPv6) to bind the outgoing sockets to. If no value is - specified, it will use the one assigned by the operating system. You can - for example use outgoing_bind=192.168.1.11 to force biboumi to use the - interface with this address. Note that this is only used for connections - to IRC servers. - -The configuration can be re-read at runtime (you can for example change the -log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 -(see kill(1)) to the process. - -USAGE ------ - -Biboumi acts as a server, it should be run as a daemon that lives in the -background for as long as it is needed. Note that biboumi does not -daemonize itself, this task should be done by your init system (SysVinit, -systemd, upstart). - -When started, biboumi connects, without encryption (see *SECURITY*), to the -local XMPP server on the port `5347` and authenticates with the provided -password. Biboumi then serves the configured `hostname`: this means that -all XMPP stanza with a `to` JID on that domain will be forwarded to biboumi -by the XMPP server, and biboumi will only send messages coming from that -hostname. - -When a user joins an IRC channel on an IRC server (see *Join an IRC -channel*), biboumi connects to the remote IRC server, sets the user’s nick -as requested, and then tries to join the specified channel. If the same -user subsequently tries to connect to an other channel on the same server, -the same IRC connection is used. If, however, an other user wants to join -an IRC channel on that same IRC server, biboumi opens a new connection to -that server. Biboumi connects once to each IRC server, for each user on it. - -To cleanly shutdown the component, send a SIGINT or SIGTERM signal to it. -It will send messages to all connected IRC and XMPP servers to indicate a -reason why the users are being disconnected. Biboumi exits when the end of -communication is acknowledged by all IRC servers. If one or more IRC -servers do not respond, biboumi will only exit if it receives the same -signal again or if a 2 seconds delay has passed. - -### Addressing - -IRC entities are represented by XMPP JIDs. The domain part of the JID is -the domain served by biboumi (the part after the `@`, biboumi.example.com in -the examples), and the local part (the part before the `@`) depends on the -concerned entity. - -IRC channels have a local part formed like this: -`channel_name`%`irc_server`. - -If the IRC channel you want to adress starts with the `'#'` character (or an -other character, announced by the IRC server, like `'&'`, `'+'` or `'!'`), -then you must include it in the JID. Some other gateway implementations, as -well as some IRC clients, do not require them to be started by one of these -characters, adding an implicit `'#'` in that case. Biboumi does not do that -because this gets confusing when trying to understand the difference between -the channels *#foo*, and *##foo*. Note that biboumi does not use the -presence of these special characters to identify an IRC channel, only the -presence of the separator `%` is used for that. - -The channel name can also be empty (for example `%irc.example.com`), in that -case this represents the virtual channel provided by biboumi. See *Connect -to an IRC server* for more details. - -There is two ways to address an IRC user, using a local part like this: -`nickname`!`irc_server` -or by using the in-room address of the participant, like this: -`channel_name`%`irc_server`@`biboumi.example.com`/`Nickname` - -The second JID is available only to be compatible with XMPP clients when the -user wants to send a private message to the participant `Nickname` in the -room `channel_name%irc_server@biboumi.example.com`. - -On XMPP, the node part of the JID can only be lowercase. On the other hand, -IRC nicknames are case-insensitive, this means that the nicknames toto, -Toto, tOtO and TOTO all represent the same IRC user. This means you can -talk to the user toto, and this will work. - -Also note that some IRC nicknames may contain characters that are not -allowed in the local part of a JID (for example '@'). If you need to send a -message to a nick containing such a character, you have to use a jid like -`%irc.example.com@biboumi.example.com/AnnoyingNickn@me`, because the JID -`AnnoyingNickn@me!irc.example.com@biboumi.example.com` would not work. - -Examples: - - `#foo%irc.example.com@biboumi.example.com` is the #foo IRC channel, on the - irc.example.com IRC server, and this is served by the biboumi instance on - biboumi.example.com - - `toto!irc.example.com@biboumi.example.com` is the IRC user named toto, or - TotO, etc. - - `irc.example.com@biboumi.example.com` is the IRC server irc.example.com. - - `%irc.example.com@biboumi.example.com` is the virtual channel provided by - biboumi, for the IRC server irc.example.com. - -Note: Some JIDs are valid but make no sense in the context of -biboumi: - - `!irc.example.com@biboumi.example.com` is the empty-string nick on the - irc.example.com server. It makes no sense to try to send messages to it. - - `#test%@biboumi.example.com`, or any other JID that does not contain an - IRC server is invalid. Any message to that kind of JID will trigger an - error, or will be ignored. - -If compiled with Libidn, an IRC channel participant has a bare JID -representing the “hostname” provided by the IRC server. This JID can only -be used to set IRC modes (for example to ban a user based on its IP), or to -identify user. It cannot be used to contact that user using biboumi. - -### Join an IRC channel - -To join an IRC channel `#foo` on the IRC server `irc.example.com`, -join the XMPP MUC `#foo%irc.example.com@hostname`. - -### Connect to an IRC server - -The connection to the IRC server is automatically made when the user tries -to join any channel on that IRC server. The connection is closed whenever -the last channel on that server is left by the user. To be able to stay -connected to an IRC server without having to be in a real IRC channel, -biboumi provides a virtual channel on the jid -`%irc.example.com@biboumi.example.com`. For example if you want to join the -channel `#foo` on the server `irc.example.com`, but you need to authenticate -to a bot of the server before you’re allowed to join it, you can first join -the room `%irc.example.com@biboumi.example.com` (this will effectively -connect you to the IRC server without joining any room), then send your -authentication message to the user `bot!irc.example.com@biboumi.example.com` -and finally join the room `#foo%irc.example.com@biboumi.example.com`. - -### Channel messages - -On XMPP, unlike on IRC, the displayed order of the messages is the same for -all participants of a MUC. Biboumi can not however provide this feature, as -it cannot know whether the IRC server has received and forwarded the -messages to other users. This means that the order of the messages -displayed in your XMPP client may not be the same than the order on other -IRC users’. - -### List channels - -You can list the IRC channels on a given IRC server by sending an XMPP disco -items request on the IRC server JID. The number of channels on some servers -is huge, and biboumi does not (yet) support result set management (XEP 0059) -so the result stanza may be very big. - -### Nicknames - -On IRC, nicknames are server-wide. This means that one user only has one -single nickname at one given time on all the channels of a server. This is -different from XMPP where a user can have a different nick on each MUC, -even if these MUCs are on the same server. - -This means that the nick you choose when joining your first IRC channel on a -given IRC server will be your nickname in all other channels that you join -on that same IRC server. -If you explicitely change your nickname on one channel, your nickname will -be changed on all channels on the same server as well. -Joining a new channel with a different nick, however, will not change your -nick. The provided nick will be ignored, in order to avoid changing your -nick on the whole server by mistake. If you want to have a different -nickname in the channel you’re going to join, you need to do it explicitly -with the NICK command before joining the channel. - -### Private messages - -Private messages are handled differently on IRC and on XMPP. On IRC, you -talk directly to one server-user: toto on the channel #foo is the same user -as toto on the channel #bar (as long as these two channels are on the same -IRC server). By default you will receive private messages from the “global” -user (aka nickname!irc.example.com@biboumi.example.com), unless you -previously sent a message to an in-room participant (something like -\#test%irc.example.com@biboumi.example.com/nickname), in which case future -messages from that same user will be received from that same “in-room” JID. - -### Notices - -Notices are received exactly like private messages. It is not possible to -send a notice. - -### Kicks and bans - -Kicks are transparently translated from one protocol to another. However -banning an XMPP participant has no effect. To ban an user you need to set a -mode +b on that user nick or host (see *MODES*) and then kick it. - -### Encoding - -On XMPP, the encoding is always `UTF-8`, whereas on IRC the encoding of -each message can be anything. - -This means that biboumi has to convert everything coming from IRC into UTF-8 -without knowing the encoding of the received messages. To do so, it checks -if each message is UTF-8 valid, if not it tries to convert from -`iso_8859-1` (because this appears to be the most common case, at least -on the channels I visit) to `UTF-8`. If that conversion fails at some -point, a placeholder character `'�'` is inserted to indicate this -decoding error. - -Messages are always sent in UTF-8 over IRC, no conversion is done in that -direction. - -### IRC modes - -One feature that doesn’t exist on XMPP but does on IRC is the `modes`. -Although some of these modes have a correspondance in the XMPP world (for -example the `+o` mode on a user corresponds to the `moderator` role in -XMPP), it is impossible to map all these modes to an XMPP feature. To -circumvent this problem, biboumi provides a raw notification when modes are -changed, and lets the user change the modes directly. - -To change modes, simply send a message starting with “`/mode`” followed by -the modes and the arguments you want to send to the IRC server. For example -“/mode +aho louiz”. Note that your XMPP client may interprete messages -begining with “/” like a command. To actually send a message starting with -a slash, you may need to start your message with “//mode” or “/say /mode”, -depending on your client. - -When a mode is changed, the user is notified by a message coming from the -MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if -the mode change can be translated to an XMPP feature, the user will be -notified of this XMPP event as well. For example if a mode “+o toto” is -received, then toto’s role will be changed to moderator. The mapping -between IRC modes and XMPP features is as follow: - -`+q` - - Sets the participant’s role to `moderator` and its affiliation to `owner`. - -`+a` - - Sets the participant’s role to `moderator` and its affiliation to `owner`. - -`+o` - - Sets the participant’s role to `moderator` and its affiliation to `admin`. - -`+h` - - Sets the participant’s role to `moderator` and its affiliation to `member`. - -`+v` - - Sets the participant’s role to `participant` and its affiliation to `member`. - -Similarly, when a biboumi user changes some participant's affiliation or role, biboumi translates that in an IRC mode change. - -Affiliation set to `none` - - Sets mode to -vhoaq - -Affiliation set to `member` - - Sets mode to +v-hoaq - -Role set to `moderator` - - Sets mode to +h-oaq - -Affiliation set to `admin` - - Sets mode to +o-aq - -Affiliation set to `owner` - - Sets mode to +a-q - -### Ad-hoc commands - -Biboumi supports a few ad-hoc commands, as described in the XEP 0050. -Different ad-hoc commands are available for each JID type. - -#### On the gateway itself (e.g on the JID biboumi.example.com): - - - ping: Just respond “pong” - - - hello: Provide a form, where the user enters their name, and biboumi - responds with a nice greeting. - - - disconnect-user: Only available to the administrator. The user provides - a list of JIDs, and a quit message. All the selected users are - disconnected from all the IRC servers to which they were connected, - using the provided quit message. Sending SIGINT to biboumi is equivalent - to using this command by selecting all the connected JIDs and using the - “Gateway shutdown” quit message, except that biboumi does not exit when - using this ad-hoc command. - - - disconnect-from-irc-servers: Disconnect a single user from one or more - IRC server. The user is immediately disconnected by closing the socket, - no message is sent to the IRC server, but the user is of course notified - with an XMPP message. The administrator can disconnect any user, while - the other users can only disconnect themselves. - -#### On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) - - - Configure: Lets each user configure some options that applies to the - concerned IRC server. - -#### On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) - - - Configure: Lets each user configure some options that applies to the - concerned IRC channel. Some of these options, if not configured for a - specific channel, defaults to the value configured at the IRC server - level. For example the encoding can be specified for both the channel - and the server. If an encoding is not specified for a channel, the - encoding configured in the server applies. - -### Raw IRC messages - -Biboumi tries to support as many IRC features as possible, but doesn’t -handle everything yet (or ever). In order to let the user send any -arbitrary IRC message, biboumi forwards any XMPP message received on an IRC -Server JID (see *ADDRESSING*) as a raw command to that IRC server. - -For example, to WHOIS the user Foo on the server irc.example.com, a user can -send the message “WHOIS Foo” to “irc.example.com@biboumi.example.com”. - -The message will be forwarded as is, without any modification appart from -adding "\r\n" at the end (to make it a valid IRC message). You need to have -a little bit of understanding of the IRC protocol to use this feature. - -SECURITY --------- - -The connection to the XMPP server can only be made on localhost. The -XMPP server is not supposed to accept non-local connections from components. -Thus, encryption is not used to connect to the local XMPP server because it -is useless. - -If compiled with the Botan library, biboumi can use TLS when communicating -with the IRC serveres. It will first try ports 6697 and 6670 and use TLS if -it succeeds, if connection fails on both these ports, the connection is -established on port 6667 without any encryption. - -Biboumi does not check if the received JIDs are properly formatted using -nodeprep. This must be done by the XMPP server to which biboumi is directly -connected. - -Note if you use a biboumi that you have no control on: remember that the -administrator of the gateway you use is able to view all your IRC -conversations, whether you’re using encryption or not. This is exactly as -if you were running your IRC client on someone else’s server. Only use -biboumi if you trust its administrator (or, better, if you are the -administrator) or if you don’t intend to have any private conversation. - -Biboumi does not provide a way to ban users from connecting to it, has no -protection against flood or any sort of abuse that your users may cause on -the IRC servers. Some XMPP server however offer the possibility to restrict -what JID can access a gateway. Use that feature if you wish to grant access -to your biboumi instance only to a list of trusted users. - -AUTHORS -------- - -This software and man page are both written by Florent Le Coz. - -LICENSE -------- - -Biboumi is released under the zlib license. diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst new file mode 100644 index 0000000..6411efe --- /dev/null +++ b/doc/biboumi.1.rst @@ -0,0 +1,507 @@ +======================= +Biboumi(1) User Manual +======================= + +.. contents:: :depth: 2 + + +NAME +==== + +biboumi - XMPP gateway to IRC + +Description +=========== + +Biboumi is an XMPP gateway that connects to IRC servers and translates +between the two protocols. It can be used to access IRC channels using any +XMPP client as if these channels were XMPP MUCs. + +Synopsis +======== + +biboumi [*config_filename*\ ] + +Options +======= + +Available command line options: + +config_filename +--------------- + +Specify the file to read for configuration. See *CONFIG* section for more +details on its content. + +Configuration +============= + +The configuration file uses a simple format of the form +``option=value``. Here is a description of each possible option: + +The configuration can be re-read at runtime (you can for example change the +log level without having to restart biboumi) by sending SIGUSR1 or SIGUSR2 +(see kill(1)) to the process. + +hostname +-------- + +Mandatory. The hostname served by the XMPP gateway. This domain must be +configured in the XMPP server as an external component. See the manual +for your XMPP server for more information. For prosody, see +http://prosody.im/doc/components#adding_an_external_component + +password +-------- + +Mandatory. The password used to authenticate the XMPP component to your +XMPP server. This password must be configured in the XMPP server, +associated with the external component on *hostname*. + +xmpp_server_ip +-------------- + +The IP address to connect to the XMPP server on. The connection to the +XMPP server is unencrypted, so the biboumi instance and the server should +normally be on the same host. The default value is 127.0.0.1. + +port +---- + +The TCP port to use to connect to the local XMPP component. The default +value is 5347. + +admin +----- + +The bare JID of the gateway administrator. This JID will have more +privileges than other standard users (the admin thus needs to check their +privileges), for example some administration ad-hoc commands will only be +available to that JID. + +fixed_irc_server +---------------- + +If this option contains the hostname of an IRC server (for example +irc.example.org), then biboumi will enforce the connexion to that IRC +server only. This means that a JID like "#chan@biboumi.example.com" must +be used instead of "#chan%irc.example.org@biboumi.example.com". In that +mode, the virtual channel (see `Connect to an IRC server`_) is not +available and you still need to use the ! separator to send message to an +IRC user (for example "foo!@biboumi.example.com" to send a message to +foo), although the in-room JID still work as expected +("#channel@biboumi.example.com/Nick"). On the other hand, the '%' lose +any meaning. It can appear in the JID but will not be interpreted as a +separator (thus the JID "#channel%hello@biboumi.example.com" points to the +channel named "#channel%hello" on the configured IRC server) This option +can for example be used by an administrator that just wants to let their +users join their own IRC server using an XMPP client, while forbidding +access to any other IRC server. + +realname_customization +---------------------- + +If this option is set to “false” (default is “true”), the users will not be +able to use the ad-hoc commands that lets them configure their realname and +username. + +realname_from_jid +----------------- + +If this option is set to “true”, the realname and username of each biboumi +user will be extracted from their JID. The realname is their bare JID, and +the username is the node-part of their JID. Note that if +``realname_customization`` is “true”, each user will still be able to +customize their realname and username, this option just decides the default +realname and username. + +If this option is set to “false” (the default value), the realname and +username of each user will be set to the nick they used to connect to the +IRC server. + +webirc_password +--------------- + +Configure a password to be communicated to the IRC server, as part of the +WEBIRC message (see https://kiwiirc.com/docs/webirc). If this option is +set, an additional DNS resolution of the hostname of each XMPP server will +be made when connecting to an IRC server. + +log_file +-------- + +A filename into which logs are written. If none is provided, the logs are +written on standard output. + +log_level +--------- + +Indicate what type of log messages to write in the logs. Value can be +from 0 to 3. 0 is debug, 1 is info, 2 is warning, 3 is error. The +default is 0, but a more practical value for production use is 1. + +ca_file +------- + +Specifies which file should be use as the list of trusted CA when +negociating a TLS session. By default this value is unset and biboumi +tries a list of well-known paths. + +outgoing_bind +------------- + +An address (IPv4 or IPv6) to bind the outgoing sockets to. If no value is +specified, it will use the one assigned by the operating system. You can +for example use outgoing_bind=192.168.1.11 to force biboumi to use the +interface with this address. Note that this is only used for connections +to IRC servers. + +Usage +===== + +Biboumi acts as a server, it should be run as a daemon that lives in the +background for as long as it is needed. Note that biboumi does not +daemonize itself, this task should be done by your init system (SysVinit, +systemd, upstart). + +When started, biboumi connects, without encryption (see `Security`_), to the +local XMPP server on the port ``5347`` and authenticates with the provided +password. Biboumi then serves the configured ``hostname``: this means that +all XMPP stanza with a `to` JID on that domain will be forwarded to biboumi +by the XMPP server, and biboumi will only send messages coming from that +hostname. + +When a user joins an IRC channel on an IRC server (see `Join an IRC +channel`_), biboumi connects to the remote IRC server, sets the user’s nick +as requested, and then tries to join the specified channel. If the same +user subsequently tries to connect to an other channel on the same server, +the same IRC connection is used. If, however, an other user wants to join +an IRC channel on that same IRC server, biboumi opens a new connection to +that server. Biboumi connects once to each IRC server, for each user on it. + +To cleanly shutdown the component, send a SIGINT or SIGTERM signal to it. +It will send messages to all connected IRC and XMPP servers to indicate a +reason why the users are being disconnected. Biboumi exits when the end of +communication is acknowledged by all IRC servers. If one or more IRC +servers do not respond, biboumi will only exit if it receives the same +signal again or if a 2 seconds delay has passed. + +Addressing +---------- + +IRC entities are represented by XMPP JIDs. The domain part of the JID is +the domain served by biboumi (the part after the ``@``, biboumi.example.com in +the examples), and the local part (the part before the ``@``) depends on the +concerned entity. + +IRC channels have a local part formed like this: +``channel_name`` % ``irc_server``. + +If the IRC channel you want to adress starts with the ``'#'`` character (or an +other character, announced by the IRC server, like ``'&'``, ``'+'`` or ``'!'``), +then you must include it in the JID. Some other gateway implementations, as +well as some IRC clients, do not require them to be started by one of these +characters, adding an implicit ``'#'`` in that case. Biboumi does not do that +because this gets confusing when trying to understand the difference between +the channels *#foo*, and *##foo*. Note that biboumi does not use the +presence of these special characters to identify an IRC channel, only the +presence of the separator `%` is used for that. + +The channel name can also be empty (for example ``%irc.example.com``), in that +case this represents the virtual channel provided by biboumi. See *Connect +to an IRC server* for more details. + +There is two ways to address an IRC user, using a local part like this: +``nickname`` ! ``irc_server`` +or by using the in-room address of the participant, like this: +``channel_name`` % ``irc_server`` @ ``biboumi.example.com`` / ``Nickname`` + +The second JID is available only to be compatible with XMPP clients when the +user wants to send a private message to the participant ``Nickname`` in the +room ``channel_name%irc_server@biboumi.example.com``. + +On XMPP, the node part of the JID can only be lowercase. On the other hand, +IRC nicknames are case-insensitive, this means that the nicknames toto, +Toto, tOtO and TOTO all represent the same IRC user. This means you can +talk to the user toto, and this will work. + +Also note that some IRC nicknames may contain characters that are not +allowed in the local part of a JID (for example '@'). If you need to send a +message to a nick containing such a character, you have to use a jid like +``%irc.example.com@biboumi.example.com/AnnoyingNickn@me``, because the JID +``AnnoyingNickn@me!irc.example.com@biboumi.example.com`` would not work. + +Examples: + +* ``#foo%irc.example.com@biboumi.example.com`` is the #foo IRC channel, on the + irc.example.com IRC server, and this is served by the biboumi instance on + biboumi.example.com + +* ``toto!irc.example.com@biboumi.example.com`` is the IRC user named toto, or + TotO, etc. + +* ``irc.example.com@biboumi.example.com`` is the IRC server irc.example.com. + +* ``%irc.example.com@biboumi.example.com`` is the virtual channel provided by + biboumi, for the IRC server irc.example.com. + +Note: Some JIDs are valid but make no sense in the context of +biboumi: + +* ``!irc.example.com@biboumi.example.com`` is the empty-string nick on the + irc.example.com server. It makes no sense to try to send messages to it. + +* ``#test%@biboumi.example.com``, or any other JID that does not contain an + IRC server is invalid. Any message to that kind of JID will trigger an + error, or will be ignored. + +If compiled with Libidn, an IRC channel participant has a bare JID +representing the “hostname” provided by the IRC server. This JID can only +be used to set IRC modes (for example to ban a user based on its IP), or to +identify user. It cannot be used to contact that user using biboumi. + +Join an IRC channel +------------------- + +To join an IRC channel ``#foo`` on the IRC server ``irc.example.com``, +join the XMPP MUC ``#foo%irc.example.com@biboumi.example.com``. + +Connect to an IRC server +------------------------ + +The connection to the IRC server is automatically made when the user tries +to join any channel on that IRC server. The connection is closed whenever +the last channel on that server is left by the user. To be able to stay +connected to an IRC server without having to be in a real IRC channel, +biboumi provides a virtual channel on the jid +``%irc.example.com@biboumi.example.com``. For example if you want to join the +channel ``#foo`` on the server ``irc.example.com``, but you need to authenticate +to a bot of the server before you’re allowed to join it, you can first join +the room ``%irc.example.com@biboumi.example.com`` (this will effectively +connect you to the IRC server without joining any room), then send your +authentication message to the user ``bot!irc.example.com@biboumi.example.com`` +and finally join the room ``#foo%irc.example.com@biboumi.example.com``. + +Channel messages +---------------- + +On XMPP, unlike on IRC, the displayed order of the messages is the same for +all participants of a MUC. Biboumi can not however provide this feature, as +it cannot know whether the IRC server has received and forwarded the +messages to other users. This means that the order of the messages +displayed in your XMPP client may not be the same than the order on other +IRC users’. + +List channels +------------- + +You can list the IRC channels on a given IRC server by sending an XMPP disco +items request on the IRC server JID. The number of channels on some servers +is huge, and biboumi does not (yet) support result set management (XEP 0059) +so the result stanza may be very big. + +Nicknames +--------- + +On IRC, nicknames are server-wide. This means that one user only has one +single nickname at one given time on all the channels of a server. This is +different from XMPP where a user can have a different nick on each MUC, +even if these MUCs are on the same server. + +This means that the nick you choose when joining your first IRC channel on a +given IRC server will be your nickname in all other channels that you join +on that same IRC server. +If you explicitely change your nickname on one channel, your nickname will +be changed on all channels on the same server as well. +Joining a new channel with a different nick, however, will not change your +nick. The provided nick will be ignored, in order to avoid changing your +nick on the whole server by mistake. If you want to have a different +nickname in the channel you’re going to join, you need to do it explicitly +with the NICK command before joining the channel. + +Private messages +---------------- + +Private messages are handled differently on IRC and on XMPP. On IRC, you +talk directly to one server-user: toto on the channel #foo is the same user +as toto on the channel #bar (as long as these two channels are on the same +IRC server). By default you will receive private messages from the “global” +user (aka nickname!irc.example.com@biboumi.example.com), unless you +previously sent a message to an in-room participant (something like +\#test%irc.example.com@biboumi.example.com/nickname), in which case future +messages from that same user will be received from that same “in-room” JID. + +Notices +------- + +Notices are received exactly like private messages. It is not possible to +send a notice. + +Kicks and bans +-------------- + +Kicks are transparently translated from one protocol to another. However +banning an XMPP participant has no effect. To ban an user you need to set a +mode +b on that user nick or host (see `IRC modes`_) and then kick it. + +Encoding +-------- + +On XMPP, the encoding is always ``UTF-8``, whereas on IRC the encoding of +each message can be anything. + +This means that biboumi has to convert everything coming from IRC into UTF-8 +without knowing the encoding of the received messages. To do so, it checks +if each message is UTF-8 valid, if not it tries to convert from +``iso_8859-1`` (because this appears to be the most common case, at least +on the channels I visit) to ``UTF-``. If that conversion fails at some +point, a placeholder character ``'�'`` is inserted to indicate this +decoding error. + +Messages are always sent in UTF-8 over IRC, no conversion is done in that +direction. + +IRC modes +--------- + +One feature that doesn’t exist on XMPP but does on IRC is the ``modes``. +Although some of these modes have a correspondance in the XMPP world (for +example the ``+o`` mode on a user corresponds to the ``moderator`` role in +XMPP), it is impossible to map all these modes to an XMPP feature. To +circumvent this problem, biboumi provides a raw notification when modes are +changed, and lets the user change the modes directly. + +To change modes, simply send a message starting with “``/mode``” followed by +the modes and the arguments you want to send to the IRC server. For example +“/mode +aho louiz”. Note that your XMPP client may interprete messages +begining with “/” like a command. To actually send a message starting with +a slash, you may need to start your message with “//mode” or “/say /mode”, +depending on your client. + +When a mode is changed, the user is notified by a message coming from the +MUC bare JID, looking like “Mode #foo [+ov] [toto tutu]”. In addition, if +the mode change can be translated to an XMPP feature, the user will be +notified of this XMPP event as well. For example if a mode “+o toto” is +received, then toto’s role will be changed to moderator. The mapping +between IRC modes and XMPP features is as follow: + +``+q`` + Sets the participant’s role to ``moderator`` and its affiliation to ``owner``. + +``+a`` + Sets the participant’s role to ``moderator`` and its affiliation to ``owner``. + +``+o`` + Sets the participant’s role to ``moderator`` and its affiliation to ``admin``. + +``+h`` + Sets the participant’s role to ``moderator`` and its affiliation to ``member``. + +``+v`` + Sets the participant’s role to `participant` and its affiliation to ``member``. + +Similarly, when a biboumi user changes some participant's affiliation or role, biboumi translates that in an IRC mode change. + +Affiliation set to ``none`` + Sets mode to -vhoaq + +Affiliation set to ``member`` + Sets mode to +v-hoaq + +Role set to ``moderator`` + Sets mode to +h-oaq + +Affiliation set to ``admin`` + Sets mode to +o-aq + +Affiliation set to ``owner`` + Sets mode to +a-q + +Ad-hoc commands +--------------- + +Biboumi supports a few ad-hoc commands, as described in the XEP 0050. +Different ad-hoc commands are available for each JID type. + +On the gateway itself (e.g on the JID biboumi.example.com): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ping: Just respond “pong” + +- hello: Provide a form, where the user enters their name, and biboumi + responds with a nice greeting. + +- disconnect-user: Only available to the administrator. The user provides + a list of JIDs, and a quit message. All the selected users are + disconnected from all the IRC servers to which they were connected, + using the provided quit message. Sending SIGINT to biboumi is equivalent + to using this command by selecting all the connected JIDs and using the + “Gateway shutdown” quit message, except that biboumi does not exit when + using this ad-hoc command. + +- disconnect-from-irc-servers: Disconnect a single user from one or more + IRC server. The user is immediately disconnected by closing the socket, + no message is sent to the IRC server, but the user is of course notified + with an XMPP message. The administrator can disconnect any user, while + the other users can only disconnect themselves. + +On a server JID (e.g on the JID chat.freenode.org@biboumi.example.com) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Configure: Lets each user configure some options that applies to the + concerned IRC server. + +On a channel JID (e.g on the JID #test%chat.freenode.org@biboumi.example.com) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Configure: Lets each user configure some options that applies to the + concerned IRC channel. Some of these options, if not configured for a + specific channel, defaults to the value configured at the IRC server + level. For example the encoding can be specified for both the channel + and the server. If an encoding is not specified for a channel, the + encoding configured in the server applies. + +Raw IRC messages +---------------- + +Biboumi tries to support as many IRC features as possible, but doesn’t +handle everything yet (or ever). In order to let the user send any +arbitrary IRC message, biboumi forwards any XMPP message received on an IRC +Server JID (see *ADDRESSING*) as a raw command to that IRC server. + +For example, to WHOIS the user Foo on the server irc.example.com, a user can +send the message “WHOIS Foo” to “irc.example.com@biboumi.example.com”. + +The message will be forwarded as is, without any modification appart from +adding "\r\n" at the end (to make it a valid IRC message). You need to have +a little bit of understanding of the IRC protocol to use this feature. + +Security +======== + +The connection to the XMPP server can only be made on localhost. The +XMPP server is not supposed to accept non-local connections from components. +Thus, encryption is not used to connect to the local XMPP server because it +is useless. + +If compiled with the Botan library, biboumi can use TLS when communicating +with the IRC serveres. It will first try ports 6697 and 6670 and use TLS if +it succeeds, if connection fails on both these ports, the connection is +established on port 6667 without any encryption. + +Biboumi does not check if the received JIDs are properly formatted using +nodeprep. This must be done by the XMPP server to which biboumi is directly +connected. + +Note if you use a biboumi that you have no control on: remember that the +administrator of the gateway you use is able to view all your IRC +conversations, whether you’re using encryption or not. This is exactly as +if you were running your IRC client on someone else’s server. Only use +biboumi if you trust its administrator (or, better, if you are the +administrator) or if you don’t intend to have any private conversation. + +Biboumi does not provide a way to ban users from connecting to it, has no +protection against flood or any sort of abuse that your users may cause on +the IRC servers. Some XMPP server however offer the possibility to restrict +what JID can access a gateway. Use that feature if you wish to grant access +to your biboumi instance only to a list of trusted users. -- cgit v1.2.3 From 38202e5f6e16d8fc1e07811049851bfa417fb9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 23 May 2016 11:51:03 +0200 Subject: Update the build process to take into account the rst files --- CMakeLists.txt | 12 +++++++----- INSTALL.rst | 2 +- doc/biboumi.1.rst | 1 - docker/biboumi-test/debian/Dockerfile | 2 +- docker/biboumi-test/fedora/Dockerfile | 2 +- packaging/biboumi.spec.cmake | 7 ++----- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3321cd..bb13460 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,15 +61,17 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}/") # ## Documentation # +execute_process(COMMAND "date" "+%Y-%m-%d" OUTPUT_VARIABLE DOC_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) set(MAN_PAGE ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1) -set(DOC_PAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/${PROJECT_NAME}.1.md) -find_program(RONN_EXECUTABLE NAMES ronn - DOC "The ronn software, to build the man page from the markdown documentation") -if(RONN_EXECUTABLE) +set(DOC_PAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/${PROJECT_NAME}.1.rst) +find_program(PANDOC_EXECUTABLE NAMES pandoc + DOC "The pandoc software, to build the man page from the rst documentation") +if(PANDOC_EXECUTABLE) set(WITH_DOC true) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/) add_custom_command(OUTPUT ${MAN_PAGE} - COMMAND ${RONN_EXECUTABLE} --roff < ${DOC_PAGE} > ${MAN_PAGE} + COMMAND ${PANDOC_EXECUTABLE} -M date="${DOC_DATE}" -s -t man ${DOC_PAGE} -o ${MAN_PAGE} DEPENDS ${DOC_PAGE}) add_custom_target(doc ALL DEPENDS ${MAN_PAGE}) endif() diff --git a/INSTALL.rst b/INSTALL.rst index 1e5d0f5..1526d7e 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -18,7 +18,7 @@ Tools: - A C++14 compiler (clang >= 3.4 or gcc >= 4.9 for example) - CMake -- ronn (optional) to build the man page +- pandoc (optional) to build the man page Libraries: ~~~~~~~~~~ diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 6411efe..2a6dae3 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -4,7 +4,6 @@ Biboumi(1) User Manual .. contents:: :depth: 2 - NAME ==== diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index 32f294f..fd341c5 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -18,7 +18,7 @@ RUN apt install -y libexpat1-dev RUN apt install -y libidn11-dev RUN apt install -y uuid-dev RUN apt install -y libsystemd-dev -RUN apt install -y ruby-ronn +RUN apt install -y pandoc # Needed to run tests RUN apt install -y git diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 704b413..853c444 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -18,7 +18,7 @@ RUN dnf install -y expat-devel RUN dnf install -y libidn-devel RUN dnf install -y uuid-devel RUN dnf install -y systemd-devel -RUN dnf install -y rubygem-ronn +RUN dnf install -y pandoc # Needed to run tests RUN dnf install -y git diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index 08b5c6d..f38f4c4 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -13,7 +13,7 @@ BuildRequires: libuuid-devel BuildRequires: systemd-devel BuildRequires: cmake BuildRequires: systemd -BuildRequires: rubygem-ronn +BuildRequires: pandoc %global _hardened_build 1 @@ -39,9 +39,6 @@ cmake . -DCMAKE_CXX_FLAGS="%{optflags}" \ -DWITH_SYSTEMD=1 \ -DWITH_LIBIDN=1 -# The documentation is in utf-8, ronn fails to build it if that locale is -# not specified -export LC_ALL=en_GB.utf-8 make %{?_smp_mflags} @@ -56,7 +53,7 @@ make check %{?_smp_mflags} %files %{_bindir}/%{name} %{_mandir}/man1/%{name}.1* -%doc README.md COPYING doc/biboumi.1.md +%doc README.rst COPYING doc/biboumi.1.rst %{_unitdir}/%{name}.service %config(noreplace) %{biboumi_confdir}/biboumi.cfg -- cgit v1.2.3 From d78bc8c6ba698db862c6e6de02de00c261fc1fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 23 May 2016 18:00:17 +0200 Subject: ci: include only the rpm and srpm directory in the artifacts The rest is useless and takes a lot of space --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 93c206b..51a8b2f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,7 +23,8 @@ build:fedora: artifacts: paths: - build/coverage/ - - build/rpmbuild/ + - build/rpmbuild/RPMS + - build/rpmbuild/SRPMS - build/tests_outputs/ build:debian: -- cgit v1.2.3 From 62c88a62ae855b733259d7c346e8dfbc454aaa6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 26 May 2016 20:22:09 +0200 Subject: Changelog into rst --- CHANGELOG | 41 ----------------------------------------- CHANGELOG.rst | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 41 deletions(-) delete mode 100644 CHANGELOG create mode 100644 CHANGELOG.rst diff --git a/CHANGELOG b/CHANGELOG deleted file mode 100644 index 020e7ee..0000000 --- a/CHANGELOG +++ /dev/null @@ -1,41 +0,0 @@ -Version 2.0 2015-05-29 - - - List channels on an IRC server through an XMPP disco items request - - Let the user send any arbitrary raw IRC command by sending a - message to the IRC server’s JID. - - By default, look for the configuration file as per the XDG - basedir spec. - - Support PING requests in all directions. - - Improve the way we forward received NOTICEs by remembering to - which users we previously sent a private message. This improves the - user experience when talking to NickServ. - - Support joining key-protected channels - - Setting a participant's role/affiliation now results in a change of IRC - mode, instead of being ignored. Setting Toto's affiliation to admin is - now equivalent to “/mode +o Toto” - - Fix the reconnection to the XMPP server to try every 2 seconds - instead of immediately. This avoid hogging resources for nothing - - Asynchronously resolve domain names by optionally using the DNS - library c-ares. - - Add a reload add-hoc command, to reload biboumi's configuration - - Add a fixed_irc_server option. With this option enabled, - biboumi can only connect to the one single IRC server configured - -Version 1.1 2014-07-16 - - - Fix a segmentation fault when connecting to an IRC server using IPv6 - -Version 1.0 2014-07-12 - - - First stable release. - - Mostly complete MUC to IRC, and IRC to MUC support - - Complete handling of private messages - - Full IRC modes support: setting any IRC mode, and receiving notifications - for every mode change - - Verbose connection status notifications - - Conversion from IRC formatting to XHTML-im - - Ad-hoc commands support - - Basic TLS support: auto-accepts all certificates, no cipher - configuration, no way to force usage of TLS (it is used only if - available, clear connection is automatically used as a fallback) - - IPv6 support diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..fb5d14b --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,44 @@ +Version 2.0 - 2015-05-29 +======================== + +- List channels on an IRC server through an XMPP disco items request +- Let the user send any arbitrary raw IRC command by sending a + message to the IRC server’s JID. +- By default, look for the configuration file as per the XDG + basedir spec. +- Support PING requests in all directions. +- Improve the way we forward received NOTICEs by remembering to + which users we previously sent a private message. This improves the + user experience when talking to NickServ. +- Support joining key-protected channels +- Setting a participant's role/affiliation now results in a change of IRC + mode, instead of being ignored. Setting Toto's affiliation to admin is + now equivalent to “/mode +o Toto” +- Fix the reconnection to the XMPP server to try every 2 seconds + instead of immediately. This avoid hogging resources for nothing +- Asynchronously resolve domain names by optionally using the DNS + library c-ares. +- Add a reload add-hoc command, to reload biboumi's configuration +- Add a fixed_irc_server option. With this option enabled, + biboumi can only connect to the one single IRC server configured + +Version 1.1 - 2014-07-16 +======================== + +- Fix a segmentation fault when connecting to an IRC server using IPv6 + +Version 1.0 - 2014-07-12 +======================== + +- First stable release. +- Mostly complete MUC to IRC, and IRC to MUC support +- Complete handling of private messages +- Full IRC modes support: setting any IRC mode, and receiving notifications + for every mode change +- Verbose connection status notifications +- Conversion from IRC formatting to XHTML-im +- Ad-hoc commands support +- Basic TLS support: auto-accepts all certificates, no cipher + configuration, no way to force usage of TLS (it is used only if + available, clear connection is automatically used as a fallback) +- IPv6 support -- cgit v1.2.3 From a641a26327e04016dfd62e1cb1f2141b9364631b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 2 Jun 2016 16:03:28 +0200 Subject: Check the length of the JID parts when copying into the jidprep buffer We trust the XMPP server, but maybe not enough to not check that --- louloulibs/xmpp/jid.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/louloulibs/xmpp/jid.cpp b/louloulibs/xmpp/jid.cpp index dcd7012..7b62f3e 100644 --- a/louloulibs/xmpp/jid.cpp +++ b/louloulibs/xmpp/jid.cpp @@ -47,7 +47,7 @@ std::string jidprep(const std::string& original) Jid jid(original); char local[max_jid_part_len] = {}; - memcpy(local, jid.local.data(), jid.local.size()); + memcpy(local, jid.local.data(), std::min(max_jid_part_len, jid.local.size())); Stringprep_rc rc = static_cast(::stringprep(local, max_jid_part_len, static_cast(0), stringprep_xmpp_nodeprep)); if (rc != STRINGPREP_OK) @@ -57,7 +57,7 @@ std::string jidprep(const std::string& original) } char domain[max_jid_part_len] = {}; - memcpy(domain, jid.domain.data(), jid.domain.size()); + memcpy(domain, jid.domain.data(), std::min(max_jid_part_len, jid.domain.size())); rc = static_cast(::stringprep(domain, max_jid_part_len, static_cast(0), stringprep_nameprep)); if (rc != STRINGPREP_OK) @@ -81,7 +81,7 @@ std::string jidprep(const std::string& original) // Otherwise, also process the resource part char resource[max_jid_part_len] = {}; - memcpy(resource, jid.resource.data(), jid.resource.size()); + memcpy(resource, jid.resource.data(), std::min(max_jid_part_len, jid.resource.size())); rc = static_cast(::stringprep(resource, max_jid_part_len, static_cast(0), stringprep_xmpp_resourceprep)); if (rc != STRINGPREP_OK) -- cgit v1.2.3 From 0a2123f8a2db3f94aa5758868e999cb3224235c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 26 May 2016 20:22:09 +0200 Subject: Changelog into rst --- CHANGELOG.rst | 66 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb5d14b..599ee70 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,44 +1,44 @@ Version 2.0 - 2015-05-29 ======================== -- List channels on an IRC server through an XMPP disco items request -- Let the user send any arbitrary raw IRC command by sending a - message to the IRC server’s JID. -- By default, look for the configuration file as per the XDG - basedir spec. -- Support PING requests in all directions. -- Improve the way we forward received NOTICEs by remembering to - which users we previously sent a private message. This improves the - user experience when talking to NickServ. -- Support joining key-protected channels -- Setting a participant's role/affiliation now results in a change of IRC - mode, instead of being ignored. Setting Toto's affiliation to admin is - now equivalent to “/mode +o Toto” -- Fix the reconnection to the XMPP server to try every 2 seconds - instead of immediately. This avoid hogging resources for nothing -- Asynchronously resolve domain names by optionally using the DNS - library c-ares. -- Add a reload add-hoc command, to reload biboumi's configuration -- Add a fixed_irc_server option. With this option enabled, - biboumi can only connect to the one single IRC server configured + - List channels on an IRC server through an XMPP disco items request + - Let the user send any arbitrary raw IRC command by sending a + message to the IRC server’s JID. + - By default, look for the configuration file as per the XDG + basedir spec. + - Support PING requests in all directions. + - Improve the way we forward received NOTICEs by remembering to + which users we previously sent a private message. This improves the + user experience when talking to NickServ. + - Support joining key-protected channels + - Setting a participant's role/affiliation now results in a change of IRC + mode, instead of being ignored. Setting Toto's affiliation to admin is + now equivalent to “/mode +o Toto” + - Fix the reconnection to the XMPP server to try every 2 seconds + instead of immediately. This avoid hogging resources for nothing + - Asynchronously resolve domain names by optionally using the DNS + library c-ares. + - Add a reload add-hoc command, to reload biboumi's configuration + - Add a fixed_irc_server option. With this option enabled, + biboumi can only connect to the one single IRC server configured Version 1.1 - 2014-07-16 ======================== -- Fix a segmentation fault when connecting to an IRC server using IPv6 + - Fix a segmentation fault when connecting to an IRC server using IPv6 Version 1.0 - 2014-07-12 ======================== -- First stable release. -- Mostly complete MUC to IRC, and IRC to MUC support -- Complete handling of private messages -- Full IRC modes support: setting any IRC mode, and receiving notifications - for every mode change -- Verbose connection status notifications -- Conversion from IRC formatting to XHTML-im -- Ad-hoc commands support -- Basic TLS support: auto-accepts all certificates, no cipher - configuration, no way to force usage of TLS (it is used only if - available, clear connection is automatically used as a fallback) -- IPv6 support + - First stable release. + - Mostly complete MUC to IRC, and IRC to MUC support + - Complete handling of private messages + - Full IRC modes support: setting any IRC mode, and receiving notifications + for every mode change + - Verbose connection status notifications + - Conversion from IRC formatting to XHTML-im + - Ad-hoc commands support + - Basic TLS support: auto-accepts all certificates, no cipher + configuration, no way to force usage of TLS (it is used only if + available, clear connection is automatically used as a fallback) + - IPv6 support -- cgit v1.2.3 From 367de4826c3c7298a88e80c92e325081a3c3d794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 18 May 2016 22:53:17 +0200 Subject: Associate a bridge with a bare JID instead of a full JID ref #2556 --- src/xmpp/biboumi_component.cpp | 10 ++++++---- src/xmpp/biboumi_component.hpp | 2 +- tests/end_to_end/__main__.py | 26 ++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 5b1d0e0..94d85c6 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -507,22 +507,24 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) { + auto bare_jid = Jid{user_jid}.bare(); try { - return this->bridges.at(user_jid).get(); + return this->bridges.at(bare_jid).get(); } catch (const std::out_of_range& exception) { - this->bridges.emplace(user_jid, std::make_unique(user_jid, *this, this->poller)); - return this->bridges.at(user_jid).get(); + this->bridges.emplace(bare_jid, std::make_unique(bare_jid, *this, this->poller)); + return this->bridges.at(bare_jid).get(); } } Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid) { + auto bare_jid = Jid{user_jid}.bare(); try { - return this->bridges.at(user_jid).get(); + return this->bridges.at(bare_jid).get(); } catch (const std::out_of_range& exception) { diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index f1817e2..4d5995e 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -83,7 +83,7 @@ public: private: /** - * Return the bridge associated with the given full JID. Create a new one + * Return the bridge associated with the bare JID. Create a new one * if none already exist. */ Bridge* get_user_bridge(const std::string& user_jid); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 706d177..630f59a 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -296,6 +296,7 @@ common_replacements = { 'irc_host_one': 'irc.localhost', 'biboumi_host': 'biboumi.localhost', 'resource_one': 'resource1', + 'resource_two': 'resource2', 'nick_one': 'Nick', 'jid_one': 'first@example.com', 'jid_two': 'second@example.com', @@ -579,6 +580,31 @@ if __name__ == '__main__': partial(send_stanza, "COUCOU"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") ]), + Scenario("simple_multisessionnick", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # The other resources joins the same room, with the same nick + partial(send_stanza, + ""), + # We receive our own join + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + ) failures = 0 -- cgit v1.2.3 From 711861d40e365564e3828a251066c16e924d30f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 22 May 2016 12:54:18 +0200 Subject: Add methods to know which resource is on which server or channel --- src/bridge/bridge.cpp | 36 +++++++++++++++++++++++++++++++++--- src/bridge/bridge.hpp | 11 +++++++++++ src/xmpp/biboumi_component.cpp | 4 ++-- src/xmpp/biboumi_component.hpp | 4 ++-- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6008dfc..484c860 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -46,11 +46,12 @@ Bridge::Bridge(const std::string& user_jid, BiboumiComponent& xmpp, std::shared_ } /** - * Return the role and affiliation, corresponding to the given irc mode */ + * Return the role and affiliation, corresponding to the given irc mode + */ static std::tuple get_role_affiliation_from_irc_mode(const char mode) { - if (mode == 'a' || mode == 'q') - return std::make_tuple("moderator", "owner"); + if (mode == 'a' || mode == 'q'){ + return std::make_tuple("moderator", "owner");} else if (mode == 'o') return std::make_tuple("moderator", "admin"); else if (mode == 'h') @@ -720,3 +721,32 @@ std::unordered_map>& Bridge::get_irc_cli { return this->irc_clients; } + +void Bridge::add_resource_to_chan(const std::string& channel, const std::string& resource) +{ + auto it = this->resources_in_chan.find(channel); + if (it == this->resources_in_chan.end()) + this->resources_in_chan[channel] = {resource}; + else + it->second.insert(resource); +} + +void Bridge::remove_resource_from_chan(const std::string& channel, const std::string& resource) +{ + auto it = this->resources_in_chan.find(channel); + if (it != this->resources_in_chan.end()) + { + it->second.erase(resource); + if (it->second.empty()) + this->resources_in_chan.erase(it); + } +} + +bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& resource) const +{ + auto it = this->resources_in_chan.find(channel); + if (it != this->resources_in_chan.end()) + if (it->second.count(resource) == 1) + return true; + return false; +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 2676d1d..b852a30 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -194,6 +194,13 @@ public: void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); + /** + * Manage which resource is in which channel + */ + void add_resource_to_chan(const std::string& channel, const std::string& resource); + void remove_resource_from_chan(const std::string& channel, const std::string& resource); + bool is_resource_in_chan(const std::string& channel, const std::string& resource) const; + private: /** * Returns the client for the given hostname, create one (and use the @@ -244,6 +251,10 @@ private: * response iq. */ std::list waiting_irc; + /** + * Keep track of which resource is in which channel. + */ + std::map> resources_in_chan; }; struct IRCNotConnected: public std::exception diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 94d85c6..6dae92c 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -519,9 +519,9 @@ Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid) } } -Bridge* BiboumiComponent::find_user_bridge(const std::string& user_jid) +Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid) { - auto bare_jid = Jid{user_jid}.bare(); + auto bare_jid = Jid{full_jid}.bare(); try { return this->bridges.at(bare_jid).get(); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 4d5995e..25982f2 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -35,7 +35,7 @@ public: * Returns the bridge for the given user. If it does not exist, return * nullptr. */ - Bridge* find_user_bridge(const std::string& user_jid); + Bridge* find_user_bridge(const std::string& full_jid); /** * Return a list of all the managed bridges. */ @@ -97,7 +97,7 @@ private: std::map waiting_iq; /** - * One bridge for each user of the component. Indexed by the user's full + * One bridge for each user of the component. Indexed by the user's bare * jid */ std::unordered_map> bridges; -- cgit v1.2.3 From 66609cfba2b581be52de6096193751d1bb4ec3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 22 May 2016 12:52:05 +0200 Subject: Remove all usage of std::list --- louloulibs/network/tcp_socket_handler.hpp | 2 +- louloulibs/utils/timed_events.cpp | 11 +---------- louloulibs/utils/timed_events.hpp | 18 +++++++++--------- src/bridge/bridge.hpp | 2 +- src/xmpp/biboumi_component.cpp | 4 ++-- src/xmpp/biboumi_component.hpp | 2 +- 6 files changed, 15 insertions(+), 24 deletions(-) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index dd4e82e..55f4113 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -180,7 +180,7 @@ private: /** * Where data is added, when we want to send something to the client. */ - std::list out_buf; + std::vector out_buf; /** * DNS resolver */ diff --git a/louloulibs/utils/timed_events.cpp b/louloulibs/utils/timed_events.cpp index 930380b..6b936c4 100644 --- a/louloulibs/utils/timed_events.cpp +++ b/louloulibs/utils/timed_events.cpp @@ -20,15 +20,6 @@ TimedEvent::TimedEvent(std::chrono::milliseconds&& duration, { } -TimedEvent::TimedEvent(TimedEvent&& other): - time_point(std::move(other.time_point)), - callback(std::move(other.callback)), - repeat(other.repeat), - repeat_delay(std::move(other.repeat_delay)), - name(std::move(other.name)) -{ -} - bool TimedEvent::is_after(const TimedEvent& other) const { return this->is_after(other.time_point); @@ -47,7 +38,7 @@ std::chrono::milliseconds TimedEvent::get_timeout() const return std::chrono::duration_cast(this->time_point - now); } -void TimedEvent::execute() +void TimedEvent::execute() const { this->callback(); } diff --git a/louloulibs/utils/timed_events.hpp b/louloulibs/utils/timed_events.hpp index c3dfc40..70e2eff 100644 --- a/louloulibs/utils/timed_events.hpp +++ b/louloulibs/utils/timed_events.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include using namespace std::literals::chrono_literals; @@ -30,12 +30,12 @@ public: explicit TimedEvent(std::chrono::milliseconds&& duration, std::function callback, const std::string& name=""); - explicit TimedEvent(TimedEvent&&); + explicit TimedEvent(TimedEvent&&) = default; + TimedEvent& operator=(TimedEvent&&) = default; ~TimedEvent() = default; TimedEvent(const TimedEvent&) = delete; TimedEvent& operator=(const TimedEvent&) = delete; - TimedEvent& operator=(TimedEvent&&) = delete; /** * Whether or not this event happens after the other one. @@ -48,7 +48,7 @@ public: * returned value is 0 instead. The value cannot then be negative. */ std::chrono::milliseconds get_timeout() const; - void execute(); + void execute() const; const std::string& get_name() const; private: @@ -59,22 +59,22 @@ private: /** * The function to execute. */ - const std::function callback; + std::function callback; /** * Whether or not this events repeats itself until it is destroyed. */ - const bool repeat; + bool repeat; /** * This value is added to the time_point each time the event is executed, * if repeat is true. Otherwise it is ignored. */ - const std::chrono::milliseconds repeat_delay; + std::chrono::milliseconds repeat_delay; /** * A name that is used to identify that event. If you want to find your * event (for example if you want to cancel it), the name should be * unique. */ - const std::string name; + std::string name; }; /** @@ -128,7 +128,7 @@ public: std::size_t size() const; private: - std::list events; + std::vector events; explicit TimedEventsManager() = default; }; diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index b852a30..469a959 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -250,7 +250,7 @@ private: * request and we need a response from IRC to be able to provide the * response iq. */ - std::list waiting_irc; + std::vector waiting_irc; /** * Keep track of which resource is in which channel. */ diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6dae92c..e5aee9a 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -532,9 +532,9 @@ Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid) } } -std::list BiboumiComponent::get_bridges() const +std::vector BiboumiComponent::get_bridges() const { - std::list res; + std::vector res; for (auto it = this->bridges.begin(); it != this->bridges.end(); ++it) res.push_back(it->second.get()); return res; diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 25982f2..0eb3bc4 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -39,7 +39,7 @@ public: /** * Return a list of all the managed bridges. */ - std::list get_bridges() const; + std::vector get_bridges() const; /** * Send a "close" message to all our connected peers. That message -- cgit v1.2.3 From 507d0c2cbe3c41e3d8e6d38862fe418cb551adf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 2 Jun 2016 01:35:15 +0200 Subject: Forward everything to all concerned XMPP resources --- src/bridge/bridge.cpp | 128 ++++++++++++++++++++++++++++++++--------- src/bridge/bridge.hpp | 37 +++++++++--- src/irc/irc_channel.hpp | 2 + src/irc/irc_client.cpp | 2 +- src/xmpp/biboumi_component.cpp | 19 +++--- 5 files changed, 142 insertions(+), 46 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 484c860..bfd5d68 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -156,9 +156,15 @@ IrcClient* Bridge::find_irc_client(const std::string& hostname) } } -bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password) -{ - IrcClient* irc = this->make_irc_client(iid.get_server(), nickname); +bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, + const std::string& resource) +{ + const auto hostname = iid.get_server(); + IrcClient* irc = this->make_irc_client(hostname, nickname); + this->add_resource_to_server(hostname, resource); + auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource); + if (!res_in_chan) + this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource); if (iid.get_local().empty()) { // Join the dummy channel if (irc->is_welcomed()) @@ -185,6 +191,8 @@ bool Bridge::join_irc_channel(const Iid& iid, const std::string& nickname, const { irc->send_join_command(iid.get_local(), password); return true; + } else if (!res_in_chan) { + this->generate_channel_join_for_resource(iid, resource); } return false; } @@ -193,11 +201,12 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) { if (iid.get_server().empty()) { - this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "", - "cancel", "remote-server-not-found", - std::to_string(iid) + " is not a valid channel name. " - "A correct room jid is of the form: #%", - false); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_stanza_error("message", this->user_jid + "/" + resource, std::to_string(iid), "", + "cancel", "remote-server-not-found", + std::to_string(iid) + " is not a valid channel name. " + "A correct room jid is of the form: #%", + false); return; } IrcClient* irc = this->get_irc_client(iid.get_server()); @@ -226,8 +235,9 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else irc->send_channel_message(iid.get_local(), line); - this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), - this->make_xmpp_body(line), this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), + this->make_xmpp_body(line), this->user_jid + "/" + resource); } } @@ -554,8 +564,13 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st { const auto encoding = in_encoding_for(*this, iid); if (muc) - this->xmpp.send_muc_message(std::to_string(iid), nick, - this->make_xmpp_body(body, encoding), this->user_jid); + { + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + { + this->xmpp.send_muc_message(std::to_string(iid), nick, + this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); + } + } else { std::string target = std::to_string(iid); @@ -566,8 +581,11 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st target = it->second; fulljid = true; } - this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), - this->user_jid, "chat", fulljid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + { + this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, "chat", fulljid); + } } } @@ -580,7 +598,8 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) { - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -596,8 +615,9 @@ void Bridge::send_nick_change(Iid&& iid, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp.send_nick_change(std::to_string(iid), - old_nick, new_nick, affiliation, role, this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_nick_change(std::to_string(iid), + old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self); } void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg) @@ -614,7 +634,10 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho body = msg; const auto encoding = in_encoding_for(*this, {from}); - this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid, "chat"); + for (const auto& resource: this->resources_in_server[from]) + { + this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat"); + } } void Bridge::send_user_join(const std::string& hostname, @@ -627,15 +650,21 @@ void Bridge::send_user_join(const std::string& hostname, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, - affiliation, role, this->user_jid, self); + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + { + this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, + affiliation, role, this->user_jid + "/" + resource, self); + } } void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who) { const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( - "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid, who); + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + { + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( + "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); + } } std::string Bridge::get_own_nick(const Iid& iid) @@ -653,12 +682,14 @@ size_t Bridge::active_clients() const void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { - this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid, "cancel", "conflict", "409", ""); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", ""); } void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode) @@ -667,7 +698,8 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::string affiliation; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); - this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid); + for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource); } void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) @@ -722,7 +754,7 @@ std::unordered_map>& Bridge::get_irc_cli return this->irc_clients; } -void Bridge::add_resource_to_chan(const std::string& channel, const std::string& resource) +void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource) { auto it = this->resources_in_chan.find(channel); if (it == this->resources_in_chan.end()) @@ -731,7 +763,7 @@ void Bridge::add_resource_to_chan(const std::string& channel, const std::string& it->second.insert(resource); } -void Bridge::remove_resource_from_chan(const std::string& channel, const std::string& resource) +void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource) { auto it = this->resources_in_chan.find(channel); if (it != this->resources_in_chan.end()) @@ -742,7 +774,7 @@ void Bridge::remove_resource_from_chan(const std::string& channel, const std::st } } -bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& resource) const +bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const { auto it = this->resources_in_chan.find(channel); if (it != this->resources_in_chan.end()) @@ -750,3 +782,43 @@ bool Bridge::is_resource_in_chan(const std::string& channel, const std::string& return true; return false; } + +void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it == this->resources_in_server.end()) + this->resources_in_server[irc_hostname] = {resource}; + else + it->second.insert(resource); +} + +void Bridge::remove_resource_from_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it != this->resources_in_server.end()) + { + it->second.erase(resource); + if (it->second.empty()) + this->resources_in_server.erase(it); + } +} + +bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource) const +{ + auto it = this->resources_in_server.find(irc_hostname); + if (it != this->resources_in_server.end()) + if (it->second.count(resource) == 1) + return true; + return false; +} + +void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource) +{ + IrcClient* irc = this->get_irc_client(iid.get_server()); + IrcChannel* channel = irc->get_channel(iid.get_local()); + // Send the occupant list + for (const auto& user: channel->get_users()) + { + + } +} diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 469a959..e614779 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -65,7 +65,7 @@ public: * Try to join an irc_channel, does nothing and return true if the channel * was already joined. */ - bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password); + bool join_irc_channel(const Iid& iid, const std::string& nickname, const std::string& password, const std::string& resource); void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); @@ -195,11 +195,9 @@ public: std::unordered_map>& get_irc_clients(); /** - * Manage which resource is in which channel + * Manage which resource is connected to which IRC server */ - void add_resource_to_chan(const std::string& channel, const std::string& resource); - void remove_resource_from_chan(const std::string& channel, const std::string& resource); - bool is_resource_in_chan(const std::string& channel, const std::string& resource) const; + private: /** @@ -218,7 +216,7 @@ private: */ IrcClient* find_irc_client(const std::string& hostname); /** - * The JID of the user associated with this bridge. Messages from/to this + * The bare JID of the user associated with this bridge. Messages from/to this * JID are only managed by this bridge. */ const std::string user_jid; @@ -251,10 +249,33 @@ private: * response iq. */ std::vector waiting_irc; + + /** + * Resources to IRC channel/server mapping: + */ + using Resource = std::string; + using ChannelName = std::string; + using IrcHostname = std::string; + using ChannelKey = std::tuple; + std::map> resources_in_chan; + std::map> resources_in_server; + /** + * Manage which resource is in which channel + */ + void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource); + void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource); + bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const; + + void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); + void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource); + bool is_resource_in_server(const IrcHostname& irc_hostname, const std::string& resource) const; + /** - * Keep track of which resource is in which channel. + * Generate all the stanzas to be sent to this resource, simulating a join on this channel. + * This means sending the whole user list, the topic, etc + * TODO: send message history */ - std::map> resources_in_chan; + void generate_channel_join_for_resource(const Iid& iid, const std::string& resource); }; struct IRCNotConnected: public std::exception diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index e22947b..3319505 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -31,6 +31,8 @@ public: IrcUser* find_user(const std::string& name) const; void remove_user(const IrcUser* user); void remove_all_users(); + const std::vector>& get_users() const + { return this->users; } protected: std::unique_ptr self; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 0b1b079..ae68528 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -701,7 +701,7 @@ void IrcClient::empty_motd(const IrcMessage&) void IrcClient::on_empty_topic(const IrcMessage& message) { - const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 1]); + const std::string chan_name = utils::tolower(message.arguments[1]); log_debug("empty topic for " << chan_name); IrcChannel* channel = this->get_channel(chan_name); if (channel) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e5aee9a..6a9bc87 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -105,26 +105,27 @@ void BiboumiComponent::clean() void BiboumiComponent::handle_presence(const Stanza& stanza) { - std::string from = stanza.get_tag("from"); + std::string from_str = stanza.get_tag("from"); std::string id = stanza.get_tag("id"); std::string to_str = stanza.get_tag("to"); std::string type = stanza.get_tag("type"); // Check for mandatory tags - if (from.empty()) + if (from_str.empty()) { log_warning("Received an invalid presence stanza: tag 'from' is missing."); return; } if (to_str.empty()) { - this->send_stanza_error("presence", from, this->served_hostname, id, + this->send_stanza_error("presence", from_str, this->served_hostname, id, "modify", "bad-request", "Missing 'to' tag"); return; } - Bridge* bridge = this->get_user_bridge(from); + Bridge* bridge = this->get_user_bridge(from_str); Jid to(to_str); + Jid from(from_str); Iid iid(to.local); // An error stanza is sent whenever we exit this function without @@ -136,7 +137,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) std::string error_type("cancel"); std::string error_name("internal-server-error"); utils::ScopeGuard stanza_error([&](){ - this->send_stanza_error("presence", from, to_str, id, + this->send_stanza_error("presence", from_str, to_str, id, error_type, error_name, ""); }); @@ -151,8 +152,8 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) bridge->send_irc_nick_change(iid, to.resource); const XmlNode* x = stanza.get_child("x", MUC_NS); const XmlNode* password = x ? x->get_child("password", MUC_NS): nullptr; - bridge->join_irc_channel(iid, to.resource, - password ? password->get_inner() : ""); + bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "", + from.resource); } else if (type == "unavailable") { @@ -164,12 +165,12 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) { // An user wants to join an invalid IRC channel, return a presence error to him if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, from); + this->send_invalid_room_error(to.local, to.resource, from_str); } } catch (const IRCNotConnected& ex) { - this->send_stanza_error("presence", from, to_str, id, + this->send_stanza_error("presence", from_str, to_str, id, "cancel", "remote-server-not-found", "Not connected to IRC server "s + ex.hostname, true); -- cgit v1.2.3 From 2d11a5f49454717c404b25825f18e696281207d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Jun 2016 01:32:39 +0200 Subject: Support multiple nick session, except for IQs ref #2556 --- src/bridge/bridge.cpp | 123 +++++++++++++++++++++++++++++------------ src/bridge/bridge.hpp | 16 ++++-- src/irc/iid.cpp | 5 ++ src/irc/iid.hpp | 2 + src/irc/irc_channel.cpp | 15 ++--- src/irc/irc_client.cpp | 12 +--- src/irc/irc_client.hpp | 2 + src/xmpp/biboumi_component.cpp | 2 +- 8 files changed, 118 insertions(+), 59 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index bfd5d68..95ca68e 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -201,7 +201,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) { if (iid.get_server().empty()) { - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_stanza_error("message", this->user_jid + "/" + resource, std::to_string(iid), "", "cancel", "remote-server-not-found", std::to_string(iid) + " is not a valid channel name. " @@ -235,7 +235,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body) irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01"); else irc->send_channel_message(iid.get_local(), line); - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line), this->user_jid + "/" + resource); } @@ -335,11 +335,29 @@ void Bridge::send_raw_message(const std::string& hostname, const std::string& bo irc->send_raw(body); } -void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message) +void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource) { IrcClient* irc = this->get_irc_client(iid.get_server()); - irc->send_part_command(iid.get_local(), status_message); -} + const auto key = iid.to_tuple(); + if (!this->is_resource_in_chan(key, resource)) + return ; + + const auto resources = this->number_of_resources_in_chan(key); + if (resources == 1) + irc->send_part_command(iid.get_local(), status_message); + else + { + IrcChannel* chan = irc->get_channel(iid.get_local()); + if (chan) + { + auto nick = chan->get_self()->nick; + this->remove_resource_from_chan(key, resource); + this->send_muc_leave(std::move(iid), std::move(nick), + "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", + true, resource); + } + } + } void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) { @@ -565,7 +583,7 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st const auto encoding = in_encoding_for(*this, iid); if (muc) { - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) { this->xmpp.send_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource); @@ -574,17 +592,19 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st else { std::string target = std::to_string(iid); - bool fulljid = false; - auto it = this->preferred_user_from.find(iid.get_local()); + const auto it = this->preferred_user_from.find(iid.get_local()); if (it != this->preferred_user_from.end()) { - target = it->second; - fulljid = true; + const auto chan_name = Iid(Jid(it->second).local).get_local(); + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}]) + this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, "chat", true); } - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + else { - this->xmpp.send_message(target, this->make_xmpp_body(body, encoding), - this->user_jid + "/" + resource, "chat", fulljid); + for (const auto& resource: this->resources_in_server[iid.get_server()]) + this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding), + this->user_jid + "/" + resource, "chat", false); } } } @@ -596,10 +616,15 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text); } -void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self) +void Bridge::send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource) { - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) - this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, self); + if (!resource.empty()) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + resource, + self); + else + for (const auto& res: this->resources_in_chan[iid.to_tuple()]) + this->xmpp.send_muc_leave(std::to_string(iid), std::move(nick), this->make_xmpp_body(message), this->user_jid + "/" + res, + self); IrcClient* irc = this->find_irc_client(iid.get_server()); if (irc && irc->number_of_joined_channels() == 0) irc->send_quit_command(""); @@ -615,7 +640,7 @@ void Bridge::send_nick_change(Iid&& iid, std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_nick_change(std::to_string(iid), old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self); } @@ -640,33 +665,43 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho } } -void Bridge::send_user_join(const std::string& hostname, - const std::string& chan_name, - const IrcUser* user, - const char user_mode, - const bool self) +void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, + const IrcUser* user, const char user_mode, const bool self) +{ + for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) + this->send_user_join(hostname, chan_name, user, user_mode, self, resource); +} + +void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name, + const IrcUser* user, const char user_mode, + const bool self, const std::string& resource) { std::string affiliation; std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) - { - this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, - affiliation, role, this->user_jid + "/" + resource, self); - } + this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, + affiliation, role, this->user_jid + "/" + resource, self); } void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who) { - const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}]) { - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( - "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); + this->send_topic(hostname, chan_name, topic, who, resource); } } +void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, + const std::string& topic, const std::string& who, + const std::string& resource) +{ + const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); + this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( + "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); + +} + std::string Bridge::get_own_nick(const Iid& iid) { IrcClient* irc = this->find_irc_client(iid.get_server()); @@ -682,13 +717,13 @@ size_t Bridge::active_clients() const void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author) { - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource); } void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname) { - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource, "cancel", "conflict", "409", ""); } @@ -698,7 +733,7 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar std::string affiliation; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode); - for (const auto& resource: this->resources_in_chan[ChannelKey{iid.get_local(), iid.get_server()}]) + for (const auto& resource: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role, this->user_jid + "/" + resource); } @@ -812,13 +847,33 @@ bool Bridge::is_resource_in_server(const Bridge::IrcHostname& irc_hostname, cons return false; } +std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel_key) const +{ + auto it = this->resources_in_chan.find(channel_key); + if (it == this->resources_in_chan.end()) + return 0; + return it->second.size(); +} + void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource) { IrcClient* irc = this->get_irc_client(iid.get_server()); IrcChannel* channel = irc->get_channel(iid.get_local()); + const auto self = channel->get_self(); + // Send the occupant list for (const auto& user: channel->get_users()) { - + if (user->nick != self->nick) + { + log_debug(user->nick); + this->send_user_join(iid.get_server(), iid.get_local(), + user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()), + false, resource); + } } + this->send_user_join(iid.get_server(), iid.get_local(), + self, self->get_most_significant_mode(irc->get_sorted_user_modes()), + true, resource); + this->send_topic(iid.get_server(), iid.get_local(), channel->topic, channel->topic_author, resource); } diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index e614779..eabd9af 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -70,7 +70,7 @@ public: void send_channel_message(const Iid& iid, const std::string& body); void send_private_message(const Iid& iid, const std::string& body, const std::string& type="PRIVMSG"); void send_raw_message(const std::string& hostname, const std::string& body); - void leave_irc_channel(Iid&& iid, std::string&& status_message); + void leave_irc_channel(Iid&& iid, std::string&& status_message, const std::string& resource); void send_irc_nick_change(const Iid& iid, const std::string& new_nick); void send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason, const std::string& iq_id, const std::string& to_jid); @@ -119,15 +119,18 @@ public: /** * Send the presence of a new user in the MUC. */ - void send_user_join(const std::string& hostname, - const std::string& chan_name, - const IrcUser* user, - const char user_mode, + void send_user_join(const std::string& hostname, const std::string& chan_name, + const IrcUser* user, const char user_mode, + const bool self, const std::string& resource); + void send_user_join(const std::string& hostname, const std::string& chan_name, + const IrcUser* user, const char user_mode, const bool self); + /** * Send the topic of the MUC to the user */ void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who); + void send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic, const std::string& who, const std::string& resource); /** * Send a MUC message from some participant */ @@ -139,7 +142,7 @@ public: /** * Send an unavailable presence from this participant */ - void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self); + void send_muc_leave(Iid&& iid, std::string&& nick, const std::string& message, const bool self, const std::string& resource=""); /** * Send presences to indicate that an user old_nick (ourself if self == * true) changed his nick to new_nick. The user_mode is needed because @@ -265,6 +268,7 @@ private: void add_resource_to_chan(const ChannelKey& channel_key, const std::string& resource); void remove_resource_from_chan(const ChannelKey& channel_key, const std::string& resource); bool is_resource_in_chan(const ChannelKey& channel_key, const std::string& resource) const; + std::size_t number_of_resources_in_chan(const ChannelKey& channel_key) const; void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource); diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 212fb8f..66b66b7 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -100,3 +100,8 @@ namespace std { } } } + +std::tuple Iid::to_tuple() const +{ + return std::make_tuple(this->get_local(), this->get_server()); +} diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 1c026e8..9747595 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -60,6 +60,8 @@ public: std::string get_sep() const; + std::tuple to_tuple() const; + private: void init(const std::string& iid); diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index b1b3983..9801513 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -1,4 +1,5 @@ #include +#include IrcChannel::IrcChannel(): joined(false), @@ -36,15 +37,11 @@ IrcUser* IrcChannel::find_user(const std::string& name) const void IrcChannel::remove_user(const IrcUser* user) { - for (auto it = this->users.begin(); it != this->users.end(); ++it) - { - IrcUser* u = it->get(); - if (u->nick == user->nick) - { - this->users.erase(it); - break ; - } - } + this->users.erase(std::remove_if(this->users.begin(), this->users.end(), + [user](const std::unique_ptr& u) + { + return user->nick == u->nick; + }), this->users.end()); } void IrcChannel::remove_all_users() diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index ae68528..d16ffc7 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -618,9 +618,7 @@ void IrcClient::set_and_forward_user_list(const IrcMessage& message) const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); if (user->nick != channel->get_self()->nick) { - this->bridge.send_user_join(this->hostname, chan_name, user, - user->get_most_significant_mode(this->sorted_user_modes), - false); + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } else { @@ -644,9 +642,7 @@ void IrcClient::on_channel_join(const IrcMessage& message) else { const IrcUser* user = channel->add_user(nick, this->prefix_to_mode); - this->bridge.send_user_join(this->hostname, chan_name, user, - user->get_most_significant_mode(this->sorted_user_modes), - false); + this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false); } } @@ -746,9 +742,7 @@ void IrcClient::on_channel_completely_joined(const IrcMessage& message) const std::string chan_name = utils::tolower(message.arguments[1]); IrcChannel* channel = this->get_channel(chan_name); channel->joined = true; - this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), - channel->get_self()->get_most_significant_mode(this->sorted_user_modes), - true); + this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(), channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true); this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 7af097c..f075ce6 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -276,6 +276,8 @@ public: const Resolver& get_resolver() const { return this->dns_resolver; } + const std::vector& get_sorted_user_modes() const { return sorted_user_modes; } + private: /** * The hostname of the server we are connected to. diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 6a9bc87..62e17d0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -158,7 +158,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) else if (type == "unavailable") { const XmlNode* status = stanza.get_child("status", COMPONENT_NS); - bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : ""); + bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource); } } else -- cgit v1.2.3 From 28300dff5b87d6a45ef5bba2a9b916b39022c7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Jun 2016 10:31:19 +0200 Subject: Add a missing vector include --- louloulibs/network/tcp_socket_handler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 55f4113..91fa7a8 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -13,11 +13,11 @@ #include #include +#include #include #include #include - /** * An interface, with a series of callbacks that should be implemented in * subclasses that deal with a socket. These callbacks are called on various events -- cgit v1.2.3 From 199f010f523b1613afcc760f2b9aedc1888ab7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 8 Jun 2016 11:49:44 +0200 Subject: Add a basic e2e test for channel and private messages --- tests/end_to_end/__main__.py | 53 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 630f59a..186ada1 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -302,6 +302,7 @@ common_replacements = { 'jid_two': 'second@example.com', 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', + 'lower_nick_one': 'nick', } @@ -592,7 +593,7 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"), # The other resources joins the same room, with the same nick partial(send_stanza, @@ -602,9 +603,57 @@ if __name__ == '__main__': ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", "/presence/muc_user:x/muc_user:status[@code='110']") ), - partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), ]), + Scenario("channel_messages", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@jid='~nick@localhost'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + # Our presence, sent to the other user + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), + # The other user presence + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), + # Our own presence + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + # Send a channel message + partial(send_stanza, "coucou"), + # Receive the message, forwarded to the two users + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_one}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_one}'][@to='{jid_two}/{resource_one}'][@type='groupchat']/body[text()='coucou']"), + + # Send a private message, to a in-room JID + partial(send_stanza, "coucou in private"), + # Message is received with a server-wide JID + partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='coucou in private']"), + + # Respond to the message, to the server-wide JID + partial(send_stanza, "yes"), + # The response is received from the in-room JID + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']"), + ] + ) ) failures = 0 -- cgit v1.2.3 From 7540f6793342aeb1e9cec53208b347ca8a346c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 9 Jun 2016 16:32:23 +0200 Subject: Add some e2e tests to check that private messages come from the right JIDs --- tests/end_to_end/__main__.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 186ada1..1408ea9 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -610,7 +610,7 @@ if __name__ == '__main__': handshake_sequence(), # First user joins partial(send_stanza, - ""), + ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), partial(expect_stanza, "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), @@ -652,6 +652,27 @@ if __name__ == '__main__': partial(send_stanza, "yes"), # The response is received from the in-room JID partial(expect_stanza, "/message[@from='#foo%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='yes']"), + + ## Do the exact same thing, from a different chan, + # to check if the response comes from the right JID + + # Join the virtual channel + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@from='%{irc_server_one}'][@type='groupchat']/subject"), + + + # Send a private message, to a in-room JID + partial(send_stanza, "re in private"), + # Message is received with a server-wide JID + partial(expect_stanza, "/message[@from='{lower_nick_one}!{irc_server_one}'][@to='{jid_two}/{resource_one}'][@type='chat']/body[text()='re in private']"), + + # Respond to the message, to the server-wide JID + partial(send_stanza, "re"), + # The response is received from the in-room JID + partial(expect_stanza, "/message[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), ] ) ) -- cgit v1.2.3 From 272c0e4995f2fe94fb2366c15453fdada341861a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 10 Jun 2016 10:00:48 +0200 Subject: Reset the preferred private JID when all resources leave a room MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For example if we are talking in private with nick Joe from room #foo, and then we leave that room, we start receiving Joe’s message from the server-wide JID e2e tests included!!! --- src/bridge/bridge.cpp | 19 ++++++++++++++++++- src/bridge/bridge.hpp | 5 +++++ tests/end_to_end/__main__.py | 12 ++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 95ca68e..3a7a147 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -344,7 +344,12 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const st const auto resources = this->number_of_resources_in_chan(key); if (resources == 1) - irc->send_part_command(iid.get_local(), status_message); + { + irc->send_part_command(iid.get_local(), status_message); + // Since there are no resources left in that channel, we don't + // want to receive private messages using this room's JID + this->remove_all_preferred_from_jid_of_room(iid.get_local()); + } else { IrcChannel* chan = irc->get_channel(iid.get_local()); @@ -767,6 +772,18 @@ void Bridge::remove_preferred_from_jid(const std::string& nick) this->preferred_user_from.erase(it); } +void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_name) +{ + for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();) + { + Iid iid(Jid(it->second).local); + if (iid.get_local() == channel_name) + it = this->preferred_user_from.erase(it); + else + ++it; + } +} + void Bridge::add_waiting_irc(irc_responder_callback_t&& callback) { this->waiting_irc.emplace_back(std::move(callback)); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index eabd9af..01f8f78 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -185,6 +185,11 @@ public: * Remove the preferred jid for the given IRC nick */ void remove_preferred_from_jid(const std::string& nick); + /** + * Given a channel_name, remove all preferred from_jid that come + * from this chan. + */ + void remove_all_preferred_from_jid_of_room(const std::string& channel_name); /** * Add a callback to the waiting list of irc callbacks. */ diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 1408ea9..0e2ca64 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -303,6 +303,7 @@ common_replacements = { 'jid_admin': 'admin@example.com', 'nick_two': 'Bobby', 'lower_nick_one': 'nick', + 'lower_nick_two': 'bobby', } @@ -673,6 +674,17 @@ if __name__ == '__main__': partial(send_stanza, "re"), # The response is received from the in-room JID partial(expect_stanza, "/message[@from='%{irc_server_one}/{nick_two}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='re']"), + + # Now we leave the room, to check if the subsequent private messages are still received properly + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"), + + # The private messages from this nick should now come (again) from the server-wide JID + partial(send_stanza, "hihihoho"), + partial(expect_stanza, + "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ] ) ) -- cgit v1.2.3 From ad15924ac3f0852214fd35d93482fc18dbbe4bec Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 11 Jun 2016 01:34:03 +0100 Subject: =?UTF-8?q?Allow=20cmake=20to=20configure=20the=20systemd=20unit?= =?UTF-8?q?=E2=80=99s=20user=20and=20group?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 6 ++++++ unit/biboumi.service.cmake | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bb13460..4559201 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,6 +267,12 @@ else() set(SYSTEMD_SERVICE_TYPE "simple") set(WATCHDOG_SEC "") endif() +if(NOT DEFINED SERVICE_USER) + set(SERVICE_USER "nobody") +endif() +if(NOT DEFINED SERVICE_GROUP) + set(SERVICE_GROUP "nobody") +endif() configure_file(unit/biboumi.service.cmake biboumi.service) # The date MUST be in english format diff --git a/unit/biboumi.service.cmake b/unit/biboumi.service.cmake index 4685f43..1cdf4e1 100644 --- a/unit/biboumi.service.cmake +++ b/unit/biboumi.service.cmake @@ -9,8 +9,8 @@ ExecStart=${CMAKE_INSTALL_PREFIX}/bin/biboumi /etc/biboumi/biboumi.cfg ExecReload=/bin/kill -s USR1 $MAINPID WatchdogSec=${WATCHDOG_SEC} Restart=always -User=nobody -Group=nobody +User=${SERVICE_USER} +Group=${SERVICE_GROUP} [Install] WantedBy=multi-user.target -- cgit v1.2.3 From abedca196fffb5904f2b01fc0237600fe939168a Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 11 Jun 2016 18:02:54 +0100 Subject: Make the default config file put the database in /var/lib --- conf/biboumi.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/conf/biboumi.cfg b/conf/biboumi.cfg index 205b936..e6b8ed5 100644 --- a/conf/biboumi.cfg +++ b/conf/biboumi.cfg @@ -1,5 +1,6 @@ hostname=biboumi.example.com password=secret +db_name=/var/lib/biboumi/biboumi.sqlite log_file=/var/log/biboumi/biboumi.log log_level=0 admin= -- cgit v1.2.3 From ad4ccdbbea129cfbab89773bea040d4149afcb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Jun 2016 16:41:02 +0200 Subject: Display a message to tell if we found pandoc or not fix #3177 --- CMakeLists.txt | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4559201..dc3a707 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,15 +65,20 @@ execute_process(COMMAND "date" "+%Y-%m-%d" OUTPUT_VARIABLE DOC_DATE OUTPUT_STRIP_TRAILING_WHITESPACE) set(MAN_PAGE ${CMAKE_CURRENT_BINARY_DIR}/doc/${PROJECT_NAME}.1) set(DOC_PAGE ${CMAKE_CURRENT_SOURCE_DIR}/doc/${PROJECT_NAME}.1.rst) -find_program(PANDOC_EXECUTABLE NAMES pandoc - DOC "The pandoc software, to build the man page from the rst documentation") -if(PANDOC_EXECUTABLE) - set(WITH_DOC true) - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/) - add_custom_command(OUTPUT ${MAN_PAGE} - COMMAND ${PANDOC_EXECUTABLE} -M date="${DOC_DATE}" -s -t man ${DOC_PAGE} -o ${MAN_PAGE} - DEPENDS ${DOC_PAGE}) - add_custom_target(doc ALL DEPENDS ${MAN_PAGE}) +if (NOT PANDOC_EXECUTABLE) + find_program(PANDOC_EXECUTABLE NAMES pandoc + DOC "The pandoc software, to build the man page from the rst documentation") + if(PANDOC_EXECUTABLE) + message(STATUS "Found Pandoc: ${PANDOC_EXECUTABLE}") + set(WITH_DOC true) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/) + add_custom_command(OUTPUT ${MAN_PAGE} + COMMAND ${PANDOC_EXECUTABLE} -M date="${DOC_DATE}" -s -t man ${DOC_PAGE} -o ${MAN_PAGE} + DEPENDS ${DOC_PAGE}) + add_custom_target(doc ALL DEPENDS ${MAN_PAGE}) + else() + message(STATUS "Pandoc not found, documentation cannot be built") + endif() endif() # Look for litesql and enable the database if found -- cgit v1.2.3 From 5a2e61161792cf51209f240e40e28036195f35be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Jun 2016 19:59:17 +0200 Subject: Show off, with some variadic templates, for the logger module --- louloulibs/logger/logger.hpp | 74 ++++++++++++++++++++++++------ louloulibs/network/credentials_manager.cpp | 6 +-- louloulibs/network/poller.cpp | 14 +++--- louloulibs/network/tcp_socket_handler.cpp | 40 ++++++++-------- louloulibs/xmpp/adhoc_commands_handler.cpp | 2 +- louloulibs/xmpp/xmpp_component.cpp | 20 ++++---- louloulibs/xmpp/xmpp_parser.cpp | 6 +-- src/irc/irc_client.cpp | 22 ++++----- tests/logger.cpp | 2 +- 9 files changed, 115 insertions(+), 71 deletions(-) diff --git a/louloulibs/logger/logger.hpp b/louloulibs/logger/logger.hpp index 3547513..8ff4dcd 100644 --- a/louloulibs/logger/logger.hpp +++ b/louloulibs/logger/logger.hpp @@ -33,21 +33,6 @@ # define __FILENAME__ __FILE__ #endif -#define WHERE\ - __FILENAME__ << ":" << __LINE__ - -#define log_debug(text)\ - Logger::instance()->get_stream(debug_lvl) << SD_DEBUG << WHERE << ":\t" << text << std::endl - -#define log_info(text)\ - Logger::instance()->get_stream(info_lvl) << SD_INFO << WHERE << ":\t" << text << std::endl - -#define log_warning(text)\ - Logger::instance()->get_stream(warning_lvl) << SD_WARNING << WHERE << ":\t" << text << std::endl - -#define log_error(text)\ - Logger::instance()->get_stream(error_lvl) << SD_ERR << WHERE << ":\t" << text << std::endl - /** * Juste a structure representing a stream doing nothing with its input. */ @@ -79,4 +64,63 @@ private: std::ostream stream; }; +#define WHERE __FILENAME__, ":", __LINE__, ":\t" + +namespace logging_details +{ + template + void log(std::ostream& os, const T& arg) + { + os << arg << std::endl; + } + + template + void log(std::ostream& os, const T& first, U&&... rest) + { + os << first; + log(os, std::forward(rest)...); + } + + template + void log_debug(U&&... args) + { + auto& os = Logger::instance()->get_stream(debug_lvl); + os << SD_DEBUG; + log(os, std::forward(args)...); + } + + template + void log_info(U&&... args) + { + auto& os = Logger::instance()->get_stream(info_lvl); + os << SD_INFO; + log(os, std::forward(args)...); + } + + template + void log_warning(U&&... args) + { + auto& os = Logger::instance()->get_stream(warning_lvl); + os << SD_WARNING; + log(os, std::forward(args)...); + } + + template + void log_error(U&&... args) + { + auto& os = Logger::instance()->get_stream(error_lvl); + os << SD_ERR; + log(os, std::forward(args)...); + } +} + +#define log_info(...) logging_details::log_info(WHERE, __VA_ARGS__) + +#define log_warning(...) logging_details::log_warning(WHERE, __VA_ARGS__) + +#define log_error(...) logging_details::log_error(WHERE, __VA_ARGS__) + +#define log_debug(...) logging_details::log_debug(WHERE, __VA_ARGS__) + + #endif // LOGGER_INCLUDED diff --git a/louloulibs/network/credentials_manager.cpp b/louloulibs/network/credentials_manager.cpp index c5b8493..ee83c3b 100644 --- a/louloulibs/network/credentials_manager.cpp +++ b/louloulibs/network/credentials_manager.cpp @@ -41,7 +41,7 @@ void BasicCredentialsManager::verify_certificate_chain(const std::string& type, const std::string& purported_hostname, const std::vector& certs) { - log_debug("Checking remote certificate (" << type << ") for hostname " << purported_hostname); + log_debug("Checking remote certificate (", type, ") for hostname ", purported_hostname); try { Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); @@ -49,7 +49,7 @@ void BasicCredentialsManager::verify_certificate_chain(const std::string& type, } catch (const std::exception& tls_exception) { - log_warning("TLS certificate check failed: " << tls_exception.what()); + log_warning("TLS certificate check failed: ", tls_exception.what()); if (!this->trusted_fingerprint.empty() && !certs.empty() && this->trusted_fingerprint == certs[0].fingerprint() && certs[0].matches_dns_name(purported_hostname)) @@ -78,7 +78,7 @@ void BasicCredentialsManager::load_certs() try { Botan::DataSource_Stream bundle(path); - log_debug("Using ca bundle: " << path); + log_debug("Using ca bundle: ", path); while (!bundle.end_of_data() && bundle.check_available(27)) { // TODO: remove this work-around for Botan 1.11.29 diff --git a/louloulibs/network/poller.cpp b/louloulibs/network/poller.cpp index 959567e..8a6fd97 100644 --- a/louloulibs/network/poller.cpp +++ b/louloulibs/network/poller.cpp @@ -20,7 +20,7 @@ Poller::Poller() this->epfd = ::epoll_create1(0); if (this->epfd == -1) { - log_error("epoll failed: " << strerror(errno)); + log_error("epoll failed: ", strerror(errno)); throw std::runtime_error("Could not create epoll instance"); } #endif @@ -54,7 +54,7 @@ void Poller::add_socket_handler(SocketHandler* socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_ADD, socket_handler->get_socket(), &event); if (res == -1) { - log_error("epoll_ctl failed: " << strerror(errno)); + log_error("epoll_ctl failed: ", strerror(errno)); throw std::runtime_error("Could not add socket to epoll"); } #endif @@ -86,7 +86,7 @@ void Poller::remove_socket_handler(const socket_t socket) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_DEL, socket, nullptr); if (res == -1) { - log_error("epoll_ctl failed: " << strerror(errno)); + log_error("epoll_ctl failed: ", strerror(errno)); throw std::runtime_error("Could not remove socket from epoll"); } #endif @@ -109,7 +109,7 @@ void Poller::watch_send_events(SocketHandler* socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { - log_error("epoll_ctl failed: " << strerror(errno)); + log_error("epoll_ctl failed: ", strerror(errno)); throw std::runtime_error("Could not modify socket flags in epoll"); } #endif @@ -132,7 +132,7 @@ void Poller::stop_watching_send_events(SocketHandler* socket_handler) const int res = ::epoll_ctl(this->epfd, EPOLL_CTL_MOD, socket_handler->get_socket(), &event); if (res == -1) { - log_error("epoll_ctl failed: " << strerror(errno)); + log_error("epoll_ctl failed: ", strerror(errno)); throw std::runtime_error("Could not modify socket flags in epoll"); } #endif @@ -165,7 +165,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) { if (errno == EINTR) return true; - log_error("poll failed: " << strerror(errno)); + log_error("poll failed: ", strerror(errno)); throw std::runtime_error("Poll failed"); } // We cannot possibly have more ready events than the number of fds we are @@ -205,7 +205,7 @@ int Poller::poll(const std::chrono::milliseconds& timeout) { if (errno == EINTR) return 0; - log_error("epoll wait: " << strerror(errno)); + log_error("epoll wait: ", strerror(errno)); throw std::runtime_error("Epoll_wait failed"); } for (int i = 0; i < nb_events; ++i) diff --git a/louloulibs/network/tcp_socket_handler.cpp b/louloulibs/network/tcp_socket_handler.cpp index 81369dd..5420b1c 100644 --- a/louloulibs/network/tcp_socket_handler.cpp +++ b/louloulibs/network/tcp_socket_handler.cpp @@ -64,8 +64,8 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) struct addrinfo* result; int err = ::getaddrinfo(this->bind_addr.data(), nullptr, nullptr, &result); if (err != 0 || !result) - log_error("Failed to bind socket to " << this->bind_addr << ": " - << gai_strerror(err)); + log_error("Failed to bind socket to ", this->bind_addr, ": ", + gai_strerror(err)); else { utils::ScopeGuard sg([result](){ freeaddrinfo(result); }); @@ -79,15 +79,15 @@ void TCPSocketHandler::init_socket(const struct addrinfo* rp) break; } if (!rp) - log_error("Failed to bind socket to " << this->bind_addr << ": " - << strerror(bind_error)); + log_error("Failed to bind socket to ", this->bind_addr, ": ", + strerror(bind_error)); else - log_info("Socket successfully bound to " << this->bind_addr); + log_info("Socket successfully bound to ", this->bind_addr); } } int optval = 1; if (::setsockopt(this->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) - log_warning("Failed to enable TCP keepalive on socket: " << strerror(errno)); + log_warning("Failed to enable TCP keepalive on socket: ", strerror(errno)); // Set the socket on non-blocking mode. This is useful to receive a EAGAIN // error when connect() would block, to not block the whole process if a // remote is not responsive. @@ -113,7 +113,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po // this is the first call of this function. if (!this->resolver.is_resolved()) { - log_info("Trying to connect to " << address << ":" << port); + log_info("Trying to connect to ", address, ":", port); // Start the asynchronous process of resolving the hostname. Once // the addresses have been found and `resolved` has been set to true // (but connecting will still be false), TCPSocketHandler::connect() @@ -161,7 +161,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po this->init_socket(rp); } catch (const std::runtime_error& error) { - log_error("Failed to init socket: " << error.what()); + log_error("Failed to init socket: ", error.what()); break; } } @@ -204,7 +204,7 @@ void TCPSocketHandler::connect(const std::string& address, const std::string& po "connection_timeout"s + std::to_string(this->socket))); return ; } - log_info("Connection failed:" << strerror(errno)); + log_info("Connection failed:", strerror(errno)); } log_error("All connection attempts failed."); this->close(); @@ -268,9 +268,9 @@ ssize_t TCPSocketHandler::do_recv(void* recv_buf, const size_t buf_size) else if (-1 == size) { if (this->connecting) - log_warning("Error connecting: " << strerror(errno)); + log_warning("Error connecting: ", strerror(errno)); else - log_warning("Error while reading from socket: " << strerror(errno)); + log_warning("Error while reading from socket: ", strerror(errno)); // Remember if we were connecting, or already connected when this // happened, because close() sets this->connecting to false const auto were_connecting = this->connecting; @@ -300,7 +300,7 @@ void TCPSocketHandler::on_send() ssize_t res = ::sendmsg(this->socket, &msg, MSG_NOSIGNAL); if (res < 0) { - log_error("sendmsg failed: " << strerror(errno)); + log_error("sendmsg failed: ", strerror(errno)); this->on_connection_close(strerror(errno)); this->close(); } @@ -351,9 +351,9 @@ void TCPSocketHandler::close() void TCPSocketHandler::display_resolved_ip(struct addrinfo* rp) const { if (rp->ai_family == AF_INET) - log_debug("Trying IPv4 address " << addr_to_string(rp)); + log_debug("Trying IPv4 address ", addr_to_string(rp)); else if (rp->ai_family == AF_INET6) - log_debug("Trying IPv6 address " << addr_to_string(rp)); + log_debug("Trying IPv6 address ", addr_to_string(rp)); } void TCPSocketHandler::send_data(std::string&& data) @@ -478,18 +478,18 @@ void TCPSocketHandler::tls_output_fn(const Botan::byte* data, size_t size) void TCPSocketHandler::tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t) { - log_debug("tls_alert: " << alert.type_string()); + log_debug("tls_alert: ", alert.type_string()); } bool TCPSocketHandler::tls_handshake_cb(const Botan::TLS::Session& session) { - log_debug("Handshake with " << session.server_info().hostname() << " complete." - << " Version: " << session.version().to_string() - << " using " << session.ciphersuite().to_string()); + log_debug("Handshake with ", session.server_info().hostname(), " complete.", + " Version: ", session.version().to_string(), + " using ", session.ciphersuite().to_string()); if (!session.session_id().empty()) - log_debug("Session ID " << Botan::hex_encode(session.session_id())); + log_debug("Session ID ", Botan::hex_encode(session.session_id())); if (!session.session_ticket().empty()) - log_debug("Session ticket " << Botan::hex_encode(session.session_ticket())); + log_debug("Session ticket ", Botan::hex_encode(session.session_ticket())); return true; } diff --git a/louloulibs/xmpp/adhoc_commands_handler.cpp b/louloulibs/xmpp/adhoc_commands_handler.cpp index 714c440..17c4e67 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.cpp +++ b/louloulibs/xmpp/adhoc_commands_handler.cpp @@ -119,5 +119,5 @@ void AdhocCommandsHandler::remove_session(const std::string& session_id, const s this->sessions.erase(session_it); return ; } - log_error("Tried to remove ad-hoc session for [" << session_id << ", " << initiator_jid << "] but none found"); + log_error("Tried to remove ad-hoc session for [", session_id, ", ", initiator_jid, "] but none found"); } diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index b92d9a3..1be7a06 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -72,14 +72,14 @@ bool XmppComponent::is_document_open() const void XmppComponent::send_stanza(const Stanza& stanza) { std::string str = stanza.to_string(); - log_debug("XMPP SENDING: " << str); + log_debug("XMPP SENDING: ", str); this->send_data(std::move(str)); } void XmppComponent::on_connection_failed(const std::string& reason) { this->first_connection_try = false; - log_error("Failed to connect to the XMPP server: " << reason); + log_error("Failed to connect to the XMPP server: ", reason); #ifdef SYSTEMD_FOUND sd_notifyf(0, "STATUS=Failed to connect to the XMPP server: %s", reason.data()); #endif @@ -91,7 +91,7 @@ void XmppComponent::on_connected() this->first_connection_try = true; auto data = ""; - log_debug("XMPP SENDING: " << data); + log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); this->doc_open = true; // We may have some pending data to send: this happens when we try to send @@ -104,7 +104,7 @@ void XmppComponent::on_connection_close(const std::string& error) if (error.empty()) log_info("XMPP server closed connection"); else - log_info("XMPP server closed connection: " << error); + log_info("XMPP server closed connection: ", error); } void XmppComponent::parse_in_buffer(const size_t size) @@ -125,7 +125,7 @@ void XmppComponent::parse_in_buffer(const size_t size) void XmppComponent::on_remote_stream_open(const XmlNode& node) { - log_debug("XMPP RECEIVING: " << node.to_string()); + log_debug("XMPP RECEIVING: ", node.to_string()); this->stream_id = node.get_tag("id"); if (this->stream_id.empty()) { @@ -147,13 +147,13 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) digest[HASH_LENGTH * 2] = '\0'; auto data = ""s + digest + ""; - log_debug("XMPP SENDING: " << data); + log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); } void XmppComponent::on_remote_stream_close(const XmlNode& node) { - log_debug("XMPP RECEIVING: " << node.to_string()); + log_debug("XMPP RECEIVING: ", node.to_string()); this->doc_open = false; } @@ -164,7 +164,7 @@ void XmppComponent::reset() void XmppComponent::on_stanza(const Stanza& stanza) { - log_debug("XMPP RECEIVING: " << stanza.to_string()); + log_debug("XMPP RECEIVING: ", stanza.to_string()); std::function handler; try { @@ -172,7 +172,7 @@ void XmppComponent::on_stanza(const Stanza& stanza) } catch (const std::out_of_range& exception) { - log_warning("No handler for stanza of type " << stanza.get_name()); + log_warning("No handler for stanza of type ", stanza.get_name()); return; } handler(stanza); @@ -257,7 +257,7 @@ void XmppComponent::handle_error(const Stanza& stanza) std::string error_message("Unspecified error"); if (text) error_message = text->get_inner(); - log_error("Stream error received from the XMPP server: " << error_message); + log_error("Stream error received from the XMPP server: ", error_message); #ifdef SYSTEMD_FOUND if (!this->ever_auth) sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); diff --git a/louloulibs/xmpp/xmpp_parser.cpp b/louloulibs/xmpp/xmpp_parser.cpp index dc12000..0488be9 100644 --- a/louloulibs/xmpp/xmpp_parser.cpp +++ b/louloulibs/xmpp/xmpp_parser.cpp @@ -56,7 +56,7 @@ int XmppParser::feed(const char* data, const int len, const bool is_final) int res = XML_Parse(this->parser, data, len, is_final); if (res == XML_STATUS_ERROR && (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED)) - log_error("Xml_Parse encountered an error: " << + log_error("Xml_Parse encountered an error: ", XML_ErrorString(XML_GetErrorCode(this->parser))); return res; } @@ -65,7 +65,7 @@ int XmppParser::parse(const int len, const bool is_final) { int res = XML_ParseBuffer(this->parser, len, is_final); if (res == XML_STATUS_ERROR) - log_error("Xml_Parsebuffer encountered an error: " << + log_error("Xml_Parsebuffer encountered an error: ", XML_ErrorString(XML_GetErrorCode(this->parser))); return res; } @@ -139,7 +139,7 @@ void XmppParser::stanza_event(const Stanza& stanza) const try { callback(stanza); } catch (const std::exception& e) { - log_error("Unhandled exception: " << e.what()); + log_error("Unhandled exception: ", e.what()); } } } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index d16ffc7..e320db9 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -333,7 +333,7 @@ void IrcClient::parse_in_buffer(const size_t) break ; IrcMessage message(this->in_buf.substr(0, pos)); this->in_buf = this->in_buf.substr(pos + 2, std::string::npos); - log_debug("IRC RECEIVING: (" << this->get_hostname() << ") " << message); + log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message); // Call the standard callback (if any), associated with the command // name that we just received. @@ -346,21 +346,21 @@ void IrcClient::parse_in_buffer(const size_t) // second is the max if (message.arguments.size() < limits.first || (limits.second > 0 && message.arguments.size() > limits.second)) - log_warning("Invalid number of arguments for IRC command “" << message.command << - "”: " << message.arguments.size()); + log_warning("Invalid number of arguments for IRC command “", message.command, + "”: ", message.arguments.size()); else { const auto& cb = it->second.first; try { (this->*(cb))(message); } catch (const std::exception& e) { - log_error("Unhandled exception: " << e.what()); + log_error("Unhandled exception: ", e.what()); } } } else { - log_info("No handler for command " << message.command << + log_info("No handler for command ", message.command, ", forwarding the arguments to the user"); this->on_unknown_message(message); } @@ -371,7 +371,7 @@ void IrcClient::parse_in_buffer(const size_t) void IrcClient::send_message(IrcMessage&& message) { - log_debug("IRC SENDING: (" << this->get_hostname() << ") " << message); + log_debug("IRC SENDING: (", this->get_hostname(), ") ", message); std::string res; if (!message.prefix.empty()) res += ":" + std::move(message.prefix) + " "; @@ -392,7 +392,7 @@ void IrcClient::send_message(IrcMessage&& message) void IrcClient::send_raw(const std::string& txt) { - log_debug("IRC SENDING (raw): (" << this->get_hostname() << ") " << txt); + log_debug("IRC SENDING (raw): (", this->get_hostname(), ") ", txt); this->send_data(txt + "\r\n"); } @@ -452,7 +452,7 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st IrcChannel* channel = this->get_channel(chan_name); if (channel->joined == false) { - log_warning("Cannot send message to channel " << chan_name << ", it is not joined"); + log_warning("Cannot send message to channel ", chan_name, ", it is not joined"); return false; } // Cut the message body into 400-bytes parts (because the whole command @@ -698,7 +698,7 @@ void IrcClient::empty_motd(const IrcMessage&) void IrcClient::on_empty_topic(const IrcMessage& message) { const std::string chan_name = utils::tolower(message.arguments[1]); - log_debug("empty topic for " << chan_name); + log_debug("empty topic for ", chan_name); IrcChannel* channel = this->get_channel(chan_name); if (channel) channel->topic.clear(); @@ -1026,8 +1026,8 @@ void IrcClient::on_channel_mode(const IrcMessage& message) IrcUser* user = channel->find_user(target); if (!user) { - log_warning("Trying to set mode for non-existing user '" << target - << "' in channel" << iid.get_local()); + log_warning("Trying to set mode for non-existing user '", target + , "' in channel", iid.get_local()); return; } if (add) diff --git a/tests/logger.cpp b/tests/logger.cpp index 2a99374..7ae4f03 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -24,7 +24,7 @@ TEST_CASE("Basic logging") WHEN("we log some debug text") { IoTester out(std::cout); - log_debug("debug"); + log_debug("deb", "ug"); THEN("debug logs are written") CHECK(out.str() == debug_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\tdebug\n"); } -- cgit v1.2.3 From 550ab46f02511614d7ce7b46a4b4f63beae1aebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 13 Jun 2016 20:45:12 +0200 Subject: Make the logger tests more useful --- tests/logger.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/logger.cpp b/tests/logger.cpp index 7ae4f03..1d59a22 100644 --- a/tests/logger.cpp +++ b/tests/logger.cpp @@ -31,9 +31,9 @@ TEST_CASE("Basic logging") WHEN("we log some errors") { IoTester out(std::cout); - log_error("error"); + log_error("err", 12, "or"); THEN("error logs are written") - CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terr12or\n"); } } GIVEN("A logger with log_level 3") @@ -42,16 +42,16 @@ TEST_CASE("Basic logging") WHEN("we log some debug text") { IoTester out(std::cout); - log_debug("debug"); + log_debug(123, "debug"); THEN("nothing is written") CHECK(out.str().empty()); } WHEN("we log some errors") { IoTester out(std::cout); - log_error("error"); + log_error(123, " errors"); THEN("error logs are still written") - CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\terror\n"); + CHECK(out.str() == error_header + "tests/logger.cpp:" + std::to_string(__LINE__ - 2) + ":\t123 errors\n"); } } } -- cgit v1.2.3 From 46ff73662cc94220c5ee962b591c8ee327de6f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 03:02:36 +0200 Subject: Clean the Config module, use static things instead of a stupid singleton --- louloulibs/config/config.cpp | 66 +++++++++++++++----------------------------- louloulibs/config/config.hpp | 28 ++++++++----------- louloulibs/utils/reload.cpp | 4 +-- src/main.cpp | 22 +++++---------- tests/config.cpp | 10 +++---- 5 files changed, 48 insertions(+), 82 deletions(-) diff --git a/louloulibs/config/config.cpp b/louloulibs/config/config.cpp index c785632..967f581 100644 --- a/louloulibs/config/config.cpp +++ b/louloulibs/config/config.cpp @@ -6,22 +6,21 @@ #include std::string Config::filename{}; -bool Config::file_must_exist = false; +std::map Config::values{}; +std::vector Config::callbacks{}; std::string Config::get(const std::string& option, const std::string& def) { - Config* self = Config::instance().get(); - auto it = self->values.find(option); + auto it = Config::values.find(option); - if (it == self->values.end()) + if (it == Config::values.end()) return def; return it->second; } int Config::get_int(const std::string& option, const int& def) { - Config* self = Config::instance().get(); - std::string res = self->get(option, ""); + std::string res = Config::get(option, ""); if (!res.empty()) return atoi(res.c_str()); else @@ -30,65 +29,48 @@ int Config::get_int(const std::string& option, const int& def) void Config::set(const std::string& option, const std::string& value, bool save) { - Config* self = Config::instance().get(); - self->values[option] = value; + Config::values[option] = value; if (save) { - self->save_to_file(); - self->trigger_configuration_change(); + Config::save_to_file(); + Config::trigger_configuration_change(); } } void Config::connect(t_config_changed_callback callback) { - Config* self = Config::instance().get(); - self->callbacks.push_back(callback); + Config::callbacks.push_back(callback); } -void Config::close() +void Config::clear() { - Config* self = Config::instance().get(); - self->values.clear(); - Config::instance().reset(); + Config::values.clear(); } /** * Private methods */ - void Config::trigger_configuration_change() { std::vector::iterator it; - for (it = this->callbacks.begin(); it < this->callbacks.end(); ++it) + for (it = Config::callbacks.begin(); it < Config::callbacks.end(); ++it) (*it)(); } -std::unique_ptr& Config::instance() +bool Config::read_conf(const std::string& name) { - static std::unique_ptr instance; + if (!name.empty()) + Config::filename = name; - if (!instance) - { - instance = std::make_unique(); - instance->read_conf(); - } - return instance; -} - -bool Config::read_conf() -{ - std::ifstream file; - file.open(filename.data()); + std::ifstream file(Config::filename.data()); if (!file.is_open()) { - if (Config::file_must_exist) - { - perror(("Error while opening file " + filename + " for reading.").c_str()); - file.exceptions(std::ifstream::failbit); - } + perror(("Error while opening file " + filename + " for reading.").c_str()); return false; } + Config::clear(); + std::string line; size_t pos; std::string option; @@ -103,20 +85,18 @@ bool Config::read_conf() continue ; option = line.substr(0, pos); value = line.substr(pos+1); - this->values[option] = value; + Config::values[option] = value; } - return true; } -void Config::save_to_file() const +void Config::save_to_file() { - std::ofstream file(this->filename.data()); + std::ofstream file(Config::filename.data()); if (file.fail()) { std::cerr << "Could not save config file." << std::endl; return ; } - for (auto& it: this->values) + for (const auto& it: Config::values) file << it.first << "=" << it.second << '\n'; - file.close(); } diff --git a/louloulibs/config/config.hpp b/louloulibs/config/config.hpp index 72620c0..b46768e 100644 --- a/louloulibs/config/config.hpp +++ b/louloulibs/config/config.hpp @@ -60,38 +60,34 @@ public: * Destroy the instance, forcing it to be recreated (with potentially * different parameters) the next time it’s needed. */ - static void close(); + static void clear(); /** - * Set the value of the filename to use, before calling any method. + * Read the configuration file at the given path. */ - static std::string filename; + static bool read_conf(const std::string& name=""); /** - * Set to true if you want an exception to be raised if the file does not - * exist when reading it. + * Get the filename */ - static bool file_must_exist; + static const std::string& get_filename() + { return Config::filename; } private: /** - * Get the singleton instance - */ - static std::unique_ptr& instance(); - /** - * Read the configuration file at the given path. + * Set the value of the filename to use, before calling any method. */ - bool read_conf(); + static std::string filename; /** * Write all the config values into the configuration file */ - void save_to_file() const; + static void save_to_file(); /** * Call all the callbacks previously registered using connect(). * This is used to notify any class that a configuration change occured. */ - void trigger_configuration_change(); + static void trigger_configuration_change(); - std::map values; - std::vector callbacks; + static std::map values; + static std::vector callbacks; }; diff --git a/louloulibs/utils/reload.cpp b/louloulibs/utils/reload.cpp index 6600c75..bddfd86 100644 --- a/louloulibs/utils/reload.cpp +++ b/louloulibs/utils/reload.cpp @@ -3,9 +3,7 @@ void reload_process() { - // Closing the config will just force it to be reopened the next time - // a configuration option is needed - Config::close(); + Config::read_conf(); // Destroy the logger instance, to be recreated the next time a log // line needs to be written Logger::instance().reset(); diff --git a/src/main.cpp b/src/main.cpp index 226b400..cbec4a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,7 +47,7 @@ static void sigint_handler(int sig, siginfo_t*, void*) { // We reset the SIGTERM or SIGINT (the one that didn't trigger this // handler) signal handler to its default value. This avoid calling this - // handler twice, if the process receive both signals in a quick + // handler twice, if the process receives both signals in a quick // succession. int sig_to_reset = (sig == SIGINT? SIGTERM: SIGINT); sigset_t mask; @@ -70,24 +70,16 @@ static void sigusr_handler(int, siginfo_t*, void*) int main(int ac, char** av) { - if (ac > 1) - Config::filename = av[1]; - else - Config::filename = xdg_config_path("biboumi.cfg"); + const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg"); + std::cerr << "Using configuration file: " << conf_filename << std::endl; - Config::file_must_exist = true; - std::cerr << "Using configuration file: " << Config::filename << std::endl; - - std::string password; - try { // The file must exist - password = Config::get("password", ""); - } - catch (const std::ios::failure& e) { + if (!Config::read_conf(conf_filename)) return config_help(""); - } - const std::string hostname = Config::get("hostname", ""); + + const std::string password = Config::get("password", ""); if (password.empty()) return config_help("password"); + const std::string hostname = Config::get("hostname", ""); if (hostname.empty()) return config_help("hostname"); diff --git a/tests/config.cpp b/tests/config.cpp index 346dea1..ddea151 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -4,18 +4,18 @@ TEST_CASE("Config basic") { - Config::filename = "test.cfg"; - Config::file_must_exist = false; + // Write a value in the config file + Config::read_conf("test.cfg"); Config::set("coucou", "bonjour", true); - Config::close(); + Config::clear(); bool error = false; try { - Config::file_must_exist = true; + CHECK(Config::read_conf()); CHECK(Config::get("coucou", "") == "bonjour"); CHECK(Config::get("does not exist", "default") == "default"); - Config::close(); + Config::clear(); } catch (const std::ios::failure& e) { -- cgit v1.2.3 From e8671042f792d8d967e476ea01821243c3465412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 03:21:15 +0200 Subject: Improve the signal handling by disabling them while an handler is running --- src/main.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index cbec4a6..ed05d36 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,18 +45,6 @@ int config_help(const std::string& missing_option) static void sigint_handler(int sig, siginfo_t*, void*) { - // We reset the SIGTERM or SIGINT (the one that didn't trigger this - // handler) signal handler to its default value. This avoid calling this - // handler twice, if the process receives both signals in a quick - // succession. - int sig_to_reset = (sig == SIGINT? SIGTERM: SIGINT); - sigset_t mask; - sigemptyset(&mask); - struct sigaction sigreset = {}; - sigreset.sa_handler = SIG_DFL; - sigreset.sa_mask = mask; - sigaction(sig_to_reset, &sigreset, nullptr); - // In 2 seconds, repeat the same signal, to force the exit TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, [sig]() { raise(sig); })); @@ -96,10 +84,10 @@ int main(int ac, char** av) // Install the signals used to exit the process cleanly, or reload the // config - sigemptyset(&mask); struct sigaction on_sigint; on_sigint.sa_sigaction = &sigint_handler; - on_sigint.sa_mask = mask; + // All signals must be blocked while a signal handler is running + sigfillset(&on_sigint.sa_mask); // we want to catch that signal only once. // Sending SIGINT again will "force" an exit on_sigint.sa_flags = SA_RESETHAND; @@ -109,7 +97,7 @@ int main(int ac, char** av) // Install a signal to reload the config on SIGUSR1/2 struct sigaction on_sigusr; on_sigusr.sa_sigaction = &sigusr_handler; - on_sigusr.sa_mask = mask; + sigfillset(&on_sigusr.sa_mask); on_sigusr.sa_flags = 0; sigaction(SIGUSR1, &on_sigusr, nullptr); sigaction(SIGUSR2, &on_sigusr, nullptr); -- cgit v1.2.3 From 4661f7fe5bd05fa669586789b46561ebdbd53e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 14:52:04 +0200 Subject: Fix a doc typo --- doc/biboumi.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 2a6dae3..b8bf794 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -353,7 +353,7 @@ This means that biboumi has to convert everything coming from IRC into UTF-8 without knowing the encoding of the received messages. To do so, it checks if each message is UTF-8 valid, if not it tries to convert from ``iso_8859-1`` (because this appears to be the most common case, at least -on the channels I visit) to ``UTF-``. If that conversion fails at some +on the channels I visit) to ``UTF-8``. If that conversion fails at some point, a placeholder character ``'�'`` is inserted to indicate this decoding error. -- cgit v1.2.3 From 19bca5c2f2b104d534a7c8be7f61dc48d928cf24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 20:08:55 +0200 Subject: Add a missing return bool in Config --- louloulibs/config/config.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/louloulibs/config/config.cpp b/louloulibs/config/config.cpp index 967f581..8027d20 100644 --- a/louloulibs/config/config.cpp +++ b/louloulibs/config/config.cpp @@ -87,6 +87,7 @@ bool Config::read_conf(const std::string& name) value = line.substr(pos+1); Config::values[option] = value; } + return true; } void Config::save_to_file() -- cgit v1.2.3 From 80d0c19c5a8d548a8c6019033bf574ff2be4c0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 14 Jun 2016 20:14:36 +0200 Subject: Refactor, test and improve the way we cut too-long messages for IRC --- louloulibs/utils/string.cpp | 12 ++++++++++++ louloulibs/utils/string.hpp | 2 ++ src/irc/irc_client.cpp | 17 ++++++++--------- tests/utils.cpp | 6 ++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/louloulibs/utils/string.cpp b/louloulibs/utils/string.cpp index 3977feb..7ed1aa3 100644 --- a/louloulibs/utils/string.cpp +++ b/louloulibs/utils/string.cpp @@ -4,3 +4,15 @@ bool to_bool(const std::string& val) { return (val == "1" || val == "true"); } + +std::vector cut(const std::string& val, const std::size_t size) +{ + std::vector res; + std::string::size_type pos = 0; + while (pos < val.size()) + { + res.emplace_back(val.substr(pos, size)); + pos += size; + } + return res; +} diff --git a/louloulibs/utils/string.hpp b/louloulibs/utils/string.hpp index 3775c36..1c8f001 100644 --- a/louloulibs/utils/string.hpp +++ b/louloulibs/utils/string.hpp @@ -1,8 +1,10 @@ #ifndef STRING_UTILS_HPP_INCLUDED #define STRING_UTILS_HPP_INCLUDED +#include #include bool to_bool(const std::string& val); +std::vector cut(const std::string& val, const std::size_t size); #endif /* STRING_UTILS_HPP_INCLUDED */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index e320db9..2cf0840 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -455,15 +456,13 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st log_warning("Cannot send message to channel ", chan_name, ", it is not joined"); return false; } - // Cut the message body into 400-bytes parts (because the whole command - // must fit into 512 bytes, that's an easy way to make sure the chan name - // + body fits. I’m lazy.) - std::string::size_type pos = 0; - while (pos < body.size()) - { - this->send_message(IrcMessage("PRIVMSG", {chan_name, body.substr(pos, 400)})); - pos += 400; - } + // Cut the message body into 512-bytes parts, because the whole command + // must fit into 512 bytes. + // Count the ':' at the start of the text, and two spaces + const auto line_size = 512 - ::strlen("PRIVMSG") - chan_name.length() - 3; + const auto lines = cut(body, line_size); + for (const auto& line: lines) + this->send_message(IrcMessage("PRIVMSG", {chan_name, line})); return true; } diff --git a/tests/utils.cpp b/tests/utils.cpp index 8691910..54e743f 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -88,3 +88,9 @@ TEST_CASE("empty if fixed irc server") } } + +TEST_CASE("string cut") +{ + CHECK(cut("coucou", 2).size() == 3); + CHECK(cut("bonjour les copains", 6).size() == 4); +} \ No newline at end of file -- cgit v1.2.3 From 6235fb2d0326b18a9e013ae13dfb1fd0577ffd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 00:38:43 +0200 Subject: Add get_next_codepoint_size --- louloulibs/utils/encoding.cpp | 28 ++++++++++++++++------------ louloulibs/utils/encoding.hpp | 5 +++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/louloulibs/utils/encoding.cpp b/louloulibs/utils/encoding.cpp index f738ce2..507f38a 100644 --- a/louloulibs/utils/encoding.cpp +++ b/louloulibs/utils/encoding.cpp @@ -23,6 +23,17 @@ namespace utils /** * Based on http://en.wikipedia.org/wiki/UTF-8#Description */ + std::size_t get_next_codepoint_size(const unsigned char c) + { + if ((c & 0b11111000) == 0b11110000) // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return 4; + else if ((c & 0b11110000) == 0b11100000) // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx + return 3; + else if ((c & 0b11100000) == 0b11000000) // 2 bytes: 110xxxxx 10xxxxxx + return 2; + return 1; // 1 byte: 0xxxxxxx + } + bool is_valid_utf8(const char* s) { if (!s) @@ -32,38 +43,31 @@ namespace utils while (*str) { - // 4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - if ((str[0] & 0b11111000) == 0b11110000) + const auto codepoint_size = get_next_codepoint_size(str[0]); + if (codepoint_size == 4) { if (!str[1] || !str[2] || !str[3] || ((str[1] & 0b11000000) != 0b10000000) || ((str[2] & 0b11000000) != 0b10000000) || ((str[3] & 0b11000000) != 0b10000000)) return false; - str += 4; } - // 3 bytes: 1110xxx 10xxxxxx 10xxxxxx - else if ((str[0] & 0b11110000) == 0b11100000) + else if (codepoint_size == 3) { if (!str[1] || !str[2] || ((str[1] & 0b11000000) != 0b10000000) || ((str[2] & 0b11000000) != 0b10000000)) return false; - str += 3; } - // 2 bytes: 110xxxxx 10xxxxxx - else if (((str[0]) & 0b11100000) == 0b11000000) + else if (codepoint_size == 2) { if (!str[1] || ((str[1] & 0b11000000) != 0b10000000)) return false; - str += 2; } - // 1 byte: 0xxxxxxx else if ((str[0] & 0b10000000) != 0) return false; - else - str++; + str += codepoint_size; } return true; } diff --git a/louloulibs/utils/encoding.hpp b/louloulibs/utils/encoding.hpp index 6b7ccd2..3f55055 100644 --- a/louloulibs/utils/encoding.hpp +++ b/louloulibs/utils/encoding.hpp @@ -5,6 +5,11 @@ namespace utils { + /** + * Return the size, in bytes, of the next UTF-8 codepoint, based on + * the given char. + */ + std::size_t get_next_codepoint_size(const unsigned char c); /** * Returns true if the given null-terminated string is valid utf-8. * -- cgit v1.2.3 From 4b1c580bb9bc03d656e59d702c72c3e793a1bbe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 12:19:19 +0200 Subject: cut messages at 512 bytes, taking into account the UTF-8 codepoints ref #3067 --- louloulibs/utils/string.cpp | 14 ++++++++++++-- src/irc/irc_client.cpp | 5 +++-- tests/utils.cpp | 8 +++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/louloulibs/utils/string.cpp b/louloulibs/utils/string.cpp index 7ed1aa3..2447f84 100644 --- a/louloulibs/utils/string.cpp +++ b/louloulibs/utils/string.cpp @@ -1,4 +1,5 @@ #include +#include bool to_bool(const std::string& val) { @@ -11,8 +12,17 @@ std::vector cut(const std::string& val, const std::size_t size) std::string::size_type pos = 0; while (pos < val.size()) { - res.emplace_back(val.substr(pos, size)); - pos += size; + // Get the number of chars, <= size, that contain only whole + // UTF-8 codepoints. + std::size_t s = 0; + auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + while (s + codepoint_size <= size) + { + s += codepoint_size; + codepoint_size = utils::get_next_codepoint_size(val[pos + s]); + } + res.emplace_back(val.substr(pos, s)); + pos += s; } return res; } diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 2cf0840..1d56361 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -388,6 +388,8 @@ void IrcClient::send_message(IrcMessage&& message) res += " " + arg; } res += "\r\n"; + log_debug("Effective size: ", res.size()); + log_debug(res); this->send_data(std::move(res)); } @@ -458,8 +460,7 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st } // Cut the message body into 512-bytes parts, because the whole command // must fit into 512 bytes. - // Count the ':' at the start of the text, and two spaces - const auto line_size = 512 - ::strlen("PRIVMSG") - chan_name.length() - 3; + const auto line_size = 500 - ::strlen("PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n"); const auto lines = cut(body, line_size); for (const auto& line: lines) this->send_message(IrcMessage("PRIVMSG", {chan_name, line})); diff --git a/tests/utils.cpp b/tests/utils.cpp index 54e743f..01d070e 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -93,4 +93,10 @@ TEST_CASE("string cut") { CHECK(cut("coucou", 2).size() == 3); CHECK(cut("bonjour les copains", 6).size() == 4); -} \ No newline at end of file + CHECK(cut("««««", 2).size() == 4); + CHECK(cut("a««««", 2).size() == 5); + const auto res = cut("rhello, ♥", 10); + CHECK(res.size() == 2); + CHECK(res[0] == "rhello, "); + CHECK(res[1] == "♥"); +} -- cgit v1.2.3 From 849c50f9f33d5a391f623272b007810c816aca68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 15:47:05 +0200 Subject: Save our own host, as reported by the server --- src/irc/irc_client.cpp | 13 +++++++++++++ src/irc/irc_client.hpp | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 1d56361..324f725 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -59,6 +59,7 @@ static const std::unordered_mapbridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author); } +void IrcClient::on_own_host_received(const IrcMessage& message) +{ + this->own_host = message.arguments[1]; + const std::string from = message.prefix; + if (message.arguments.size() >= 3) + this->bridge.send_xmpp_message(this->hostname, from, + this->own_host + " " + message.arguments[2]); + else + this->bridge.send_xmpp_message(this->hostname, from, this->own_host + + " is now your displayed host"); +} + void IrcClient::on_erroneous_nickname(const IrcMessage& message) { const std::string error_msg = message.arguments.size() >= 3 ? diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index f075ce6..718d8a7 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -217,6 +217,10 @@ public: * received etc), send the self presence and topic to the XMPP user. */ void on_channel_completely_joined(const IrcMessage& message); + /** + * Save our own host, as reported by the server + */ + void on_own_host_received(const IrcMessage& message); /** * We tried to set an invalid nickname */ @@ -283,6 +287,12 @@ private: * The hostname of the server we are connected to. */ const std::string hostname; + /** + * Our own host, as reported by the IRC server. + * By default (and if it is not overridden by the server), it is a + * meaningless string, with the maximum allowed size + */ + std::string own_host{63, '*'}; /** * The hostname of the user. This is used in the USER and the WEBIRC * commands, but only the one in WEBIRC will be used by the IRC server. -- cgit v1.2.3 From 430bf3a6610da0f44bdeb0ea37bdf9ceabb8ed01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 15:47:43 +0200 Subject: Properly calculate the maximum size of each message line, before cutting fix #3067 --- src/irc/irc_client.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 324f725..4492176 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -389,8 +389,6 @@ void IrcClient::send_message(IrcMessage&& message) res += " " + arg; } res += "\r\n"; - log_debug("Effective size: ", res.size()); - log_debug(res); this->send_data(std::move(res)); } @@ -459,9 +457,15 @@ bool IrcClient::send_channel_message(const std::string& chan_name, const std::st log_warning("Cannot send message to channel ", chan_name, ", it is not joined"); return false; } - // Cut the message body into 512-bytes parts, because the whole command - // must fit into 512 bytes. - const auto line_size = 500 - ::strlen("PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n"); + // The max size is 512, taking into account the whole message, not just + // the text we send. + // This includes our own nick, username and host (because this will be + // added by the server into our message), in addition to the basic + // components of the message we send (command name, chan name, \r\n et) + // : + NICK + ! + USER + @ + HOST + + PRIVMSG + + CHAN + + : + \r\n + const auto line_size = 512 - + this->current_nick.size() - this->username.size() - this->own_host.size() - + ::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n"); const auto lines = cut(body, line_size); for (const auto& line: lines) this->send_message(IrcMessage("PRIVMSG", {chan_name, line})); -- cgit v1.2.3 From c1f678e4503083e56d0bef0386657c083c0117ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 15 Jun 2016 16:47:11 +0200 Subject: Fix a missing include for strlen --- src/irc/irc_client.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 4492176..dd83307 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 350d48a5bf2412f5eee347fc832d9257b2ba3fbc Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Wed, 15 Jun 2016 21:54:22 +0100 Subject: Fix typo in bridge.hpp --- src/bridge/bridge.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 01f8f78..6feb282 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -54,7 +54,7 @@ public: const std::string& get_jid() const; std::string get_bare_jid() const; - static Xmpp::body make_xmpp_body(const std::string& str, const std::string& encodin = "ISO-8859-1"); + static Xmpp::body make_xmpp_body(const std::string& str, const std::string& encoding = "ISO-8859-1"); /*** ** ** From XMPP to IRC. -- cgit v1.2.3 From 0391f17f999618decffaf3c9261024ab04a33f63 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Tue, 21 Jun 2016 23:15:25 +0100 Subject: Add XEP-0106 support to the bridge This allows the user to join channels containing forbidden characters in the local part, like #r&d or #group/project. --- src/bridge/bridge.cpp | 17 +++++++++++------ src/irc/iid.cpp | 19 +++++++++++++++---- src/irc/iid.hpp | 1 + 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 3a7a147..87667db 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -685,7 +685,10 @@ void Bridge::send_user_join(const std::string& hostname, const std::string& chan std::string role; std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode); - this->xmpp.send_user_join(chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, + std::string encoded_chan_name(chan_name); + xep0106::encode(encoded_chan_name); + + this->xmpp.send_user_join(encoded_chan_name + utils::empty_if_fixed_server("%" + hostname), user->nick, user->host, affiliation, role, this->user_jid + "/" + resource, self); } @@ -701,8 +704,10 @@ void Bridge::send_topic(const std::string& hostname, const std::string& chan_nam const std::string& topic, const std::string& who, const std::string& resource) { - const auto encoding = in_encoding_for(*this, {chan_name + '%' + hostname}); - this->xmpp.send_topic(chan_name + utils::empty_if_fixed_server( + std::string encoded_chan_name(chan_name); + xep0106::encode(encoded_chan_name); + const auto encoding = in_encoding_for(*this, {encoded_chan_name + '%' + hostname}); + this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server( "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who); } @@ -884,13 +889,13 @@ void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::strin if (user->nick != self->nick) { log_debug(user->nick); - this->send_user_join(iid.get_server(), iid.get_local(), + this->send_user_join(iid.get_server(), iid.get_encoded_local(), user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()), false, resource); } } - this->send_user_join(iid.get_server(), iid.get_local(), + this->send_user_join(iid.get_server(), iid.get_encoded_local(), self, self->get_most_significant_mode(irc->get_sorted_user_modes()), true, resource); - this->send_topic(iid.get_server(), iid.get_local(), channel->topic, channel->topic_author, resource); + this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource); } diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index 66b66b7..0e2841e 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -3,6 +3,8 @@ #include +#include + Iid::Iid(const std::string& iid): is_channel(false), is_user(false) @@ -59,7 +61,9 @@ Iid::Iid(): void Iid::set_local(const std::string& loc) { - this->local = utils::tolower(loc); + std::string local(utils::tolower(loc)); + xep0106::decode(local); + this->local = local; } void Iid::set_server(const std::string& serv) @@ -72,6 +76,13 @@ const std::string& Iid::get_local() const return this->local; } +const std::string Iid::get_encoded_local() const +{ + std::string local(this->local); + xep0106::encode(local); + return local; +} + const std::string& Iid::get_server() const { return this->server; @@ -90,13 +101,13 @@ namespace std { const std::string to_string(const Iid& iid) { if (Config::get("fixed_irc_server", "").empty()) - return iid.get_local() + iid.get_sep() + iid.get_server(); + return iid.get_encoded_local() + iid.get_sep() + iid.get_server(); else { if (iid.get_sep() == "!") - return iid.get_local() + iid.get_sep(); + return iid.get_encoded_local() + iid.get_sep(); else - return iid.get_local(); + return iid.get_encoded_local(); } } } diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index 9747595..a55ae21 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -53,6 +53,7 @@ public: void set_local(const std::string& loc); void set_server(const std::string& serv); const std::string& get_local() const; + const std::string get_encoded_local() const; const std::string& get_server() const; bool is_channel; -- cgit v1.2.3 From 57263961b487bd839cbce5fe7547933240792fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 23 Jun 2016 22:14:32 +0200 Subject: Fix a use after free in IrcChannel::remove_user --- src/irc/irc_channel.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/irc/irc_channel.cpp b/src/irc/irc_channel.cpp index 9801513..e769245 100644 --- a/src/irc/irc_channel.cpp +++ b/src/irc/irc_channel.cpp @@ -37,11 +37,14 @@ IrcUser* IrcChannel::find_user(const std::string& name) const void IrcChannel::remove_user(const IrcUser* user) { - this->users.erase(std::remove_if(this->users.begin(), this->users.end(), - [user](const std::unique_ptr& u) - { - return user->nick == u->nick; - }), this->users.end()); + const auto nick = user->nick; + const auto it = std::find_if(this->users.begin(), this->users.end(), + [nick](const std::unique_ptr& u) + { + return nick == u->nick; + }); + if (it != this->users.end()) + this->users.erase(it); } void IrcChannel::remove_all_users() -- cgit v1.2.3 From 6bd9b1ec1429024a49cf8b6d7be29f90f35110fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 23 Jun 2016 22:18:42 +0200 Subject: Remove unused function --- src/bridge/bridge.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 87667db..4976ed2 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -16,17 +16,6 @@ using namespace std::string_literals; static const char* action_prefix = "\01ACTION "; -static std::string out_encoding_for(const Bridge& bridge, const Iid& iid) -{ -#ifdef USE_DATABASE - const auto jid = bridge.get_bare_jid(); - auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local()); - return options.encodingOut.value(); -#else - return {"ISO-8859-1"}; -#endif -} - static std::string in_encoding_for(const Bridge& bridge, const Iid& iid) { #ifdef USE_DATABASE -- cgit v1.2.3 From 9206f4723b8c5feb8a96daa9c283d30cf73290e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 23 Jun 2016 22:40:53 +0200 Subject: Fix a bug in cut() --- louloulibs/utils/string.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/utils/string.cpp b/louloulibs/utils/string.cpp index 2447f84..635e71a 100644 --- a/louloulibs/utils/string.cpp +++ b/louloulibs/utils/string.cpp @@ -16,7 +16,7 @@ std::vector cut(const std::string& val, const std::size_t size) // UTF-8 codepoints. std::size_t s = 0; auto codepoint_size = utils::get_next_codepoint_size(val[pos + s]); - while (s + codepoint_size <= size) + while (s + codepoint_size <= size && pos + s < val.size()) { s += codepoint_size; codepoint_size = utils::get_next_codepoint_size(val[pos + s]); -- cgit v1.2.3 From 4e959a3869c4a69b9d59de69694644c37380ff11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 23 Jun 2016 22:50:05 +0200 Subject: Add a simple e2e test that joins a channel with xep0106-encoded name --- tests/end_to_end/__main__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0e2ca64..7859202 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -686,7 +686,22 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}']"), ] - ) + ), + Scenario("encoded_channel_join", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #biboumi@louiz.org:80 [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + ]), + ) failures = 0 -- cgit v1.2.3 From b2e7edeea8bf08b6b7e75d60af3af0c30fdaa4f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:23:01 +0200 Subject: =?UTF-8?q?Properly=20set=20the=20=E2=80=9Cfrom=E2=80=9D=20of=20th?= =?UTF-8?q?e=20ping=20results=20to=20the=20correct=20full=20JID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/xmpp/xmpp_component.cpp | 10 ++++++++++ louloulibs/xmpp/xmpp_component.hpp | 2 ++ src/bridge/bridge.cpp | 3 +-- src/xmpp/biboumi_component.cpp | 4 ++-- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/louloulibs/xmpp/xmpp_component.cpp b/louloulibs/xmpp/xmpp_component.cpp index 1be7a06..e87cdf7 100644 --- a/louloulibs/xmpp/xmpp_component.cpp +++ b/louloulibs/xmpp/xmpp_component.cpp @@ -631,6 +631,16 @@ void XmppComponent::send_iq_version_request(const std::string& from, this->send_stanza(iq); } +void XmppComponent::send_iq_result_full_jid(const std::string& id, const std::string& to_jid, const std::string& from_full_jid) +{ + Stanza iq("iq"); + iq["from"] = from_full_jid; + iq["to"] = to_jid; + iq["id"] = id; + iq["type"] = "result"; + this->send_stanza(iq); +} + void XmppComponent::send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from_local_part) { Stanza iq("iq"); diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index 913e337..ae4d76b 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -200,6 +200,8 @@ public: * Send an empty iq of type result */ void send_iq_result(const std::string& id, const std::string& to_jid, const std::string& from); + void send_iq_result_full_jid(const std::string& id, const std::string& to_jid, + const std::string& from_full_jid); void handle_handshake(const Stanza& stanza); void handle_error(const Stanza& stanza); diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 4976ed2..eee4bd2 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -483,8 +483,7 @@ void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const s const std::string id = body.substr(6, body.size() - 7); if (id != iq_id) return false; - Jid jid(from_jid); - this->xmpp.send_iq_result(iq_id, to_jid, jid.local); + this->xmpp.send_iq_result_full_jid(iq_id, to_jid, from_jid); return true; } if (message.command == "401" && message.arguments[1] == nick) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 62e17d0..e4d4899 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -580,8 +580,8 @@ void BiboumiComponent::send_iq_version_request(const std::string& from, } void BiboumiComponent::send_ping_request(const std::string& from, - const std::string& jid_to, - const std::string& id) + const std::string& jid_to, + const std::string& id) { Stanza iq("iq"); iq["type"] = "get"; -- cgit v1.2.3 From 7d2a2dc8cc9d2d9bcd83fb1bd869c29322855fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:23:51 +0200 Subject: Forward ping requests from IRC to XMPP, to one single resource --- src/bridge/bridge.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index eee4bd2..6de2516 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -746,7 +746,10 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Use revstr because the forwarded ping to target XMPP user must not be // the same that the request iq, but we also need to get it back easily // (revstr again) - this->xmpp.send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid, utils::revstr(id)); + // Forward to the first resource (arbitrary, based on the “order” of the std::set) only + const auto resources = this->resources_in_server[hostname]; + if (resources.begin() != resources.end()) + this->xmpp.send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) -- cgit v1.2.3 From fcb2681b7b3f221160fac027731fc3688d73d59d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:24:40 +0200 Subject: Log a warning when we receive an iq without a from --- src/xmpp/biboumi_component.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index e4d4899..a6aac21 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -285,8 +285,10 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) std::string to_str = stanza.get_tag("to"); std::string type = stanza.get_tag("type"); - if (from.empty()) + if (from.empty()) { + log_warning("Received an iq without a 'from'. Ignoring."); return; + } if (id.empty() || to_str.empty() || type.empty()) { this->send_stanza_error("iq", from, this->served_hostname, id, -- cgit v1.2.3 From 7ae29890c3fd498d8d4fecd64cfc0da703b77a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:25:04 +0200 Subject: Add a e2e for self-ping, with a single channel resource --- tests/end_to_end/__main__.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7859202..ebd2a73 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -701,7 +701,32 @@ if __name__ == '__main__': ), partial(expect_stanza, "/message[@from='#biboumi\\40louiz.org\\3a80%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), ]), + Scenario("self_ping_on_real_channel", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + # Send a ping to ourself + partial(send_stanza, + ""), + # We receive our own ping request, + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}'][@id='gnip_tsrif']"), + # Respond to the request + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + ]), ) failures = 0 -- cgit v1.2.3 From 2fe8878169b094a12d74d69ea97314d172647ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 24 Jun 2016 11:43:13 +0200 Subject: Add a e2e test that checks self-ping with multiple resources behind one nick --- tests/end_to_end/__main__.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index ebd2a73..06aa1ce 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -726,6 +726,40 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_ping']"), + + # Now join the same room, from the same bare JID, behind the same nick + partial(send_stanza, + ""), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + partial(send_stanza, + ""), + # We receive our own ping request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_dnoces']", + after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), + + ## And re-do exactly the same thing, just change the resource initiating the self ping + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to][@id='gnip_driht']", + after = partial(save_value, "to", partial(extract_attribute, "/iq", "to"))), + partial(send_stanza, + ""), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), + ]), ) -- cgit v1.2.3 From 79002aa5c9ada3a09d5f09cde0cac4c456adaa47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 29 Jun 2016 19:52:43 +0200 Subject: Run e2e through with valgrind if BIBOUMI_E2E_VALGRIND is set in the env --- CMakeLists.txt | 2 ++ tests/end_to_end/__main__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dc3a707..7249aa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,6 +205,8 @@ add_custom_target(check COMMAND "test_suite" DEPENDS test_suite biboumi) add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) +add_custom_target(e2e_valgrind COMMAND "E2E_BIBOUMI_VALGRIND=1" "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" + DEPENDS biboumi) # diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 06aa1ce..e6ed911 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -782,7 +782,7 @@ if __name__ == '__main__': for scenario in scenarios: test = BiboumiTest(scenario) - if not test.run(False): + if not test.run(os.getenv("E2E_BIBOUMI_VALGRIND") is not None): print("You can check the files slixmpp_%s_output.txt and biboumi_%s_output.txt to help you debug." % (scenario.name, scenario.name)) failures += 1 -- cgit v1.2.3 From 8cef7303187297bc98d8a9ddceef4674a9297e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 2 Jul 2016 15:08:40 +0200 Subject: Add a valgrind suppression file --- CMakeLists.txt | 4 ++-- tests/end_to_end/__main__.py | 2 +- tests/end_to_end/biboumi.supp | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 tests/end_to_end/biboumi.supp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7249aa4..132e353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,8 +205,8 @@ add_custom_target(check COMMAND "test_suite" DEPENDS test_suite biboumi) add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) -add_custom_target(e2e_valgrind COMMAND "E2E_BIBOUMI_VALGRIND=1" "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" - DEPENDS biboumi) +add_custom_target(e2e_valgrind COMMAND "E2E_BIBOUMI_SUPP_DIR=${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" "E2E_BIBOUMI_VALGRIND=1" "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" + DEPENDS biboumi) # diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index e6ed911..0223e47 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -187,7 +187,7 @@ class BiboumiRunner(ProcessRunner): self.name = name self.fd = open("biboumi_%s_output.txt" % (name,), "w") if with_valgrind: - self.create = asyncio.create_subprocess_exec("valgrind", "--leak-check=full", "--show-leak-kinds=all", + self.create = asyncio.create_subprocess_exec("valgrind", "--suppressions=" + (os.environ.get("E2E_BIBOUMI_SUPP_DIR") or "") + "biboumi.supp", "--leak-check=full", "--show-leak-kinds=all", "--errors-for-leak-kinds=all", "--error-exitcode=16", "./biboumi", "test.conf", stdin=None, stdout=self.fd, stderr=self.fd, loop=None, limit=None) diff --git a/tests/end_to_end/biboumi.supp b/tests/end_to_end/biboumi.supp new file mode 100644 index 0000000..d153665 --- /dev/null +++ b/tests/end_to_end/biboumi.supp @@ -0,0 +1,10 @@ +{ + stdlibc++ thingy + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:call_init.part.0 + fun:_dl_init + ... +} -- cgit v1.2.3 From 29567eefcf797d1530630a2cc3121df3874c5fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 2 Jul 2016 15:09:38 +0200 Subject: Add https to the unit file DOC thing --- unit/biboumi.service.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit/biboumi.service.cmake b/unit/biboumi.service.cmake index 1cdf4e1..150045b 100644 --- a/unit/biboumi.service.cmake +++ b/unit/biboumi.service.cmake @@ -1,6 +1,6 @@ [Unit] Description=Biboumi, XMPP to IRC gateway -Documentation=man:biboumi(1) http://biboumi.louiz.org +Documentation=man:biboumi(1) https://biboumi.louiz.org After=network.target [Service] -- cgit v1.2.3 From dadfc349d96ca7d365d5d073e2f10f8f71f16bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 3 Jul 2016 14:36:13 +0200 Subject: Use latest git of botan in the dockerfiles --- docker/biboumi-test/debian/Dockerfile | 2 +- docker/biboumi-test/fedora/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/biboumi-test/debian/Dockerfile b/docker/biboumi-test/debian/Dockerfile index fd341c5..f2a26ea 100644 --- a/docker/biboumi-test/debian/Dockerfile +++ b/docker/biboumi-test/debian/Dockerfile @@ -27,7 +27,7 @@ RUN apt install -y lcov # Install botan RUN git clone https://github.com/randombit/botan.git -RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install +RUN cd botan && ./configure.py --prefix=/usr && make -j8 && make install RUN rm -rf /botan # Install litesql diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index 853c444..b199ed2 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -30,7 +30,7 @@ RUN dnf install -y rpmdevtools # Install botan RUN git clone https://github.com/randombit/botan.git -RUN cd botan && git checkout 1.11.28 && ./configure.py --prefix=/usr && make -j8 && make install +RUN cd botan && ./configure.py --prefix=/usr && make -j8 && make install RUN rm -rf /botan # Install litesql -- cgit v1.2.3 From 0ce75ab52111ba27ca99961057b36b68f0a135a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 3 Jul 2016 15:43:11 +0200 Subject: Properly remove the resource from the server when we leave the last channel --- src/bridge/bridge.cpp | 15 ++++++++++++++- src/bridge/bridge.hpp | 1 + tests/end_to_end/__main__.py | 46 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 6de2516..613e0e2 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -349,9 +349,11 @@ void Bridge::leave_irc_channel(Iid&& iid, std::string&& status_message, const st this->send_muc_leave(std::move(iid), std::move(nick), "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", true, resource); + if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) + this->remove_resource_from_server(iid.get_server(), resource); } } - } +} void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick) { @@ -868,6 +870,17 @@ std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channe return it->second.size(); } +std::size_t Bridge::number_of_channels_the_resource_is_in(const std::string& irc_hostname, const std::string& resource) const +{ + std::size_t res = 0; + for (auto pair: this->resources_in_chan) + { + if (std::get<0>(pair.first) == irc_hostname && pair.second.count(resource) != 0) + res++; + } + return res; +} + void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource) { IrcClient* irc = this->get_irc_client(iid.get_server()); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 6feb282..3430b06 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -278,6 +278,7 @@ private: void add_resource_to_server(const IrcHostname& irc_hostname, const std::string& resource); void remove_resource_from_server(const IrcHostname& irc_hostname, const std::string& resource); bool is_resource_in_server(const IrcHostname& irc_hostname, const std::string& resource) const; + size_t number_of_channels_the_resource_is_in(const std::string& irc_hostname, const std::string& resource) const; /** * Generate all the stanzas to be sent to this resource, simulating a join on this channel. diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 0223e47..d1d653d 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -229,7 +229,7 @@ class BiboumiTest: def __init__(self, scenario, expected_code=0): self.scenario = scenario - self.expected_code = 0 + self.expected_code = expected_code def run(self, with_valgrind=True): print("Running scenario: %s%s" % (self.scenario.name, " (with valgrind)" if with_valgrind else '')) @@ -355,6 +355,8 @@ def connection_end_sequence(irc_host, jid): xpath_re % (r'^%s: This server was created .*$' % irc_host)), partial(expect_stanza, xpath_re % (r'^%s: There are \d+ users and \d+ invisible on \d+ servers$' % irc_host)), + partial(expect_stanza, + xpath_re % (r'^%s: \d+ unknown connection\(s\)$' % irc_host), optional=True), partial(expect_stanza, xpath_re % (r'^%s: \d+ channels formed$' % irc_host), optional=True), partial(expect_stanza, @@ -582,7 +584,7 @@ if __name__ == '__main__': partial(send_stanza, "COUCOU"), partial(expect_stanza, "/iq[@type='result']/commands:command[@node='hello'][@status='completed']/commands:note[@type='info'][text()='Hello COUCOU!']") ]), - Scenario("simple_multisessionnick", + Scenario("multisessionnick", [ handshake_sequence(), partial(send_stanza, @@ -605,6 +607,46 @@ if __name__ == '__main__': "/presence/muc_user:x/muc_user:status[@code='110']") ), partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # A different user joins the same room + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']",)), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']",)), + + partial(expect_stanza, + "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # That second user sends a private message to the first one + partial(send_stanza, "RELLO"), + # Message is received with a server-wide JID, by the two resources behind nick_one + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='RELLO']"), + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_two}'][@type='chat']/body[text()='RELLO']"), + + # One resource leaves the server entirely. + partial(send_stanza, ""), + # The leave is forwarded only to us + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']", + "/presence/status[text()='Biboumi note: 1 resources are still in this channel.']") + ), + # The second user sends two new private messages to the first user + partial(send_stanza, "first"), + partial(send_stanza, "second"), + # The first user receives the two messages, on the connected resource, once each + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='first']"), + partial(expect_stanza, "/message[@from='{lower_nick_two}!{irc_server_one}'][@to='{jid_one}/{resource_one}'][@type='chat']/body[text()='second']"), + + ]), Scenario("channel_messages", [ -- cgit v1.2.3 From 964784497a7dd1278789f63322cb8acc8ed419ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 4 Jul 2016 10:20:16 +0200 Subject: Remove forgotten comment --- src/bridge/bridge.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 3430b06..f8eea94 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -202,11 +202,6 @@ public: void trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message); std::unordered_map>& get_irc_clients(); - /** - * Manage which resource is connected to which IRC server - */ - - private: /** * Returns the client for the given hostname, create one (and use the -- cgit v1.2.3 From 5321d29cbda7d69d306f36d0f84d2c599c85c90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 4 Jul 2016 17:00:05 +0200 Subject: List of channels are saved per-request and not globally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The list would keep the previous results in memory, forever, and the list would grow each time a new request was made (even with results from unrelated servers)… --- src/bridge/bridge.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 613e0e2..1ca611a 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -368,11 +368,11 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq irc->send_list_command(); - irc_responder_callback_t cb = [this, iid, iq_id, to_jid](const std::string& irc_hostname, - const IrcMessage& message) -> bool - { - static std::vector list; + std::vector list; + irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname, + const IrcMessage& message) mutable -> bool + { if (irc_hostname != iid.get_server()) return false; if (message.command == "263" || message.command == "RPL_TRYAGAIN" || -- cgit v1.2.3 From 81f8f45b371d1a0ef72c2768fbd1f9188fe83616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 4 Jul 2016 17:53:53 +0200 Subject: Replace all include guards by #pragma once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s $CURRENT_YEAR --- louloulibs/config/config.hpp | 6 +++--- louloulibs/logger/logger.hpp | 6 +++--- louloulibs/network/credentials_manager.hpp | 5 ++--- louloulibs/network/dns_handler.hpp | 4 +--- louloulibs/network/dns_socket_handler.hpp | 4 +--- louloulibs/network/poller.hpp | 6 +++--- louloulibs/network/resolver.hpp | 6 +++--- louloulibs/network/socket_handler.hpp | 4 +--- louloulibs/network/tcp_socket_handler.hpp | 6 +++--- louloulibs/utils/encoding.hpp | 6 +++--- louloulibs/utils/reload.hpp | 6 +++--- louloulibs/utils/revstr.hpp | 6 +++--- louloulibs/utils/scopeguard.hpp | 4 +--- louloulibs/utils/split.hpp | 6 +++--- louloulibs/utils/string.hpp | 6 +++--- louloulibs/utils/timed_events.hpp | 5 +---- louloulibs/utils/tolower.hpp | 6 +++--- louloulibs/utils/xdg.hpp | 6 +++--- louloulibs/xmpp/adhoc_command.hpp | 5 +---- louloulibs/xmpp/adhoc_commands_handler.hpp | 5 +---- louloulibs/xmpp/adhoc_session.hpp | 5 +---- louloulibs/xmpp/body.hpp | 6 +++--- louloulibs/xmpp/jid.hpp | 6 +++--- louloulibs/xmpp/roster.hpp | 6 +++--- louloulibs/xmpp/xmpp_component.hpp | 6 +++--- louloulibs/xmpp/xmpp_parser.hpp | 6 +++--- louloulibs/xmpp/xmpp_stanza.hpp | 6 +++--- src/bridge/bridge.hpp | 6 +++--- src/bridge/colors.hpp | 6 +++--- src/bridge/list_element.hpp | 6 +++--- src/database/database.hpp | 6 +++--- src/irc/iid.hpp | 6 +++--- src/irc/irc_channel.hpp | 6 +++--- src/irc/irc_client.hpp | 6 +++--- src/irc/irc_message.hpp | 6 +++--- src/irc/irc_user.hpp | 6 +++--- src/utils/empty_if_fixed_server.hpp | 6 +++--- src/xmpp/biboumi_adhoc_commands.hpp | 6 +++--- src/xmpp/biboumi_component.hpp | 6 +++--- tests/io_tester.hpp | 4 +--- 40 files changed, 101 insertions(+), 124 deletions(-) diff --git a/louloulibs/config/config.hpp b/louloulibs/config/config.hpp index b46768e..6728df8 100644 --- a/louloulibs/config/config.hpp +++ b/louloulibs/config/config.hpp @@ -13,8 +13,8 @@ * save the config into the file. */ -#ifndef CONFIG_INCLUDED -# define CONFIG_INCLUDED +#pragma once + #include #include @@ -91,4 +91,4 @@ private: }; -#endif // CONFIG_INCLUDED + diff --git a/louloulibs/logger/logger.hpp b/louloulibs/logger/logger.hpp index 8ff4dcd..0893c77 100644 --- a/louloulibs/logger/logger.hpp +++ b/louloulibs/logger/logger.hpp @@ -1,5 +1,5 @@ -#ifndef LOGGER_INCLUDED -# define LOGGER_INCLUDED +#pragma once + /** * Singleton used in logger macros to write into files or stdout, with @@ -123,4 +123,4 @@ namespace logging_details #define log_debug(...) logging_details::log_debug(WHERE, __VA_ARGS__) -#endif // LOGGER_INCLUDED + diff --git a/louloulibs/network/credentials_manager.hpp b/louloulibs/network/credentials_manager.hpp index c0d23ae..0fc4b89 100644 --- a/louloulibs/network/credentials_manager.hpp +++ b/louloulibs/network/credentials_manager.hpp @@ -1,5 +1,4 @@ -#ifndef BIBOUMI_CREDENTIALS_MANAGER_HPP -#define BIBOUMI_CREDENTIALS_MANAGER_HPP +#pragma once #include "louloulibs.h" @@ -37,4 +36,4 @@ private: }; #endif //BOTAN_FOUND -#endif //BIBOUMI_CREDENTIALS_MANAGER_HPP + diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index d2b48d2..e0feb11 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -1,5 +1,4 @@ -#ifndef DNS_HANDLER_HPP_INCLUDED -#define DNS_HANDLER_HPP_INCLUDED +#pragma once #include #ifdef CARES_FOUND @@ -56,4 +55,3 @@ private: }; #endif /* CARES_FOUND */ -#endif /* DNS_HANDLER_HPP_INCLUDED */ diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index 5ea9846..dba2f26 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -1,5 +1,4 @@ -#ifndef DNS_SOCKET_HANDLER_HPP -# define DNS_SOCKET_HANDLER_HPP +#pragma once #include #ifdef CARES_FOUND @@ -44,4 +43,3 @@ private: }; #endif // CARES_FOUND -#endif // DNS_SOCKET_HANDLER_HPP diff --git a/louloulibs/network/poller.hpp b/louloulibs/network/poller.hpp index 4bf7432..fc1a1a1 100644 --- a/louloulibs/network/poller.hpp +++ b/louloulibs/network/poller.hpp @@ -1,5 +1,5 @@ -#ifndef POLLER_INCLUDED -# define POLLER_INCLUDED +#pragma once + #include @@ -91,4 +91,4 @@ private: #endif }; -#endif // POLLER_INCLUDED + diff --git a/louloulibs/network/resolver.hpp b/louloulibs/network/resolver.hpp index fd57e25..afe6e2b 100644 --- a/louloulibs/network/resolver.hpp +++ b/louloulibs/network/resolver.hpp @@ -1,5 +1,5 @@ -#ifndef RESOLVER_HPP_INCLUDED -#define RESOLVER_HPP_INCLUDED +#pragma once + #include "louloulibs.h" @@ -125,4 +125,4 @@ private: std::string addr_to_string(const struct addrinfo* rp); -#endif /* RESOLVER_HPP_INCLUDED */ + diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index feabcf1..eeb41fe 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -1,5 +1,4 @@ -#ifndef SOCKET_HANDLER_HPP -# define SOCKET_HANDLER_HPP +#pragma once #include #include @@ -41,4 +40,3 @@ protected: socket_t socket; }; -#endif // SOCKET_HANDLER_HPP diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp index 91fa7a8..b0ba493 100644 --- a/louloulibs/network/tcp_socket_handler.hpp +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -1,5 +1,5 @@ -#ifndef SOCKET_HANDLER_INCLUDED -# define SOCKET_HANDLER_INCLUDED +#pragma once + #include "louloulibs.h" @@ -270,5 +270,5 @@ private: #endif // BOTAN_FOUND }; -#endif // SOCKET_HANDLER_INCLUDED + diff --git a/louloulibs/utils/encoding.hpp b/louloulibs/utils/encoding.hpp index 3f55055..586edd8 100644 --- a/louloulibs/utils/encoding.hpp +++ b/louloulibs/utils/encoding.hpp @@ -1,5 +1,5 @@ -#ifndef ENCODING_INCLUDED -# define ENCODING_INCLUDED +#pragma once + #include @@ -40,4 +40,4 @@ namespace xep0106 void encode(std::string&); } -#endif // ENCODING_INCLUDED + diff --git a/louloulibs/utils/reload.hpp b/louloulibs/utils/reload.hpp index 16d64f7..6a56acd 100644 --- a/louloulibs/utils/reload.hpp +++ b/louloulibs/utils/reload.hpp @@ -1,5 +1,5 @@ -#ifndef RELOAD_HPP_INCLUDED -#define RELOAD_HPP_INCLUDED +#pragma once + /** * Reload the server's configuration, and close the logger (so that it @@ -7,4 +7,4 @@ */ void reload_process(); -#endif /* RELOAD_HPP_INCLUDED */ + diff --git a/louloulibs/utils/revstr.hpp b/louloulibs/utils/revstr.hpp index 27c9e3e..8e521ea 100644 --- a/louloulibs/utils/revstr.hpp +++ b/louloulibs/utils/revstr.hpp @@ -1,5 +1,5 @@ -#ifndef REVSTR_HPP_INCLUDED -# define REVSTR_HPP_INCLUDED +#pragma once + #include @@ -8,4 +8,4 @@ namespace utils std::string revstr(const std::string& original); } -#endif // REVSTR_HPP_INCLUDED + diff --git a/louloulibs/utils/scopeguard.hpp b/louloulibs/utils/scopeguard.hpp index fed40ff..ee1e2ef 100644 --- a/louloulibs/utils/scopeguard.hpp +++ b/louloulibs/utils/scopeguard.hpp @@ -1,5 +1,4 @@ -#ifndef SCOPEGUARD_HPP -#define SCOPEGUARD_HPP +#pragma once #include #include @@ -88,4 +87,3 @@ private: } -#endif diff --git a/louloulibs/utils/split.hpp b/louloulibs/utils/split.hpp index 6b487a9..3755ef8 100644 --- a/louloulibs/utils/split.hpp +++ b/louloulibs/utils/split.hpp @@ -1,5 +1,5 @@ -#ifndef SPLIT_INCLUDED -# define SPLIT_INCLUDED +#pragma once + #include #include @@ -9,4 +9,4 @@ namespace utils std::vector split(const std::string &s, const char delim, const bool allow_empty=true); } -#endif // SPLIT_INCLUDED + diff --git a/louloulibs/utils/string.hpp b/louloulibs/utils/string.hpp index 1c8f001..84ba101 100644 --- a/louloulibs/utils/string.hpp +++ b/louloulibs/utils/string.hpp @@ -1,5 +1,5 @@ -#ifndef STRING_UTILS_HPP_INCLUDED -#define STRING_UTILS_HPP_INCLUDED +#pragma once + #include #include @@ -7,4 +7,4 @@ bool to_bool(const std::string& val); std::vector cut(const std::string& val, const std::size_t size); -#endif /* STRING_UTILS_HPP_INCLUDED */ + diff --git a/louloulibs/utils/timed_events.hpp b/louloulibs/utils/timed_events.hpp index 70e2eff..6e28206 100644 --- a/louloulibs/utils/timed_events.hpp +++ b/louloulibs/utils/timed_events.hpp @@ -1,5 +1,4 @@ -#ifndef TIMED_EVENTS_HPP -# define TIMED_EVENTS_HPP +#pragma once #include #include @@ -131,5 +130,3 @@ private: std::vector events; explicit TimedEventsManager() = default; }; - -#endif // TIMED_EVENTS_HPP diff --git a/louloulibs/utils/tolower.hpp b/louloulibs/utils/tolower.hpp index 0019182..650e05d 100644 --- a/louloulibs/utils/tolower.hpp +++ b/louloulibs/utils/tolower.hpp @@ -1,5 +1,5 @@ -#ifndef TOLOWER_INCLUDED -# define TOLOWER_INCLUDED +#pragma once + #include @@ -8,4 +8,4 @@ namespace utils std::string tolower(const std::string& original); } -#endif // SPLIT_INCLUDED + diff --git a/louloulibs/utils/xdg.hpp b/louloulibs/utils/xdg.hpp index 15f3d0b..56e11da 100644 --- a/louloulibs/utils/xdg.hpp +++ b/louloulibs/utils/xdg.hpp @@ -1,5 +1,5 @@ -#ifndef XDG_HPP_INCLUDED -#define XDG_HPP_INCLUDED +#pragma once + #include @@ -11,4 +11,4 @@ std::string xdg_config_path(const std::string& filename); std::string xdg_data_path(const std::string& filename); -#endif /* XDG_HPP_INCLUDED */ + diff --git a/louloulibs/xmpp/adhoc_command.hpp b/louloulibs/xmpp/adhoc_command.hpp index 1c4e4de..7c4de47 100644 --- a/louloulibs/xmpp/adhoc_command.hpp +++ b/louloulibs/xmpp/adhoc_command.hpp @@ -1,5 +1,4 @@ -#ifndef ADHOC_COMMAND_HPP -# define ADHOC_COMMAND_HPP +#pragma once /** * Describe an ad-hoc command. @@ -43,5 +42,3 @@ void PingStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void HelloStep1(XmppComponent&, AdhocSession& session, XmlNode& command_node); void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void Reload(XmppComponent&, AdhocSession& session, XmlNode& command_node); - -#endif // ADHOC_COMMAND_HPP diff --git a/louloulibs/xmpp/adhoc_commands_handler.hpp b/louloulibs/xmpp/adhoc_commands_handler.hpp index 65b094d..91eb5bd 100644 --- a/louloulibs/xmpp/adhoc_commands_handler.hpp +++ b/louloulibs/xmpp/adhoc_commands_handler.hpp @@ -1,5 +1,4 @@ -#ifndef ADHOC_COMMANDS_HANDLER_HPP -# define ADHOC_COMMANDS_HANDLER_HPP +#pragma once /** * Manage a list of available AdhocCommands and the list of ongoing @@ -70,5 +69,3 @@ private: */ std::map, AdhocSession> sessions; }; - -#endif // ADHOC_COMMANDS_HANDLER_HPP diff --git a/louloulibs/xmpp/adhoc_session.hpp b/louloulibs/xmpp/adhoc_session.hpp index 0966c4e..0de8d13 100644 --- a/louloulibs/xmpp/adhoc_session.hpp +++ b/louloulibs/xmpp/adhoc_session.hpp @@ -1,5 +1,4 @@ -#ifndef ADHOC_SESSION_HPP -# define ADHOC_SESSION_HPP +#pragma once #include @@ -87,5 +86,3 @@ public: */ std::map vars; }; - -#endif // ADHOC_SESSION_HPP diff --git a/louloulibs/xmpp/body.hpp b/louloulibs/xmpp/body.hpp index df86feb..068d1a4 100644 --- a/louloulibs/xmpp/body.hpp +++ b/louloulibs/xmpp/body.hpp @@ -1,5 +1,5 @@ -#ifndef XMPP_BODY_HPP_INCLUDED -#define XMPP_BODY_HPP_INCLUDED +#pragma once + namespace Xmpp { @@ -9,4 +9,4 @@ namespace Xmpp using body = std::tuple>; } -#endif /* XMPP_BODY_HPP_INCLUDED */ + diff --git a/louloulibs/xmpp/jid.hpp b/louloulibs/xmpp/jid.hpp index 5ff5a4f..08327ef 100644 --- a/louloulibs/xmpp/jid.hpp +++ b/louloulibs/xmpp/jid.hpp @@ -1,5 +1,5 @@ -#ifndef JID_INCLUDED -# define JID_INCLUDED +#pragma once + #include @@ -41,4 +41,4 @@ public: */ std::string jidprep(const std::string& original); -#endif // JID_INCLUDED + diff --git a/louloulibs/xmpp/roster.hpp b/louloulibs/xmpp/roster.hpp index 0aebca5..aa1b449 100644 --- a/louloulibs/xmpp/roster.hpp +++ b/louloulibs/xmpp/roster.hpp @@ -1,5 +1,5 @@ -#ifndef ROSTER_HPP_INCLUDED -#define ROSTER_HPP_INCLUDED +#pragma once + #include #include @@ -68,4 +68,4 @@ private: Roster& operator=(Roster&&) = delete; }; -#endif /* ROSTER_HPP_INCLUDED */ + diff --git a/louloulibs/xmpp/xmpp_component.hpp b/louloulibs/xmpp/xmpp_component.hpp index ae4d76b..5fc6d2e 100644 --- a/louloulibs/xmpp/xmpp_component.hpp +++ b/louloulibs/xmpp/xmpp_component.hpp @@ -1,5 +1,5 @@ -#ifndef XMPP_COMPONENT_INCLUDED -# define XMPP_COMPONENT_INCLUDED +#pragma once + #include #include @@ -240,4 +240,4 @@ protected: AdhocCommandsHandler adhoc_commands_handler; }; -#endif // XMPP_COMPONENT_INCLUDED + diff --git a/louloulibs/xmpp/xmpp_parser.hpp b/louloulibs/xmpp/xmpp_parser.hpp index 93c6c53..9d67228 100644 --- a/louloulibs/xmpp/xmpp_parser.hpp +++ b/louloulibs/xmpp/xmpp_parser.hpp @@ -1,5 +1,5 @@ -#ifndef XMPP_PARSER_INCLUDED -# define XMPP_PARSER_INCLUDED +#pragma once + #include @@ -130,4 +130,4 @@ private: std::vector> stream_close_callbacks; }; -#endif // XMPP_PARSER_INCLUDED + diff --git a/louloulibs/xmpp/xmpp_stanza.hpp b/louloulibs/xmpp/xmpp_stanza.hpp index 28b8414..4ca758e 100644 --- a/louloulibs/xmpp/xmpp_stanza.hpp +++ b/louloulibs/xmpp/xmpp_stanza.hpp @@ -1,5 +1,5 @@ -#ifndef XMPP_STANZA_INCLUDED -# define XMPP_STANZA_INCLUDED +#pragma once + #include #include @@ -143,4 +143,4 @@ std::ostream& operator<<(std::ostream& os, const XmlNode& node); */ using Stanza = XmlNode; -#endif // XMPP_STANZA_INCLUDED + diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index f8eea94..69b7bd5 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -1,5 +1,5 @@ -#ifndef BRIDGE_INCLUDED -# define BRIDGE_INCLUDED +#pragma once + #include #include @@ -290,4 +290,4 @@ struct IRCNotConnected: public std::exception const std::string hostname; }; -#endif // BRIDGE_INCLUDED + diff --git a/src/bridge/colors.hpp b/src/bridge/colors.hpp index 2ba80ee..e2c8a87 100644 --- a/src/bridge/colors.hpp +++ b/src/bridge/colors.hpp @@ -1,5 +1,5 @@ -#ifndef COLORS_INCLUDED -# define COLORS_INCLUDED +#pragma once + /** * A module handling the conversion between IRC colors and XHTML-IM, and @@ -53,4 +53,4 @@ static const char irc_format_char[] = { */ Xmpp::body irc_format_to_xhtmlim(const std::string& str); -#endif // COLORS_INCLUDED + diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp index bd28185..1eff2ee 100644 --- a/src/bridge/list_element.hpp +++ b/src/bridge/list_element.hpp @@ -1,5 +1,5 @@ -#ifndef LIST_ELEMENT_HPP_INCLUDED -#define LIST_ELEMENT_HPP_INCLUDED +#pragma once + #include @@ -16,4 +16,4 @@ struct ListElement std::string topic; }; -#endif /* LIST_ELEMENT_HPP_INCLUDED */ + diff --git a/src/database/database.hpp b/src/database/database.hpp index 7bd41a3..0131669 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -1,5 +1,5 @@ -#ifndef DATABASE_HPP_INCLUDED -#define DATABASE_HPP_INCLUDED +#pragma once + #include #ifdef USE_DATABASE @@ -50,4 +50,4 @@ private: }; #endif /* USE_DATABASE */ -#endif /* DATABASE_HPP_INCLUDED */ + diff --git a/src/irc/iid.hpp b/src/irc/iid.hpp index a55ae21..3b11470 100644 --- a/src/irc/iid.hpp +++ b/src/irc/iid.hpp @@ -1,5 +1,5 @@ -#ifndef IID_INCLUDED -# define IID_INCLUDED +#pragma once + #include @@ -76,4 +76,4 @@ namespace std { const std::string to_string(const Iid& iid); } -#endif // IID_INCLUDED + diff --git a/src/irc/irc_channel.hpp b/src/irc/irc_channel.hpp index 3319505..2bcefaf 100644 --- a/src/irc/irc_channel.hpp +++ b/src/irc/irc_channel.hpp @@ -1,5 +1,5 @@ -#ifndef IRC_CHANNEL_INCLUDED -# define IRC_CHANNEL_INCLUDED +#pragma once + #include #include @@ -67,4 +67,4 @@ public: bool joining; }; -#endif // IRC_CHANNEL_INCLUDED + diff --git a/src/irc/irc_client.hpp b/src/irc/irc_client.hpp index 718d8a7..fc3918e 100644 --- a/src/irc/irc_client.hpp +++ b/src/irc/irc_client.hpp @@ -1,5 +1,5 @@ -#ifndef IRC_CLIENT_INCLUDED -# define IRC_CLIENT_INCLUDED +#pragma once + #include #include @@ -380,4 +380,4 @@ private: Resolver dns_resolver; }; -#endif // IRC_CLIENT_INCLUDED + diff --git a/src/irc/irc_message.hpp b/src/irc/irc_message.hpp index 29ed946..fe954e4 100644 --- a/src/irc/irc_message.hpp +++ b/src/irc/irc_message.hpp @@ -1,5 +1,5 @@ -#ifndef IRC_MESSAGE_INCLUDED -# define IRC_MESSAGE_INCLUDED +#pragma once + #include #include @@ -25,4 +25,4 @@ public: std::ostream& operator<<(std::ostream& os, const IrcMessage& message); -#endif // IRC_MESSAGE_INCLUDED + diff --git a/src/irc/irc_user.hpp b/src/irc/irc_user.hpp index 4b6cd7a..c84030e 100644 --- a/src/irc/irc_user.hpp +++ b/src/irc/irc_user.hpp @@ -1,5 +1,5 @@ -#ifndef IRC_USER_INCLUDED -# define IRC_USER_INCLUDED +#pragma once + #include #include @@ -30,4 +30,4 @@ public: std::set modes; }; -#endif // IRC_USER_INCLUDED + diff --git a/src/utils/empty_if_fixed_server.hpp b/src/utils/empty_if_fixed_server.hpp index 8739fd9..9ccf5fd 100644 --- a/src/utils/empty_if_fixed_server.hpp +++ b/src/utils/empty_if_fixed_server.hpp @@ -1,5 +1,5 @@ -#ifndef EMPTY_IF_FIXED_SERVER_HPP_INCLUDED -#define EMPTY_IF_FIXED_SERVER_HPP_INCLUDED +#pragma once + #include @@ -23,4 +23,4 @@ namespace utils } -#endif /* EMPTY_IF_FIXED_SERVER_HPP_INCLUDED */ + diff --git a/src/xmpp/biboumi_adhoc_commands.hpp b/src/xmpp/biboumi_adhoc_commands.hpp index ffa8be4..2763a9f 100644 --- a/src/xmpp/biboumi_adhoc_commands.hpp +++ b/src/xmpp/biboumi_adhoc_commands.hpp @@ -1,5 +1,5 @@ -#ifndef BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED -#define BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED +#pragma once + #include #include @@ -20,4 +20,4 @@ void DisconnectUserFromServerStep1(XmppComponent&, AdhocSession& session, XmlNod void DisconnectUserFromServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node); void DisconnectUserFromServerStep3(XmppComponent&, AdhocSession& session, XmlNode& command_node); -#endif /* BIBOUMI_ADHOC_COMMANDS_HPP_INCLUDED */ + diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 0eb3bc4..24d768a 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -1,5 +1,5 @@ -#ifndef BIBOUMI_COMPONENT_INCLUDED -# define BIBOUMI_COMPONENT_INCLUDED +#pragma once + #include @@ -106,4 +106,4 @@ private: AdhocCommandsHandler irc_channel_adhoc_commands_handler; }; -#endif // BIBOUMI_COMPONENT_INCLUDED + diff --git a/tests/io_tester.hpp b/tests/io_tester.hpp index 8afa6f6..b9cdaa7 100644 --- a/tests/io_tester.hpp +++ b/tests/io_tester.hpp @@ -1,5 +1,4 @@ -#ifndef BIBOUMI_IO_TESTER_HPP -#define BIBOUMI_IO_TESTER_HPP +#pragma once #include #include @@ -44,4 +43,3 @@ private: std::streambuf* const old_buf; }; -#endif //BIBOUMI_IO_TESTER_HPP -- cgit v1.2.3 From 03feb403f8fc702481f4e7a0ec0264aa2912ae51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 3 Jul 2016 16:06:52 +0200 Subject: Send the iq requests to one random resource instead of the bare JID --- src/bridge/bridge.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 1ca611a..edf1700 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -739,7 +739,9 @@ void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& tar void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname) { - this->xmpp.send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid); + const auto resources = this->resources_in_server[hostname]; + if (resources.begin() != resources.end()) + this->xmpp.send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, -- cgit v1.2.3 From 4c1b9abe7e230a39b119bdc45ebcd5e677fad488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 00:31:57 +0200 Subject: Properly catch and handle database errors Do not use a singleton for the database. fix #3203 --- louloulibs/utils/reload.cpp | 19 ++++++++++++++++++- louloulibs/utils/reload.hpp | 8 +------- src/database/database.cpp | 33 +++++++++++++++------------------ src/database/database.hpp | 6 +++--- src/main.cpp | 19 +++++++++---------- tests/database.cpp | 2 +- 6 files changed, 47 insertions(+), 40 deletions(-) diff --git a/louloulibs/utils/reload.cpp b/louloulibs/utils/reload.cpp index bddfd86..7125a75 100644 --- a/louloulibs/utils/reload.cpp +++ b/louloulibs/utils/reload.cpp @@ -1,11 +1,28 @@ +#include +#include #include +#include #include +void open_database() +{ + const auto db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite")); + log_info("Opening database: ", db_filename); + Database::open(db_filename); + log_info("database successfully opened."); +} + void reload_process() { Config::read_conf(); // Destroy the logger instance, to be recreated the next time a log // line needs to be written Logger::instance().reset(); - log_debug("Configuration and logger reloaded."); + log_info("Configuration and logger reloaded."); + try { + open_database(); + } catch (const litesql::DatabaseError&) { + log_warning("Re-using the previous database."); + } } + diff --git a/louloulibs/utils/reload.hpp b/louloulibs/utils/reload.hpp index 6a56acd..408426a 100644 --- a/louloulibs/utils/reload.hpp +++ b/louloulibs/utils/reload.hpp @@ -1,10 +1,4 @@ #pragma once - -/** - * Reload the server's configuration, and close the logger (so that it - * closes its files etc, to take into account the new configuration) - */ +void open_database(); void reload_process(); - - diff --git a/src/database/database.cpp b/src/database/database.cpp index 0c7f425..61e1b47 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -2,8 +2,6 @@ #ifdef USE_DATABASE #include -#include -#include #include #include @@ -11,37 +9,36 @@ using namespace std::string_literals; std::unique_ptr Database::db; -db::BibouDB& Database::get_db() +void Database::open(const std::string& filename, const std::string& db_type) { - if (!Database::db) + try { - const std::string db_filename = Config::get("db_name", - xdg_data_path("biboumi.sqlite")); - Database::db = std::make_unique("sqlite3", - "database="s + db_filename); + auto new_db = std::make_unique(db_type, + "database="s + filename); + if (new_db->needsUpgrade()) + new_db->upgrade(); + Database::db.reset(new_db.release()); + } catch (const litesql::DatabaseError& e) { + log_error("Failed to open database ", filename, ". ", e.what()); + throw; } - - if (Database::db->needsUpgrade()) - Database::db->upgrade(); - - return *Database::db.get(); } void Database::set_verbose(const bool val) { - Database::get_db().verbose = val; + Database::db->verbose = val; } db::IrcServerOptions Database::get_irc_server_options(const std::string& owner, const std::string& server) { try { - auto options = litesql::select(Database::get_db(), + auto options = litesql::select(*Database::db, db::IrcServerOptions::Owner == owner && db::IrcServerOptions::Server == server).one(); return options; } catch (const litesql::NotFound& e) { - db::IrcServerOptions options(Database::get_db()); + db::IrcServerOptions options(*Database::db); options.owner = owner; options.server = server; // options.update(); @@ -54,13 +51,13 @@ db::IrcChannelOptions Database::get_irc_channel_options(const std::string& owner const std::string& channel) { try { - auto options = litesql::select(Database::get_db(), + auto options = litesql::select(*Database::db, db::IrcChannelOptions::Owner == owner && db::IrcChannelOptions::Server == server && db::IrcChannelOptions::Channel == channel).one(); return options; } catch (const litesql::NotFound& e) { - db::IrcChannelOptions options(Database::get_db()); + db::IrcChannelOptions options(*Database::db); options.owner = owner; options.server = server; options.channel = channel; diff --git a/src/database/database.hpp b/src/database/database.hpp index 0131669..7173bcd 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -26,7 +26,7 @@ public: template static size_t count() { - return litesql::select(Database::get_db()).count(); + return litesql::select(*Database::db).count(); } /** * Return the object from the db. Create it beforehand (with all default @@ -42,11 +42,11 @@ public: const std::string& channel); static void close(); + static void open(const std::string& filename, const std::string& db_type="sqlite3"); + private: static std::unique_ptr db; - - static db::BibouDB& get_db(); }; #endif /* USE_DATABASE */ diff --git a/src/main.cpp b/src/main.cpp index ed05d36..422219a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,22 +3,15 @@ #include #include #include -#include #include - -#include -#include -#include - -#include +#include #ifdef CARES_FOUND # include #endif -#ifdef SYSTEMD_FOUND -# include -#endif +#include +#include // A flag set by the SIGINT signal handler. static volatile std::atomic stop(false); @@ -71,6 +64,12 @@ int main(int ac, char** av) if (hostname.empty()) return config_help("hostname"); + try { + open_database(); + } catch (...) { + return 1; + } + // Block the signals we want to manage. They will be unblocked only during // the epoll_pwait or ppoll calls. This avoids some race conditions, // explained in man 2 pselect on linux diff --git a/tests/database.cpp b/tests/database.cpp index b059d0d..4e2be14 100644 --- a/tests/database.cpp +++ b/tests/database.cpp @@ -7,7 +7,7 @@ TEST_CASE("Database") { #ifdef USE_DATABASE - Config::set("db_name", ":memory:"); + Database::open(":memory:"); Database::set_verbose(false); SECTION("Basic retrieve and update") -- cgit v1.2.3 From 9fb2e116c47a9d4e2866d34450d12dcb90d4a26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 01:15:34 +0200 Subject: Move reload.*pp from louloulibs to src --- CMakeLists.txt | 7 +++++++ louloulibs/utils/reload.cpp | 28 ---------------------------- louloulibs/utils/reload.hpp | 4 ---- src/utils/reload.cpp | 28 ++++++++++++++++++++++++++++ src/utils/reload.hpp | 4 ++++ 5 files changed, 39 insertions(+), 32 deletions(-) delete mode 100644 louloulibs/utils/reload.cpp delete mode 100644 louloulibs/utils/reload.hpp create mode 100644 src/utils/reload.cpp create mode 100644 src/utils/reload.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 132e353..06733bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,13 @@ if(CARES_FOUND) include_directories(${CARES_INCLUDE_DIRS}) endif() +# +## utils +# +file(GLOB source_utils + src/utils/*.[hc]pp) +target_sources(utils PUBLIC ${source_utils}) + # ## irclib # diff --git a/louloulibs/utils/reload.cpp b/louloulibs/utils/reload.cpp deleted file mode 100644 index 7125a75..0000000 --- a/louloulibs/utils/reload.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include -#include -#include - -void open_database() -{ - const auto db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite")); - log_info("Opening database: ", db_filename); - Database::open(db_filename); - log_info("database successfully opened."); -} - -void reload_process() -{ - Config::read_conf(); - // Destroy the logger instance, to be recreated the next time a log - // line needs to be written - Logger::instance().reset(); - log_info("Configuration and logger reloaded."); - try { - open_database(); - } catch (const litesql::DatabaseError&) { - log_warning("Re-using the previous database."); - } -} - diff --git a/louloulibs/utils/reload.hpp b/louloulibs/utils/reload.hpp deleted file mode 100644 index 408426a..0000000 --- a/louloulibs/utils/reload.hpp +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -void open_database(); -void reload_process(); diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp new file mode 100644 index 0000000..7125a75 --- /dev/null +++ b/src/utils/reload.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include + +void open_database() +{ + const auto db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite")); + log_info("Opening database: ", db_filename); + Database::open(db_filename); + log_info("database successfully opened."); +} + +void reload_process() +{ + Config::read_conf(); + // Destroy the logger instance, to be recreated the next time a log + // line needs to be written + Logger::instance().reset(); + log_info("Configuration and logger reloaded."); + try { + open_database(); + } catch (const litesql::DatabaseError&) { + log_warning("Re-using the previous database."); + } +} + diff --git a/src/utils/reload.hpp b/src/utils/reload.hpp new file mode 100644 index 0000000..408426a --- /dev/null +++ b/src/utils/reload.hpp @@ -0,0 +1,4 @@ +#pragma once + +void open_database(); +void reload_process(); -- cgit v1.2.3 From 5328d0806fdc5becb9344b4d4320787a2b7c0712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 7 Jul 2016 09:27:22 +0200 Subject: =?UTF-8?q?Don=E2=80=99t=20use=20unique=5Fptr=20to=20store=20dns?= =?UTF-8?q?=20socket=20handlers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- louloulibs/network/dns_handler.cpp | 14 +++++++------- louloulibs/network/dns_handler.hpp | 2 +- louloulibs/network/dns_socket_handler.hpp | 6 +++--- louloulibs/network/socket_handler.hpp | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index 5e19f8c..24c4bcf 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -68,7 +68,7 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), [&readers](const auto& dns_socket) { - return !FD_ISSET(dns_socket->get_socket(), &readers); + return !FD_ISSET(dns_socket.get_socket(), &readers); }), this->socket_handlers.end()); @@ -81,8 +81,8 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) this->socket_handlers.end(), [i](const auto& socket_handler) { - return i == socket_handler->get_socket(); - }); + return i == socket_handler.get_socket(); + }); if (!read && !write) // No need to read or write to it { // If found, erase it and stop watching it because it is not // needed anymore @@ -95,12 +95,12 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) if (it == this->socket_handlers.end()) { this->socket_handlers.emplace(this->socket_handlers.begin(), - std::make_unique(poller, i)); + poller, i); it = this->socket_handlers.begin(); } - poller->add_socket_handler(it->get()); + poller->add_socket_handler(&*it); if (write) - poller->watch_send_events(it->get()); + poller->watch_send_events(&*it); } } // Cancel previous timer, if any. @@ -116,7 +116,7 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) [this]() { for (auto& dns_socket_handler: this->socket_handlers) - dns_socket_handler->on_recv(); + dns_socket_handler.on_recv(); }, "DNS timeout")); } diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index e0feb11..d8b55c8 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -50,7 +50,7 @@ private: * call to ares_fds. DNSSocketHandlers are added to it or removed from it * in the watch_dns_sockets() method */ - std::vector> socket_handlers; + std::vector socket_handlers; ares_channel channel; }; diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index dba2f26..21ae4e1 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -17,11 +17,11 @@ class DNSSocketHandler: public SocketHandler { public: explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); - ~DNSSocketHandler() = default; + ~DNSSocketHandler(); + DNSSocketHandler(DNSSocketHandler&&) = default; + DNSSocketHandler& operator=(DNSSocketHandler&&) = default; DNSSocketHandler(const DNSSocketHandler&) = delete; - DNSSocketHandler(DNSSocketHandler&&) = delete; DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; - DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; /** * Just call dns_process_fd, c-ares will do its work of send()ing or diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index eeb41fe..869ac3b 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -15,10 +15,10 @@ public: socket(socket) {} virtual ~SocketHandler() {} - SocketHandler(const SocketHandler&) = delete; - SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(SocketHandler&&) = default; + SocketHandler(SocketHandler&&) = default; SocketHandler& operator=(const SocketHandler&) = delete; - SocketHandler& operator=(SocketHandler&&) = delete; + SocketHandler(const SocketHandler&) = delete; virtual void on_recv() = 0; virtual void on_send() = 0; -- cgit v1.2.3 From 4f730f93fa6c993f584ea07aadca19a0def7642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:21:43 +0200 Subject: Don't use target_sources() in cmake because it's >=3.1 only --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06733bd..d8f95f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,7 +125,7 @@ endif() # file(GLOB source_utils src/utils/*.[hc]pp) -target_sources(utils PUBLIC ${source_utils}) +set_property(TARGET utils APPEND PROPERTY SOURCES ${source_utils}) # ## irclib -- cgit v1.2.3 From b0d60c0c1a69a3588de42511698dea17857b6092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:22:01 +0200 Subject: Set the required cmake version to 3.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8f95f1..31df11f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 3.0) project(biboumi) -- cgit v1.2.3 From fa42d0c178faa74b6872c4e0121709ef6682175d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:25:38 +0200 Subject: Bring back DNSSocketHandler's destructor --- louloulibs/network/dns_socket_handler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index 21ae4e1..bd431e1 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -17,7 +17,7 @@ class DNSSocketHandler: public SocketHandler { public: explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); - ~DNSSocketHandler(); + ~DNSSocketHandler() = default; DNSSocketHandler(DNSSocketHandler&&) = default; DNSSocketHandler& operator=(DNSSocketHandler&&) = default; DNSSocketHandler(const DNSSocketHandler&) = delete; -- cgit v1.2.3 From f212b47830b4ef6473bbcfaab33297a169643d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:39:27 +0200 Subject: Use an ugly way, because SOURCES property does not work in cmake 3.0 --- CMakeLists.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31df11f..06121a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,9 +123,14 @@ endif() # ## utils # -file(GLOB source_utils +file(GLOB source_src_utils src/utils/*.[hc]pp) -set_property(TARGET utils APPEND PROPERTY SOURCES ${source_utils}) +# Todo, switch to target_sources(utils) when we go cmake >=3.1 only +add_library(src_utils STATIC ${source_src_utils}) +target_link_libraries(src_utils logger config) +if(USE_DATABASE) + target_link_libraries(src_utils database) +endif() # ## irclib @@ -141,7 +146,7 @@ target_link_libraries(irc network utils logger) file(GLOB source_xmpp src/xmpp/*.[hc]pp) add_library(xmpp STATIC ${source_xmpp}) -target_link_libraries(xmpp xmpplib bridge network utils logger) +target_link_libraries(xmpp xmpplib bridge network utils src_utils logger) if(USE_DATABASE) target_link_libraries(xmpp database) @@ -165,6 +170,7 @@ target_link_libraries(${PROJECT_NAME} irc bridge utils + src_utils config) if(SYSTEMD_FOUND) target_link_libraries(xmpp ${SYSTEMD_LIBRARIES}) -- cgit v1.2.3 From 2307f94e5b02c7cfbc39ca57eb8c9133f6b10ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:57:01 +0200 Subject: Clean the list of options visible in non-advanced ccmake --- CMakeLists.txt | 17 +++++++++++------ cmake/Modules/FindLITESQL.cmake | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06121a7..2b1736d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ if (NOT PANDOC_EXECUTABLE) message(STATUS "Pandoc not found, documentation cannot be built") endif() endif() +mark_as_advanced(PANDOC_EXECUTABLE) # Look for litesql and enable the database if found if(WITH_LITESQL) @@ -280,18 +281,22 @@ add_custom_target(PrintBuildParameters ALL configure_file(biboumi.h.cmake src/biboumi.h) +set(SYSTEMD_SERVICE_TYPE_DOCSTRING "The value used as the Type= in the systemd unit file.") +set(WATCHDOG_SEC_DOCSTRING "The value used as WatchdogSec= in the systemd unit file.") if(SYSTEMD_FOUND) - set(SYSTEMD_SERVICE_TYPE "notify") - set(WATCHDOG_SEC "20") + set(SYSTEMD_SERVICE_TYPE "notify" CACHE STRING SYSTEMD_SERVICE_TYPE_DOCSTRING) + set(WATCHDOG_SEC "20" CACHE STRING WATCHDOG_SEC_DOCSTRING) else() - set(SYSTEMD_SERVICE_TYPE "simple") - set(WATCHDOG_SEC "") + set(SYSTEMD_SERVICE_TYPE "simple" CACHE STRING SYSTEMD_SERVICE_TYPE_DOCSTRING) + set(WATCHDOG_SEC "" CACHE STRING WATCHDOG_SEC_DOCSTRING) endif() +set(SERVICE_USER_DOCSTRING "The value used as the User= in the systemd unit file.") if(NOT DEFINED SERVICE_USER) - set(SERVICE_USER "nobody") + set(SERVICE_USER "nobody" CACHE STRING SERVICE_USER_DOCSTRING) endif() +set(SERVICE_GROUP_DOCSTRING "The value used as the Group= in the systemd unit file.") if(NOT DEFINED SERVICE_GROUP) - set(SERVICE_GROUP "nobody") + set(SERVICE_GROUP "nobody" CACHE STRING SERVICE_GROUP_DOCSTRING) endif() configure_file(unit/biboumi.service.cmake biboumi.service) diff --git a/cmake/Modules/FindLITESQL.cmake b/cmake/Modules/FindLITESQL.cmake index 2ce8fd6..91155bb 100644 --- a/cmake/Modules/FindLITESQL.cmake +++ b/cmake/Modules/FindLITESQL.cmake @@ -36,6 +36,7 @@ foreach(DB_TYPE sqlite postgresql mysql ocilib) if(LITESQL_${DB_TYPE_UPPER}_LIB_PATH) list(APPEND LITESQL_LIBRARIES ${LITESQL_${DB_TYPE_UPPER}_LIB_PATH}) endif() + mark_as_advanced(LITESQL_${DB_TYPE_UPPER}_LIB_PATH) endforeach() find_program(LITESQLGEN_EXECUTABLE NAMES litesql-gen @@ -53,7 +54,7 @@ if(LITESQL_FOUND) set(LITESQL_LIBRARY ${LITESQL_LIBRARIES}) endif() -mark_as_advanced(LITESQL_INCLUDE_DIRS LITESQL_LIBRARIES) +mark_as_advanced(LITESQL_INCLUDE_DIRS LITESQL_LIBRARIES LITESQLGEN_EXECUTABLE) # LITESQL_GENERATE_CPP function -- cgit v1.2.3 From 8ded8cd33ee2d171f2a6da207e01130787da7ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 12 Jul 2016 11:59:27 +0200 Subject: Properly use the docstring variables --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b1736d..a7bccd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,19 +284,19 @@ configure_file(biboumi.h.cmake src/biboumi.h) set(SYSTEMD_SERVICE_TYPE_DOCSTRING "The value used as the Type= in the systemd unit file.") set(WATCHDOG_SEC_DOCSTRING "The value used as WatchdogSec= in the systemd unit file.") if(SYSTEMD_FOUND) - set(SYSTEMD_SERVICE_TYPE "notify" CACHE STRING SYSTEMD_SERVICE_TYPE_DOCSTRING) - set(WATCHDOG_SEC "20" CACHE STRING WATCHDOG_SEC_DOCSTRING) + set(SYSTEMD_SERVICE_TYPE "notify" CACHE STRING ${SYSTEMD_SERVICE_TYPE_DOCSTRING}) + set(WATCHDOG_SEC "20" CACHE STRING ${WATCHDOG_SEC_DOCSTRING}) else() - set(SYSTEMD_SERVICE_TYPE "simple" CACHE STRING SYSTEMD_SERVICE_TYPE_DOCSTRING) - set(WATCHDOG_SEC "" CACHE STRING WATCHDOG_SEC_DOCSTRING) + set(SYSTEMD_SERVICE_TYPE "simple" CACHE STRING ${SYSTEMD_SERVICE_TYPE_DOCSTRING}) + set(WATCHDOG_SEC "" CACHE STRING ${WATCHDOG_SEC_DOCSTRING}) endif() set(SERVICE_USER_DOCSTRING "The value used as the User= in the systemd unit file.") if(NOT DEFINED SERVICE_USER) - set(SERVICE_USER "nobody" CACHE STRING SERVICE_USER_DOCSTRING) + set(SERVICE_USER "nobody" CACHE STRING ${SERVICE_USER_DOCSTRING}) endif() set(SERVICE_GROUP_DOCSTRING "The value used as the Group= in the systemd unit file.") if(NOT DEFINED SERVICE_GROUP) - set(SERVICE_GROUP "nobody" CACHE STRING SERVICE_GROUP_DOCSTRING) + set(SERVICE_GROUP "nobody" CACHE STRING ${SERVICE_GROUP_DOCSTRING}) endif() configure_file(unit/biboumi.service.cmake biboumi.service) -- cgit v1.2.3 From c6cfa69cdc8929f2d1000604d4a0bfade5be8385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 18 Jul 2016 09:53:01 +0200 Subject: Always build the artifacts --- .gitlab-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 51a8b2f..0be8920 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,6 +26,7 @@ build:fedora: - build/rpmbuild/RPMS - build/rpmbuild/SRPMS - build/tests_outputs/ + when: always build:debian: stage: build @@ -39,4 +40,5 @@ build:debian: artifacts: paths: - build/coverage/ - - build/tests_outputs/ \ No newline at end of file + - build/tests_outputs/ + when: always -- cgit v1.2.3 From 24824a5015e77aced8adf8afc35b82984e8b84fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 18 Jul 2016 09:53:15 +0200 Subject: In reload.cpp, only build the database things if litesql is used --- src/utils/reload.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/reload.cpp b/src/utils/reload.cpp index 7125a75..348c5b5 100644 --- a/src/utils/reload.cpp +++ b/src/utils/reload.cpp @@ -4,12 +4,16 @@ #include #include +#include "biboumi.h" + void open_database() { +#ifdef USE_DATABASE const auto db_filename = Config::get("db_name", xdg_data_path("biboumi.sqlite")); log_info("Opening database: ", db_filename); Database::open(db_filename); log_info("database successfully opened."); +#endif } void reload_process() @@ -19,10 +23,12 @@ void reload_process() // line needs to be written Logger::instance().reset(); log_info("Configuration and logger reloaded."); +#ifdef USE_DATABASE try { open_database(); } catch (const litesql::DatabaseError&) { log_warning("Re-using the previous database."); } +#endif } -- cgit v1.2.3 From 7cc05ab4967e9fb77b6e4d8ca9800c8625895ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 18 Jul 2016 10:39:28 +0200 Subject: Refactor the CI file, build many configurations --- .gitlab-ci.yml | 92 ++++++++++++++++++++++++++++------- docker/biboumi-test/fedora/Dockerfile | 2 +- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0be8920..7b797f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,44 +1,102 @@ -stages: -- build - before_script: - uname -a - whoami - echo $LANG - g++ --version - clang++ --version - - mkdir build + - rm -rf build/ + - mkdir build/ - cd build -build:fedora: +variables: + COMPILER: "g++" + BUILD_TYPE: "Debug" + BOTAN: "-DWITH_BOTAN=1" + CARES: "-DWITH_CARES=1" + SYSTEMD: "-DWITH_SYSTEMD=1" + LIBIDN: "-DWITH_LIBIDN=1" + LITESQL: "-DWITH_LITESQL=1" + +.template:basic_build: &basic_build stage: build - image: biboumi-test-fedora:latest script: - - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - - make coverage -j$(nproc) - make check -j$(nproc) - - mkdir tests_outputs && pushd tests_outputs && make e2e -j$(nproc) -C .. && popd + +image: biboumi-test-fedora:latest + +build:1: + variables: + BOTAN: "-DWITHOUT_BOTAN=1" + <<: *basic_build + +build:2: + variables: + CARES: "-DWITHOUT_CARES=1" + <<: *basic_build + +build:3: + variables: + LITESQL: "-DWITHOUT_LITESQL=1" + <<: *basic_build + +build:4: + variables: + LITESQL: "-DWITHOUT_LITESQL=1" + BOTAN: "-DWITHOUT_BOTAN=1" + <<: *basic_build + +build:5: + variables: + LITESQL: "-DWITHOUT_LITESQL=1" + CARES: "-DWITHOUT_CARES=1" + <<: *basic_build + +build:6: + variables: + BOTAN: "-DWITHOUT_BOTAN=1" + CARES: "-DWITHOUT_CARES=1" + <<: *basic_build + +build:6: + variables: + LIBIDN: "-DWITHOUT_LIBIDN=1" + CARES: "-DWITHOUT_CARES=1" + <<: *basic_build + +build:rpm: + stage: build + script: + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make rpm -j$(nproc) artifacts: paths: - - build/coverage/ - build/rpmbuild/RPMS - build/rpmbuild/SRPMS - - build/tests_outputs/ when: always -build:debian: - stage: build - image: biboumi-test-debian:latest + +.template:basic_test: &basic_test + stage: test script: - - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DWITH_BOTAN=1 -DWITH_CARES=1 -DWITH_SYSTEMD=1 -DWITH_LIBIDN=1 -DWITH_LITESQL=1 + - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - - make coverage -j$(nproc) - - make check -j$(nproc) + - make check + - make coverage - mkdir tests_outputs && pushd tests_outputs && make e2e -j$(nproc) -C .. && popd artifacts: paths: - build/coverage/ - build/tests_outputs/ when: always + +test:debian: + stage: test + image: biboumi-test-debian:latest + <<: *basic_test + +test:fedora: + stage: test + image: biboumi-test-fedora:latest + <<: *basic_test \ No newline at end of file diff --git a/docker/biboumi-test/fedora/Dockerfile b/docker/biboumi-test/fedora/Dockerfile index b199ed2..3c48645 100644 --- a/docker/biboumi-test/fedora/Dockerfile +++ b/docker/biboumi-test/fedora/Dockerfile @@ -57,7 +57,7 @@ RUN cd /charybdis && ./autogen.sh && ./configure --prefix=/home/tester/ircd --bi RUN chown -R tester:tester /home/tester/ircd RUN rm -rf /charybdis -RUN su - tester -c "echo export LANG=fr_FR.utf-8 >> /home/tester/.bashrc" +RUN su - tester -c "echo export LANG=en_GB.utf-8 >> /home/tester/.bashrc" WORKDIR /home/tester USER tester -- cgit v1.2.3 From db503b23e86d1cb390d12298875eb0eaffdbfa3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 24 Jul 2016 19:41:01 +0200 Subject: Use log_error instead of cerr --- louloulibs/config/config.cpp | 11 ++++++----- src/main.cpp | 10 ++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/louloulibs/config/config.cpp b/louloulibs/config/config.cpp index 8027d20..417981d 100644 --- a/louloulibs/config/config.cpp +++ b/louloulibs/config/config.cpp @@ -1,9 +1,10 @@ #include +#include -#include +#include #include -#include +#include std::string Config::filename{}; std::map Config::values{}; @@ -22,7 +23,7 @@ int Config::get_int(const std::string& option, const int& def) { std::string res = Config::get(option, ""); if (!res.empty()) - return atoi(res.c_str()); + return std::atoi(res.c_str()); else return def; } @@ -65,7 +66,7 @@ bool Config::read_conf(const std::string& name) std::ifstream file(Config::filename.data()); if (!file.is_open()) { - perror(("Error while opening file " + filename + " for reading.").c_str()); + log_error("Error while opening file ", filename, " for reading: ", strerror(errno)); return false; } @@ -95,7 +96,7 @@ void Config::save_to_file() std::ofstream file(Config::filename.data()); if (file.fail()) { - std::cerr << "Could not save config file." << std::endl; + log_error("Could not save config file."); return ; } for (const auto& it: Config::values) diff --git a/src/main.cpp b/src/main.cpp index 422219a..8542e41 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,11 +28,9 @@ static bool exiting = false; int config_help(const std::string& missing_option) { if (!missing_option.empty()) - std::cerr << "Error: empty value for option " << missing_option << "." << std::endl; - std::cerr << - "Please provide a configuration file filled like this:\n\n" - "hostname=irc.example.com\npassword=S3CR3T" - << std::endl; + log_error("Error: empty value for option ", missing_option, "."); + log_error("Please provide a configuration file filled like this:\n\n" + "hostname=irc.example.com\npassword=S3CR3T"); return 1; } @@ -52,7 +50,7 @@ static void sigusr_handler(int, siginfo_t*, void*) int main(int ac, char** av) { const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg"); - std::cerr << "Using configuration file: " << conf_filename << std::endl; + log_info("Using configuration file: ", conf_filename); if (!Config::read_conf(conf_filename)) return config_help(""); -- cgit v1.2.3 From c7e4fc1386b7992c33ea912bf3bfcfcff5d85758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 25 Jul 2016 14:59:25 +0200 Subject: Test the resolving of multiple hostnames at the same time --- tests/dns.cpp | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/dns.cpp b/tests/dns.cpp index 4ec1b96..c3eda7b 100644 --- a/tests/dns.cpp +++ b/tests/dns.cpp @@ -9,6 +9,8 @@ TEST_CASE("DNS resolver") { Resolver resolver; + Resolver resolver2; + Resolver resolver3; /** * If we are using cares, we need to run a poller loop until each @@ -36,38 +38,50 @@ TEST_CASE("DNS resolver") bool success = true; - auto error_cb = [&success, &hostname, &port](const char* msg) - { - INFO("Failed to resolve " << hostname << ":" << port << ": " << msg); - success = false; - }; - auto success_cb = [&success, &hostname, &port](const struct addrinfo* addr) - { - INFO("Successfully resolved " << hostname << ":" << port << ": " << addr_to_string(addr)); - success = true; - }; + const auto error_cb = [&success](const std::string& hostname) + { + return [&success, hostname](const char *msg) + { + INFO("Failed to resolve " << hostname << ":" << msg); + success = false; + }; + }; + const auto success_cb = [&success](const std::string& hostname) + { + return [&success, hostname](const struct addrinfo *addr) + { + INFO("Successfully resolved " << hostname << ": " << addr_to_string(addr)); + success = true; + }; + }; hostname = "example.com"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); + hostname = "poez.io"; + resolver2.resolve(hostname, port, + success_cb(hostname), error_cb(hostname)); + hostname = "louiz.org"; + resolver3.resolve(hostname, port, + success_cb(hostname), error_cb(hostname)); loop(); CHECK(success); hostname = "this.should.fail.because.it.is..misformatted"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(!success); hostname = "this.should.fail.because.it.is.does.not.exist.invalid"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(!success); hostname = "localhost"; resolver.resolve(hostname, port, - success_cb, error_cb); + success_cb(hostname), error_cb(hostname)); loop(); CHECK(success); -- cgit v1.2.3 From de0eff6e944db11ae3552e0cd2c191997eebaa2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 27 Jul 2016 10:04:15 +0200 Subject: =?UTF-8?q?Revert=20"Don=E2=80=99t=20use=20unique=5Fptr=20to=20sto?= =?UTF-8?q?re=20dns=20socket=20handlers"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5328d0806fdc5becb9344b4d4320787a2b7c0712. --- louloulibs/network/dns_handler.cpp | 14 +++++++------- louloulibs/network/dns_handler.hpp | 2 +- louloulibs/network/dns_socket_handler.hpp | 4 ++-- louloulibs/network/socket_handler.hpp | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index 24c4bcf..5e19f8c 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -68,7 +68,7 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) std::remove_if(this->socket_handlers.begin(), this->socket_handlers.end(), [&readers](const auto& dns_socket) { - return !FD_ISSET(dns_socket.get_socket(), &readers); + return !FD_ISSET(dns_socket->get_socket(), &readers); }), this->socket_handlers.end()); @@ -81,8 +81,8 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) this->socket_handlers.end(), [i](const auto& socket_handler) { - return i == socket_handler.get_socket(); - }); + return i == socket_handler->get_socket(); + }); if (!read && !write) // No need to read or write to it { // If found, erase it and stop watching it because it is not // needed anymore @@ -95,12 +95,12 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) if (it == this->socket_handlers.end()) { this->socket_handlers.emplace(this->socket_handlers.begin(), - poller, i); + std::make_unique(poller, i)); it = this->socket_handlers.begin(); } - poller->add_socket_handler(&*it); + poller->add_socket_handler(it->get()); if (write) - poller->watch_send_events(&*it); + poller->watch_send_events(it->get()); } } // Cancel previous timer, if any. @@ -116,7 +116,7 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) [this]() { for (auto& dns_socket_handler: this->socket_handlers) - dns_socket_handler.on_recv(); + dns_socket_handler->on_recv(); }, "DNS timeout")); } diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index d8b55c8..e0feb11 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -50,7 +50,7 @@ private: * call to ares_fds. DNSSocketHandlers are added to it or removed from it * in the watch_dns_sockets() method */ - std::vector socket_handlers; + std::vector> socket_handlers; ares_channel channel; }; diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index bd431e1..dba2f26 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -18,10 +18,10 @@ class DNSSocketHandler: public SocketHandler public: explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); ~DNSSocketHandler() = default; - DNSSocketHandler(DNSSocketHandler&&) = default; - DNSSocketHandler& operator=(DNSSocketHandler&&) = default; DNSSocketHandler(const DNSSocketHandler&) = delete; + DNSSocketHandler(DNSSocketHandler&&) = delete; DNSSocketHandler& operator=(const DNSSocketHandler&) = delete; + DNSSocketHandler& operator=(DNSSocketHandler&&) = delete; /** * Just call dns_process_fd, c-ares will do its work of send()ing or diff --git a/louloulibs/network/socket_handler.hpp b/louloulibs/network/socket_handler.hpp index 869ac3b..eeb41fe 100644 --- a/louloulibs/network/socket_handler.hpp +++ b/louloulibs/network/socket_handler.hpp @@ -15,10 +15,10 @@ public: socket(socket) {} virtual ~SocketHandler() {} - SocketHandler& operator=(SocketHandler&&) = default; - SocketHandler(SocketHandler&&) = default; - SocketHandler& operator=(const SocketHandler&) = delete; SocketHandler(const SocketHandler&) = delete; + SocketHandler(SocketHandler&&) = delete; + SocketHandler& operator=(const SocketHandler&) = delete; + SocketHandler& operator=(SocketHandler&&) = delete; virtual void on_recv() = 0; virtual void on_send() = 0; -- cgit v1.2.3 From 11a1c0cc99af9629302184fac2b7adf3ac48516b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 10:34:01 +0200 Subject: Always remove all the DNS sockets on an c-ares event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because c-ares may close one of its socket, even if it’s not the one that has the event. Otherwise we may not know when a socket has been removed from our poller (automatically, when close()ed) and this leads to bugs. --- louloulibs/network/dns_handler.cpp | 11 ++++++++++- louloulibs/network/dns_handler.hpp | 1 + louloulibs/network/dns_socket_handler.cpp | 13 ++++++++++--- louloulibs/network/dns_socket_handler.hpp | 6 +++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/louloulibs/network/dns_handler.cpp b/louloulibs/network/dns_handler.cpp index 5e19f8c..e267944 100644 --- a/louloulibs/network/dns_handler.cpp +++ b/louloulibs/network/dns_handler.cpp @@ -37,6 +37,7 @@ ares_channel& DNSHandler::get_channel() void DNSHandler::destroy() { + this->remove_all_sockets_from_poller(); this->socket_handlers.clear(); ::ares_destroy(this->channel); ::ares_library_cleanup(); @@ -95,7 +96,7 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) if (it == this->socket_handlers.end()) { this->socket_handlers.emplace(this->socket_handlers.begin(), - std::make_unique(poller, i)); + std::make_unique(poller, *this, i)); it = this->socket_handlers.begin(); } poller->add_socket_handler(it->get()); @@ -122,4 +123,12 @@ void DNSHandler::watch_dns_sockets(std::shared_ptr& poller) } } +void DNSHandler::remove_all_sockets_from_poller() +{ + for (const auto& socket_handler: this->socket_handlers) + { + socket_handler->remove_from_poller(); + } +} + #endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_handler.hpp b/louloulibs/network/dns_handler.hpp index e0feb11..fd1729d 100644 --- a/louloulibs/network/dns_handler.hpp +++ b/louloulibs/network/dns_handler.hpp @@ -40,6 +40,7 @@ public: * and library. */ void destroy(); + void remove_all_sockets_from_poller(); ares_channel& get_channel(); static DNSHandler instance; diff --git a/louloulibs/network/dns_socket_handler.cpp b/louloulibs/network/dns_socket_handler.cpp index faaabdb..5fd08cb 100644 --- a/louloulibs/network/dns_socket_handler.cpp +++ b/louloulibs/network/dns_socket_handler.cpp @@ -8,8 +8,10 @@ #include DNSSocketHandler::DNSSocketHandler(std::shared_ptr poller, + DNSHandler& handler, const socket_t socket): - SocketHandler(poller, socket) + SocketHandler(poller, socket), + handler(handler) { } @@ -21,7 +23,7 @@ void DNSSocketHandler::on_recv() { // always stop watching send and read events. We will re-watch them if the // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); + this->handler.remove_all_sockets_from_poller(); ::ares_process_fd(DNSHandler::instance.get_channel(), this->socket, ARES_SOCKET_BAD); } @@ -29,7 +31,7 @@ void DNSSocketHandler::on_send() { // always stop watching send and read events. We will re-watch them if the // next call to ares_fds tell us to - this->poller->remove_socket_handler(this->socket); + this->handler.remove_all_sockets_from_poller(); ::ares_process_fd(DNSHandler::instance.get_channel(), ARES_SOCKET_BAD, this->socket); } @@ -38,4 +40,9 @@ bool DNSSocketHandler::is_connected() const return true; } +void DNSSocketHandler::remove_from_poller() +{ + this->poller->remove_socket_handler(this->socket); +} + #endif /* CARES_FOUND */ diff --git a/louloulibs/network/dns_socket_handler.hpp b/louloulibs/network/dns_socket_handler.hpp index dba2f26..0570196 100644 --- a/louloulibs/network/dns_socket_handler.hpp +++ b/louloulibs/network/dns_socket_handler.hpp @@ -13,10 +13,12 @@ * (Poller reported it to be writable or readeable) */ +class DNSHandler; + class DNSSocketHandler: public SocketHandler { public: - explicit DNSSocketHandler(std::shared_ptr poller, const socket_t socket); + explicit DNSSocketHandler(std::shared_ptr poller, DNSHandler& handler, const socket_t socket); ~DNSSocketHandler() = default; DNSSocketHandler(const DNSSocketHandler&) = delete; DNSSocketHandler(DNSSocketHandler&&) = delete; @@ -38,8 +40,10 @@ public: * Always true, see the comment for connect() */ bool is_connected() const override final; + void remove_from_poller(); private: + DNSHandler& handler; }; #endif // CARES_FOUND -- cgit v1.2.3 From 5c78692fcd447d94be1eb43338f79790f53051f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 10:14:04 +0200 Subject: Do not add 1ms to the timeout of our poller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Can’t remember why I did this, but that must be a stupid reason. Everything must work even with a timeout of 0. --- louloulibs/utils/timed_events_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/louloulibs/utils/timed_events_manager.cpp b/louloulibs/utils/timed_events_manager.cpp index b90a237..67d61fe 100644 --- a/louloulibs/utils/timed_events_manager.cpp +++ b/louloulibs/utils/timed_events_manager.cpp @@ -23,7 +23,7 @@ std::chrono::milliseconds TimedEventsManager::get_timeout() const { if (this->events.empty()) return utils::no_timeout; - return this->events.front().get_timeout() + std::chrono::milliseconds(1); + return this->events.front().get_timeout(); } std::size_t TimedEventsManager::execute_expired_events() -- cgit v1.2.3 From 0d1e0629e7efe07bacce6a22e45ddfd7652eb505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 10:47:44 +0200 Subject: Fix the timeout test, now that we don't wait 1ms too much everytime --- louloulibs/utils/timed_events.cpp | 2 +- tests/timed_events.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/louloulibs/utils/timed_events.cpp b/louloulibs/utils/timed_events.cpp index 6b936c4..68d009c 100644 --- a/louloulibs/utils/timed_events.cpp +++ b/louloulibs/utils/timed_events.cpp @@ -27,7 +27,7 @@ bool TimedEvent::is_after(const TimedEvent& other) const bool TimedEvent::is_after(const std::chrono::steady_clock::time_point& time_point) const { - return this->time_point >= time_point; + return this->time_point > time_point; } std::chrono::milliseconds TimedEvent::get_timeout() const diff --git a/tests/timed_events.cpp b/tests/timed_events.cpp index 3844f3d..d63abef 100644 --- a/tests/timed_events.cpp +++ b/tests/timed_events.cpp @@ -39,7 +39,7 @@ TEST_CASE("Test timed event expiration") std::chrono::milliseconds timoute = TimedEventsManager::instance().get_timeout(); INFO("Sleeping for " << timoute.count() << "ms"); - std::this_thread::sleep_for(timoute); + std::this_thread::sleep_for(timoute + 1ms); // Event is now expired CHECK(TimedEventsManager::instance().execute_expired_events() == 1); -- cgit v1.2.3 From d46e0a9196adae3fac73eb14a5b81dad01c7a732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 11:12:27 +0200 Subject: Mark some more cmake variables as advanced --- cmake/Modules/CodeCoverage.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/Modules/CodeCoverage.cmake b/cmake/Modules/CodeCoverage.cmake index 5954846..4f54327 100644 --- a/cmake/Modules/CodeCoverage.cmake +++ b/cmake/Modules/CodeCoverage.cmake @@ -85,14 +85,17 @@ IF(NOT GCOV_PATH) set(ERROR_MSG "gcov not found") return() ENDIF() +MARK_AS_ADVANCED(GCOV_PATH) IF(NOT LCOV_PATH) set(ERROR_MSG "lcov not found") return() ENDIF() +MARK_AS_ADVANCED(LCOV_PATH) IF(NOT GENHTML_PATH) set(ERROR_MSG "genhtml not found") return() ENDIF() +MARK_AS_ADVANCED(GENHTML_PATH) IF(NOT CMAKE_COMPILER_IS_GNUCXX) set(ERROR_MSG "Compiler is not gcc") return() @@ -188,6 +191,7 @@ FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname IF(NOT GCOVR_PATH) MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") ENDIF() # NOT GCOVR_PATH + MARK_AS_ADVANCED(GCOVR_PATH) ADD_CUSTOM_TARGET(${_targetname} -- cgit v1.2.3 From cf9104e3e27fe67978ccb202eecd2ab7c98518e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 11:46:24 +0200 Subject: Update the changelog by adding (all?) the changes in 3.0 --- CHANGELOG.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 599ee70..98f6bd1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,34 @@ +Version 3.0 +=========== + - Support multiple-nick sessions: a user can join an IRC channel behind + one single nick, using multiple different clients, at the same time (as + long as each client is using the same bare JID). + - Database support for persistant per-user per-server configuration. Add + `LiteSQL ` as an optional + dependency. + - Add ad-hoc commands that lets each user configure various things + - Support an after-connect command that will be sent to the server + just after the user gets connected to it. + - Support the sending of a PASS command. + - Lets the users configure their username and realname, if the + realname_customization is set to true. + - The remote TLS certificates are checked against the system’s trusted + CAs, unless the user used the configuration option that ignores these + checks. + - Lets the user set a sha-1 hash to identify a server certificate that + should always be trusted. + - Add an outgoing_bind option. + - Add an ad-hoc command to forcefully disconnect a user from one or + more servers. + - Let the user configure the incoming encoding of an IRC server (the + default behaviour remains unchanged: check if it’s valid utf-8 and if + not, decode as latin-1). + - Support `multi-prefix `. + - And of course, many bufixes. + - Run unit tests and a test suite, build the RPM and check many things + automatically using gitlab-ci. + + Version 2.0 - 2015-05-29 ======================== -- cgit v1.2.3 From 085a48ea48b9fcb2188b645d2a373d97b8dab9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 11:56:33 +0200 Subject: Do not use the logger before the configuration has been loaded first --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 8542e41..9b36880 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,7 @@ static void sigusr_handler(int, siginfo_t*, void*) int main(int ac, char** av) { const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg"); - log_info("Using configuration file: ", conf_filename); + std::cout << "Using configuration file: " << conf_filename << std::endl; if (!Config::read_conf(conf_filename)) return config_help(""); -- cgit v1.2.3 From 4463e9f5e3169d41a423982a35d9ca3cd4a9562c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 28 Jul 2016 16:05:36 +0200 Subject: Document the multi-nick session thing --- doc/biboumi.1.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index b8bf794..6d4c35a 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -178,6 +178,14 @@ the same IRC connection is used. If, however, an other user wants to join an IRC channel on that same IRC server, biboumi opens a new connection to that server. Biboumi connects once to each IRC server, for each user on it. +Additionally, if one user is using more than one clients (with the same bare +JID), they can join the same IRC channel (on the same server) behind one +single nickname. Biboumi will forward all the messages (the channel ones and +the private ones) and the presences to all the resources behind that nick. +There is no need to have multiple nicknames and multiple connections to be +able to take part in a conversation (or idle) in a channel from a mobile client +while the desktop client is still connected, for example. + To cleanly shutdown the component, send a SIGINT or SIGTERM signal to it. It will send messages to all connected IRC and XMPP servers to indicate a reason why the users are being disconnected. Biboumi exits when the end of -- cgit v1.2.3 From 6ca689650abfc39a3b0fca4a6bfc7dd1e766545c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 29 Jul 2016 10:39:40 +0200 Subject: ci: echo the cmake command line arguments --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b797f9..acbb132 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,6 +20,7 @@ variables: .template:basic_build: &basic_build stage: build script: + - echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - make check -j$(nproc) -- cgit v1.2.3 From b66c006a4bc63030c25134550c6f27aca677830a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 29 Jul 2016 10:40:00 +0200 Subject: Trivial micro fix --- CHANGELOG.rst | 1 + doc/biboumi.1.rst | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 98f6bd1..5c1a08c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,6 @@ Version 3.0 =========== + - Support multiple-nick sessions: a user can join an IRC channel behind one single nick, using multiple different clients, at the same time (as long as each client is using the same bare JID). diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 6d4c35a..dd365f0 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -1,6 +1,6 @@ -======================= +====================== Biboumi(1) User Manual -======================= +====================== .. contents:: :depth: 2 -- cgit v1.2.3 From 1dd324aef3e7df22009d037bfd21722bf59c630d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 29 Jul 2016 10:49:31 +0200 Subject: lol k gitlab-ci. It fails to parse a string that contains : --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index acbb132..cb80093 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ variables: .template:basic_build: &basic_build stage: build script: - - echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - echo Running cmake with the following parameters -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - make check -j$(nproc) -- cgit v1.2.3 From 37800c804eeb24e36190766f2414cfe401fbbc2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 29 Jul 2016 11:00:30 +0200 Subject: Actually it works when we surrount the WHOLE string with quotes Not just part of it --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb80093..c1cc979 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,7 @@ variables: .template:basic_build: &basic_build stage: build script: - - echo Running cmake with the following parameters -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} + - "echo Running cmake with the following parameters: -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL}" - cmake .. -DCMAKE_CXX_COMPILER=${COMPILER} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ${BOTAN} ${CARES} ${SYSTEMD} ${LIBIDN} ${LITESQL} - make biboumi -j$(nproc) - make check -j$(nproc) -- cgit v1.2.3 From 47902953131a6fc6fc3ab24d15f5fd163c2f7add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 31 Jul 2016 17:55:47 +0200 Subject: test kick --- tests/end_to_end/__main__.py | 61 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d1d653d..2442d03 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -220,6 +220,11 @@ def expect_stanza(xpaths, xmpp, biboumi, optional=False, after=None): print("Warning, from argument type passed to expect_stanza: %s" % (type(xpaths))) +def log_message(message, xmpp, biboumi): + print("%s" % (message,)) + asyncio.get_event_loop().call_soon(xmpp.run_scenario) + + class BiboumiTest: """ Spawns a biboumi process and a fake XMPP Component that will run a @@ -252,7 +257,6 @@ class BiboumiTest: asyncio.get_event_loop().call_soon(xmpp.run_scenario) xmpp.process() - code = asyncio.get_event_loop().run_until_complete(biboumi.wait()) xmpp.biboumi = None scenario.steps.clear() @@ -436,6 +440,8 @@ if __name__ == '__main__': [ handshake_sequence(), # First user joins + partial(log_message, + "First user joins"), partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), @@ -448,16 +454,24 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), # Second user joins + partial(log_message, + "Second user joins"), partial(send_stanza, ""), connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), # Our presence, sent to the other user + partial(log_message, + "Our presence sent to the other user"), partial(expect_stanza, ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']",)), # The other user presence + partial(log_message, + "The other user presence"), partial(expect_stanza, "/presence[@to='{jid_second}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~nick@localhost'][@role='participant']"), # Our own presence + partial(log_message, + "Our own presence"), partial(expect_stanza, ("/presence[@to='{jid_two}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_two}']/muc_user:x/muc_user:item[@affiliation='none'][@jid='~bobby@localhost'][@role='participant']", "/presence/muc_user:x/muc_user:status[@code='110']") @@ -790,7 +804,6 @@ if __name__ == '__main__': ""), partial(expect_stanza, "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_ping']"), - ## And re-do exactly the same thing, just change the resource initiating the self ping partial(send_stanza, ""), @@ -803,6 +816,50 @@ if __name__ == '__main__': "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='third_ping']"), ]), + Scenario("simple_kick", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@type='groupchat']/subject"), + + # Second user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + # Moderator kicks participant + partial(log_message, "Moderator kicks participant"), + partial(send_stanza, + "reported"), + partial(log_message, "Presence is sent to everyone"), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ), + ), + partial(expect_stanza, + "/iq[@id='kick1'][@type='result']"), + ]), ) failures = 0 -- cgit v1.2.3 From f89361c3701ef66e17a7d8159d99e3d0b0c76e4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 31 Jul 2016 18:03:33 +0200 Subject: Provide a --help option fix #3183 --- src/main.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 9b36880..905cfa6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,12 @@ int config_help(const std::string& missing_option) return 1; } +int display_help() +{ + std::cout << "Usage: biboumi [configuration_file]" << std::endl; + return 0; +} + static void sigint_handler(int sig, siginfo_t*, void*) { // In 2 seconds, repeat the same signal, to force the exit @@ -49,6 +55,20 @@ static void sigusr_handler(int, siginfo_t*, void*) int main(int ac, char** av) { + if (ac > 1) + { + const std::string arg = av[1]; + if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-') + { + if (arg == "--help") + return display_help(); + else + { + std::cerr << "Unknow command line option: " << arg << std::endl; + return 1; + } + } + } const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg"); std::cout << "Using configuration file: " << conf_filename << std::endl; -- cgit v1.2.3 From 58000c36a64fd8d4b8cdb7d76f318fca5a81381e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Mon, 1 Aug 2016 09:48:45 +0200 Subject: Rephrase an error message --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 905cfa6..53f3193 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,7 @@ static bool exiting = false; int config_help(const std::string& missing_option) { if (!missing_option.empty()) - log_error("Error: empty value for option ", missing_option, "."); + log_error("Configuration error: empty value for option ", missing_option, "."); log_error("Please provide a configuration file filled like this:\n\n" "hostname=irc.example.com\npassword=S3CR3T"); return 1; -- cgit v1.2.3 From 3fbe01bb30a38111ce058ff78b517234280c0a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 2 Aug 2016 11:11:27 +0200 Subject: Test the kick on a multisession nick --- tests/end_to_end/__main__.py | 60 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2442d03..d7e68b5 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -860,6 +860,66 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@id='kick1'][@type='result']"), ]), + Scenario("multisession_kick", + [ + handshake_sequence(), + # First user joins + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, "/message"), + partial(expect_stanza, "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message[@type='groupchat']/subject"), + + # Second user joins, from two resources + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_two}/{resource_one}'), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='none'][@role='participant']",), + + partial(expect_stanza, + "/presence/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']"), + partial(expect_stanza, + "/presence/muc_user:x/muc_user:status[@code='110']"), + partial(expect_stanza, "/message/subject"), + + partial(send_stanza, + ""), + partial(expect_stanza, + "/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_two}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_two}']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_two}/{resource_two}']/subject[not(text())]"), + + # Moderator kicks participant + partial(log_message, "Moderator kicks participant"), + partial(send_stanza, + "reported"), + partial(log_message, "Unavailable presence is sent to the two resources"), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_one}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable'][@to='{jid_second}/{resource_two}']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + "/presence/muc_user:x/muc_user:status[@code='110']" + )), + partial(expect_stanza, + ("/presence[@type='unavailable']/muc_user:x/muc_user:item[@role='none']/muc_user:actor[@nick='{nick_one}']", + "/presence/muc_user:x/muc_user:item/muc_user:reason[text()='reported']", + "/presence/muc_user:x/muc_user:status[@code='307']", + ), + ), + partial(expect_stanza, + "/iq[@id='kick1'][@type='result']"), + ]), ) failures = 0 -- cgit v1.2.3 From f0a25ccda4526f5132b459e7e6a48ea08733fb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 3 Aug 2016 11:46:13 +0200 Subject: Lower case the nick, when forwarding a version or ping request --- src/bridge/bridge.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index edf1700..17d3ec6 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -741,7 +741,7 @@ void Bridge::send_iq_version_request(const std::string& nick, const std::string& { const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_iq_version_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); + this->xmpp.send_iq_version_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin()); } void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname, @@ -753,7 +753,7 @@ void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& // Forward to the first resource (arbitrary, based on the “order” of the std::set) only const auto resources = this->resources_in_server[hostname]; if (resources.begin() != resources.end()) - this->xmpp.send_ping_request(nick + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); + this->xmpp.send_ping_request(utils::tolower(nick) + "!" + utils::empty_if_fixed_server(hostname), this->user_jid + "/" + *resources.begin(), utils::revstr(id)); } void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid) -- cgit v1.2.3 From ec0e87a965fbdbd4682f1f3bcdbea2ff7440891e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 3 Aug 2016 14:28:29 +0200 Subject: Test the version request, in many ways --- tests/end_to_end/__main__.py | 73 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index d7e68b5..4348197 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -112,7 +112,8 @@ def match(stanza, xpath): 'muc_user': 'http://jabber.org/protocol/muc#user', 'disco_items': 'http://jabber.org/protocol/disco#items', 'commands': 'http://jabber.org/protocol/commands', - 'dataform': 'jabber:x:data'}) + 'dataform': 'jabber:x:data', + 'version': 'jabber:iq:version'}) return matched @@ -122,7 +123,11 @@ def check_xpath(xpaths, xmpp, after, stanza): if not matched: raise StanzaError("Received stanza “%s” did not match expected xpath “%s”" % (stanza, xpath)) if after: - after(stanza, xmpp) + if isinstance(after, collections.Iterable): + for af in after: + af(stanza, xmpp) + else: + after(stanza, xmpp) def check_xpath_optional(xpaths, xmpp, after, stanza): @@ -920,6 +925,70 @@ if __name__ == '__main__': partial(expect_stanza, "/iq[@id='kick1'][@type='result']"), ]), + Scenario("self_version", + [ + handshake_sequence(), + partial(send_stanza, + ""), + connection_sequence("irc.localhost", '{jid_one}/{resource_one}'), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Send a version request to ourself + partial(send_stanza, + ""), + # We receive our own request, + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to='{jid_one}/{resource_one}']", + after = partial(save_value, "id", partial(extract_attribute, "/iq", 'id'))), + # Respond to the request + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='first_version']/version:query/version:name[text()='e2e test (through the biboumi gateway) 1.0 Fedora']"), + + # Now join the same room, from the same bare JID, behind the same nick + partial(send_stanza, + ""), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_two}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_two}']/subject[not(text())]"), + + # And re-send a self ping + partial(send_stanza, + ""), + # We receive our own request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]", + after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), + partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_two}'][@id='second_version']"), + + # And do exactly the same thing, but initiated by the other resource + partial(send_stanza, + ""), + # We receive our own request. Note that we don't know the to value, it could be one of our two resources. + partial(expect_stanza, + "/iq[@from='{lower_nick_one}!{irc_server_one}'][@type='get'][@to]", + after = (partial(save_value, "to", partial(extract_attribute, "/iq", "to")), + partial(save_value, "id", partial(extract_attribute, "/iq", "id")))), + # Respond to the request, using the extracted 'to' value as our 'from' + partial(send_stanza, + "e2e test1.0Fedora"), + partial(expect_stanza, + "/iq[@from='#foo%{irc_server_one}/{nick_one}'][@type='result'][@to='{jid_one}/{resource_one}'][@id='second_version']"), + ]), ) failures = 0 -- cgit v1.2.3 From 0cf7fac20217aaffc7e7d0b818a55ebd30ecdc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 3 Aug 2016 15:17:14 +0200 Subject: Be verbose with make check --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7bccd7..73eb02d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -215,7 +215,7 @@ if(NOT EXISTS ${CMAKE_SOURCE_DIR}/tests/catch.hpp) ) add_dependencies(test_suite catch) endif() -add_custom_target(check COMMAND "test_suite" +add_custom_target(check COMMAND "test_suite" "-s" DEPENDS test_suite biboumi) add_custom_target(e2e COMMAND "python3" "${CMAKE_CURRENT_SOURCE_DIR}/tests/end_to_end/" DEPENDS biboumi) -- cgit v1.2.3 From 0f14fe83ef53b08bd8fa09670c82f4996c329bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Thu, 4 Aug 2016 11:32:45 +0200 Subject: Release 3.0 --- CHANGELOG.rst | 4 ++-- CMakeLists.txt | 2 +- packaging/biboumi.spec.cmake | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5c1a08c..75a96cc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -Version 3.0 -=========== +Version 3.0 - 2016-08-03 +======================== - Support multiple-nick sessions: a user can join an IRC channel behind one single nick, using multiple different clients, at the same time (as diff --git a/CMakeLists.txt b/CMakeLists.txt index 73eb02d..d6d5ce8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(biboumi) set(${PROJECT_NAME}_VERSION_MAJOR 3) set(${PROJECT_NAME}_VERSION_MINOR 0) -set(${PROJECT_NAME}_VERSION_SUFFIX "~dev") +set(${PROJECT_NAME}_VERSION_SUFFIX "") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y -pedantic -Wall -Wextra") if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") diff --git a/packaging/biboumi.spec.cmake b/packaging/biboumi.spec.cmake index f38f4c4..c94da8b 100644 --- a/packaging/biboumi.spec.cmake +++ b/packaging/biboumi.spec.cmake @@ -59,8 +59,8 @@ make check %{?_smp_mflags} %changelog -* ${RPM_DATE} Le Coz Florent - ${RPM_VERSION}-1 -- Build latest git revision +* Thu Aug 4 2016 Le Coz Florent - 3.0-1 +- Update to 3.0 sources * Wed Jan 13 2016 Le Coz Florent - 2.0-2 - Do not install the systemd unit and configuration files, because -- cgit v1.2.3