diff options
Diffstat (limited to 'src/xmpp')
-rw-r--r-- | src/xmpp/adhoc_command.cpp | 2 | ||||
-rw-r--r-- | src/xmpp/adhoc_commands_handler.cpp | 8 | ||||
-rw-r--r-- | src/xmpp/biboumi_adhoc_commands.cpp | 400 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.cpp | 141 | ||||
-rw-r--r-- | src/xmpp/biboumi_component.hpp | 9 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.cpp | 50 | ||||
-rw-r--r-- | src/xmpp/xmpp_component.hpp | 13 |
7 files changed, 375 insertions, 248 deletions
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 */ |