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 (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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 (limited to 'src') 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 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 --- 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 +- 9 files changed, 226 insertions(+), 226 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 (limited to 'src') 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. --- src/xmpp/xmpp_parser.cpp | 103 ++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_parser.hpp | 107 +++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.cpp | 127 +++++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_stanza.hpp | 106 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 443 insertions(+) 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 (limited to 'src') 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 --- src/xmpp/xmpp_component.cpp | 137 ++++++++++++++++++++++++++++++++++++++++++++ src/xmpp/xmpp_component.hpp | 80 ++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 src/xmpp/xmpp_component.cpp create mode 100644 src/xmpp/xmpp_component.hpp (limited to 'src') 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(-) (limited to 'src') 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 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. --- 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 +++++++++++++-- 16 files changed, 682 insertions(+), 11 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 (limited to 'src') 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 (limited to 'src') 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 (limited to 'src') 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 --- src/main.cpp | 29 +++-------- src/test.cpp | 43 +++++++++++++++ src/utils/binary.hpp | 16 ++++++ src/utils/encoding.cpp | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ src/utils/encoding.hpp | 21 ++++++++ 5 files changed, 226 insertions(+), 22 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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. --- 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 ++++++++++++--- 6 files changed, 135 insertions(+), 57 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/utils/encoding.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src') 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 --- src/utils/encoding.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') 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 --- src/utils/encoding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/config/config.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/config/config.hpp | 111 +++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 27 +++++++++-- src/test.cpp | 26 +++++++++- 4 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 src/config/config.cpp create mode 100644 src/config/config.hpp (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 ++++- 7 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 src/utils/split.cpp (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/network/poller.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/irc/irc_client.cpp | 1 + src/logger/logger.cpp | 41 +++++++++++++++++++++++++++++ src/logger/logger.hpp | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/test.cpp | 9 +++++++ 4 files changed, 121 insertions(+) create mode 100644 src/logger/logger.cpp create mode 100644 src/logger/logger.hpp (limited to 'src') 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 --- 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 - 9 files changed, 27 insertions(+), 44 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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 --- src/utils/tolower.cpp | 13 +++++++++++++ src/utils/tolower.hpp | 9 +-------- 2 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 src/utils/tolower.cpp (limited to 'src') 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 --- 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 ++++- 9 files changed, 89 insertions(+), 11 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 ++++++ 10 files changed, 173 insertions(+), 4 deletions(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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 --- src/utils/sha1.cpp | 154 ++++++++++++++++++++++++++++++++++++++++++++ src/utils/sha1.hpp | 35 ++++++++++ src/xmpp/xmpp_component.cpp | 25 +++---- 3 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 src/utils/sha1.cpp create mode 100644 src/utils/sha1.hpp (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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 --- src/config/config.cpp | 1 - src/main.cpp | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 8 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 --- src/config.h.cmake | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/config.h.cmake (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/config.h.cmake | 1 + src/xmpp/xmpp_component.cpp | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 --- 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 - 7 files changed, 85 deletions(-) delete mode 100644 src/utils/make_unique.hpp (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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 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 --- src/test.cpp | 11 ++++++++--- src/xmpp/xmpp_component.cpp | 10 +++++++--- src/xmpp/xmpp_component.hpp | 2 -- 3 files changed, 15 insertions(+), 8 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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 --- 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 +- 6 files changed, 346 insertions(+), 60 deletions(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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(+) (limited to 'src') 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 --- src/xmpp/xmpp_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 ++++- 7 files changed, 253 insertions(+), 89 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/config.h.cmake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to '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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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(+) (limited to 'src') 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 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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 (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/bridge/bridge.cpp | 11 +++++++++++ src/bridge/bridge.hpp | 6 ++++++ src/xmpp/xmpp_component.cpp | 5 +++++ 3 files changed, 22 insertions(+) (limited to 'src') 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 --- src/config.h.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 27 +++++++++++++++++++++++++-- src/irc/irc_client.hpp | 4 ++++ 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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”. --- 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 ++++++ 5 files changed, 36 insertions(+), 4 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 +- 10 files changed, 486 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 (limited to 'src') 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 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 --- 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 ++- 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/utils/reload.cpp create mode 100644 src/utils/reload.hpp (limited to 'src') 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 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(-) (limited to 'src') 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 --- 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 ++++++++ 6 files changed, 180 insertions(+), 37 deletions(-) create mode 100644 src/utils/empty_if_fixed_server.cpp create mode 100644 src/utils/empty_if_fixed_server.hpp (limited to 'src') 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 --- src/irc/iid.cpp | 5 +---- src/test.cpp | 10 +++++----- src/utils/empty_if_fixed_server.cpp | 8 -------- 3 files changed, 6 insertions(+), 17 deletions(-) delete mode 100644 src/utils/empty_if_fixed_server.cpp (limited to 'src') 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(-) (limited to 'src') 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… --- src/logger/logger.hpp | 2 +- src/xmpp/xmpp_component.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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 (limited to 'src') 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 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 --- 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 --------- 44 files changed, 695 insertions(+), 4558 deletions(-) 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 (limited to 'src') 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 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 --- 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 --------------------------- 6 files changed, 859 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 (limited to 'src') 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 (limited to 'src') 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 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(+) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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 --- src/irc/iid.cpp | 2 ++ src/test.cpp | 8 ++++++++ 2 files changed, 10 insertions(+) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 +++++ 5 files changed, 23 insertions(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/test.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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. --- src/main.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'src') 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 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(-) (limited to 'src') 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. --- src/xmpp/biboumi_component.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') 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(+) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src') 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 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(-) (limited to 'src') 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) --- src/bridge/colors.cpp | 8 +------- src/xmpp/biboumi_adhoc_commands.cpp | 12 ------------ src/xmpp/biboumi_component.cpp | 12 ------------ 3 files changed, 1 insertion(+), 31 deletions(-) (limited to 'src') 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 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 --- src/bridge/colors.cpp | 12 +++++++----- src/xmpp/biboumi_adhoc_commands.cpp | 12 ++++++------ src/xmpp/biboumi_component.cpp | 24 ++++++++++++------------ 3 files changed, 25 insertions(+), 23 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/main.cpp | 16 ++-------------- src/test.cpp | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) (limited to 'src') 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 --- src/main.cpp | 2 +- src/test.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src') 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 --- src/database/database.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++++ src/database/database.hpp | 47 +++++++++++++++++++++++++++++++++++++++++ src/test.cpp | 37 +++++++++++++++++++++++++++++++- 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/database/database.cpp create mode 100644 src/database/database.hpp (limited to 'src') 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 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 --- src/xmpp/biboumi_component.cpp | 39 ++++++++++++++++++++++++++++++++------- src/xmpp/biboumi_component.hpp | 2 ++ 2 files changed, 34 insertions(+), 7 deletions(-) (limited to 'src') 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 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(+) (limited to 'src') 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 --- src/test.cpp | 8 +++ src/xmpp/biboumi_adhoc_commands.cpp | 97 +++++++++++++++++++++++++++++++++++++ src/xmpp/biboumi_adhoc_commands.hpp | 3 ++ src/xmpp/biboumi_component.cpp | 7 +++ 4 files changed, 115 insertions(+) (limited to 'src') 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(+) (limited to 'src') 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 --- src/irc/irc_client.cpp | 22 +++++++++--- src/test.cpp | 10 ------ src/xmpp/biboumi_adhoc_commands.cpp | 67 ++++++++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 27 deletions(-) (limited to 'src') 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 --- src/irc/irc_client.cpp | 7 +++++++ src/xmpp/biboumi_adhoc_commands.cpp | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 --- src/irc/irc_client.cpp | 10 ++++++++++ src/xmpp/biboumi_adhoc_commands.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/irc/irc_client.cpp | 14 +++++++---- src/xmpp/biboumi_adhoc_commands.cpp | 48 ++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 27 deletions(-) (limited to 'src') 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 --- src/bridge/bridge.cpp | 19 +++++++++++++++---- src/bridge/bridge.hpp | 5 +++-- src/irc/irc_client.cpp | 8 +++++--- src/irc/irc_client.hpp | 4 +++- 4 files changed, 26 insertions(+), 10 deletions(-) (limited to 'src') 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 --- src/test.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 1 - src/xmpp/biboumi_component.cpp | 2 +- src/xmpp/biboumi_component.hpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'src') 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 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 --- src/test.cpp | 551 ----------------------------------------------------------- 1 file changed, 551 deletions(-) delete mode 100644 src/test.cpp (limited to 'src') 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; -} -- 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- src/bridge/bridge.cpp | 8 +++++--- src/irc/irc_client.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/irc/irc_client.hpp | 17 ++++++++++++++++- 3 files changed, 63 insertions(+), 6 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 11 +++++++++++ src/irc/irc_client.hpp | 3 +++ src/xmpp/biboumi_adhoc_commands.cpp | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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. --- src/irc/irc_client.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') 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 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 --- src/xmpp/biboumi_component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') 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 --- 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 +- 6 files changed, 181 insertions(+), 4 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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 --- src/xmpp/biboumi_adhoc_commands.cpp | 34 +++++++++++++++++----------------- src/xmpp/biboumi_adhoc_commands.hpp | 14 +++++++------- src/xmpp/biboumi_component.cpp | 2 +- 3 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src') 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(+) (limited to 'src') 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 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. --- src/database/database.cpp | 33 +++++++++++++++++++++++++++++++++ src/database/database.hpp | 6 ++++++ 2 files changed, 39 insertions(+) (limited to 'src') 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(); -- 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(-) (limited to 'src') 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(+) (limited to 'src') 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 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 --- src/bridge/bridge.cpp | 40 ++++++++++++++++++++++++++++++++++------ src/bridge/bridge.hpp | 4 ++-- 2 files changed, 36 insertions(+), 8 deletions(-) (limited to 'src') 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); /** -- 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 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'src') 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): -- 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 --- src/irc/irc_client.cpp | 7 +++++++ src/xmpp/biboumi_adhoc_commands.cpp | 22 +++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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 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 --- 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 ++++ 5 files changed, 23 insertions(+), 5 deletions(-) (limited to 'src') 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 */ -- 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 --- src/xmpp/biboumi_adhoc_commands.cpp | 14 ++++++++++---- src/xmpp/biboumi_component.cpp | 11 ++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'src') 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() = { -- 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 --- 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 +++++----- 11 files changed, 49 insertions(+), 65 deletions(-) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') 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 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 +- 2 files changed, 7 insertions(+), 5 deletions(-) (limited to 'src') 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); -- 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(-) (limited to 'src') 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 --- src/bridge/bridge.hpp | 2 +- src/xmpp/biboumi_component.cpp | 4 ++-- src/xmpp/biboumi_component.hpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) (limited to 'src') 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. */ -- 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 --- src/irc/irc_client.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'src') 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) -- 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 --- src/main.cpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'src') 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"); -- 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(-) (limited to 'src') 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 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 --- src/irc/irc_client.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src') 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; } -- 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 --- src/irc/irc_client.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') 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})); -- 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(+) (limited to 'src') 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(-) (limited to 'src') 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(+) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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 --- src/bridge/bridge.cpp | 3 +-- src/xmpp/biboumi_component.cpp | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'src') 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(-) (limited to 'src') 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(-) (limited to 'src') 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 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 + 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') 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. -- 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(-) (limited to 'src') 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(-) (limited to 'src') 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 --- 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 +++--- 12 files changed, 36 insertions(+), 36 deletions(-) (limited to 'src') 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 + -- 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(-) (limited to 'src') 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 --- src/database/database.cpp | 33 +++++++++++++++------------------ src/database/database.hpp | 6 +++--- src/main.cpp | 19 +++++++++---------- 3 files changed, 27 insertions(+), 31 deletions(-) (limited to 'src') 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 -- 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 --- src/utils/reload.cpp | 28 ++++++++++++++++++++++++++++ src/utils/reload.hpp | 4 ++++ 2 files changed, 32 insertions(+) create mode 100644 src/utils/reload.cpp create mode 100644 src/utils/reload.hpp (limited to 'src') 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 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(+) (limited to 'src') 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 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 --- src/main.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src') 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 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(-) (limited to 'src') 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 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(+) (limited to 'src') 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(-) (limited to 'src') 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 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(-) (limited to 'src') 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