1 #include <xmpp/biboumi_component.hpp>
2 
3 #include <utils/timed_events.hpp>
4 #include <utils/scopeguard.hpp>
5 #include <utils/tolower.hpp>
6 #include <logger/logger.hpp>
7 #include <xmpp/adhoc_command.hpp>
8 #include <xmpp/biboumi_adhoc_commands.hpp>
9 #include <bridge/list_element.hpp>
10 #include <utils/encoding.hpp>
11 #include <config/config.hpp>
12 #include <utils/time.hpp>
13 #include <xmpp/jid.hpp>
14 
15 #include <stdexcept>
16 #include <iostream>
17 
18 #include <cstdlib>
19 
20 #include <biboumi.h>
21 
22 #ifdef SYSTEMD_FOUND
23 # include <systemd/sd-daemon.h>
24 #endif
25 
26 #include <database/database.hpp>
27 #include <bridge/result_set_management.hpp>
28 #include <bridge/history_limit.hpp>
29 
30 using namespace std::string_literals;
31 
32 static std::set<std::string> kickable_errors{
33     "gone",
34     "internal-server-error",
35     "item-not-found",
36     "jid-malformed",
37     "recipient-unavailable",
38     "redirect",
39     "remote-server-not-found",
40     "remote-server-timeout",
41     "service-unavailable",
42     "malformed-error"
43     };
44 
45 
BiboumiComponent(std::shared_ptr<Poller> & poller,const std::string & hostname,const std::string & secret)46 BiboumiComponent::BiboumiComponent(std::shared_ptr<Poller>& poller, const std::string& hostname, const std::string& secret):
47   XmppComponent(poller, hostname, secret),
48   irc_server_adhoc_commands_handler(*this),
49   irc_channel_adhoc_commands_handler(*this)
50 {
51   this->stanza_handlers.emplace("presence",
52                                 std::bind(&BiboumiComponent::handle_presence, this,std::placeholders::_1));
53   this->stanza_handlers.emplace("message",
54                                 std::bind(&BiboumiComponent::handle_message, this,std::placeholders::_1));
55   this->stanza_handlers.emplace("iq",
56                                 std::bind(&BiboumiComponent::handle_iq, this,std::placeholders::_1));
57 
58   this->adhoc_commands_handler.add_command("ping", {{&PingStep1}, "Do a ping", false});
59   this->adhoc_commands_handler.add_command("hello", {{&HelloStep1, &HelloStep2}, "Receive a custom greeting", false});
60   this->adhoc_commands_handler.add_command("disconnect-user", {{&DisconnectUserStep1, &DisconnectUserStep2}, "Disconnect selected users from the gateway", true});
61   this->adhoc_commands_handler.add_command("disconnect-from-irc-server", {{&DisconnectUserFromServerStep1, &DisconnectUserFromServerStep2, &DisconnectUserFromServerStep3}, "Disconnect from the selected IRC servers", false});
62   this->adhoc_commands_handler.add_command("reload", {{&Reload}, "Reload biboumi’s configuration", true});
63 
64   AdhocCommand get_irc_connection_info{{&GetIrcConnectionInfoStep1}, "Returns various information about your connection to this IRC server.", false};
65   if (!Config::get("fixed_irc_server", "").empty())
66     this->adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info);
67   else
68     this->irc_server_adhoc_commands_handler.add_command("get-irc-connection-info", get_irc_connection_info);
69 
70 #ifdef USE_DATABASE
71   AdhocCommand configure_server_command({&ConfigureIrcServerStep1, &ConfigureIrcServerStep2}, "Configure a few settings for that IRC server", false);
72   AdhocCommand configure_global_command({&ConfigureGlobalStep1, &ConfigureGlobalStep2}, "Configure a few settings", false);
73 
74   if (!Config::get("fixed_irc_server", "").empty())
75     {
76       this->adhoc_commands_handler.add_command("server-configure", configure_server_command);
77       this->adhoc_commands_handler.add_command("global-configure", configure_global_command);
78     }
79   else
80     {
81       this->adhoc_commands_handler.add_command("configure", configure_global_command);
82       this->irc_server_adhoc_commands_handler.add_command("configure", configure_server_command);
83     }
84 
85   this->irc_channel_adhoc_commands_handler.add_command("configure", {{&ConfigureIrcChannelStep1, &ConfigureIrcChannelStep2}, "Configure a few settings for that IRC channel", false});
86 #endif
87 }
88 
shutdown()89 void BiboumiComponent::shutdown()
90 {
91   for (auto& pair: this->bridges)
92     pair.second->shutdown("Gateway shutdown");
93 #ifdef USE_DATABASE
94   for (const Database::RosterItem& roster_item: Database::get_full_roster())
95     {
96       this->send_presence_to_contact(roster_item.col<Database::LocalJid>(),
97                                      roster_item.col<Database::RemoteJid>(),
98                                      "unavailable");
99     }
100 #endif
101 }
102 
clean()103 void BiboumiComponent::clean()
104 {
105   auto it = std::begin(this->bridges);
106   while (it != std::end(this->bridges))
107   {
108     it->second->clean();
109     if (it->second->active_clients() == 0)
110       it = this->bridges.erase(it);
111     else
112       ++it;
113   }
114 }
115 
handle_presence(const Stanza & stanza)116 void BiboumiComponent::handle_presence(const Stanza& stanza)
117 {
118   std::string from_str = stanza.get_tag("from");
119   std::string id = stanza.get_tag("id");
120   std::string to_str = stanza.get_tag("to");
121   std::string type = stanza.get_tag("type");
122 
123   // Check for mandatory tags
124   if (from_str.empty())
125     {
126       log_warning("Received an invalid presence stanza: tag 'from' is missing.");
127       return;
128     }
129   if (to_str.empty())
130     {
131       this->send_stanza_error("presence", from_str, this->served_hostname, id,
132                               "modify", "bad-request", "Missing 'to' tag");
133       return;
134     }
135 
136   Bridge* bridge = this->get_user_bridge(from_str);
137   Jid to(to_str);
138   Jid from(from_str);
139   Iid iid(to.local, bridge);
140 
141   // An error stanza is sent whenever we exit this function without
142   // disabling this scopeguard.  If error_type and error_name are not
143   // changed, the error signaled is internal-server-error. Change their
144   // value to signal an other kind of error. For example
145   // feature-not-implemented, etc.  Any non-error process should reach the
146   // stanza_error.disable() call at the end of the function.
147   std::string error_type("cancel");
148   std::string error_name("internal-server-error");
149   utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name](){
150       this->send_stanza_error("presence", from_str, to_str, id,
151                               error_type, error_name, "");
152         });
153 
154   try {
155   if (iid.type == Iid::Type::Channel && !iid.get_server().empty())
156     { // presence toward a MUC that corresponds to an irc channel
157       if (type.empty())
158         {
159           const std::string own_nick = bridge->get_own_nick(iid);
160           const XmlNode* x = stanza.get_child("x", MUC_NS);
161           const IrcClient* irc = bridge->find_irc_client(iid.get_server());
162           // if there is no <x/>, this is a presence status update, we don’t care about those
163           if (x)
164             {
165               const XmlNode* password = x->get_child("password", MUC_NS);
166               const XmlNode* history = x->get_child("history", MUC_NS);
167               HistoryLimit history_limit;
168               if (history)
169                 {
170                   const auto seconds = history->get_tag("seconds");
171                   if (!seconds.empty())
172                     {
173                       const auto now = std::chrono::system_clock::now();
174                       std::time_t timestamp = std::chrono::system_clock::to_time_t(now);
175                       int int_seconds = std::atoi(seconds.data());
176                       timestamp -= int_seconds;
177                       history_limit.since = utils::to_string(timestamp);
178                     }
179                   const auto since = history->get_tag("since");
180                   if (!since.empty())
181                     history_limit.since = since;
182                   const auto maxstanzas = history->get_tag("maxstanzas");
183                   if (!maxstanzas.empty())
184                     history_limit.stanzas = std::atoi(maxstanzas.data());
185                   // Ignore any other value, because this is too complex to implement,
186                   // so I won’t do it.
187                   if (history->get_tag("maxchars") == "0")
188                     history_limit.stanzas = 0;
189                 }
190               bridge->join_irc_channel(iid, to.resource, password ? password->get_inner(): "",
191                                        from.resource, history_limit);
192             }
193           else
194             {
195               if (irc)
196                 {
197                   const auto chan = irc->find_channel(iid.get_local());
198                   if (chan && chan->joined)
199                     bridge->send_irc_nick_change(iid, to.resource, from.resource);
200                   else
201                     { // send an error if we are not joined yet, instead of treating it as a join
202                       this->send_stanza_error("presence", from_str, to_str, id, "modify", "not-acceptable", "You are not joined to this MUC.");
203                     }
204                 }
205             }
206         }
207       else if (type == "unavailable")
208         {
209           const XmlNode* status = stanza.get_child("status", COMPONENT_NS);
210           bridge->leave_irc_channel(std::move(iid), status ? status->get_inner() : "", from.resource);
211         }
212     }
213   else if (iid.type == Iid::Type::Server || iid.type == Iid::Type::None)
214     {
215       if (type == "subscribe")
216         { // Auto-accept any subscription request for an IRC server
217           this->send_presence_to_contact(to_str, from.bare(), "subscribed", id);
218           if (iid.type == Iid::Type::None || bridge->find_irc_client(iid.get_server()))
219             this->send_presence_to_contact(to_str, from.bare(), "");
220           this->send_presence_to_contact(to_str, from.bare(), "subscribe");
221 #ifdef USE_DATABASE
222           if (!Database::has_roster_item(to_str, from.bare()))
223             Database::add_roster_item(to_str, from.bare());
224 #endif
225         }
226       else if (type == "unsubscribe")
227         {
228           this->send_presence_to_contact(to_str, from.bare(), "unavailable", id);
229           this->send_presence_to_contact(to_str, from.bare(), "unsubscribed");
230           this->send_presence_to_contact(to_str, from.bare(), "unsubscribe");
231 #ifdef USE_DATABASE
232           const bool res = Database::has_roster_item(to_str, from.bare());
233           if (res)
234             Database::delete_roster_item(to_str, from.bare());
235 #endif
236         }
237       else if (type == "probe")
238         {
239           if ((iid.type == Iid::Type::Server && bridge->find_irc_client(iid.get_server()))
240               || iid.type == Iid::Type::None)
241             {
242 #ifdef USE_DATABASE
243               if (Database::has_roster_item(to_str, from.bare()))
244 #endif
245                 this->send_presence_to_contact(to_str, from.bare(), "");
246 #ifdef USE_DATABASE
247               else // rfc 6121 4.3.2.1
248                 this->send_presence_to_contact(to_str, from.bare(), "unsubscribed");
249 #endif
250             }
251         }
252       else if (type.empty())
253         { // We just receive a presence from someone (as the result of a probe,
254           // or a directed presence, or a normal presence change)
255           if (iid.type == Iid::Type::None)
256             this->send_presence_to_contact(to_str, from.bare(), "");
257         }
258     }
259   else if (iid.type == Iid::Type::User)
260     { // Do nothing yet
261     }
262   }
263   catch (const IRCNotConnected& ex)
264     {
265       if (type != "unavailable")
266         this->send_stanza_error("presence", from_str, to_str, id,
267                                 "cancel", "remote-server-not-found",
268                                 "Not connected to IRC server " + ex.hostname,
269                                 true);
270     }
271   stanza_error.disable();
272 }
273 
handle_message(const Stanza & stanza)274 void BiboumiComponent::handle_message(const Stanza& stanza)
275 {
276   std::string from_str = stanza.get_tag("from");
277   std::string id = stanza.get_tag("id");
278   std::string to_str = stanza.get_tag("to");
279   std::string type = stanza.get_tag("type");
280 
281   if (from_str.empty())
282     return;
283   if (type.empty())
284     type = "normal";
285   Bridge* bridge = this->get_user_bridge(from_str);
286   Jid from(from_str);
287   Jid to(to_str);
288   Iid iid(to.local, bridge);
289 
290   std::string error_type("cancel");
291   std::string error_name("internal-server-error");
292   std::string error_text{};
293   utils::ScopeGuard stanza_error([this, &from_str, &to_str, &id, &error_type, &error_name, &error_text](){
294       this->send_stanza_error("message", from_str, to_str, id,
295                               error_type, error_name, error_text);
296     });
297   const XmlNode* body = stanza.get_child("body", COMPONENT_NS);
298 
299   try {                         // catch IRCNotConnected exceptions
300   if (type == "groupchat" && iid.type == Iid::Type::Channel)
301     {
302       if (body && !body->get_inner().empty())
303         {
304           if (bridge->is_resource_in_chan(iid.to_tuple(), from.resource))
305             {
306               // Extract some XML nodes that we must include in the
307               // reflection (if any), because XMPP says so
308               std::vector<XmlNode> nodes_to_reflect;
309               const XmlNode* origin_id = stanza.get_child("origin-id", STABLE_ID_NS);
310               if (origin_id)
311                 nodes_to_reflect.push_back(*origin_id);
312               const auto own_address = std::to_string(iid) + '@' + this->served_hostname;
313               for (const XmlNode* stanza_id: stanza.get_children("stanza-id", STABLE_ID_NS))
314                 {
315                   // Stanza ID generating entities, which encounter a
316                   // <stanza-id/> element where the 'by' attribute matches
317                   // the 'by' attribute they would otherwise set, MUST
318                   // delete that element even if they are not adding their
319                   // own stanza ID.
320                   if (stanza_id->get_tag("by") != own_address)
321                     nodes_to_reflect.push_back(*stanza_id);
322                 }
323               bridge->send_channel_message(iid, body->get_inner(), id, std::move(nodes_to_reflect));
324             }
325           else
326             {
327               error_type = "modify";
328               error_name = "not-acceptable";
329               error_text = "You are not a participant in this room.";
330               return;
331             }
332         }
333       const XmlNode* subject = stanza.get_child("subject", COMPONENT_NS);
334       if (subject)
335         bridge->set_channel_topic(iid, subject->get_inner());
336     }
337   else if (type == "error")
338     {
339       const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
340       // Only a set of errors are considered “fatal”. If we encounter one of
341       // them, we purge (we disconnect that resource from all the IRC servers)
342       // We consider this to be true, unless the error condition is
343       // specified and is not in the kickable_errors set
344       bool kickable_error = true;
345       if (error && error->has_children())
346         {
347           const XmlNode* condition = error->get_last_child();
348           if (kickable_errors.find(condition->get_name()) == kickable_errors.end())
349             kickable_error = false;
350         }
351       if (kickable_error)
352         bridge->remove_resource(from.resource, "Error from remote client");
353     }
354   else if (type == "chat")
355     {
356       if (body && !body->get_inner().empty())
357         {
358           const auto fixed_irc_server = Config::get("fixed_irc_server", "");
359           // a message for nick!server
360           if (iid.type == Iid::Type::User && !iid.get_local().empty())
361             {
362               bridge->send_private_message(iid, body->get_inner());
363               bridge->remove_preferred_from_jid(iid.get_local());
364             }
365           else if (iid.type != Iid::Type::User && !to.resource.empty())
366             { // a message for chan%server@biboumi/Nick or
367               // server@biboumi/Nick
368               // Convert that into a message to nick!server
369               Iid user_iid(utils::tolower(to.resource), iid.get_server(), Iid::Type::User);
370               bridge->send_private_message(user_iid, body->get_inner());
371               bridge->set_preferred_from_jid(user_iid.get_local(), to_str);
372             }
373           else if (iid.type == Iid::Type::Server)
374             bridge->send_raw_message(iid.get_server(), body->get_inner());
375           else if (iid.type == Iid::Type::None && !fixed_irc_server.empty())
376             { // Message sent to the server JID
377               // Convert the message body into a raw IRC message
378               bridge->send_raw_message(fixed_irc_server, body->get_inner());
379             }
380         }
381     }
382   else if (type == "normal" && iid.type == Iid::Type::Channel)
383     {
384       if (const XmlNode* x = stanza.get_child("x", MUC_USER_NS))
385         if (const XmlNode* invite = x->get_child("invite", MUC_USER_NS))
386           {
387             const auto invite_to = invite->get_tag("to");
388             if (!invite_to.empty())
389               {
390                 Jid invited_jid{invite_to};
391                 if (invited_jid.domain == this->get_served_hostname() || invited_jid.local.empty())
392                   bridge->send_irc_invitation(iid, invite_to);
393                 else
394                   this->send_invitation_from_fulljid(std::to_string(iid), invite_to, from_str);
395               }
396           }
397     }
398   } catch (const IRCNotConnected& ex)
399     {
400       this->send_stanza_error("message", from_str, to_str, id,
401                               "cancel", "remote-server-not-found",
402                               "Not connected to IRC server " + ex.hostname,
403                               true);
404     }
405   stanza_error.disable();
406 }
407 
408 // We MUST return an iq, whatever happens, except if the type is
409 // "result" or "error".
410 // To do this, we use a scopeguard. If an exception is raised somewhere, an
411 // iq of type error "internal-server-error" is sent. If we handle the
412 // request properly (by calling a function that registers an iq to be sent
413 // later, or that directly sends an iq), we disable the ScopeGuard. If we
414 // reach the end of the function without having disabled the scopeguard, we
415 // send a "feature-not-implemented" iq as a result.  If an other kind of
416 // error is found (for example the feature is implemented in biboumi, but
417 // the request is missing some attribute) we can just change the values of
418 // error_type and error_name and return from the function (without disabling
419 // the scopeguard); an iq error will be sent
handle_iq(const Stanza & stanza)420 void BiboumiComponent::handle_iq(const Stanza& stanza)
421 {
422   std::string id = stanza.get_tag("id");
423   std::string from = stanza.get_tag("from");
424   std::string to_str = stanza.get_tag("to");
425   std::string type = stanza.get_tag("type");
426 
427   if (from.empty()) {
428     log_warning("Received an iq without a 'from'. Ignoring.");
429     return;
430   }
431   if (id.empty() || to_str.empty() || type.empty())
432     {
433       this->send_stanza_error("iq", from, this->served_hostname, id,
434                               "modify", "bad-request", "");
435       return;
436     }
437 
438   Bridge* bridge = this->get_user_bridge(from);
439   Jid to(to_str);
440 
441   // These two values will be used in the error iq sent if we don't disable
442   // the scopeguard.
443   std::string error_type("cancel");
444   std::string error_name("internal-server-error");
445   utils::ScopeGuard stanza_error([this, &from, &to_str, &id, &error_type, &error_name](){
446       this->send_stanza_error("iq", from, to_str, id,
447                               error_type, error_name, "");
448     });
449   try {
450   if (type == "set")
451     {
452       const XmlNode* query;
453       if ((query = stanza.get_child("query", MUC_ADMIN_NS)))
454         {
455           const XmlNode* child = query->get_child("item", MUC_ADMIN_NS);
456           if (child)
457             {
458               std::string nick = child->get_tag("nick");
459               std::string role = child->get_tag("role");
460               std::string affiliation = child->get_tag("affiliation");
461               if (!nick.empty())
462                 {
463                   Iid iid(to.local, {});
464                   if (role == "none")
465                     {               // This is a kick
466                       std::string reason;
467                       const XmlNode* reason_el = child->get_child("reason", MUC_ADMIN_NS);
468                       if (reason_el)
469                         reason = reason_el->get_inner();
470                       bridge->send_irc_kick(iid, nick, reason, id, from);
471                     }
472                   else
473                     bridge->forward_affiliation_role_change(iid, from, nick, affiliation, role, id);
474                   stanza_error.disable();
475                 }
476             }
477         }
478       else if ((query = stanza.get_child("command", ADHOC_NS)))
479         {
480           Stanza response("iq");
481           response["to"] = from;
482           response["from"] = to_str;
483           response["id"] = id;
484 
485           // Depending on the 'to' jid in the request, we use one adhoc
486           // command handler or an other
487           Iid iid(to.local, {'#', '&'});
488           AdhocCommandsHandler* adhoc_handler;
489           if (to.local.empty())
490             adhoc_handler = &this->adhoc_commands_handler;
491           else
492           {
493             if (iid.type == Iid::Type::Server)
494               adhoc_handler = &this->irc_server_adhoc_commands_handler;
495             else if (iid.type == Iid::Type::Channel && to.resource.empty())
496               adhoc_handler = &this->irc_channel_adhoc_commands_handler;
497             else
498               {
499                 error_name = "feature-not-implemented";
500                 return;
501               }
502           }
503           // Execute the command, if any, and get a result XmlNode that we
504           // insert in our response
505           XmlNode inner_node = adhoc_handler->handle_request(from, to_str, *query);
506           if (inner_node.get_child("error", ADHOC_NS))
507             response["type"] = "error";
508           else
509             response["type"] = "result";
510           response.add_child(std::move(inner_node));
511           this->send_stanza(response);
512           stanza_error.disable();
513         }
514 #ifdef USE_DATABASE
515       else if ((query = stanza.get_child("query", MAM_NS)))
516         {
517           try {
518               if (this->handle_mam_request(stanza))
519                 stanza_error.disable();
520             } catch (const Database::RecordNotFound& exc) {
521               error_name = "item-not-found";
522               return;
523             }
524         }
525       else if ((query = stanza.get_child("query", MUC_OWNER_NS)))
526         {
527           if (this->handle_room_configuration_form(*query, from, to, id))
528             stanza_error.disable();
529         }
530 #endif
531     }
532   else if (type == "get")
533     {
534       const XmlNode* query;
535       if ((query = stanza.get_child("query", DISCO_INFO_NS)))
536         { // Disco info
537           Iid iid(to.local, {'#', '&'});
538           const std::string node = query->get_tag("node");
539           if (to_str == this->served_hostname)
540             {
541               if (node.empty())
542                 {
543                   // On the gateway itself
544                   this->send_self_disco_info(id, from);
545                   stanza_error.disable();
546                 }
547             }
548           else if (iid.type == Iid::Type::Server)
549             {
550                 if (node.empty())
551                 {
552                     this->send_irc_server_disco_info(id, from, to_str);
553                     stanza_error.disable();
554                 }
555             }
556           else if (iid.type == Iid::Type::Channel && to.resource.empty())
557             {
558               if (node.empty())
559                 {
560                   const IrcClient* irc_client = bridge->find_irc_client(iid.get_server());
561                   const IrcChannel* irc_channel{};
562                   if (irc_client)
563                     irc_channel = irc_client->find_channel(iid.get_local());
564                   this->send_irc_channel_disco_info(id, from, to_str, irc_channel);
565                   stanza_error.disable();
566                 }
567               else if (node == MUC_TRAFFIC_NS)
568                 {
569                   this->send_irc_channel_muc_traffic_info(id, from, to_str);
570                   stanza_error.disable();
571                 }
572             }
573         }
574       else if ((query = stanza.get_child("query", VERSION_NS)))
575         {
576           Iid iid(to.local, bridge);
577           if ((iid.type == Iid::Type::Channel && !to.resource.empty()) ||
578               (iid.type == Iid::Type::User))
579             {
580               // Get the IRC user version
581               std::string target;
582               if (iid.type == Iid::Type::User)
583                 target = iid.get_local();
584               else
585                 target = to.resource;
586               bridge->send_irc_version_request(iid.get_server(), target, id,
587                                                from, to_str);
588             }
589           else
590             {
591               // On the gateway itself or on a channel
592               this->send_version(id, from, to_str);
593             }
594           stanza_error.disable();
595         }
596       else if ((query = stanza.get_child("query", DISCO_ITEMS_NS)))
597         {
598           Iid iid(to.local, bridge);
599           const std::string node = query->get_tag("node");
600           if (node == ADHOC_NS)
601             {
602               Jid from_jid(from);
603               if (to.local.empty())
604                 {               // Get biboumi's adhoc commands
605                   this->send_adhoc_commands_list(id, from, this->served_hostname,
606                                                  Config::is_in_list("admin", from_jid.bare()),
607                                                  this->adhoc_commands_handler);
608                   stanza_error.disable();
609                 }
610               else if (iid.type == Iid::Type::Server)
611                 {               // Get the server's adhoc commands
612                   this->send_adhoc_commands_list(id, from, to_str,
613                                                  Config::is_in_list("admin", from_jid.bare()),
614                                                  this->irc_server_adhoc_commands_handler);
615                   stanza_error.disable();
616                 }
617               else if (iid.type == Iid::Type::Channel && to.resource.empty())
618                 {               // Get the channel's adhoc commands
619                   this->send_adhoc_commands_list(id, from, to_str,
620                                                  Config::is_in_list("admin", from_jid.bare()),
621                                                  this->irc_channel_adhoc_commands_handler);
622                   stanza_error.disable();
623                 }
624               else // “to” is a MUC user, not the room itself
625                 error_name = "feature-not-implemented";
626             }
627           else if (node.empty() && iid.type == Iid::Type::Server)
628             { // Disco on an IRC server: get the list of channels
629               ResultSetInfo rs_info;
630               const XmlNode* set_node = query->get_child("set", RSM_NS);
631               if (set_node)
632                 {
633                   const XmlNode* after = set_node->get_child("after", RSM_NS);
634                   if (after)
635                     rs_info.after = after->get_inner();
636                   const XmlNode* before = set_node->get_child("before", RSM_NS);
637                   if (before)
638                     rs_info.before = before->get_inner();
639                   const XmlNode* max = set_node->get_child("max", RSM_NS);
640                   if (max)
641                     rs_info.max = std::atoi(max->get_inner().data());
642                 }
643               if (rs_info.max == -1)
644                 rs_info.max = 100;
645               bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info));
646               stanza_error.disable();
647             }
648         }
649       else if ((query = stanza.get_child("ping", PING_NS)))
650         {
651           Iid iid(to.local, bridge);
652           if (iid.type == Iid::Type::User)
653             { // Ping any user (no check on the nick done ourself)
654               bridge->send_irc_user_ping_request(iid.get_server(),
655                                                  iid.get_local(), id, from, to_str);
656             }
657           else if (iid.type == Iid::Type::Channel && !to.resource.empty())
658             { // Ping a room participant (we check if the nick is in the room)
659               bridge->send_irc_participant_ping_request(iid,
660                                                         to.resource, id, from, to_str);
661             }
662           else
663             { // Ping a channel, a server or the gateway itself
664               bridge->on_gateway_ping(iid.get_server(),
665                                      id, from, to_str);
666             }
667           stanza_error.disable();
668         }
669 #ifdef USE_DATABASE
670       else if ((query = stanza.get_child("query", MUC_OWNER_NS)))
671         {
672           if (this->handle_room_configuration_form_request(from, to, id))
673             stanza_error.disable();
674         }
675 #endif
676     }
677   else if (type == "result")
678     {
679       stanza_error.disable();
680       const XmlNode* query;
681       if ((query = stanza.get_child("query", VERSION_NS)))
682         {
683           const XmlNode* name_node = query->get_child("name", VERSION_NS);
684           const XmlNode* version_node = query->get_child("version", VERSION_NS);
685           const XmlNode* os_node = query->get_child("os", VERSION_NS);
686           std::string name;
687           std::string version;
688           std::string os;
689           if (name_node)
690             name = name_node->get_inner() + " (through the biboumi gateway)";
691           if (version_node)
692             version = version_node->get_inner();
693           if (os_node)
694             os = os_node->get_inner();
695           const Iid iid(to.local, bridge);
696           bridge->send_xmpp_version_to_irc(iid, name, version, os);
697         }
698       else
699         {
700           const auto it = this->waiting_iq.find(id);
701           if (it != this->waiting_iq.end())
702             {
703               it->second(bridge, stanza);
704               this->waiting_iq.erase(it);
705             }
706         }
707     }
708   else if (type == "error")
709     {
710       stanza_error.disable();
711       const auto it = this->waiting_iq.find(id);
712       if (it != this->waiting_iq.end())
713         {
714           it->second(bridge, stanza);
715           this->waiting_iq.erase(it);
716         }
717     }
718   }
719   catch (const IRCNotConnected& ex)
720     {
721       this->send_stanza_error("iq", from, to_str, id,
722                               "cancel", "remote-server-not-found",
723                               "Not connected to IRC server " + ex.hostname,
724                               true);
725       stanza_error.disable();
726       return;
727     }
728   error_type = "cancel";
729   error_name = "feature-not-implemented";
730 }
731 
732 #ifdef USE_DATABASE
handle_mam_request(const Stanza & stanza)733 bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
734 {
735     std::string id = stanza.get_tag("id");
736     Jid from(stanza.get_tag("from"));
737     Jid to(stanza.get_tag("to"));
738 
739     const XmlNode* query = stanza.get_child("query", MAM_NS);
740 
741     Iid iid(to.local, {'#', '&'});
742     if (query && iid.type == Iid::Type::Channel && to.resource.empty())
743       {
744         const std::string query_id = query->get_tag("queryid");
745         std::string start;
746         std::string end;
747         const XmlNode* x = query->get_child("x", DATAFORM_NS);
748         if (x)
749           {
750             const XmlNode* value;
751             const auto fields = x->get_children("field", DATAFORM_NS);
752             for (const auto& field: fields)
753               {
754                 if (field->get_tag("var") == "start")
755                   {
756                     value = field->get_child("value", DATAFORM_NS);
757                     if (value)
758                       start = value->get_inner();
759                   }
760                 else if (field->get_tag("var") == "end")
761                   {
762                     value = field->get_child("value", DATAFORM_NS);
763                     if (value)
764                       end = value->get_inner();
765                   }
766               }
767           }
768         const XmlNode* set = query->get_child("set", RSM_NS);
769         int limit = -1;
770         Id::real_type reference_record_id{Id::unset_value};
771         Database::Paging paging_order{Database::Paging::first};
772         if (set)
773           {
774             const XmlNode* max = set->get_child("max", RSM_NS);
775             if (max)
776               limit = std::atoi(max->get_inner().data());
777             const XmlNode* after = set->get_child("after", RSM_NS);
778             if (after)
779               {
780                 auto after_record = Database::get_muc_log(from.bare(), iid.get_local(), iid.get_server(),
781                                                           after->get_inner(), start, end);
782                 reference_record_id = after_record.col<Id>();
783               }
784             const XmlNode* before = set->get_child("before", RSM_NS);
785             if (before)
786               {
787                 paging_order = Database::Paging::last;
788                 if (!before->get_inner().empty())
789                   {
790                     auto before_record = Database::get_muc_log(from.bare(), iid.get_local(), iid.get_server(), before->get_inner(), start, end);
791                     reference_record_id = before_record.col<Id>();
792                   }
793               }
794           }
795         // Do not send more than 100 messages, even if the client asked for more,
796         // or if it didn’t specify any limit.
797         if (limit < 0 || limit > 100)
798           limit = 100;
799         auto result = Database::get_muc_logs(from.bare(), iid.get_local(), iid.get_server(),
800                                             static_cast<std::size_t>(limit),
801                                             start, end,
802                                             reference_record_id, paging_order);
803         bool complete = std::get<bool>(result);
804         auto& lines = std::get<1>(result);
805 
806         for (const Database::MucLogLine& line: lines)
807           {
808             if (!line.col<Database::Nick>().empty())
809               this->send_archived_message(line, to.full(), from.full(), query_id);
810           }
811         {
812           auto fin_ptr = std::make_unique<XmlNode>("fin");
813           {
814             XmlNode& fin = *(fin_ptr.get());
815             fin["xmlns"] = MAM_NS;
816             if (complete)
817               fin["complete"] = "true";
818             XmlSubNode set(fin, "set");
819             set["xmlns"] = RSM_NS;
820             if (!lines.empty())
821               {
822                 XmlSubNode first(set, "first");
823                 first["index"] = "0";
824                 first.set_inner(lines[0].col<Database::Uuid>());
825                 XmlSubNode last(set, "last");
826                 last.set_inner(lines[lines.size() - 1].col<Database::Uuid>());
827               }
828           }
829           this->send_iq_result_full_jid(id, from.full(), to.full(), std::move(fin_ptr));
830         }
831         return true;
832       }
833   return false;
834 }
835 
send_archived_message(const Database::MucLogLine & log_line,const std::string & from,const std::string & to,const std::string & queryid)836 void BiboumiComponent::send_archived_message(const Database::MucLogLine& log_line, const std::string& from, const std::string& to,
837                                              const std::string& queryid)
838 {
839   Stanza message("message");
840   {
841     message["from"] = from;
842     message["to"] = to;
843 
844     XmlSubNode result(message, "result");
845     result["xmlns"] = MAM_NS;
846     if (!queryid.empty())
847       result["queryid"] = queryid;
848     result["id"] = log_line.col<Database::Uuid>();
849 
850     XmlSubNode forwarded(result, "forwarded");
851     forwarded["xmlns"] = FORWARD_NS;
852 
853     XmlSubNode delay(forwarded, "delay");
854     delay["xmlns"] = DELAY_NS;
855     delay["stamp"] = utils::to_string(log_line.col<Database::Date>());
856 
857     XmlSubNode submessage(forwarded, "message");
858     submessage["xmlns"] = CLIENT_NS;
859     submessage["from"] = from + "/" + log_line.col<Database::Nick>();
860     submessage["type"] = "groupchat";
861 
862     XmlSubNode body(submessage, "body");
863     body.set_inner(log_line.col<Database::Body>());
864   }
865   this->send_stanza(message);
866 }
867 
handle_room_configuration_form_request(const std::string & from,const Jid & to,const std::string & id)868 bool BiboumiComponent::handle_room_configuration_form_request(const std::string& from, const Jid& to, const std::string& id)
869 {
870   Iid iid(to.local, {'#', '&'});
871 
872   if (iid.type != Iid::Type::Channel || !to.resource.empty())
873     return false;
874 
875   Stanza iq("iq");
876   {
877     iq["from"] = to.full();
878     iq["to"] = from;
879     iq["id"] = id;
880     iq["type"] = "result";
881     XmlSubNode query(iq, "query");
882     query["xmlns"] = MUC_OWNER_NS;
883     Jid requester(from);
884     insert_irc_channel_configuration_form(query, requester, to);
885   }
886   this->send_stanza(iq);
887   return true;
888 }
889 
handle_room_configuration_form(const XmlNode & query,const std::string & from,const Jid & to,const std::string & id)890 bool BiboumiComponent::handle_room_configuration_form(const XmlNode& query, const std::string &from, const Jid &to, const std::string &id)
891 {
892   Iid iid(to.local, {'#', '&'});
893 
894   if (iid.type != Iid::Type::Channel || !to.resource.empty())
895     return false;
896 
897   Jid requester(from);
898   if (!handle_irc_channel_configuration_form(*this, query, requester, to))
899     return false;
900 
901   Stanza iq("iq");
902   iq["type"] = "result";
903   iq["from"] = to.full();
904   iq["to"] = from;
905   iq["id"] = id;
906 
907   this->send_stanza(iq);
908 
909   return true;
910 }
911 
912 #endif
913 
get_user_bridge(const std::string & user_jid)914 Bridge* BiboumiComponent::get_user_bridge(const std::string& user_jid)
915 {
916   auto bare_jid = Jid{user_jid}.bare();
917   try
918     {
919       return this->bridges.at(bare_jid).get();
920     }
921   catch (const std::out_of_range& exception)
922     {
923       return this->bridges.emplace(bare_jid, std::make_unique<Bridge>(bare_jid, *this, this->poller)).first->second.get();
924     }
925 }
926 
find_user_bridge(const std::string & full_jid)927 Bridge* BiboumiComponent::find_user_bridge(const std::string& full_jid)
928 {
929   auto bare_jid = Jid{full_jid}.bare();
930   try
931     {
932       return this->bridges.at(bare_jid).get();
933     }
934   catch (const std::out_of_range& exception)
935     {
936       return nullptr;
937     }
938 }
939 
get_bridges() const940 std::vector<Bridge*> BiboumiComponent::get_bridges() const
941 {
942   std::vector<Bridge*> res;
943   for (const auto& bridge: this->bridges)
944     res.push_back(bridge.second.get());
945   return res;
946 }
947 
send_self_disco_info(const std::string & id,const std::string & jid_to)948 void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to)
949 {
950   Stanza iq("iq");
951   {
952     iq["type"] = "result";
953     iq["id"] = id;
954     iq["to"] = jid_to;
955     iq["from"] = this->served_hostname;
956     XmlSubNode query(iq, "query");
957     query["xmlns"] = DISCO_INFO_NS;
958     XmlSubNode identity(query, "identity");
959     identity["category"] = "conference";
960     identity["type"] = "irc";
961     identity["name"] = "Biboumi XMPP-IRC gateway";
962     for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS})
963       {
964         XmlSubNode feature(query, "feature");
965         feature["var"] = ns;
966       }
967   }
968   this->send_stanza(iq);
969 }
970 
send_irc_server_disco_info(const std::string & id,const std::string & jid_to,const std::string & jid_from)971 void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
972 {
973   Jid from(jid_from);
974   Stanza iq("iq");
975   {
976     iq["type"] = "result";
977     iq["id"] = id;
978     iq["to"] = jid_to;
979     iq["from"] = jid_from;
980     XmlSubNode query(iq, "query");
981     query["xmlns"] = DISCO_INFO_NS;
982     XmlSubNode identity(query, "identity");
983     identity["category"] = "conference";
984     identity["type"] = "irc";
985     identity["name"] = "IRC server " + from.local + " over Biboumi";
986     for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS})
987       {
988         XmlSubNode feature(query, "feature");
989         feature["var"] = ns;
990       }
991   }
992   this->send_stanza(iq);
993 }
994 
send_irc_channel_muc_traffic_info(const std::string & id,const std::string & jid_to,const std::string & jid_from)995 void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string& id, const std::string& jid_to, const std::string& jid_from)
996 {
997   Stanza iq("iq");
998   {
999     iq["type"] = "result";
1000     iq["id"] = id;
1001     iq["from"] = jid_from;
1002     iq["to"] = jid_to;
1003 
1004     XmlSubNode query(iq, "query");
1005     query["xmlns"] = DISCO_INFO_NS;
1006     query["node"] = MUC_TRAFFIC_NS;
1007     // We drop all “special” traffic (like xhtml-im, chatstates, etc), so
1008     // don’t include any <feature/>
1009   }
1010   this->send_stanza(iq);
1011 }
1012 
send_irc_channel_disco_info(const std::string & id,const std::string & jid_to,const std::string & jid_from,const IrcChannel * irc_channel)1013 void BiboumiComponent::send_irc_channel_disco_info(const std::string& id, const std::string& jid_to,
1014                                                    const std::string& jid_from, const IrcChannel* irc_channel)
1015 {
1016   Jid from(jid_from);
1017   Iid iid(from.local, {});
1018   Stanza iq("iq");
1019   {
1020     iq["type"] = "result";
1021     iq["id"] = id;
1022     iq["to"] = jid_to;
1023     iq["from"] = jid_from;
1024     XmlSubNode query(iq, "query");
1025     query["xmlns"] = DISCO_INFO_NS;
1026     XmlSubNode identity(query, "identity");
1027     identity["category"] = "conference";
1028     identity["type"] = "irc";
1029     identity["name"] = ""s + iid.get_local() + " on " + iid.get_server();
1030     for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS, STABLE_MUC_ID_NS, SELF_PING_FLAG, "muc_nonanonymous", STABLE_ID_NS})
1031       {
1032         XmlSubNode feature(query, "feature");
1033         feature["var"] = ns;
1034       }
1035 
1036     XmlSubNode x(query, "x");
1037     x["xmlns"] = DATAFORM_NS;
1038     x["type"] = "result";
1039     {
1040       XmlSubNode field(x, "field");
1041       field["var"] = "FORM_TYPE";
1042       field["type"] = "hidden";
1043       XmlSubNode value(field, "value");
1044       value.set_inner("http://jabber.org/protocol/muc#roominfo");
1045     }
1046 
1047     if (irc_channel && irc_channel->joined)
1048       {
1049         XmlSubNode field(x, "field");
1050         field["var"] = "muc#roominfo_occupants";
1051         field["label"] = "Number of occupants";
1052         XmlSubNode value(field, "value");
1053         value.set_inner(std::to_string(irc_channel->get_users().size()));
1054       }
1055   }
1056   this->send_stanza(iq);
1057 }
1058 
send_ping_request(const std::string & from,const std::string & jid_to,const std::string & id)1059 void BiboumiComponent::send_ping_request(const std::string& from,
1060                                          const std::string& jid_to,
1061                                          const std::string& id)
1062 {
1063   Stanza iq("iq");
1064   {
1065     iq["type"] = "get";
1066     iq["id"] = id;
1067     iq["from"] = from + "@" + this->served_hostname;
1068     iq["to"] = jid_to;
1069     XmlSubNode ping(iq, "ping");
1070     ping["xmlns"] = PING_NS;
1071   }
1072   this->send_stanza(iq);
1073 
1074   auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza)
1075     {
1076       Jid to(stanza.get_tag("to"));
1077       if (to.local != from)
1078         {
1079           log_error("Received a corresponding ping result, but the 'to' from "
1080                     "the response mismatches the 'from' of the request");
1081           return;
1082         }
1083       const std::string type = stanza.get_tag("type");
1084       const XmlNode* error = stanza.get_child("error", COMPONENT_NS);
1085       // Check if what we receive is considered a valid response. And yes, those errors are valid responses
1086       if (type == "result" ||
1087           (type == "error" && error && (error->get_child("feature-not-implemented", STANZA_NS) ||
1088                                         error->get_child("service-unavailable", STANZA_NS))))
1089         bridge->send_irc_ping_result({from, bridge}, id);
1090     };
1091   this->waiting_iq[id] = result_cb;
1092 }
1093 
send_iq_room_list_result(const std::string & id,const std::string & to_jid,const std::string & from,const ChannelList & channel_list,std::vector<ListElement>::const_iterator begin,std::vector<ListElement>::const_iterator end,const ResultSetInfo & rs_info)1094 void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std::string& to_jid,
1095                                                 const std::string& from, const ChannelList& channel_list,
1096                                                 std::vector<ListElement>::const_iterator begin,
1097                                                 std::vector<ListElement>::const_iterator end,
1098                                                 const ResultSetInfo& rs_info)
1099 {
1100   Stanza iq("iq");
1101   {
1102     iq["from"] = from + "@" + this->served_hostname;
1103     iq["to"] = to_jid;
1104     iq["id"] = id;
1105     iq["type"] = "result";
1106     XmlSubNode query(iq, "query");
1107     query["xmlns"] = DISCO_ITEMS_NS;
1108 
1109     for (auto it = begin; it != end; ++it)
1110       {
1111         XmlSubNode item(query, "item");
1112         std::string channel_name = it->channel;
1113         xep0106::encode(channel_name);
1114         item["jid"] = channel_name + "@" + this->served_hostname;
1115       }
1116 
1117     if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty()))
1118       {
1119         XmlSubNode set_node(query, "set");
1120         set_node["xmlns"] = RSM_NS;
1121 
1122         if (begin != channel_list.channels.cend())
1123           {
1124             XmlSubNode first_node(set_node, "first");
1125             first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin));
1126             first_node.set_inner(begin->channel + "@" + this->served_hostname);
1127           }
1128         if (end != channel_list.channels.cbegin())
1129           {
1130             XmlSubNode last_node(set_node, "last");
1131             last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname);
1132           }
1133         if (channel_list.complete)
1134           {
1135             XmlSubNode count_node(set_node, "count");
1136             count_node.set_inner(std::to_string(channel_list.channels.size()));
1137           }
1138       }
1139   }
1140   this->send_stanza(iq);
1141 }
1142 
send_invitation(const std::string & room_target,const std::string & jid_to,const std::string & author_nick)1143 void BiboumiComponent::send_invitation(const std::string& room_target,
1144                                        const std::string& jid_to,
1145                                        const std::string& author_nick)
1146 {
1147   if (author_nick.empty())
1148     this->send_invitation_from_fulljid(room_target, jid_to, room_target + "@" + this->served_hostname);
1149   else
1150     this->send_invitation_from_fulljid(room_target, jid_to, room_target + "@" + this->served_hostname + "/" + author_nick);
1151 }
1152 
send_invitation_from_fulljid(const std::string & room_target,const std::string & jid_to,const std::string & from)1153 void BiboumiComponent::send_invitation_from_fulljid(const std::string& room_target,
1154                                        const std::string& jid_to,
1155                                        const std::string& from)
1156 {
1157   Stanza message("message");
1158   {
1159     message["from"] = room_target + "@" + this->served_hostname;
1160     message["to"] = jid_to;
1161     XmlSubNode x(message, "x");
1162     x["xmlns"] = MUC_USER_NS;
1163     XmlSubNode invite(x, "invite");
1164     invite["from"] = from;
1165   }
1166   this->send_stanza(message);
1167 }
1168 
accept_subscription(const std::string & from,const std::string & to)1169 void BiboumiComponent::accept_subscription(const std::string& from, const std::string& to)
1170 {
1171   Stanza presence("presence");
1172   presence["from"] = from;
1173   presence["to"] = to;
1174   presence["id"] = this->next_id();
1175   presence["type"] = "subscribed";
1176   this->send_stanza(presence);
1177 }
1178 
ask_subscription(const std::string & from,const std::string & to)1179 void BiboumiComponent::ask_subscription(const std::string& from, const std::string& to)
1180 {
1181   Stanza presence("presence");
1182   presence["from"] = from;
1183   presence["to"] = to;
1184   presence["id"] = this->next_id();
1185   presence["type"] = "subscribe";
1186   this->send_stanza(presence);
1187 }
1188 
send_presence_to_contact(const std::string & from,const std::string & to,const std::string & type,const std::string & id)1189 void BiboumiComponent::send_presence_to_contact(const std::string& from, const std::string& to,
1190                                                 const std::string& type, const std::string& id)
1191 {
1192   Stanza presence("presence");
1193   presence["from"] = from;
1194   presence["to"] = to;
1195   if (!type.empty())
1196     presence["type"] = type;
1197   if (!id.empty())
1198     presence["id"] = id;
1199   this->send_stanza(presence);
1200 }
1201 
on_irc_client_connected(const std::string & irc_hostname,const std::string & jid)1202 void BiboumiComponent::on_irc_client_connected(const std::string& irc_hostname, const std::string& jid)
1203 {
1204 #ifdef USE_DATABASE
1205   const auto local_jid = irc_hostname + "@" + this->served_hostname;
1206   if (Database::has_roster_item(local_jid, jid))
1207     this->send_presence_to_contact(local_jid, jid, "");
1208 #else
1209   (void)irc_hostname;
1210   (void)jid;
1211 #endif
1212 }
1213 
on_irc_client_disconnected(const std::string & irc_hostname,const std::string & jid)1214 void BiboumiComponent::on_irc_client_disconnected(const std::string& irc_hostname, const std::string& jid)
1215 {
1216 #ifdef USE_DATABASE
1217   const auto local_jid = irc_hostname + "@" + this->served_hostname;
1218   if (Database::has_roster_item(local_jid, jid))
1219     this->send_presence_to_contact(irc_hostname + "@" + this->served_hostname, jid, "unavailable");
1220 #else
1221   (void)irc_hostname;
1222   (void)jid;
1223 #endif
1224 }
1225 
after_handshake()1226 void BiboumiComponent::after_handshake()
1227 {
1228   XmppComponent::after_handshake();
1229 
1230 #ifdef USE_DATABASE
1231   const auto contacts = Database::get_contact_list(this->get_served_hostname());
1232 
1233   for (const Database::RosterItem& roster_item: contacts)
1234     {
1235       const auto remote_jid = roster_item.col<Database::RemoteJid>();
1236       // In response, we will receive a presence indicating the
1237       // contact is online, to which we will respond with our own
1238       // presence.
1239       // If the contact removed us from their roster while we were
1240       // offline, we will receive an unsubscribed presence, letting us
1241       // stay in sync.
1242       this->send_presence_to_contact(this->get_served_hostname(), remote_jid, "probe");
1243     }
1244 #endif
1245 }
1246