diff options
Diffstat (limited to 'src')
26 files changed, 541 insertions, 406 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 23ecfe9..e0cb36d 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -378,7 +378,7 @@ void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& } else if (message.command == "472" && message.arguments.size() >= 2) { - std::string error_message = "Unknown mode: "s + message.arguments[1]; + std::string error_message = "Unknown mode: " + message.arguments[1]; if (message.arguments.size() >= 3) error_message = message.arguments[2]; this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed", @@ -436,9 +436,14 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con // acknowledgment from the server bool persistent = false; #ifdef USE_DATABASE - const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, - iid.get_server(), iid.get_local()); - persistent = coptions.col<Database::Persistent>(); + const auto goptions = Database::get_global_options(this->user_jid); + if (goptions.col<Database::Persistent>()) + persistent = true; + else + { + const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, iid.get_server(), iid.get_local()); + persistent = coptions.col<Database::Persistent>(); + } #endif if (channel->joined && !channel->parting && !persistent) { @@ -450,8 +455,10 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con } else if (channel->joined) { - this->send_muc_leave(iid, channel->get_self()->nick, "", true, resource); + this->send_muc_leave(iid, channel->get_self()->nick, "", true, true, resource); } + if (persistent) + this->remove_resource_from_chan(key, resource); // 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()); @@ -460,8 +467,8 @@ void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, con { if (channel && channel->joined) this->send_muc_leave(iid, channel->get_self()->nick, - "Biboumi note: "s + std::to_string(resources - 1) + " resources are still in this channel.", - true, resource); + "Biboumi note: " + std::to_string(resources - 1) + " resources are still in this channel.", + true, true, resource); this->remove_resource_from_chan(key, resource); if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0) this->remove_resource_from_server(iid.get_server(), resource); @@ -697,12 +704,12 @@ void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, c { std::string result(name + " " + version + " " + os); - this->send_private_message(iid, "\01VERSION "s + result + "\01", "NOTICE"); + this->send_private_message(iid, "\01VERSION " + 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) + "\01", "NOTICE"); + this->send_private_message(iid, "\01PING " + utils::revstr(id) + "\01", "NOTICE"); } void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick, @@ -851,7 +858,6 @@ void Bridge::send_message(const Iid& iid, const std::string& nick, const std::st } else { - std::string target = std::to_string(iid); const auto it = this->preferred_user_from.find(iid.get_local()); if (it != this->preferred_user_from.end()) { @@ -878,16 +884,17 @@ void Bridge::send_presence_error(const Iid& iid, const std::string& nick, void Bridge::send_muc_leave(const Iid& iid, const std::string& nick, const std::string& message, const bool self, + const bool user_requested, const std::string& resource) { if (!resource.empty()) this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message), - this->user_jid + "/" + resource, self); + this->user_jid + "/" + resource, self, user_requested); else { for (const auto &res: this->resources_in_chan[iid.to_tuple()]) this->xmpp.send_muc_leave(std::to_string(iid), nick, this->make_xmpp_body(message), - this->user_jid + "/" + res, self); + this->user_jid + "/" + res, self, user_requested); if (self) this->remove_all_resources_from_chan(iid.to_tuple()); @@ -918,7 +925,7 @@ void Bridge::send_xmpp_message(const std::string& from, const std::string& autho if (!author.empty()) { IrcUser user(author); - body = "\u000303"s + user.nick + (user.host.empty()? + body = "\u000303" + user.nick + (user.host.empty()? "\u0003: ": (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg; } @@ -1076,6 +1083,16 @@ void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author) this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author); } +void Bridge::on_irc_client_connected(const std::string& hostname) +{ + this->xmpp.on_irc_client_connected(hostname, this->user_jid); +} + +void Bridge::on_irc_client_disconnected(const std::string& hostname) +{ + this->xmpp.on_irc_client_disconnected(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); diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp index 033291c..c10631b 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -169,7 +169,10 @@ public: /** * Send an unavailable presence from this participant */ - void send_muc_leave(const Iid& iid, const std::string& nick, const std::string& message, const bool self, const std::string& resource = ""); + void send_muc_leave(const Iid& iid, const std::string& nick, + const std::string& message, const bool self, + const bool user_requested, + 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 @@ -198,6 +201,8 @@ public: void send_xmpp_ping_request(const std::string& nick, const std::string& hostname, const std::string& id); void send_xmpp_invitation(const Iid& iid, const std::string& author); + void on_irc_client_connected(const std::string& hostname); + void on_irc_client_disconnected(const std::string& hostname); /** * Misc @@ -298,8 +303,8 @@ private: using ChannelKey = std::tuple<ChannelName, IrcHostname>; public: std::map<ChannelKey, std::set<Resource>> resources_in_chan; -private: std::map<IrcHostname, std::set<Resource>> resources_in_server; +private: /** * Manage which resource is in which channel */ diff --git a/src/database/count_query.hpp b/src/database/count_query.hpp index b7bbf51..0dde63c 100644 --- a/src/database/count_query.hpp +++ b/src/database/count_query.hpp @@ -29,7 +29,6 @@ struct CountQuery: public Query if (sqlite3_step(statement.get()) != SQLITE_DONE) log_warning("Count request returned more than one result."); - log_debug("Returning count: ", res); return res; } }; diff --git a/src/database/database.cpp b/src/database/database.cpp index 92f7682..85c675e 100644 --- a/src/database/database.cpp +++ b/src/database/database.cpp @@ -13,6 +13,8 @@ Database::MucLogLineTable Database::muc_log_lines("MucLogLine_"); Database::GlobalOptionsTable Database::global_options("GlobalOptions_"); Database::IrcServerOptionsTable Database::irc_server_options("IrcServerOptions_"); Database::IrcChannelOptionsTable Database::irc_channel_options("IrcChannelOptions_"); +Database::RosterTable Database::roster("roster"); + void Database::open(const std::string& filename) { @@ -36,6 +38,8 @@ void Database::open(const std::string& filename) Database::irc_server_options.upgrade(Database::db); Database::irc_channel_options.create(Database::db); Database::irc_channel_options.upgrade(Database::db); + Database::roster.create(Database::db); + Database::roster.upgrade(Database::db); } @@ -177,6 +181,51 @@ std::vector<Database::MucLogLine> Database::get_muc_logs(const std::string& owne return {result.crbegin(), result.crend()}; } +void Database::add_roster_item(const std::string& local, const std::string& remote) +{ + auto roster_item = Database::roster.row(); + + roster_item.col<Database::LocalJid>() = local; + roster_item.col<Database::RemoteJid>() = remote; + + roster_item.save(Database::db); +} + +void Database::delete_roster_item(const std::string& local, const std::string& remote) +{ + Query query("DELETE FROM "s + Database::roster.get_name()); + query << " WHERE " << Database::RemoteJid{} << "=" << remote << \ + " AND " << Database::LocalJid{} << "=" << local; + + query.execute(Database::db); +} + +bool Database::has_roster_item(const std::string& local, const std::string& remote) +{ + auto query = Database::roster.select(); + query.where() << Database::LocalJid{} << "=" << local << \ + " and " << Database::RemoteJid{} << "=" << remote; + + auto res = query.execute(Database::db); + + return !res.empty(); +} + +std::vector<Database::RosterItem> Database::get_contact_list(const std::string& local) +{ + auto query = Database::roster.select(); + query.where() << Database::LocalJid{} << "=" << local; + + return query.execute(Database::db); +} + +std::vector<Database::RosterItem> Database::get_full_roster() +{ + auto query = Database::roster.select(); + + return query.execute(Database::db); +} + void Database::close() { sqlite3_close_v2(Database::db); @@ -192,4 +241,4 @@ std::string Database::gen_uuid() return uuid_str; } -#endif
\ No newline at end of file +#endif diff --git a/src/database/database.hpp b/src/database/database.hpp index 28b6b1b..c00c938 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -20,85 +20,67 @@ class Database public: using time_point = std::chrono::system_clock::time_point; - struct Uuid: Column<std::string> { static constexpr auto name = "uuid_"; - static constexpr auto options = ""; }; + struct Uuid: Column<std::string> { static constexpr auto name = "uuid_"; }; - struct Owner: Column<std::string> { static constexpr auto name = "owner_"; - static constexpr auto options = ""; }; + struct Owner: Column<std::string> { static constexpr auto name = "owner_"; }; - struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_"; - static constexpr auto options = ""; }; + struct IrcChanName: Column<std::string> { static constexpr auto name = "ircChanName_"; }; - struct Channel: Column<std::string> { static constexpr auto name = "channel_"; - static constexpr auto options = ""; }; + struct Channel: Column<std::string> { static constexpr auto name = "channel_"; }; - struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_"; - static constexpr auto options = ""; }; + struct IrcServerName: Column<std::string> { static constexpr auto name = "ircServerName_"; }; - struct Server: Column<std::string> { static constexpr auto name = "server_"; - static constexpr auto options = ""; }; + struct Server: Column<std::string> { static constexpr auto name = "server_"; }; - struct Date: Column<time_point::rep> { static constexpr auto name = "date_"; - static constexpr auto options = ""; }; + struct Date: Column<time_point::rep> { static constexpr auto name = "date_"; }; - struct Body: Column<std::string> { static constexpr auto name = "body_"; - static constexpr auto options = ""; }; + struct Body: Column<std::string> { static constexpr auto name = "body_"; }; - struct Nick: Column<std::string> { static constexpr auto name = "nick_"; - static constexpr auto options = ""; }; + struct Nick: Column<std::string> { static constexpr auto name = "nick_"; }; - struct Pass: Column<std::string> { static constexpr auto name = "pass_"; - static constexpr auto options = ""; }; + struct Pass: Column<std::string> { static constexpr auto name = "pass_"; }; struct Ports: Column<std::string> { static constexpr auto name = "ports_"; - static constexpr auto options = ""; Ports(): Column<std::string>("6667") {} }; struct TlsPorts: Column<std::string> { static constexpr auto name = "tlsPorts_"; - static constexpr auto options = ""; TlsPorts(): Column<std::string>("6697;6670") {} }; - struct Username: Column<std::string> { static constexpr auto name = "username_"; - static constexpr auto options = ""; }; + struct Username: Column<std::string> { static constexpr auto name = "username_"; }; - struct Realname: Column<std::string> { static constexpr auto name = "realname_"; - static constexpr auto options = ""; }; + struct Realname: Column<std::string> { static constexpr auto name = "realname_"; }; - struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_"; - static constexpr auto options = ""; }; + struct AfterConnectionCommand: Column<std::string> { static constexpr auto name = "afterConnectionCommand_"; }; - struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_"; - static constexpr auto options = ""; }; + struct TrustedFingerprint: Column<std::string> { static constexpr auto name = "trustedFingerprint_"; }; - struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_"; - static constexpr auto options = ""; }; + struct EncodingOut: Column<std::string> { static constexpr auto name = "encodingOut_"; }; - struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_"; - static constexpr auto options = ""; }; + struct EncodingIn: Column<std::string> { static constexpr auto name = "encodingIn_"; }; struct MaxHistoryLength: Column<int> { static constexpr auto name = "maxHistoryLength_"; - static constexpr auto options = ""; MaxHistoryLength(): Column<int>(20) {} }; struct RecordHistory: Column<bool> { static constexpr auto name = "recordHistory_"; - static constexpr auto options = ""; RecordHistory(): Column<bool>(true) {}}; - struct RecordHistoryOptional: Column<OptionalBool> { static constexpr auto name = "recordHistory_"; - static constexpr auto options = ""; }; + struct RecordHistoryOptional: Column<OptionalBool> { static constexpr auto name = "recordHistory_"; }; struct VerifyCert: Column<bool> { static constexpr auto name = "verifyCert_"; - static constexpr auto options = ""; VerifyCert(): Column<bool>(true) {} }; struct Persistent: Column<bool> { static constexpr auto name = "persistent_"; - static constexpr auto options = ""; Persistent(): Column<bool>(false) {} }; + struct LocalJid: Column<std::string> { static constexpr auto name = "local"; }; + + struct RemoteJid: Column<std::string> { static constexpr auto name = "remote"; }; + + using MucLogLineTable = Table<Id, Uuid, Owner, IrcChanName, IrcServerName, Date, Body, Nick>; using MucLogLine = MucLogLineTable::RowType; - using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory>; + using GlobalOptionsTable = Table<Id, Owner, MaxHistoryLength, RecordHistory, Persistent>; using GlobalOptions = GlobalOptionsTable::RowType; using IrcServerOptionsTable = Table<Id, Owner, Server, Pass, AfterConnectionCommand, TlsPorts, Ports, Username, Realname, VerifyCert, TrustedFingerprint, EncodingOut, EncodingIn, MaxHistoryLength>; @@ -107,6 +89,9 @@ class Database using IrcChannelOptionsTable = Table<Id, Owner, Server, Channel, EncodingOut, EncodingIn, MaxHistoryLength, Persistent, RecordHistoryOptional>; using IrcChannelOptions = IrcChannelOptionsTable::RowType; + using RosterTable = Table<LocalJid, RemoteJid>; + using RosterItem = RosterTable::RowType; + Database() = default; ~Database() = default; @@ -132,6 +117,12 @@ class Database static std::string store_muc_message(const std::string& owner, const std::string& chan_name, const std::string& server_name, time_point date, const std::string& body, const std::string& nick); + static void add_roster_item(const std::string& local, const std::string& remote); + static bool has_roster_item(const std::string& local, const std::string& remote); + static void delete_roster_item(const std::string& local, const std::string& remote); + static std::vector<Database::RosterItem> get_contact_list(const std::string& local); + static std::vector<Database::RosterItem> get_full_roster(); + static void close(); static void open(const std::string& filename); @@ -146,6 +137,7 @@ class Database static GlobalOptionsTable global_options; static IrcServerOptionsTable irc_server_options; static IrcChannelOptionsTable irc_channel_options; + static RosterTable roster; static sqlite3* db; private: diff --git a/src/database/insert_query.hpp b/src/database/insert_query.hpp index 9e410ce..2ece69d 100644 --- a/src/database/insert_query.hpp +++ b/src/database/insert_query.hpp @@ -20,8 +20,6 @@ actual_bind(Statement& statement, std::vector<std::string>& params, const std::t params.erase(params.begin()); if (sqlite3_bind_text(statement.get(), N + 1, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) != SQLITE_OK) log_error("Failed to bind ", value, " to param ", N); - else - log_debug("Bound (not id) [", value, "] to ", N); } template <int N, typename ColumnType, typename... T> @@ -36,8 +34,6 @@ actual_bind(Statement& statement, std::vector<std::string>&, const std::tuple<T. } else if (sqlite3_bind_null(statement.get(), N + 1) != SQLITE_OK) log_error("Failed to bind NULL to param ", N); - else - log_debug("Bound NULL to ", N); } struct InsertQuery: public Query diff --git a/src/database/query.hpp b/src/database/query.hpp index f103fe9..6e1db12 100644 --- a/src/database/query.hpp +++ b/src/database/query.hpp @@ -23,7 +23,6 @@ struct Query Statement prepare(sqlite3* db) { sqlite3_stmt* stmt; - log_debug(this->body); auto res = sqlite3_prepare(db, this->body.data(), static_cast<int>(this->body.size()) + 1, &stmt, nullptr); if (res != SQLITE_OK) @@ -36,9 +35,7 @@ struct Query for (const std::string& param: this->params) { if (sqlite3_bind_text(statement.get(), i, param.data(), static_cast<int>(param.size()), SQLITE_TRANSIENT) != SQLITE_OK) - log_debug("Failed to bind ", param, " to param ", i); - else - log_debug("Bound ", param, " to ", i); + log_error("Failed to bind ", param, " to param ", i); i++; } diff --git a/src/database/row.hpp b/src/database/row.hpp index e7a58c4..2b50874 100644 --- a/src/database/row.hpp +++ b/src/database/row.hpp @@ -17,9 +17,7 @@ typename std::enable_if<std::is_same<std::decay_t<ColumnType>, Id>::value, void> update_id(std::tuple<T...>& columns, sqlite3* db) { auto&& column = std::get<ColumnType>(columns); - log_debug("Found an autoincrement col."); auto res = sqlite3_last_insert_rowid(db); - log_debug("Value is now: ", res); column.value = static_cast<Id::real_type>(res); } @@ -45,7 +43,7 @@ struct Row {} template <typename Type> - auto& col() + typename Type::real_type& col() { auto&& col = std::get<Type>(this->columns); return col.value; @@ -63,7 +61,6 @@ struct Row InsertQuery query(this->table_name); query.insert_col_names(this->columns); query.insert_values(this->columns); - log_debug(query.body); query.execute(this->columns, db); diff --git a/src/database/select_query.hpp b/src/database/select_query.hpp index f4d71af..872001c 100644 --- a/src/database/select_query.hpp +++ b/src/database/select_query.hpp @@ -79,7 +79,7 @@ struct SelectQuery: public Query using ColumnsType = std::tuple<T...>; using ColumnType = typename std::remove_reference<decltype(std::get<N>(std::declval<ColumnsType>()))>::type; - this->body += " "s + ColumnType::name; + this->body += " " + std::string{ColumnType::name}; if (N < (sizeof...(T) - 1)) this->body += ", "; diff --git a/src/database/table.cpp b/src/database/table.cpp index 5929f33..9224d79 100644 --- a/src/database/table.cpp +++ b/src/database/table.cpp @@ -4,12 +4,10 @@ std::set<std::string> get_all_columns_from_table(sqlite3* db, const std::string& { std::set<std::string> result; char* errmsg; - std::string query{"PRAGMA table_info("s + table_name + ")"}; - log_debug(query); + std::string query{"PRAGMA table_info(" + table_name + ")"}; int res = sqlite3_exec(db, query.data(), [](void* param, int columns_nb, char** columns, char**) -> int { constexpr int name_column = 1; std::set<std::string>* result = static_cast<std::set<std::string>*>(param); - log_debug("Table has column ", columns[name_column]); if (name_column < columns_nb) result->insert(columns[name_column]); return 0; diff --git a/src/database/table.hpp b/src/database/table.hpp index 411ac6a..0060211 100644 --- a/src/database/table.hpp +++ b/src/database/table.hpp @@ -2,7 +2,6 @@ #include <database/select_query.hpp> #include <database/type_to_sql.hpp> -#include <logger/logger.hpp> #include <database/row.hpp> #include <algorithm> @@ -17,8 +16,7 @@ template <typename ColumnType> void add_column_to_table(sqlite3* db, const std::string& table_name) { const std::string name = ColumnType::name; - std::string query{"ALTER TABLE "s + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType<typename ColumnType::real_type>::type}; - log_debug(query); + std::string query{"ALTER TABLE " + table_name + " ADD " + ColumnType::name + " " + TypeToSQLType<typename ColumnType::real_type>::type}; char* error; const auto result = sqlite3_exec(db, query.data(), nullptr, nullptr, &error); if (result != SQLITE_OK) @@ -28,6 +26,17 @@ void add_column_to_table(sqlite3* db, const std::string& table_name) } } + +template <typename ColumnType, decltype(ColumnType::options) = nullptr> +void append_option(std::string& s) +{ + s += " "s + ColumnType::options; +} + +template <typename, typename... Args> +void append_option(Args&& ...) +{ } + template <typename... T> class Table { @@ -55,11 +64,8 @@ class Table this->add_column_create(res); res += ")"; - log_debug(res); - char* error; const auto result = sqlite3_exec(db, res.data(), nullptr, nullptr, &error); - log_debug("result: ", +result); if (result != SQLITE_OK) { log_error("Error executing query: ", error); @@ -110,14 +116,13 @@ class Table str += ColumnType::name; str += " "; str += TypeToSQLType<RealType>::type; - str += " "s + ColumnType::options; + append_option<ColumnType>(str); if (N != sizeof...(T) - 1) str += ","; str += "\n"; add_column_create<N+1>(str); } - template <std::size_t N=0> typename std::enable_if<N == sizeof...(T), void>::type add_column_create(std::string&) diff --git a/src/irc/iid.cpp b/src/irc/iid.cpp index a63a1c3..131c18c 100644 --- a/src/irc/iid.cpp +++ b/src/irc/iid.cpp @@ -35,7 +35,8 @@ Iid::Iid(const std::string& iid, const Bridge *bridge) void Iid::set_type(const std::set<char>& chantypes) { - if (this->local.empty() && this->server.empty()) + if (this->local.empty() && ( + !Config::get("fixed_irc_server", "").empty() || this->server.empty())) this->type = Iid::Type::None; if (this->local.empty()) return; diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index bacb89e..46dbdbe 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -178,7 +178,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" + this->hostname + this->bridge.get_jid()); } void IrcClient::start() @@ -194,7 +194,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 " + this->hostname + ":" + port + " (" + (tls ? "encrypted" : "not encrypted") + ")"); @@ -213,7 +213,7 @@ void IrcClient::start() void IrcClient::on_connection_failed(const std::string& reason) { this->bridge.send_xmpp_message(this->hostname, "", - "Connection failed: "s + reason); + "Connection failed: " + reason); if (this->hostname_resolution_failed) while (!this->ports_to_try.empty()) @@ -260,7 +260,7 @@ void IrcClient::on_connected() { if (this->is_connected()) { - this->on_connection_close("Could not resolve hostname "s + this->user_hostname + + this->on_connection_close("Could not resolve hostname " + this->user_hostname + ": " + error_msg); this->send_quit_command(""); } @@ -297,6 +297,7 @@ void IrcClient::on_connected() #endif this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + "."); this->send_pending_data(); + this->bridge.on_irc_client_connected(this->get_hostname()); } void IrcClient::on_connection_close(const std::string& error_msg) @@ -309,6 +310,7 @@ void IrcClient::on_connection_close(const std::string& error_msg) const IrcMessage error{"ERROR", {message}}; this->on_error(error); log_warning(message); + this->bridge.on_irc_client_disconnected(this->get_hostname()); } IrcChannel* IrcClient::get_channel(const std::string& n) @@ -585,7 +587,7 @@ void IrcClient::on_notice(const IrcMessage& message) // 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}); + IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 " + body}); this->on_channel_message(modified_message); } } @@ -697,7 +699,7 @@ 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); + "/me" + 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 ") @@ -899,7 +901,7 @@ void IrcClient::on_welcome_message(const IrcMessage& message) #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" + this->hostname + this->bridge.get_jid())); std::string channels{}; std::string channels_with_key{}; std::string keys{}; @@ -981,7 +983,7 @@ void IrcClient::on_part(const IrcMessage& message) // channel pointer is now invalid channel = nullptr; } - this->bridge.send_muc_leave(iid, std::move(nick), txt, self); + this->bridge.send_muc_leave(iid, std::move(nick), txt, self, true); } } @@ -999,10 +1001,10 @@ void IrcClient::on_error(const IrcMessage& message) if (!channel->joined) continue; std::string own_nick = channel->get_self()->nick; - this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true); + this->bridge.send_muc_leave(iid, std::move(own_nick), leave_message, true, false); } this->channels.clear(); - this->send_gateway_message("ERROR: "s + leave_message); + this->send_gateway_message("ERROR: " + leave_message); } void IrcClient::on_quit(const IrcMessage& message) @@ -1015,6 +1017,9 @@ void IrcClient::on_quit(const IrcMessage& message) const std::string& chan_name = pair.first; IrcChannel* channel = pair.second.get(); const IrcUser* user = channel->find_user(message.prefix); + bool self = false; + if (user == channel->get_self()) + self = true; if (user) { std::string nick = user->nick; @@ -1023,7 +1028,7 @@ void IrcClient::on_quit(const IrcMessage& message) iid.set_local(chan_name); iid.set_server(this->hostname); iid.type = Iid::Type::Channel; - this->bridge.send_muc_leave(iid, std::move(nick), txt, false); + this->bridge.send_muc_leave(iid, std::move(nick), txt, self, false); } } } @@ -1135,7 +1140,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 " + iid.get_local() + " [" + mode_arguments + "] by " + user.nick, true); const IrcChannel* channel = this->get_channel(iid.get_local()); @@ -1213,7 +1218,7 @@ void IrcClient::on_channel_mode(const IrcMessage& message) void IrcClient::on_user_mode(const IrcMessage& message) { this->bridge.send_xmpp_message(this->hostname, "", - "User mode for "s + message.arguments[0] + + "User mode for " + message.arguments[0] + " is [" + message.arguments[1] + "]"); } @@ -1252,7 +1257,7 @@ void IrcClient::leave_dummy_channel(const std::string& exit_message, const std:: 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, this->chantypes), std::string(this->current_nick), exit_message, true, resource); + this->bridge.send_muc_leave(Iid("%" + this->hostname, this->chantypes), std::string(this->current_nick), exit_message, true, true, resource); } #ifdef BOTAN_FOUND diff --git a/src/network/credentials_manager.cpp b/src/network/credentials_manager.cpp index f93a366..7f07cef 100644 --- a/src/network/credentials_manager.cpp +++ b/src/network/credentials_manager.cpp @@ -54,29 +54,6 @@ void check_tls_certificate(const std::vector<Botan::X509_Certificate>& certs, std::rethrow_exception(exc); } -#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) -void BasicCredentialsManager::verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector<Botan::X509_Certificate>& certs) -{ - log_debug("Checking remote certificate (", type, ") for hostname ", purported_hostname); - try - { - Botan::Credentials_Manager::verify_certificate_chain(type, purported_hostname, certs); - log_debug("Certificate is valid"); - } - catch (const std::exception& tls_exception) - { - log_warning("TLS certificate check failed: ", tls_exception.what()); - std::exception_ptr exception_ptr{}; - if (this->socket_handler->abort_on_invalid_cert()) - exception_ptr = std::current_exception(); - - check_tls_certificate(certs, purported_hostname, this->trusted_fingerprint, exception_ptr); - } -} -#endif - bool BasicCredentialsManager::try_to_open_one_ca_bundle(const std::vector<std::string>& paths) { for (const auto& path: paths) diff --git a/src/network/credentials_manager.hpp b/src/network/credentials_manager.hpp index e7c247d..aa4732a 100644 --- a/src/network/credentials_manager.hpp +++ b/src/network/credentials_manager.hpp @@ -31,11 +31,6 @@ public: BasicCredentialsManager& operator=(const BasicCredentialsManager&) = delete; BasicCredentialsManager& operator=(BasicCredentialsManager&&) = delete; -#if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) - void verify_certificate_chain(const std::string& type, - const std::string& purported_hostname, - const std::vector<Botan::X509_Certificate>&) override final; -#endif std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type, const std::string& context) override final; void set_trusted_fingerprint(const std::string& fingerprint); diff --git a/src/network/tcp_client_socket_handler.cpp b/src/network/tcp_client_socket_handler.cpp index 35f2446..aac13d0 100644 --- a/src/network/tcp_client_socket_handler.cpp +++ b/src/network/tcp_client_socket_handler.cpp @@ -146,7 +146,7 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri || errno == EISCONN) { log_info("Connection success."); - TimedEventsManager::instance().cancel("connection_timeout"s + + TimedEventsManager::instance().cancel("connection_timeout" + std::to_string(this->socket)); this->poller->add_socket_handler(this); this->connected = true; @@ -196,7 +196,7 @@ void TCPClientSocketHandler::connect(const std::string& address, const std::stri TimedEventsManager::instance().add_event( TimedEvent(std::chrono::steady_clock::now() + 5s, std::bind(&TCPClientSocketHandler::on_connection_timeout, this), - "connection_timeout"s + std::to_string(this->socket))); + "connection_timeout" + std::to_string(this->socket))); return ; } log_info("Connection failed:", std::strerror(errno)); @@ -220,7 +220,7 @@ void TCPClientSocketHandler::connect() void TCPClientSocketHandler::close() { - TimedEventsManager::instance().cancel("connection_timeout"s + + TimedEventsManager::instance().cancel("connection_timeout" + std::to_string(this->socket)); TCPSocketHandler::close(); diff --git a/src/network/tcp_socket_handler.cpp b/src/network/tcp_socket_handler.cpp index 1049375..6239162 100644 --- a/src/network/tcp_socket_handler.cpp +++ b/src/network/tcp_socket_handler.cpp @@ -237,14 +237,7 @@ void TCPSocketHandler::start_tls(const std::string& address, const std::string& this->policy.load(policy_directory + "policy.txt"); this->policy.load(policy_directory + address + ".policy.txt"); this->tls = std::make_unique<Botan::TLS::Client>( -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) *this, -# else - [this](const Botan::byte* data, size_t size) { this->tls_emit_data(data, size); }, - [this](const Botan::byte* data, size_t size) { this->tls_record_received(0, data, size); }, - [this](Botan::TLS::Alert alert, const Botan::byte*, size_t) { this->tls_alert(alert); }, - [this](const Botan::TLS::Session& session) { return this->tls_session_established(session); }, -# endif get_session_manager(), this->credential_manager, this->policy, get_rng(), server_info, Botan::TLS::Protocol_Version::latest_tls_version()); } @@ -327,7 +320,6 @@ bool TCPSocketHandler::tls_session_established(const Botan::TLS::Session& sessio return true; } -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, const std::vector<Botan::Certificate_Store*>& trusted_roots, @@ -350,7 +342,6 @@ void TCPSocketHandler::tls_verify_cert_chain(const std::vector<Botan::X509_Certi check_tls_certificate(cert_chain, hostname, this->credential_manager.get_trusted_fingerprint(), exception_ptr); } } -#endif void TCPSocketHandler::on_tls_activated() { diff --git a/src/network/tcp_socket_handler.hpp b/src/network/tcp_socket_handler.hpp index f68698e..5cef739 100644 --- a/src/network/tcp_socket_handler.hpp +++ b/src/network/tcp_socket_handler.hpp @@ -25,22 +25,14 @@ # include <botan/tls_session_manager.h> # include <network/tls_policy.hpp> -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) -# define BOTAN_TLS_CALLBACKS_OVERRIDE override final -# else -# define BOTAN_TLS_CALLBACKS_OVERRIDE -# endif #endif - /** * Does all the read/write, buffering etc. With optional tls. * But doesn’t do any connect() or accept() or anything else. */ class TCPSocketHandler: public SocketHandler #ifdef BOTAN_FOUND -# if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,32) ,public Botan::TLS::Callbacks -# endif #endif { protected: @@ -146,31 +138,29 @@ private: * Called by the tls object that some data has been decrypt. We call * parse_in_buffer() to handle that unencrypted data. */ - void tls_record_received(uint64_t rec_no, const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; + void tls_record_received(uint64_t rec_no, const Botan::byte* data, size_t size) override final; /** * 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_emit_data(const Botan::byte* data, size_t size) BOTAN_TLS_CALLBACKS_OVERRIDE; + void tls_emit_data(const Botan::byte* data, size_t size) override final; /** * 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(Botan::TLS::Alert alert) BOTAN_TLS_CALLBACKS_OVERRIDE; + void tls_alert(Botan::TLS::Alert alert) override final; /** * 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_session_established(const Botan::TLS::Session& session) BOTAN_TLS_CALLBACKS_OVERRIDE; + bool tls_session_established(const Botan::TLS::Session& session) override final; -#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,11,34) void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain, const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& ocsp_responses, const std::vector<Botan::Certificate_Store*>& trusted_roots, Botan::Usage_Type usage, const std::string& hostname, - const Botan::TLS::Policy& policy) BOTAN_TLS_CALLBACKS_OVERRIDE; -#endif + const Botan::TLS::Policy& policy) override final; /** * Called whenever the tls session goes from inactive to active. This * means that the handshake has just been successfully done, and we can diff --git a/src/utils/sha1.cpp b/src/utils/sha1.cpp index 2e6efc2..1a0d185 100644 --- a/src/utils/sha1.cpp +++ b/src/utils/sha1.cpp @@ -18,13 +18,7 @@ std::string sha1(const std::string& input) { #ifdef BOTAN_FOUND -# if BOTAN_VERSION_CODE < BOTAN_VERSION_CODE_FOR(1,11,34) - auto sha1 = Botan::HashFunction::create("SHA-1"); - if (!sha1) - throw Botan::Algorithm_Not_Found("SHA-1"); -# else auto sha1 = Botan::HashFunction::create_or_throw("SHA-1"); -# endif sha1->update(input); return Botan::hex_encode(sha1->final(), false); #endif diff --git a/src/xmpp/adhoc_command.cpp b/src/xmpp/adhoc_command.cpp index e02bf35..fbf4ce2 100644 --- a/src/xmpp/adhoc_command.cpp +++ b/src/xmpp/adhoc_command.cpp @@ -59,7 +59,7 @@ void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) command_node.delete_all_children(); XmlSubNode note(command_node, "note"); note["type"] = "info"; - note.set_inner("Hello "s + value_str + "!"s); + note.set_inner("Hello " + value_str + "!"s); return; } } diff --git a/src/xmpp/adhoc_commands_handler.cpp b/src/xmpp/adhoc_commands_handler.cpp index 040d0ff..e4dcd5c 100644 --- a/src/xmpp/adhoc_commands_handler.cpp +++ b/src/xmpp/adhoc_commands_handler.cpp @@ -19,7 +19,7 @@ void AdhocCommandsHandler::add_command(std::string name, AdhocCommand command) { const auto found = this->commands.find(name); if (found != this->commands.end()) - throw std::runtime_error("Trying to add an ad-hoc command that already exist: "s + name); + throw std::runtime_error("Trying to add an ad-hoc command that already exist: " + name); this->commands.emplace(std::make_pair(std::move(name), std::move(command))); } @@ -59,7 +59,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co std::forward_as_tuple(command_it->second, executor_jid, to)); TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 3600s, std::bind(&AdhocCommandsHandler::remove_session, this, sessionid, executor_jid), - "adhocsession"s + sessionid + executor_jid)); + "adhocsession" + sessionid + executor_jid)); } auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid)); if ((session_it != this->sessions.end()) && @@ -74,7 +74,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co { this->sessions.erase(session_it); command_node["status"] = "completed"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + TimedEventsManager::instance().cancel("adhocsession" + sessionid + executor_jid); } else { @@ -87,7 +87,7 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co { this->sessions.erase(session_it); command_node["status"] = "canceled"; - TimedEventsManager::instance().cancel("adhocsession"s + sessionid + executor_jid); + TimedEventsManager::instance().cancel("adhocsession" + sessionid + executor_jid); } else // unsupported action { diff --git a/src/xmpp/biboumi_adhoc_commands.cpp b/src/xmpp/biboumi_adhoc_commands.cpp index ad4faf8..60af506 100644 --- a/src/xmpp/biboumi_adhoc_commands.cpp +++ b/src/xmpp/biboumi_adhoc_commands.cpp @@ -122,30 +122,48 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman XmlSubNode instructions(x, "instructions"); instructions.set_inner("Edit the form, to configure your global settings for the component."); - XmlSubNode max_histo_length(x, "field"); - max_histo_length["var"] = "max_history_length"; - max_histo_length["type"] = "text-single"; - max_histo_length["label"] = "Max history length"; - max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; - { - XmlSubNode value(max_histo_length, "value"); - value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>())); + XmlSubNode max_histo_length(x, "field"); + max_histo_length["var"] = "max_history_length"; + max_histo_length["type"] = "text-single"; + max_histo_length["label"] = "Max history length"; + max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel"; + { + XmlSubNode value(max_histo_length, "value"); + value.set_inner(std::to_string(options.col<Database::MaxHistoryLength>())); + } } - XmlSubNode record_history(x, "field"); - record_history["var"] = "record_history"; - record_history["type"] = "boolean"; - record_history["label"] = "Record history"; - record_history["desc"] = "Whether to save the messages into the database, or not"; + { + XmlSubNode record_history(x, "field"); + record_history["var"] = "record_history"; + record_history["type"] = "boolean"; + record_history["label"] = "Record history"; + record_history["desc"] = "Whether to save the messages into the database, or not"; + { + XmlSubNode value(record_history, "value"); + value.set_name("value"); + if (options.col<Database::RecordHistory>()) + value.set_inner("true"); + else + value.set_inner("false"); + } + } { - XmlSubNode value(record_history, "value"); - value.set_name("value"); - if (options.col<Database::RecordHistory>()) - value.set_inner("true"); - else - value.set_inner("false"); + XmlSubNode persistent(x, "field"); + persistent["var"] = "persistent"; + persistent["type"] = "boolean"; + persistent["label"] = "Make all channels persistent"; + persistent["desc"] = "If true, all channels will be persistent"; + { + XmlSubNode value(persistent, "value"); + value.set_name("value"); + if (options.col<Database::Persistent>()) + value.set_inner("true"); + else + value.set_inner("false"); + } } } @@ -173,6 +191,9 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, if (bridge) bridge->set_record_history(options.col<Database::RecordHistory>()); } + else if (field->get_tag("var") == "persistent" && + value) + options.col<Database::Persistent>() = to_bool(value->get_inner()); } options.save(Database::db); @@ -202,100 +223,116 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode x(command_node, "jabber:x:data:x"); x["type"] = "form"; XmlSubNode title(x, "title"); - title.set_inner("Configure the IRC server "s + server_domain); + title.set_inner("Configure the IRC server " + server_domain); XmlSubNode instructions(x, "instructions"); - instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain); - - XmlSubNode ports(x, "field"); - ports["var"] = "ports"; - ports["type"] = "text-multi"; - ports["label"] = "Ports"; - ports["desc"] = "List of ports to try, without TLS. Defaults: 6667."; - for (const auto& val: utils::split(options.col<Database::Ports>(), ';', false)) - { - XmlSubNode ports_value(ports, "value"); - ports_value.set_inner(val); - } + instructions.set_inner("Edit the form, to configure the settings of the IRC server " + server_domain); + + { + XmlSubNode ports(x, "field"); + ports["var"] = "ports"; + ports["type"] = "text-multi"; + ports["label"] = "Ports"; + ports["desc"] = "List of ports to try, without TLS. Defaults: 6667."; + for (const auto& val: utils::split(options.col<Database::Ports>(), ';', false)) + { + XmlSubNode ports_value(ports, "value"); + ports_value.set_inner(val); + } + } #ifdef BOTAN_FOUND - XmlSubNode tls_ports(x, "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."; - for (const auto& val: utils::split(options.col<Database::TlsPorts>(), ';', false)) - { - XmlSubNode tls_ports_value(tls_ports, "value"); - tls_ports_value.set_inner(val); - } + { + XmlSubNode tls_ports(x, "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."; + for (const auto& val: utils::split(options.col<Database::TlsPorts>(), ';', false)) + { + XmlSubNode tls_ports_value(tls_ports, "value"); + tls_ports_value.set_inner(val); + } + } - XmlSubNode verify_cert(x, "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"; - XmlSubNode verify_cert_value(verify_cert, "value"); - if (options.col<Database::VerifyCert>()) - verify_cert_value.set_inner("true"); - else - verify_cert_value.set_inner("false"); + { + XmlSubNode verify_cert(x, "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"; + XmlSubNode verify_cert_value(verify_cert, "value"); + if (options.col<Database::VerifyCert>()) + verify_cert_value.set_inner("true"); + else + verify_cert_value.set_inner("false"); + } - XmlSubNode fingerprint(x, "field"); - fingerprint["var"] = "fingerprint"; - fingerprint["type"] = "text-single"; - fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust."; - if (!options.col<Database::TrustedFingerprint>().empty()) - { - XmlSubNode fingerprint_value(fingerprint, "value"); - fingerprint_value.set_inner(options.col<Database::TrustedFingerprint>()); - } + { + XmlSubNode fingerprint(x, "field"); + fingerprint["var"] = "fingerprint"; + fingerprint["type"] = "text-single"; + fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust."; + if (!options.col<Database::TrustedFingerprint>().empty()) + { + XmlSubNode fingerprint_value(fingerprint, "value"); + fingerprint_value.set_inner(options.col<Database::TrustedFingerprint>()); + } + } #endif + { + XmlSubNode pass(x, "field"); + pass["var"] = "pass"; + pass["type"] = "text-private"; + pass["label"] = "Server password"; + pass["desc"] = "Will be used in a PASS command when connecting"; + if (!options.col<Database::Pass>().empty()) + { + XmlSubNode pass_value(pass, "value"); + pass_value.set_inner(options.col<Database::Pass>()); + } + } - XmlSubNode pass(x, "field"); - pass["var"] = "pass"; - pass["type"] = "text-private"; - pass["label"] = "Server password"; - pass["desc"] = "Will be used in a PASS command when connecting"; - if (!options.col<Database::Pass>().empty()) - { - XmlSubNode pass_value(pass, "value"); - pass_value.set_inner(options.col<Database::Pass>()); - } - - XmlSubNode after_cnt_cmd(x, "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.col<Database::AfterConnectionCommand>().empty()) - { - XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); - after_cnt_cmd_value.set_inner(options.col<Database::AfterConnectionCommand>()); - } + { + XmlSubNode after_cnt_cmd(x, "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.col<Database::AfterConnectionCommand>().empty()) + { + XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value"); + after_cnt_cmd_value.set_inner(options.col<Database::AfterConnectionCommand>()); + } + } if (Config::get("realname_customization", "true") == "true") { - XmlSubNode username(x, "field"); - username["var"] = "username"; - username["type"] = "text-single"; - username["label"] = "Username"; - if (!options.col<Database::Username>().empty()) - { - XmlSubNode username_value(username, "value"); - username_value.set_inner(options.col<Database::Username>()); - } - - XmlSubNode realname(x, "field"); - realname["var"] = "realname"; - realname["type"] = "text-single"; - realname["label"] = "Realname"; - if (!options.col<Database::Realname>().empty()) - { - XmlSubNode realname_value(realname, "value"); - realname_value.set_inner(options.col<Database::Realname>()); - } + { + XmlSubNode username(x, "field"); + username["var"] = "username"; + username["type"] = "text-single"; + username["label"] = "Username"; + if (!options.col<Database::Username>().empty()) + { + XmlSubNode username_value(username, "value"); + username_value.set_inner(options.col<Database::Username>()); + } + } + + { + XmlSubNode realname(x, "field"); + realname["var"] = "realname"; + realname["type"] = "text-single"; + realname["label"] = "Realname"; + if (!options.col<Database::Realname>().empty()) + { + XmlSubNode realname_value(realname, "value"); + realname_value.set_inner(options.col<Database::Realname>()); + } + } } + { XmlSubNode encoding_out(x, "field"); encoding_out["var"] = "encoding_out"; encoding_out["type"] = "text-single"; @@ -306,17 +343,20 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com XmlSubNode encoding_out_value(encoding_out, "value"); encoding_out_value.set_inner(options.col<Database::EncodingOut>()); } + } - XmlSubNode encoding_in(x, "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.col<Database::EncodingIn>().empty()) - { - XmlSubNode encoding_in_value(encoding_in, "value"); - encoding_in_value.set_inner(options.col<Database::EncodingIn>()); - } + { + XmlSubNode encoding_in(x, "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.col<Database::EncodingIn>().empty()) + { + XmlSubNode encoding_in_value(encoding_in, "value"); + encoding_in_value.set_inner(options.col<Database::EncodingIn>()); + } + } } void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node) @@ -359,24 +399,20 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com options.col<Database::VerifyCert>() = val; } - else if (field->get_tag("var") == "fingerprint" && value && - !value->get_inner().empty()) + else if (field->get_tag("var") == "fingerprint" && value) { options.col<Database::TrustedFingerprint>() = value->get_inner(); } #endif // BOTAN_FOUND - else if (field->get_tag("var") == "pass" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "pass" && value) options.col<Database::Pass>() = value->get_inner(); - else if (field->get_tag("var") == "after_connect_command" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "after_connect_command") options.col<Database::AfterConnectionCommand>() = value->get_inner(); - else if (field->get_tag("var") == "username" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "username" && value) { auto username = value->get_inner(); // The username must not contain spaces @@ -384,16 +420,13 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com options.col<Database::Username>() = username; } - else if (field->get_tag("var") == "realname" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "realname" && value) options.col<Database::Realname>() = value->get_inner(); - else if (field->get_tag("var") == "encoding_out" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "encoding_out" && value) options.col<Database::EncodingOut>() = value->get_inner(); - else if (field->get_tag("var") == "encoding_in" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "encoding_in" && value) options.col<Database::EncodingIn>() = value->get_inner(); } @@ -426,68 +459,74 @@ void insert_irc_channel_configuration_form(XmlNode& node, const Jid& requester, auto options = Database::get_irc_channel_options_with_server_default(requester.local + "@" + requester.domain, iid.get_server(), iid.get_local()); - XmlSubNode x(node, "jabber:x:data:x"); x["type"] = "form"; XmlSubNode title(x, "title"); - title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server()); + title.set_inner("Configure the IRC channel " + iid.get_local() + " on server " + iid.get_server()); XmlSubNode instructions(x, "instructions"); - instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local()); - - XmlSubNode record_history(x, "field"); - record_history["var"] = "record_history"; - record_history["type"] = "list-single"; - record_history["label"] = "Record history for this channel"; - record_history["desc"] = "If unset, the value is the one configured globally"; + instructions.set_inner("Edit the form, to configure the settings of the IRC channel " + iid.get_local()); { - // Value selected by default - XmlSubNode value(record_history, "value"); - value.set_inner(options.col<Database::RecordHistoryOptional>().to_string()); + XmlSubNode record_history(x, "field"); + record_history["var"] = "record_history"; + record_history["type"] = "list-single"; + record_history["label"] = "Record history for this channel"; + record_history["desc"] = "If unset, the value is the one configured globally"; + { + // Value selected by default + XmlSubNode value(record_history, "value"); + value.set_inner(options.col<Database::RecordHistoryOptional>().to_string()); + } + // All three possible values + for (const auto& val: {"unset", "true", "false"}) + { + XmlSubNode option(record_history, "option"); + option["label"] = val; + XmlSubNode value(option, "value"); + value.set_inner(val); + } } - // All three possible values - for (const auto& val: {"unset", "true", "false"}) + { - XmlSubNode option(record_history, "option"); - option["label"] = val; - XmlSubNode value(option, "value"); - value.set_inner(val); + XmlSubNode encoding_out(x, "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.col<Database::EncodingOut>().empty()) + { + XmlSubNode encoding_out_value(encoding_out, "value"); + encoding_out_value.set_inner(options.col<Database::EncodingOut>()); + } } - XmlSubNode encoding_out(x, "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.col<Database::EncodingOut>().empty()) - { - XmlSubNode encoding_out_value(encoding_out, "value"); - encoding_out_value.set_inner(options.col<Database::EncodingOut>()); - } + { + XmlSubNode encoding_in(x, "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.col<Database::EncodingIn>().empty()) + { + XmlSubNode encoding_in_value(encoding_in, "value"); + encoding_in_value.set_inner(options.col<Database::EncodingIn>()); + } + } - XmlSubNode encoding_in(x, "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.col<Database::EncodingIn>().empty()) + { + XmlSubNode persistent(x, "field"); + persistent["var"] = "persistent"; + persistent["type"] = "boolean"; + persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command."; + persistent["label"] = "Persistent"; { - XmlSubNode encoding_in_value(encoding_in, "value"); - encoding_in_value.set_inner(options.col<Database::EncodingIn>()); + XmlSubNode value(persistent, "value"); + value.set_name("value"); + if (options.col<Database::Persistent>()) + value.set_inner("true"); + else + value.set_inner("false"); } - - XmlSubNode persistent(x, "field"); - persistent["var"] = "persistent"; - persistent["type"] = "boolean"; - persistent["desc"] = "If set to true, when all XMPP clients have left this channel, biboumi will stay idle in it, without sending a PART command."; - persistent["label"] = "Persistent"; - { - XmlSubNode value(persistent, "value"); - value.set_name("value"); - if (options.col<Database::Persistent>()) - value.set_inner("true"); - else - value.set_inner("false"); } } @@ -526,16 +565,13 @@ bool handle_irc_channel_configuration_form(XmppComponent& xmpp_component, const { const XmlNode *value = field->get_child("value", "jabber:x:data"); - if (field->get_tag("var") == "encoding_out" && - value && !value->get_inner().empty()) + if (field->get_tag("var") == "encoding_out" && value) options.col<Database::EncodingOut>() = value->get_inner(); - else if (field->get_tag("var") == "encoding_in" && - value && !value->get_inner().empty()) + else if (field->get_tag("var") == "encoding_in" && value) options.col<Database::EncodingIn>() = value->get_inner(); - else if (field->get_tag("var") == "persistent" && - value) + else if (field->get_tag("var") == "persistent" && value) options.col<Database::Persistent>() = to_bool(value->get_inner()); else if (field->get_tag("var") == "record_history" && value && !value->get_inner().empty()) @@ -647,7 +683,7 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession& { XmlSubNode note(command_node, "note"); note["type"] = "info"; - note.set_inner("User "s + jid_to_disconnect + " is not connected to any IRC server."); + note.set_inner("User " + jid_to_disconnect + " is not connected to any IRC server."); session.terminate(); return ; } @@ -751,7 +787,7 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, IrcClient* irc = bridge->find_irc_client(hostname); if (!irc || !irc->is_connected()) { - message = "You are not connected to the IRC server "s + hostname; + message = "You are not connected to the IRC server " + hostname; return; } diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 32f3968..0e1d270 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -83,6 +83,14 @@ void BiboumiComponent::shutdown() { for (auto& pair: this->bridges) pair.second->shutdown("Gateway shutdown"); +#ifdef USE_DATABASE + for (const Database::RosterItem& roster_item: Database::get_full_roster()) + { + this->send_presence_to_contact(roster_item.col<Database::LocalJid>(), + roster_item.col<Database::RemoteJid>(), + "unavailable"); + } +#endif } void BiboumiComponent::clean() @@ -160,16 +168,50 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) { if (type == "subscribe") { // Auto-accept any subscription request for an IRC server - this->accept_subscription(to_str, from.bare()); - this->ask_subscription(to_str, from.bare()); + this->send_presence_to_contact(to_str, from.bare(), "subscribed", id); + if (iid.type == Iid::Type::None || bridge->find_irc_client(iid.get_server())) + this->send_presence_to_contact(to_str, from.bare(), ""); + this->send_presence_to_contact(to_str, from.bare(), "subscribe"); +#ifdef USE_DATABASE + if (!Database::has_roster_item(to_str, from.bare())) + Database::add_roster_item(to_str, from.bare()); +#endif + } + else if (type == "unsubscribe") + { + this->send_presence_to_contact(to_str, from.bare(), "unavailable", id); + this->send_presence_to_contact(to_str, from.bare(), "unsubscribed"); + this->send_presence_to_contact(to_str, from.bare(), "unsubscribe"); +#ifdef USE_DATABASE + const bool res = Database::has_roster_item(to_str, from.bare()); + if (res) + Database::delete_roster_item(to_str, from.bare()); +#endif + } + else if (type == "probe") + { + if ((iid.type == Iid::Type::Server && bridge->find_irc_client(iid.get_server())) + || iid.type == Iid::Type::None) + { +#ifdef USE_DATABASE + if (Database::has_roster_item(to_str, from.bare())) +#endif + this->send_presence_to_contact(to_str, from.bare(), ""); +#ifdef USE_DATABASE + else // rfc 6121 4.3.2.1 + this->send_presence_to_contact(to_str, from.bare(), "unsubscribed"); +#endif + } + } + else if (type.empty()) + { // We just receive a presence from someone (as the result of a probe, + // or a directed presence, or a normal presence change) + if (iid.type == Iid::Type::None) + this->send_presence_to_contact(to_str, from.bare(), ""); } - } - else - { - // A user wants to join an invalid IRC channel, return a presence error to him/her - if (type.empty()) - this->send_invalid_room_error(to.local, to.resource, from_str); + else if (iid.type == Iid::Type::User) + { // Do nothing yet } } catch (const IRCNotConnected& ex) @@ -177,7 +219,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) if (type != "unavailable") this->send_stanza_error("presence", from_str, to_str, id, "cancel", "remote-server-not-found", - "Not connected to IRC server "s + ex.hostname, + "Not connected to IRC server " + ex.hostname, true); } stanza_error.disable(); @@ -268,7 +310,11 @@ void BiboumiComponent::handle_message(const Stanza& stanza) const auto invite_to = invite->get_tag("to"); if (!invite_to.empty()) { - bridge->send_irc_invitation(iid, invite_to); + Jid invited_jid{invite_to}; + if (invited_jid.domain == this->get_served_hostname() || invited_jid.local.empty()) + bridge->send_irc_invitation(iid, invite_to); + else + this->send_invitation_from_fulljid(std::to_string(iid), invite_to, from_str); } } @@ -277,7 +323,7 @@ void BiboumiComponent::handle_message(const Stanza& stanza) { this->send_stanza_error("message", from_str, to_str, id, "cancel", "remote-server-not-found", - "Not connected to IRC server "s + ex.hostname, + "Not connected to IRC server " + ex.hostname, true); } stanza_error.disable(); @@ -586,7 +632,7 @@ void BiboumiComponent::handle_iq(const Stanza& stanza) { this->send_stanza_error("iq", from, to_str, id, "cancel", "remote-server-not-found", - "Not connected to IRC server "s + ex.hostname, + "Not connected to IRC server " + ex.hostname, true); stanza_error.disable(); return; @@ -806,7 +852,7 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s XmlSubNode identity(query, "identity"); identity["category"] = "conference"; identity["type"] = "irc"; - identity["name"] = "IRC server "s + from.local + " over Biboumi"; + identity["name"] = "IRC server " + from.local + " over Biboumi"; for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) { XmlSubNode feature(query, "feature"); @@ -849,7 +895,7 @@ void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const XmlSubNode identity(query, "identity"); identity["category"] = "conference"; identity["type"] = "irc"; - identity["name"] = "IRC channel "s + iid.get_local() + " from server " + iid.get_server() + " over biboumi"; + identity["name"] = "IRC channel " + iid.get_local() + " from server " + iid.get_server() + " over biboumi"; for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS}) { XmlSubNode feature(query, "feature"); @@ -945,6 +991,16 @@ void BiboumiComponent::send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick) { + if (author_nick.empty()) + this->send_invitation_from_fulljid(room_target, jid_to, room_target + "@" + this->served_hostname); + else + this->send_invitation_from_fulljid(room_target, jid_to, room_target + "@" + this->served_hostname + "/" + author_nick); +} + +void BiboumiComponent::send_invitation_from_fulljid(const std::string& room_target, + const std::string& jid_to, + const std::string& from) +{ Stanza message("message"); { message["from"] = room_target + "@" + this->served_hostname; @@ -952,10 +1008,7 @@ void BiboumiComponent::send_invitation(const std::string& room_target, XmlSubNode x(message, "x"); x["xmlns"] = MUC_USER_NS; XmlSubNode invite(x, "invite"); - if (author_nick.empty()) - invite["from"] = room_target + "@" + this->served_hostname; - else - invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick; + invite["from"] = from; } this->send_stanza(message); } @@ -979,3 +1032,55 @@ void BiboumiComponent::ask_subscription(const std::string& from, const std::stri presence["type"] = "subscribe"; this->send_stanza(presence); } + +void BiboumiComponent::send_presence_to_contact(const std::string& from, const std::string& to, + const std::string& type, const std::string& id) +{ + Stanza presence("presence"); + presence["from"] = from; + presence["to"] = to; + if (!type.empty()) + presence["type"] = type; + if (!id.empty()) + presence["id"] = id; + this->send_stanza(presence); +} + +void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname, const std::string& jid) +{ +#ifdef USE_DATABASE + const auto local_jid = irc_hostname + "@" + this->served_hostname; + if (Database::has_roster_item(local_jid, jid)) + this->send_presence_to_contact(local_jid, jid, ""); +#endif +} + +void BiboumiComponent::on_irc_client_disconnected(const std::string& irc_hostname, const std::string& jid) +{ +#ifdef USE_DATABASE + const auto local_jid = irc_hostname + "@" + this->served_hostname; + if (Database::has_roster_item(local_jid, jid)) + this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable"); +#endif +} + +void BiboumiComponent::after_handshake() +{ + XmppComponent::after_handshake(); + +#ifdef USE_DATABASE + const auto contacts = Database::get_contact_list(this->get_served_hostname()); + + for (const Database::RosterItem& roster_item: contacts) + { + const auto remote_jid = roster_item.col<Database::RemoteJid>(); + // In response, we will receive a presence indicating the + // contact is online, to which we will respond with our own + // presence. + // If the contact removed us from their roster while we were + // offline, we will receive an unsubscribed presence, letting us + // stay in sync. + this->send_presence_to_contact(this->get_served_hostname(), remote_jid, "probe"); + } +#endif +} diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 87311f9..caf990e 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -36,6 +36,8 @@ public: BiboumiComponent& operator=(const BiboumiComponent&) = delete; BiboumiComponent& operator=(BiboumiComponent&&) = delete; + void after_handshake() override final; + /** * Returns the bridge for the given user. If it does not exist, return * nullptr. @@ -85,8 +87,15 @@ public: const ChannelList& channel_list, std::vector<ListElement>::const_iterator begin, std::vector<ListElement>::const_iterator end, const ResultSetInfo& rs_info); void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); +private: + void send_invitation_from_fulljid(const std::string& room_target, const std::string& jid_to, const std::string& from); +public: void accept_subscription(const std::string& from, const std::string& to); void ask_subscription(const std::string& from, const std::string& to); + void send_presence_to_contact(const std::string& from, const std::string& to, const std::string& type, const std::string& id=""); + void on_irc_client_connected(const std::string& irc_hostname, const std::string& jid); + void on_irc_client_disconnected(const std::string& irc_hostname, const std::string& jid); + /** * Handle the various stanza types */ diff --git a/src/xmpp/xmpp_component.cpp b/src/xmpp/xmpp_component.cpp index b138ed9..42a5392 100644 --- a/src/xmpp/xmpp_component.cpp +++ b/src/xmpp/xmpp_component.cpp @@ -92,7 +92,7 @@ void XmppComponent::on_connected() { log_info("connected to XMPP server"); this->first_connection_try = true; - auto data = "<stream:stream to='"s + this->served_hostname + \ + auto data = "<stream:stream to='" + this->served_hostname + \ "' xmlns:stream='http://etherx.jabber.org/streams' xmlns='" COMPONENT_NS "'>"; log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); @@ -142,7 +142,7 @@ void XmppComponent::on_remote_stream_open(const XmlNode& node) } // Try to authenticate - auto data = "<handshake xmlns='" COMPONENT_NS "'>"s + get_handshake_digest(this->stream_id, this->secret) + "</handshake>"; + auto data = "<handshake xmlns='" COMPONENT_NS "'>" + get_handshake_digest(this->stream_id, this->secret) + "</handshake>"; log_debug("XMPP SENDING: ", data); this->send_data(std::move(data)); } @@ -261,7 +261,6 @@ void XmppComponent::handle_error(const Stanza& stanza) if (!this->ever_auth) sd_notifyf(0, "STATUS=Failed to authenticate to the XMPP server: %s", error_message.data()); #endif - } void* XmppComponent::get_receive_buffer(const size_t size) const @@ -338,35 +337,6 @@ void XmppComponent::send_user_join(const std::string& from, this->send_stanza(presence); } -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"; - XmlSubNode x(presence, "x"); - x["xmlns"] = MUC_NS; - XmlSubNode error(presence, "error"); - error["by"] = muc_name + "@" + this->served_hostname; - error["type"] = "cancel"; - XmlSubNode item_not_found(error, "item-not-found"); - item_not_found["xmlns"] = STANZA_NS; - XmlSubNode text(error, "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: #<chan>%<server>@" + - this->served_hostname); - } - this->send_stanza(presence); -} - void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who) { Stanza message("message"); @@ -441,7 +411,8 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std: this->send_stanza(message); } -void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self) +void XmppComponent::send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, + const std::string& jid_to, const bool self, const bool user_requested) { Stanza presence("presence"); { @@ -453,8 +424,15 @@ void XmppComponent::send_muc_leave(const std::string& muc_name, const std::strin x["xmlns"] = MUC_USER_NS; if (self) { - XmlSubNode status(x, "status"); - status["code"] = "110"; + { + XmlSubNode status(x, "status"); + status["code"] = "110"; + } + if (!user_requested) + { + XmlSubNode status(x, "status"); + status["code"] = "332"; + } } if (!message_str.empty()) { @@ -642,7 +620,7 @@ void XmppComponent::send_iq_version_request(const std::string& from, Stanza iq("iq"); { iq["type"] = "get"; - iq["id"] = "version_"s + XmppComponent::next_id(); + iq["id"] = "version_" + XmppComponent::next_id(); iq["from"] = from + "@" + this->served_hostname; iq["to"] = jid_to; XmlSubNode query(iq, "query"); diff --git a/src/xmpp/xmpp_component.hpp b/src/xmpp/xmpp_component.hpp index ebe3ec8..22d5c48 100644 --- a/src/xmpp/xmpp_component.hpp +++ b/src/xmpp/xmpp_component.hpp @@ -124,12 +124,6 @@ public: 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 */ void send_topic(const std::string& from, Xmpp::body&& xmpp_topic, const std::string& to, const std::string& who); @@ -146,7 +140,12 @@ public: /** * Send an unavailable presence for this nick */ - void send_muc_leave(const std::string& muc_name, const std::string& nick, Xmpp::body&& message, const std::string& jid_to, const bool self); + void send_muc_leave(const std::string& muc_name, + const std::string& nick, + Xmpp::body&& message, + const std::string& jid_to, + const bool self, + const bool user_requested); /** * Indicate that a participant changed his nick */ |