summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bridge/bridge.cpp55
-rw-r--r--src/bridge/bridge.hpp3
-rw-r--r--src/irc/irc_client.cpp5
-rw-r--r--src/xmpp/biboumi_component.cpp11
-rw-r--r--tests/end_to_end/__main__.py65
5 files changed, 137 insertions, 2 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index cc2ef66..24959d1 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -569,6 +569,57 @@ void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq
}
}
+void Bridge::force_connect_to_server(const std::string& hostname, const std::string& resource)
+{
+ auto soptions = Database::get_irc_server_options(this->get_bare_jid(), hostname);
+ const auto& nickname = soptions.col<Database::Nick>();
+ IrcClient* irc = nullptr;
+ if (nickname.empty())
+ {
+ irc = this->find_irc_client(hostname);
+ if (!irc)
+ return;
+ }
+ else
+ {
+ irc = this->make_irc_client(hostname, nickname);
+ this->add_resource_to_server(hostname, resource);
+ irc->start();
+ }
+ auto result = this->force_connected_resources.insert(std::make_pair(irc, std::set<Resource>{resource}));
+ const bool& inserted = result.second;
+ if (!inserted)
+ {
+ auto& it = result.first;
+ std::set<Resource>& resources = it->second;
+ resources.insert(resource);
+ }
+}
+
+void Bridge::unforce_connect_to_server(const std::string& hostname, const std::string& resource)
+{
+ IrcClient* irc = this->find_irc_client(hostname);
+ if (!irc)
+ return;
+ auto server_it = this->force_connected_resources.find(irc);
+ if (server_it == this->force_connected_resources.end())
+ return;
+ std::set<Resource>& forced_resources = server_it->second;
+ auto resource_it = forced_resources.find(resource);
+ if (resource_it == forced_resources.end())
+ return;
+ forced_resources.erase(resource_it);
+ if (forced_resources.empty())
+ {
+ this->force_connected_resources.erase(server_it);
+ irc->send_quit_command("");
+ }
+ else
+ {
+ this->remove_resource_from_server(hostname, resource);
+ }
+}
+
bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info,
const std::string& id, const std::string& to_jid, const std::string& from)
{
@@ -902,7 +953,9 @@ void Bridge::send_muc_leave(const Iid& iid, const IrcUser& user,
}
}
IrcClient* irc = this->find_irc_client(iid.get_server());
- if (self && irc && irc->number_of_joined_channels() == 0)
+ auto forced_resources = this->force_connected_resources.find(irc);
+ const bool force_connected = forced_resources != this->force_connected_resources.end() && !forced_resources->second.empty();
+ if (self && irc && irc->number_of_joined_channels() == 0 && !force_connected)
irc->send_quit_command("");
}
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index 5c547ff..a1934bb 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -97,6 +97,8 @@ public:
const std::string& from_jid);
void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
ResultSetInfo rs_info);
+ void force_connect_to_server(const std::string& hostname, const std::string& resource);
+ void unforce_connect_to_server(const std::string& hostname, const std::string& resource);
/**
* Check if the channel list contains what is needed to answer the RSM request,
* if it does, send the iq result. If the list is complete but does not contain
@@ -310,6 +312,7 @@ private:
public:
std::map<ChannelKey, std::set<Resource>> resources_in_chan;
std::map<IrcHostname, std::set<Resource>> resources_in_server;
+ std::map<const IrcClient*, std::set<Resource>> force_connected_resources;
private:
/**
* Manage which resource is in which channel
diff --git a/src/irc/irc_client.cpp b/src/irc/irc_client.cpp
index 0b5715e..b72c078 100644
--- a/src/irc/irc_client.cpp
+++ b/src/irc/irc_client.cpp
@@ -481,7 +481,10 @@ void IrcClient::send_topic_command(const std::string& chan_name, const std::stri
void IrcClient::send_quit_command(const std::string& reason)
{
- this->send_message(IrcMessage("QUIT", {reason}), {}, false);
+ if (!reason.empty())
+ this->send_message(IrcMessage("QUIT", {reason}), {}, false);
+ else
+ this->send_message(IrcMessage("QUIT", {}), {}, false);
}
void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
diff --git a/src/xmpp/biboumi_component.cpp b/src/xmpp/biboumi_component.cpp
index 4ed47cf..c6dc323 100644
--- a/src/xmpp/biboumi_component.cpp
+++ b/src/xmpp/biboumi_component.cpp
@@ -238,11 +238,22 @@ void BiboumiComponent::handle_presence(const Stanza& stanza)
#endif
}
}
+ else if (type == "unavailable")
+ {
+ if (iid.type == Iid::Type::Server)
+ {
+ bridge->unforce_connect_to_server(iid.get_server(), from.resource);
+ }
+ }
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 if (iid.type == Iid::Type::Server)
+ {
+ bridge->force_connect_to_server(iid.get_server(), from.resource);
+ }
}
}
else if (iid.type == Iid::Type::User)
diff --git a/tests/end_to_end/__main__.py b/tests/end_to_end/__main__.py
index cb92cb0..81263c2 100644
--- a/tests/end_to_end/__main__.py
+++ b/tests/end_to_end/__main__.py
@@ -3383,6 +3383,71 @@ if __name__ == '__main__':
partial(send_stanza, "<presence type='unavailable' from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
partial(expect_stanza, "/presence[@type='unavailable'][@from='#foo%{irc_server_one}/{nick_one}']"),
partial(expect_stanza, "/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"),
+
+ # Send a directed available presence to the IRC server JID
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' />"),
+ # Nothing happens because no Nick is configured.
+ # Send a directed unavailable presence
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' type='unavailable'/>"),
+ # Now, configure a nick
+
+ partial(send_stanza, "<iq type='set' id='id1' from='{jid_one}/{resource_one}' to='{irc_server_one}'><command xmlns='http://jabber.org/protocol/commands' node='configure' action='execute' /></iq>"),
+ partial(expect_stanza, ("/iq[@type='result']/commands:command[@node='configure'][@sessionid][@status='executing']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:title[text()='Configure the IRC server irc.localhost']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:instructions[text()='Edit the form, to configure the settings of the IRC server irc.localhost']",
+ "/iq/commands:command/dataform:x[@type='form']/dataform:field[@type='text-single'][@var='nick']",
+ ),
+ after = partial(save_value, "sessionid", partial(extract_attribute, "/iq[@type='result']/commands:command[@node='configure']", "sessionid"))
+ ),
+ partial(send_stanza, "<iq type='set' id='id2' from='{jid_one}/{resource_one}' to='{irc_server_one}'>"
+ "<command xmlns='http://jabber.org/protocol/commands' node='configure' sessionid='{sessionid}' action='next'>"
+ "<x xmlns='jabber:x:data' type='submit'>"
+ "<field var='nick'><value>{nick_one}</value></field>"
+ "<field var='ports'><value>6667</value></field>"
+ "<field var='tls_ports'><value>6697</value><value>6670</value></field>"
+ "<field var='throttle_limit'><value>9999</value><value>6670</value></field>"
+ "</x></command></iq>"),
+ partial(expect_stanza, "/iq[@type='result']/commands:command[@node='configure'][@status='completed']/commands:note[@type='info'][text()='Configuration successfully applied.']"),
+
+ # Send a directed available presence to the IRC server JID, again
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}', True),
+ # also from a second resource
+ partial(send_stanza, "<presence from='{jid_one}/{resource_two}' to='{irc_server_one}' />"),
+
+ # Second resource stops being force connected. But we are not disconnected yet
+ partial(send_stanza, "<presence from='{jid_one}/{resource_two}' to='{irc_server_one}' type='unavailable'/>"),
+ # first (and last) resources stops being force connected
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' type='unavailable'/>"),
+ # we get disconnected from the server
+ # TODO, these two next stanzas aren’t supposed to be there.
+ 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']"),
+
+ # Re-force connect from one resource
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' />"),
+ connection_sequence("irc.localhost", '{jid_one}/{resource_one}', True),
+
+ # and then join some channel
+ partial(send_stanza,
+ "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' />"),
+ partial(expect_stanza,
+ "/message/body[text()='Mode #foo [+nt] by {irc_host_one}']"),
+ partial(expect_stanza,
+ ("/presence[@to='{jid_one}/{resource_one}'][@from='#foo%{irc_server_one}/{nick_one}']/muc_user:x/muc_user:item[@affiliation='admin'][@role='moderator']",
+ "/presence/muc_user:x/muc_user:status[@code='110']")
+ ),
+ partial(expect_stanza, "/message[@from='#foo%{irc_server_one}'][@type='groupchat'][@to='{jid_one}/{resource_one}']/subject[not(text())]"),
+
+ # leave that channel
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='#foo%{irc_server_one}/{nick_one}' type='unavailable' />"),
+ partial(expect_stanza, "/presence[@type='unavailable']/muc_user:x/muc_user:status[@code='110']"),
+
+ # first (and last) resources stops being force connected
+ partial(send_stanza, "<presence from='{jid_one}/{resource_one}' to='{irc_server_one}' type='unavailable'/>"),
+ # we get disconnected from the server
+ partial(expect_stanza, "/presence[@from='{irc_server_one}'][@to='{jid_one}'][@type='unavailable']"),
])
)