From 84034ed3dc19f718dcc93a35dbf4c840a55efb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 23 Jun 2017 00:16:24 +0200 Subject: =?UTF-8?q?Use=20a=20db=20roster=20to=20manage=20biboumi=E2=80=99s?= =?UTF-8?q?=20presence=20with=20the=20contacts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/database/database.cpp | 51 ++++++++++++++++++++++++++++++++++- src/database/database.hpp | 15 +++++++++++ src/xmpp/biboumi_component.cpp | 61 +++++++++++++++++++++++++++++++++++++++--- src/xmpp/biboumi_component.hpp | 3 +++ 4 files changed, 126 insertions(+), 4 deletions(-) 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::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() = local; + roster_item.col() = 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::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::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 b5f2ff0..c00c938 100644 --- a/src/database/database.hpp +++ b/src/database/database.hpp @@ -72,6 +72,11 @@ class Database struct Persistent: Column { static constexpr auto name = "persistent_"; Persistent(): Column(false) {} }; + struct LocalJid: Column { static constexpr auto name = "local"; }; + + struct RemoteJid: Column { static constexpr auto name = "remote"; }; + + using MucLogLineTable = Table; using MucLogLine = MucLogLineTable::RowType; @@ -84,6 +89,9 @@ class Database using IrcChannelOptionsTable = Table; using IrcChannelOptions = IrcChannelOptionsTable::RowType; + using RosterTable = Table; + using RosterItem = RosterTable::RowType; + Database() = default; ~Database() = default; @@ -109,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 get_contact_list(const std::string& local); + static std::vector get_full_roster(); + static void close(); static void open(const std::string& filename); @@ -123,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/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 1c7cd92..f3381aa 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -83,6 +83,15 @@ void BiboumiComponent::shutdown() { for (auto& pair: this->bridges) pair.second->shutdown("Gateway shutdown"); +#ifdef USE_DATABASE + const auto full_roster = Database::get_full_roster(); + for (const Database::RosterItem& roster_item: full_roster) + { + this->send_presence_to_contact(roster_item.col(), + roster_item.col(), + "unavailable"); + } +#endif } void BiboumiComponent::clean() @@ -160,10 +169,28 @@ 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) + 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") + { +#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.empty()) + { // We just receive a presence from someone (as the result of a probe, + // or a directed presence, or a normal presence change) + this->send_presence_to_contact(to_str, from.bare(), ""); } - } else { @@ -979,3 +1006,31 @@ 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::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(); + 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..2d67f8b 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. @@ -87,6 +89,7 @@ public: void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick); 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=""); /** * Handle the various stanza types */ -- cgit v1.2.3 From 71f125db1a11f4b728beee1d1aa2ef7d37f38000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 23 Jun 2017 14:45:44 +0200 Subject: Send responses when we receive an unsubscribed presence --- src/xmpp/biboumi_component.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index f3381aa..263b7bd 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -180,6 +180,8 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } else if (type == "unsubscribe") { + this->send_presence_to_contact(to_str, from.bare(), "unavailable", id); + 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) -- cgit v1.2.3 From 7e5cd2f13036b61781e1799565597ab798b50a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 23 Jun 2017 14:49:10 +0200 Subject: Add e2e tests for the (un)subscribe thing --- tests/end_to_end/__main__.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 7259999..a718a63 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -49,6 +49,8 @@ class XMPPComponent(slixmpp.BaseXMPP): def __init__(self, scenario, biboumi): super().__init__(jid="biboumi.localhost", default_ns="jabber:component:accept") self.is_component = True + self.auto_authorize = None # Do not accept or reject subscribe requests automatically + self.auto_subscribe = False self.stream_header = '' % ( 'xmlns="jabber:component:accept"', 'xmlns:stream="%s"' % self.stream_ns, @@ -2671,7 +2673,28 @@ if __name__ == '__main__': partial(expect_stanza, "/message[@to='{jid_two}/{resource_two}'][@type='chat']/body[text()='irc.localhost: {nick_one}: Nickname is already in use.']"), partial(expect_stanza, "/presence[@type='error']/error[@type='cancel'][@code='409']/stanza:conflict"), partial(send_stanza, "") - ]) + ]), + Scenario("basic_subscribe_unsubscribe", + [ + handshake_sequence(), + + # Mutual subscription exchange + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='subscribed'][@id='subid1']"), + + # Get the current presence of the biboumi gateway + partial(expect_stanza, "/presence"), + + partial(expect_stanza, "/presence[@type='subscribe']"), + partial(send_stanza, ""), + + + # Unsubscribe + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unsubscribe']"), + partial(send_stanza, ""), + ]) ) failures = 0 -- cgit v1.2.3 From 368bb82818d4b68e4984698ea4454091ecb049a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 23 Jun 2017 15:05:07 +0200 Subject: Send an additional unsubscribed presence when receiving an unsubscribe one --- src/xmpp/biboumi_component.cpp | 1 + tests/end_to_end/__main__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 263b7bd..91e92aa 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -181,6 +181,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) 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()); diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index a718a63..2957820 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -2692,8 +2692,10 @@ if __name__ == '__main__': # Unsubscribe partial(send_stanza, ""), partial(expect_stanza, "/presence[@type='unavailable']"), + partial(expect_stanza, "/presence[@type='unsubscribed']"), partial(expect_stanza, "/presence[@type='unsubscribe']"), partial(send_stanza, ""), + partial(send_stanza, ""), ]) ) -- cgit v1.2.3 From f9a6f973966430b108642ac57d54db5fd0d5535e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 4 Jul 2017 20:42:40 +0200 Subject: Implement the roster presences from IRC servers --- src/bridge/bridge.cpp | 10 ++++++++++ src/bridge/bridge.hpp | 4 +++- src/irc/irc_client.cpp | 2 ++ src/xmpp/biboumi_component.cpp | 18 ++++++++++++++---- src/xmpp/biboumi_component.hpp | 3 +++ 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp index 27726e4..33006c3 100644 --- a/src/bridge/bridge.cpp +++ b/src/bridge/bridge.cpp @@ -1081,6 +1081,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 496b439..c10631b 100644 --- a/src/bridge/bridge.hpp +++ b/src/bridge/bridge.hpp @@ -201,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 @@ -301,8 +303,8 @@ private: using ChannelKey = std::tuple; public: std::map> resources_in_chan; -private: std::map> resources_in_server; +private: /** * Manage which resource is in which channel */ diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp index 67221c5..46dbdbe 100644 --- a/src/irc/irc_client.cpp +++ b/src/irc/irc_client.cpp @@ -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) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 91e92aa..71a5f3d 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -84,8 +84,7 @@ void BiboumiComponent::shutdown() for (auto& pair: this->bridges) pair.second->shutdown("Gateway shutdown"); #ifdef USE_DATABASE - const auto full_roster = Database::get_full_roster(); - for (const Database::RosterItem& roster_item: full_roster) + for (const Database::RosterItem& roster_item: Database::get_full_roster()) { this->send_presence_to_contact(roster_item.col(), roster_item.col(), @@ -170,7 +169,7 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) if (type == "subscribe") { // Auto-accept any subscription request for an IRC server this->send_presence_to_contact(to_str, from.bare(), "subscribed", id); - if (iid.type == Iid::Type::None) + 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 @@ -192,7 +191,8 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) 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) - this->send_presence_to_contact(to_str, from.bare(), ""); + if (iid.type == Iid::Type::None) + this->send_presence_to_contact(to_str, from.bare(), ""); } } else @@ -1023,6 +1023,16 @@ void BiboumiComponent::send_presence_to_contact(const std::string& from, const s this->send_stanza(presence); } +void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname, const std::string& jid) +{ + this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, ""); +} + +void BiboumiComponent::on_irc_client_disconnected(const std::string& irc_hostname, const std::string& jid) +{ + this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable"); +} + void BiboumiComponent::after_handshake() { XmppComponent::after_handshake(); diff --git a/src/xmpp/biboumi_component.hpp b/src/xmpp/biboumi_component.hpp index 2d67f8b..e5547f9 100644 --- a/src/xmpp/biboumi_component.hpp +++ b/src/xmpp/biboumi_component.hpp @@ -90,6 +90,9 @@ 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 */ -- cgit v1.2.3 From dd343609d561c95a3231ab9db26d44dec6395a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Wed, 5 Jul 2017 20:18:26 +0200 Subject: Only send the IRC server presence if the user has this JID in their roster --- src/xmpp/biboumi_component.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index 71a5f3d..b951629 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -1025,12 +1025,20 @@ void BiboumiComponent::send_presence_to_contact(const std::string& from, const s void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname, const std::string& jid) { - this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, 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) { - this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable"); +#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() -- cgit v1.2.3 From a95197509c262e6294930c4a60d2c96487986e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sat, 8 Jul 2017 19:37:28 +0200 Subject: Add an e2e test for the IRC server roster presence --- tests/end_to_end/__main__.py | 49 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py index 2957820..2a722e8 100644 --- a/tests/end_to_end/__main__.py +++ b/tests/end_to_end/__main__.py @@ -403,11 +403,11 @@ def handshake_sequence(): partial(send_stanza, "")) -def connection_begin_sequence(irc_host, jid): +def connection_begin_sequence(irc_host, jid, expected_irc_presence=False): jid = jid.format_map(common_replacements) xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" xpath_re = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[re:test(text(), '%s')]" - return ( + result = ( partial(expect_stanza, xpath % ('Connecting to %s:6697 (encrypted)' % irc_host)), partial(expect_stanza, @@ -419,8 +419,13 @@ def connection_begin_sequence(irc_host, jid): partial(expect_stanza, xpath % ('Connecting to %s:6667 (not encrypted)' % irc_host)), partial(expect_stanza, - xpath % 'Connected to IRC server.'), + xpath % 'Connected to IRC server.')) + + if expected_irc_presence: + result += (partial(expect_stanza, "/presence[@from='" + irc_host + "@biboumi.localhost']"),) + # These five messages can be receive in any order + result += ( partial(expect_stanza, xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), partial(expect_stanza, @@ -433,6 +438,8 @@ def connection_begin_sequence(irc_host, jid): xpath_re % (r'^%s: (\*\*\* Checking Ident|\*\*\* Looking up your hostname\.\.\.|\*\*\* Found your hostname: .*|ACK multi-prefix|\*\*\* Got Ident response)$' % 'irc.localhost')), ) + return result + def connection_tls_begin_sequence(irc_host, jid): jid = jid.format_map(common_replacements) xpath = "/message[@to='" + jid + "'][@from='" + irc_host + "@biboumi.localhost']/body[text()='%s']" @@ -494,8 +501,8 @@ def connection_middle_sequence(irc_host, jid): ) -def connection_sequence(irc_host, jid): - return connection_begin_sequence(irc_host, jid) +\ +def connection_sequence(irc_host, jid, expected_irc_presence=False): + return connection_begin_sequence(irc_host, jid, expected_irc_presence) +\ connection_middle_sequence(irc_host, jid) +\ connection_end_sequence(irc_host, jid) @@ -2696,6 +2703,38 @@ if __name__ == '__main__': partial(expect_stanza, "/presence[@type='unsubscribe']"), partial(send_stanza, ""), partial(send_stanza, ""), + ]), + Scenario("irc_server_presence_in_roster", + [ + handshake_sequence(), + + # Mutual subscription exchange + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='subscribed'][@id='subid1']"), + + partial(expect_stanza, "/presence[@type='subscribe']"), + partial(send_stanza, ""), + + # Join a channel on that server + partial(send_stanza, + ""), + + # We must receive the IRC server presence, in the connection sequence + connection_sequence("irc.localhost", '{jid_one}/{resource_one}', True), + partial(expect_stanza, + "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"), + partial(expect_stanza, + ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']", + "/presence/muc_user:x/muc_user:status[@code='110']") + ), + partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat']/subject[not(text())]"), + + # Leave the channel, and thus the IRC server + partial(send_stanza, ""), + partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Closing Link: localhost (Client Quit)']"), + partial(expect_stanza, "/message[@from='{irc_server_one}']/body[text()='ERROR: Connection closed.']"), + partial(expect_stanza, "/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"), ]) ) -- cgit v1.2.3 From befdefd98712c940637b9913f895259e7f1952bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 9 Jul 2017 01:17:18 +0200 Subject: =?UTF-8?q?Document=20the=20biboumi=20and=20IRC-server=20JIDs=20in?= =?UTF-8?q?=20user=E2=80=99s=20roster?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/biboumi.1.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/biboumi.1.rst b/doc/biboumi.1.rst index 91b2f6d..606fe87 100644 --- a/doc/biboumi.1.rst +++ b/doc/biboumi.1.rst @@ -344,6 +344,29 @@ connect you to the IRC server without joining any channel), then send your authentication message to the user ``bot%irc.example.com@biboumi.example.com`` and finally join the room ``#foo%irc.example.com@biboumi.example.com``. +Roster +------ + +You can add some JIDs provided by biboumi into your own roster, to receive +presence from them. Biboumi will always automatically accept your requests. + +Biboumi’s JID +------------- + +By adding the component JID into your roster, the user will receive an available +presence whenever it is started, and an unavailable presence whenever it is being +shutdown. This is useful to quickly view if that biboumi instance is started or +not. + +IRC server JID +-------------- + +These presence will appear online in the user’s roster whenever they are +connected to that IRC server (see *Connect to an IRC server* for more +details). This is useful to keep track of which server an user is connected +to: this is sometimes hard to remember, when they have many clients, or if +they are using persistent channels. + Channel messages ---------------- -- cgit v1.2.3 From 50a2bd736ef76f7ebb7067372d5f89b59a337bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Tue, 11 Jul 2017 19:52:09 +0200 Subject: Answer to presences of type='probe' --- src/xmpp/biboumi_component.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index b951629..ab66519 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -188,6 +188,12 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) 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) + this->send_presence_to_contact(to_str, from.bare(), ""); + } 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) -- cgit v1.2.3 From 729ea131c9857ecb2e9579359e174483c73194d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Fri, 14 Jul 2017 15:15:53 +0200 Subject: =?UTF-8?q?Send=20an=20unsubscribed=20presence=20on=20a=20probe=20?= =?UTF-8?q?if=20we=20don=E2=80=99t=20have=20a=20roster=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/xmpp/biboumi_component.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp index ab66519..d1c75d0 100644 --- a/src/xmpp/biboumi_component.cpp +++ b/src/xmpp/biboumi_component.cpp @@ -190,9 +190,18 @@ void BiboumiComponent::handle_presence(const Stanza& stanza) } else if (type == "probe") { - if ((iid.type == Iid::Type::Server && bridge->find_irc_client(iid.get_server())) || - iid.type == Iid::Type::None) - this->send_presence_to_contact(to_str, from.bare(), ""); + 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, @@ -1057,6 +1066,12 @@ void BiboumiComponent::after_handshake() for (const Database::RosterItem& roster_item: contacts) { const auto remote_jid = roster_item.col(); + // 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 -- cgit v1.2.3 From 88770979c3a46f3dde76fa2756e3e07ff79c3e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?louiz=E2=80=99?= Date: Sun, 16 Jul 2017 00:12:01 +0200 Subject: Add a changelog entry for the roster feature --- CHANGELOG.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bcb7510..e2e985e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,9 @@ Version 6.0 - The LiteSQL dependency was removed. Only libsqlite3 is now necessary to work with the database. + - Some JIDs can be added into users’ rosters. The component JID tells if + biboumi is started or not, and the IRC-server JIDs tell if the user is + currently connected to that server. - The RecordHistory option can now also be configured for each IRC channel, individually. - Add a global option to make all channels persistent. -- cgit v1.2.3