1 #include <bridge/bridge.hpp>
2 #include <utility>
3 #include <xmpp/biboumi_component.hpp>
4 #include <network/poller.hpp>
5 #include <utils/empty_if_fixed_server.hpp>
6 #include <utils/encoding.hpp>
7 #include <utils/tolower.hpp>
8 #include <utils/uuid.hpp>
9 #include <logger/logger.hpp>
10 #include <utils/revstr.hpp>
11 #include <utils/split.hpp>
12 #include <xmpp/jid.hpp>
13 #include <database/database.hpp>
14 #include "result_set_management.hpp"
15 #include <algorithm>
16 
17 using namespace std::string_literals;
18 
19 static const char* action_prefix = "\01ACTION ";
20 
21 
in_encoding_for(const Bridge & bridge,const Iid & iid)22 static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
23 {
24 #ifdef USE_DATABASE
25   const auto jid = bridge.get_bare_jid();
26   return Database::get_encoding_in(jid, iid.get_server(), iid.get_local());
27 #else
28   (void)bridge;
29   (void)iid;
30   return {"ISO-8859-1"};
31 #endif
32 }
33 
Bridge(std::string user_jid,BiboumiComponent & xmpp,std::shared_ptr<Poller> & poller)34 Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Poller>& poller):
35  user_jid(std::move(user_jid)),
36   xmpp(xmpp),
37   poller(poller)
38 {
39 #ifdef USE_DATABASE
40   const auto options = Database::get_global_options(this->user_jid);
41   this->set_record_history(options.col<Database::RecordHistory>());
42 #endif
43 }
44 
45 /**
46  * Return the role and affiliation, corresponding to the given irc mode
47  */
get_role_affiliation_from_irc_mode(const char mode)48 static std::tuple<std::string, std::string> get_role_affiliation_from_irc_mode(const char mode)
49 {
50   if (mode == 'a' || mode == 'q'){
51     return std::make_tuple("moderator", "owner");}
52   else if (mode == 'o')
53     return std::make_tuple("moderator", "admin");
54   else if (mode == 'h')
55     return std::make_tuple("moderator", "member");
56   else if (mode == 'v')
57     return std::make_tuple("participant", "member");
58   else
59     return std::make_tuple("participant", "none");
60 }
61 
shutdown(const std::string & exit_message)62 void Bridge::shutdown(const std::string& exit_message)
63 {
64   for (auto& pair: this->irc_clients)
65   {
66     std::unique_ptr<IrcClient>& irc = pair.second;
67     irc->send_quit_command(exit_message);
68   }
69 }
70 
remove_resource(const std::string & resource,const std::string & part_message)71 void Bridge::remove_resource(const std::string& resource,
72                              const std::string& part_message)
73 {
74   const auto resources_in_chan_copy = this->resources_in_chan;
75   for (const auto& chan_pair: resources_in_chan_copy)
76   {
77     const ChannelKey& channel_key = chan_pair.first;
78     const std::set<Resource>& resources = chan_pair.second;
79     if (resources.count(resource))
80       this->leave_irc_channel({std::get<0>(channel_key), std::get<1>(channel_key), {}},
81                               part_message, resource);
82   }
83 }
84 
clean()85 void Bridge::clean()
86 {
87   auto it = this->irc_clients.begin();
88   while (it != this->irc_clients.end())
89   {
90     IrcClient* client = it->second.get();
91     if (!client->is_connected() && !client->is_connecting() &&
92         !client->get_resolver().is_resolving())
93       it = this->irc_clients.erase(it);
94     else
95       ++it;
96   }
97 }
98 
get_jid() const99 const std::string& Bridge::get_jid() const
100 {
101   return this->user_jid;
102 }
103 
get_bare_jid() const104 std::string Bridge::get_bare_jid() const
105 {
106   Jid jid(this->user_jid);
107   return jid.bare();
108 }
109 
make_xmpp_body(const std::string & str,const std::string & encoding)110 Xmpp::body Bridge::make_xmpp_body(const std::string& str, const std::string& encoding)
111 {
112   std::string res;
113   if (utils::is_valid_utf8(str.c_str()))
114     res = str;
115   else
116     res = utils::convert_to_utf8(str, encoding.data());
117   return irc_format_to_xhtmlim(res);
118 }
119 
make_irc_client(const std::string & hostname,const std::string & nickname)120 IrcClient* Bridge::make_irc_client(const std::string& hostname, const std::string& nickname)
121 {
122   try
123     {
124       return this->irc_clients.at(hostname).get();
125     }
126   catch (const std::out_of_range& exception)
127     {
128       auto username = nickname;
129       auto realname = nickname;
130       Jid jid(this->user_jid);
131       if (Config::get("realname_from_jid", "false") == "true")
132         {
133           username = jid.local;
134           realname = this->get_bare_jid();
135         }
136       this->irc_clients.emplace(hostname,
137                                 std::make_unique<IrcClient>(this->poller, hostname,
138                                                             nickname, username,
139                                                             realname, jid.domain,
140                                                             *this));
141       std::unique_ptr<IrcClient>& irc = this->irc_clients.at(hostname);
142       return irc.get();
143     }
144 }
145 
get_irc_client(const std::string & hostname)146 IrcClient* Bridge::get_irc_client(const std::string& hostname)
147 {
148   try
149     {
150       return this->irc_clients.at(hostname).get();
151     }
152   catch (const std::out_of_range& exception)
153     {
154       throw IRCNotConnected(hostname);
155     }
156 }
157 
find_irc_client(const std::string & hostname) const158 IrcClient* Bridge::find_irc_client(const std::string& hostname) const
159 {
160   try
161     {
162       return this->irc_clients.at(hostname).get();
163     }
164   catch (const std::out_of_range& exception)
165     {
166       return nullptr;
167     }
168 }
169 
join_irc_channel(const Iid & iid,std::string nickname,const std::string & password,const std::string & resource,HistoryLimit history_limit)170 bool Bridge::join_irc_channel(const Iid& iid, std::string nickname,
171                               const std::string& password,
172                               const std::string& resource,
173                               HistoryLimit history_limit)
174 {
175   const auto& hostname = iid.get_server();
176 #ifdef USE_DATABASE
177   auto soptions = Database::get_irc_server_options(this->get_bare_jid(), hostname);
178   if (!soptions.col<Database::Nick>().empty())
179     nickname = soptions.col<Database::Nick>();
180 #endif
181   IrcClient* irc = this->make_irc_client(hostname, nickname);
182   irc->history_limit = history_limit;
183   this->add_resource_to_server(hostname, resource);
184   auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), hostname}, resource);
185   if (!res_in_chan)
186     this->add_resource_to_chan(ChannelKey{iid.get_local(), hostname}, resource);
187   if (!irc->is_channel_joined(iid.get_local()))
188     {
189       irc->send_join_command(iid.get_local(), password);
190       return true;
191     } else {
192       // See https://github.com/xsf/xeps/pull/499
193       this->generate_channel_join_for_resource(iid, resource);
194     }
195   return false;
196 }
197 
send_channel_message(const Iid & iid,const std::string & body,std::string id,std::vector<XmlNode> nodes_to_reflect)198 void Bridge::send_channel_message(const Iid& iid, const std::string& body, std::string id, std::vector<XmlNode> nodes_to_reflect)
199 {
200   if (iid.get_server().empty())
201     {
202       for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
203         this->xmpp.send_stanza_error("message", this->user_jid + "/" + resource, std::to_string(iid), "",
204                                      "cancel", "remote-server-not-found",
205                                      std::to_string(iid) + " is not a valid channel name. "
206                                          "A correct room jid is of the form: #<chan>%<server>",
207                                      false);
208       return;
209     }
210   IrcClient* irc = this->get_irc_client(iid.get_server());
211 
212   // Because an IRC message cannot contain \n, we need to convert each line
213   // of text into a separate IRC message. For conveniance, we also cut the
214   // message into submessages on the XMPP side, this way the user of the
215   // gateway sees what was actually sent over IRC.  For example if an user
216   // sends “hello\n/me waves”, two messages will be generated: “hello” and
217   // “/me waves”. Note that the “command” handling (messages starting with
218   // /me, /mode, etc) is done for each message generated this way. In the
219   // above example, the /me will be interpreted.
220   std::vector<std::string> lines = utils::split(body, '\n', true);
221   if (lines.empty())
222     return ;
223   bool first = true;
224   for (const std::string& line: lines)
225     {
226       std::string uuid;
227 #ifdef USE_DATABASE
228       const auto xmpp_body = this->make_xmpp_body(line);
229       if (this->record_history)
230         uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
231                                     std::get<0>(xmpp_body), irc->get_own_nick());
232 #endif
233       if (!first || id.empty())
234         id = utils::gen_uuid();
235 
236       MessageCallback mirror_to_all_resources = [this, iid, uuid, id, nodes_to_reflect](const IrcClient* irc, const IrcMessage& message) {
237         std::string line = message.arguments[1];
238         // “temporary” workaround for \01ACTION…\01 -> /me messages
239         if ((line.size() > strlen("\01ACTION\01")) &&
240             (line.substr(0, 7) == "\01ACTION") && line[line.size() - 1] == '\01')
241           line = "/me " + line.substr(8, line.size() - 9);
242         for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
243           {
244             auto stanza = this->xmpp.make_muc_message(std::to_string(iid), irc->get_own_nick(), this->make_xmpp_body(line),
245                                                        this->user_jid + "/"
246                                                        + resource, uuid, id);
247             for (const auto& node: nodes_to_reflect)
248               stanza.add_child(node);
249             this->xmpp.send_stanza(stanza);
250           }
251       };
252 
253       if (line.substr(0, 5) == "/mode")
254         {
255           std::vector<std::string> args = utils::split(line.substr(5), ' ', false);
256           irc->send_mode_command(iid.get_local(), args);
257           continue;             // We do not want to send that back to the
258                                 // XMPP user, that’s not a textual message.
259         }
260       else if (line.substr(0, 4) == "/me ")
261         irc->send_channel_message(iid.get_local(), action_prefix + line.substr(4) + "\01",
262                                   std::move(mirror_to_all_resources));
263       else
264         irc->send_channel_message(iid.get_local(), line, std::move(mirror_to_all_resources));
265 
266       first = false;
267     }
268 }
269 
forward_affiliation_role_change(const Iid & iid,const std::string & from,const std::string & nick,const std::string & affiliation,const std::string & role,const std::string & id)270 void Bridge::forward_affiliation_role_change(const Iid& iid, const std::string& from,
271                                              const std::string& nick,
272                                              const std::string& affiliation,
273                                              const std::string& role,
274                                              const std::string& id)
275 {
276   IrcClient* irc = this->get_irc_client(iid.get_server());
277   IrcChannel* chan = irc->get_channel(iid.get_local());
278   if (!chan || !chan->joined)
279     return;
280   IrcUser* user = chan->find_user(nick);
281   if (!user)
282     {
283       this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel",
284                                    "item-not-found", "no such nick", false);
285       return;
286     }
287   // For each affiliation or role, we have a “maximal” mode that we want to
288   // set. We must remove any superior mode at the same time. For example if
289   // the user already has +o mode, and we set its affiliation to member, we
290   // remove the +o mode, and add +v.  For each “superior” mode (for example,
291   // for +v, the superior modes are 'h', 'a', 'o' and 'q') we check if that
292   // user has it, and if yes we remove that mode
293 
294   std::size_t nb = 1;               // the number of times the nick must be
295                                     // repeated in the argument list
296   std::string modes;                // The string of modes to
297                                     // add/remove. For example "+v-aoh"
298   std::vector<char> modes_to_remove; // List of modes to check for removal
299   if (affiliation == "none")
300     {
301       modes = "";
302       nb = 0;
303       modes_to_remove = {'v', 'h', 'o', 'a', 'q'};
304     }
305   else if (affiliation == "member")
306     {
307       modes = "+v";
308       modes_to_remove = {'h', 'o', 'a', 'q'};
309     }
310   else if (role == "moderator")
311     {
312       modes = "+h";
313       modes_to_remove = {'o', 'a', 'q'};
314     }
315   else if (affiliation == "admin")
316     {
317       modes = "+o";
318       modes_to_remove = {'a', 'q'};
319     }
320   else if (affiliation == "owner")
321     {
322       modes = "+a";
323       modes_to_remove = {'q'};
324     }
325   else
326     return;
327   for (const char mode: modes_to_remove)
328     if (user->modes.find(mode) != user->modes.end())
329       {
330         modes += "-"s + mode;
331         nb++;
332       }
333   if (modes.empty())
334     return;
335   std::vector<std::string> args(nb, nick);
336   args.insert(args.begin(), modes);
337   irc->send_mode_command(iid.get_local(), args);
338 
339   irc_responder_callback_t cb = [this, iid, irc, id, from, nick](const std::string& irc_hostname, const IrcMessage& message) -> bool
340   {
341     if (irc_hostname != iid.get_server())
342       return false;
343 
344     if (message.command == "MODE" && message.arguments.size() >= 2)
345       {
346         const std::string& chan_name = message.arguments[0];
347         if (chan_name != iid.get_local())
348           return false;
349         const std::string actor_nick = IrcUser{message.prefix}.nick;
350         if (!irc || irc->get_own_nick() != actor_nick)
351           return false;
352 
353         this->xmpp.send_iq_result(id, from, std::to_string(iid));
354       }
355     else if (message.command == "401" && message.arguments.size() >= 2)
356         {
357           const std::string target_later = message.arguments[1];
358           if (target_later != nick)
359             return false;
360           std::string error_message = "No such nick";
361           if (message.arguments.size() >= 3)
362             error_message = message.arguments[2];
363           this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "item-not-found",
364                                         error_message, false);
365         }
366     else if (message.command == "482" && message.arguments.size() >= 2)
367       {
368         const std::string chan_name_later = utils::tolower(message.arguments[1]);
369         if (chan_name_later != iid.get_local())
370           return false;
371         std::string error_message = "You're not channel operator";
372         if (message.arguments.size() >= 3)
373           error_message = message.arguments[2];
374         this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
375                                      error_message, false);
376       }
377     else if (message.command == "472" && message.arguments.size() >= 2)
378       {
379           std::string error_message = "Unknown mode: " + message.arguments[1];
380           if (message.arguments.size() >= 3)
381             error_message = message.arguments[2];
382           this->xmpp.send_stanza_error("iq", from, std::to_string(iid), id, "cancel", "not-allowed",
383                                         error_message, false);
384       }
385     return true;
386   };
387   this->add_waiting_irc(std::move(cb));
388 }
389 
send_private_message(const Iid & iid,const std::string & body,const std::string & type)390 void Bridge::send_private_message(const Iid& iid, const std::string& body, const std::string& type)
391 {
392   if (iid.get_local().empty() || iid.get_server().empty())
393     {
394       this->xmpp.send_stanza_error("message", this->user_jid, std::to_string(iid), "",
395                                     "cancel", "remote-server-not-found",
396                                     std::to_string(iid) + " is not a valid channel name. "
397                                     "A correct room jid is of the form: #<chan>%<server>",
398                                     false);
399       return;
400     }
401   IrcClient* irc = this->get_irc_client(iid.get_server());
402   std::vector<std::string> lines = utils::split(body, '\n', true);
403   if (lines.empty())
404     return ;
405   for (const std::string& line: lines)
406     {
407       if (line.substr(0, 4) == "/me ")
408         irc->send_private_message(iid.get_local(), action_prefix + line.substr(4) + "\01", type);
409       else
410         irc->send_private_message(iid.get_local(), line, type);
411     }
412 }
413 
send_raw_message(const std::string & hostname,const std::string & body)414 void Bridge::send_raw_message(const std::string& hostname, const std::string& body)
415 {
416   IrcClient* irc = this->get_irc_client(hostname);
417   irc->send_raw(body);
418 }
419 
leave_irc_channel(Iid && iid,const std::string & status_message,const std::string & resource)420 void Bridge::leave_irc_channel(Iid&& iid, const std::string& status_message, const std::string& resource)
421 {
422   IrcClient* irc = this->get_irc_client(iid.get_server());
423   const auto key = iid.to_tuple();
424   if (!this->is_resource_in_chan(key, resource))
425     return ;
426 
427   IrcChannel* channel = irc->get_channel(iid.get_local());
428 
429   const auto resources = this->number_of_resources_in_chan(key);
430   if (resources == 1)
431     {
432       // Do not send a PART message if we actually are not in that channel
433       // or if we already sent a PART but we are just waiting for the
434       // acknowledgment from the server
435       bool persistent = false;
436 #ifdef USE_DATABASE
437       const auto goptions = Database::get_global_options(this->user_jid);
438       if (goptions.col<Database::GlobalPersistent>())
439         persistent = true;
440       else
441         {
442           const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid, iid.get_server(), iid.get_local());
443           persistent = coptions.col<Database::Persistent>();
444         }
445 #endif
446       if (channel->joined && !channel->parting && !persistent)
447         {
448           irc->send_part_command(iid.get_local(), status_message);
449         }
450       else if (channel->joined)
451         {
452           this->send_muc_leave(iid, *channel->get_self(), "", true, true, resource, irc);
453         }
454       if (persistent)
455         this->remove_resource_from_chan(key, resource);
456       // Since there are no resources left in that channel, we don't
457       // want to receive private messages using this room's JID
458       this->remove_all_preferred_from_jid_of_room(iid.get_local());
459     }
460   else
461     {
462       if (channel && channel->joined)
463         this->send_muc_leave(iid, *channel->get_self(),
464                              "Biboumi note: " + std::to_string(resources - 1) + " resources are still in this channel.",
465                              true, true, resource, irc);
466       this->remove_resource_from_chan(key, resource);
467     }
468   if (this->number_of_channels_the_resource_is_in(iid.get_server(), resource) == 0)
469     this->remove_resource_from_server(iid.get_server(), resource);
470 }
471 
send_irc_nick_change(const Iid & iid,const std::string & new_nick,const std::string & requesting_resource)472 void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick, const std::string& requesting_resource)
473 {
474   // We don’t change the nick if the presence was sent to a channel the resource is not in.
475   auto res_in_chan = this->is_resource_in_chan(ChannelKey{iid.get_local(), iid.get_server()}, requesting_resource);
476   if (!res_in_chan)
477     return;
478   IrcClient* irc = this->get_irc_client(iid.get_server());
479   irc->send_nick_command(new_nick);
480 }
481 
send_irc_channel_list_request(const Iid & iid,const std::string & iq_id,const std::string & to_jid,ResultSetInfo rs_info)482 void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
483                                            ResultSetInfo rs_info)
484 {
485   auto& list = this->channel_list_cache[iid.get_server()];
486 
487   // We fetch the list from the IRC server only if we have a complete
488   // cached list that needs to be invalidated (that is, when the request
489   // doesn’t have a after or before, or when the list is empty).
490   // If the list is not complete, this means that a request is already
491   // ongoing, so we just need to add the callback.
492   // By default the list is complete and empty.
493   if (list.complete &&
494       (list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty())))
495     {
496       IrcClient* irc = this->get_irc_client(iid.get_server());
497       irc->send_list_command();
498 
499       // Add a callback that will populate our list
500       list.channels.clear();
501       list.complete = false;
502       irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname,
503                                                 const IrcMessage& message) -> bool
504       {
505         if (irc_hostname != iid.get_server())
506           return false;
507 
508         auto& list = this->channel_list_cache[iid.get_server()];
509 
510         if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
511             || message.command == "ERR_NOSUCHSERVER")
512           {
513             list.complete = true;
514             return true;
515           }
516         else if (message.command == "322" || message.command == "RPL_LIST")
517           { // Add element to list
518             if (message.arguments.size() == 4)
519               {
520                 list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()),
521                                            message.arguments[2], message.arguments[3]);
522               }
523             return false;
524           }
525         else if (message.command == "323" || message.command == "RPL_LISTEND")
526           { // Send the iq response with the content of the list
527             list.complete = true;
528             return true;
529           }
530         return false;
531       };
532 
533       this->add_waiting_irc(std::move(cb));
534     }
535 
536   // If the list is complete, we immediately send the answer.
537   // Otherwise, we install a callback, that will populate our list and send
538   // the answer when we can.
539   if (list.complete)
540     {
541       this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
542     }
543   else
544     {
545       // Add a callback to answer the request as soon as we can
546       irc_responder_callback_t cb = [this, iid, iq_id, to_jid,
547                                      rs_info=std::move(rs_info)](const std::string& irc_hostname,
548                                                                  const IrcMessage& message) -> bool
549       {
550         if (irc_hostname != iid.get_server())
551           return false;
552 
553         if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
554             || message.command == "ERR_NOSUCHSERVER")
555           {
556             std::string text;
557             if (message.arguments.size() >= 2)
558               text = message.arguments[1];
559             this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false);
560             return true;
561           }
562         else if (message.command == "322" || message.command == "RPL_LIST")
563           {
564             auto& list = channel_list_cache[iid.get_server()];
565             const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
566             return res;
567           }
568         else if (message.command == "323" || message.command == "RPL_LISTEND")
569           { // Send the iq response with the content of the list
570             auto& list = channel_list_cache[iid.get_server()];
571             this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
572             return true;
573           }
574         return false;
575       };
576 
577       this->add_waiting_irc(std::move(cb));
578     }
579 }
580 
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)581 bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info,
582                                         const std::string& id, const std::string& to_jid, const std::string& from)
583 {
584   auto begin = channel_list.channels.begin();
585   auto end = channel_list.channels.end();
586   if (channel_list.complete)
587     {
588       begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
589       {
590         return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
591       });
592       if (begin == channel_list.channels.end())
593         begin = channel_list.channels.begin();
594       else
595         begin = std::next(begin);
596       end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
597       {
598         return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
599       });
600       if (rs_info.max >= 0)
601         {
602           if (std::distance(begin, end) >= rs_info.max)
603             end = begin + rs_info.max;
604         }
605     }
606   else
607     {
608       if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0)
609         return false;
610       if (!rs_info.after.empty())
611         {
612           begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
613           {
614             return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
615           });
616           if (begin == channel_list.channels.end())
617             return false;
618           begin = std::next(begin);
619         }
620         if (!rs_info.before.empty())
621         {
622           end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
623           {
624             return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
625           });
626           if (end == channel_list.channels.end())
627             return false;
628         }
629       if (rs_info.max >= 0)
630         {
631           if (std::distance(begin, end) < rs_info.max)
632             return false;
633           else
634             end = begin + rs_info.max;
635         }
636     }
637   this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info);
638   return true;
639 }
640 
send_irc_kick(const Iid & iid,const std::string & target,const std::string & reason,const std::string & iq_id,const std::string & to_jid)641 void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
642                            const std::string& iq_id, const std::string& to_jid)
643 {
644   IrcClient* irc = this->get_irc_client(iid.get_server());
645 
646   irc->send_kick_command(iid.get_local(), target, reason);
647   irc_responder_callback_t cb = [this, target, iq_id, to_jid, iid](const std::string& irc_hostname,
648                                                                    const IrcMessage& message) -> bool
649     {
650       if (irc_hostname != iid.get_server())
651         return false;
652       if (message.command == "KICK" && message.arguments.size() >= 2)
653         {
654           const std::string target_later = message.arguments[1];
655           const std::string chan_name_later = utils::tolower(message.arguments[0]);
656           if (target_later != target || chan_name_later != iid.get_local())
657             return false;
658           this->xmpp.send_iq_result(iq_id, to_jid, std::to_string(iid));
659         }
660       else if (message.command == "401" && message.arguments.size() >= 2)
661         {
662           const std::string target_later = message.arguments[1];
663           if (target_later != target)
664             return false;
665           std::string error_message = "No such nick";
666           if (message.arguments.size() >= 3)
667             error_message = message.arguments[2];
668           this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "item-not-found",
669                                         error_message, false);
670         }
671       else if (message.command == "482" && message.arguments.size() >= 2)
672         {
673           const std::string chan_name_later = utils::tolower(message.arguments[1]);
674           if (chan_name_later != iid.get_local())
675             return false;
676           std::string error_message = "You're not channel operator";
677           if (message.arguments.size() >= 3)
678             error_message = message.arguments[2];
679           this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "cancel", "not-allowed",
680                                         error_message, false);
681         }
682       return true;
683     };
684   this->add_waiting_irc(std::move(cb));
685 }
686 
set_channel_topic(const Iid & iid,std::string subject)687 void Bridge::set_channel_topic(const Iid& iid, std::string subject)
688 {
689   IrcClient* irc = this->get_irc_client(iid.get_server());
690   std::string::size_type pos{0};
691   while ((pos = subject.find('\n', pos)) != std::string::npos)
692     subject[pos] = ' ';
693   irc->send_topic_command(iid.get_local(), subject);
694 }
695 
send_xmpp_version_to_irc(const Iid & iid,const std::string & name,const std::string & version,const std::string & os)696 void Bridge::send_xmpp_version_to_irc(const Iid& iid, const std::string& name, const std::string& version, const std::string& os)
697 {
698   std::string result(name + " " + version + " " + os);
699 
700   this->send_private_message(iid, "\01VERSION " + result + "\01", "NOTICE");
701 }
702 
send_irc_ping_result(const Iid & iid,const std::string & id)703 void Bridge::send_irc_ping_result(const Iid& iid, const std::string& id)
704 {
705   this->send_private_message(iid, "\01PING " + utils::revstr(id) + "\01", "NOTICE");
706 }
707 
send_irc_user_ping_request(const std::string & irc_hostname,const std::string & nick,const std::string & iq_id,const std::string & to_jid,const std::string & from_jid)708 void Bridge::send_irc_user_ping_request(const std::string& irc_hostname, const std::string& nick,
709                                         const std::string& iq_id, const std::string& to_jid,
710                                         const std::string& from_jid)
711 {
712   Iid iid(nick, irc_hostname, Iid::Type::User);
713   this->send_private_message(iid, "\01PING " + iq_id + "\01");
714 
715   irc_responder_callback_t cb = [this, nick=utils::tolower(nick), iq_id, to_jid, irc_hostname, from_jid]
716           (const std::string& hostname, const IrcMessage& message) -> bool
717     {
718       if (irc_hostname != hostname || message.arguments.size() < 2)
719         return false;
720       IrcUser user(message.prefix);
721       const std::string body = message.arguments[1];
722       if (message.command == "NOTICE" && utils::tolower(user.nick) == nick
723           && body.substr(0, 6) == "\01PING ")
724         {
725           const std::string id = body.substr(6, body.size() - 7);
726           if (id != iq_id)
727             return false;
728           this->xmpp.send_iq_result_full_jid(iq_id, to_jid, from_jid);
729           return true;
730         }
731       if (message.command == "401" && message.arguments[1] == nick)
732         {
733           std::string error_message = "No such nick";
734           if (message.arguments.size() >= 3)
735             error_message = message.arguments[2];
736           this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
737                                         error_message, true);
738           return true;
739         }
740 
741       return false;
742     };
743   this->add_waiting_irc(std::move(cb));
744 }
745 
send_irc_participant_ping_request(const Iid & iid,const std::string & nick,const std::string & iq_id,const std::string & to_jid,const std::string & from_jid)746 void Bridge::send_irc_participant_ping_request(const Iid& iid, const std::string& nick,
747                                                const std::string& iq_id, const std::string& to_jid,
748                                                const std::string& from_jid)
749 {
750   Jid from(to_jid);
751   IrcClient* irc = this->get_irc_client(iid.get_server());
752   IrcChannel* chan = irc->get_channel(iid.get_local());
753   if (!chan->joined || !this->is_resource_in_chan(iid.to_tuple(), from.resource))
754     {
755       this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "not-acceptable",
756                                     "", true);
757       return;
758     }
759   if (chan->get_self()->nick == nick)
760     {
761       // XEP-0410 self-ping optimisation: always reply without going the full
762       // round-trip through IRC and possibly another XMPP client. See the XEP
763       // for details.
764       Jid iq_from(from_jid);
765       iq_from.local = std::to_string(iid);
766       iq_from.resource = nick;
767 
768       Stanza iq("iq");
769       iq["from"] = iq_from.full();
770       iq["to"] = to_jid;
771       iq["id"] = iq_id;
772       iq["type"] = "result";
773       this->xmpp.send_stanza(iq);
774       return;
775     }
776   if (chan->get_self()->nick != nick && !chan->find_user(nick))
777     {
778       this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
779                                     "Recipient not in room", true);
780       return;
781     }
782 
783   // The user is in the room, send it a direct PING
784   this->send_irc_user_ping_request(iid.get_server(), nick, iq_id, to_jid, from_jid);
785 }
786 
on_gateway_ping(const std::string & irc_hostname,const std::string & iq_id,const std::string & to_jid,const std::string & from_jid)787 void Bridge::on_gateway_ping(const std::string& irc_hostname, const std::string& iq_id, const std::string& to_jid,
788                              const std::string& from_jid)
789 {
790   Jid jid(from_jid);
791   if (irc_hostname.empty() || this->find_irc_client(irc_hostname))
792     this->xmpp.send_iq_result(iq_id, to_jid, jid.local);
793   else
794     this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "service-unavailable",
795                                   "", true);
796 }
797 
send_irc_invitation(const Iid & iid,const std::string & to)798 void Bridge::send_irc_invitation(const Iid& iid, const std::string& to)
799 {
800   IrcClient* irc = this->get_irc_client(iid.get_server());
801   Jid to_jid(to);
802   std::string target_nick;
803   // Many ways to address a nick:
804   // A jid (ANY jid…) with a resource
805   if (!to_jid.resource.empty())
806     target_nick = to_jid.resource;
807   else if (!to_jid.local.empty()) // A jid with a iid with a local part
808     target_nick = Iid(to_jid.local, {}).get_local();
809   else
810     target_nick = to; // Not a jid, just the nick
811   irc->send_invitation(iid.get_local(), target_nick);
812 }
813 
send_irc_version_request(const std::string & irc_hostname,const std::string & target,const std::string & iq_id,const std::string & to_jid,const std::string & from_jid)814 void Bridge::send_irc_version_request(const std::string& irc_hostname, const std::string& target,
815                                       const std::string& iq_id, const std::string& to_jid,
816                                       const std::string& from_jid)
817 {
818   Iid iid(target, irc_hostname, Iid::Type::User);
819   this->send_private_message(iid, "\01VERSION\01");
820   // TODO, add a timer to remove that waiting iq if the server does not
821   // respond with a matching command before n seconds
822   irc_responder_callback_t cb = [this, target, iq_id, to_jid, irc_hostname, from_jid]
823           (const std::string& hostname, const IrcMessage& message) -> bool
824     {
825       if (irc_hostname != hostname)
826         return false;
827       IrcUser user(message.prefix);
828       if (message.command == "NOTICE" && utils::tolower(user.nick) == utils::tolower(target) &&
829           message.arguments.size() >= 2 && message.arguments[1].substr(0, 9) == "\01VERSION ")
830         {
831           // remove the "\01VERSION " and the "\01" parts from the string
832           const std::string version = message.arguments[1].substr(9, message.arguments[1].size() - 10);
833           this->xmpp.send_version(iq_id, to_jid, from_jid, version);
834           return true;
835         }
836       if (message.command == "401" && message.arguments.size() >= 2
837           && message.arguments[1] == target)
838         {
839           std::string error_message = "No such nick";
840           if (message.arguments.size() >= 3)
841             error_message = message.arguments[2];
842           this->xmpp.send_stanza_error("iq", to_jid, from_jid, iq_id, "cancel", "item-not-found",
843                                         error_message, true);
844           return true;
845         }
846       return false;
847     };
848   this->add_waiting_irc(std::move(cb));
849 }
850 
send_message(const Iid & iid,const std::string & nick,const std::string & body,const bool muc,const bool log)851 void Bridge::send_message(const Iid& iid, const std::string& nick, const std::string& body, const bool muc, const bool log)
852 {
853   const auto encoding = in_encoding_for(*this, iid);
854   std::string uuid{};
855   if (muc)
856     {
857 #ifdef USE_DATABASE
858       const auto xmpp_body = this->make_xmpp_body(body, encoding);
859       if (log && this->record_history)
860         uuid = Database::store_muc_message(this->get_bare_jid(), iid.get_local(), iid.get_server(), std::chrono::system_clock::now(),
861                                            std::get<0>(xmpp_body), nick);
862 #else
863       (void)log;
864 #endif
865       for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
866         {
867           auto stanza = this->xmpp.make_muc_message(std::to_string(iid), nick, this->make_xmpp_body(body, encoding),
868                                                      this->user_jid + "/"
869                                                      + resource, uuid, utils::gen_uuid());
870           this->xmpp.send_stanza(stanza);
871         }
872     }
873   else
874     {
875       const auto it = this->preferred_user_from.find(iid.get_local());
876       if (it != this->preferred_user_from.end())
877         {
878           const auto chan_name = Iid(Jid(it->second).local, {}).get_local();
879           for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, iid.get_server()}])
880             this->xmpp.send_message(it->second, this->make_xmpp_body(body, encoding),
881                                     this->user_jid + "/"
882                                     + resource, "chat", true, true, true);
883         }
884       else
885         {
886           for (const auto& resource: this->resources_in_server[iid.get_server()])
887             this->xmpp.send_message(std::to_string(iid), this->make_xmpp_body(body, encoding),
888                                     this->user_jid + "/" + resource, "chat", false, true);
889         }
890     }
891 }
892 
send_presence_error(const Iid & iid,const std::string & nick,const std::string & type,const std::string & condition,const std::string & error_code,const std::string & text)893 void Bridge::send_presence_error(const Iid& iid, const std::string& nick,
894                                  const std::string& type, const std::string& condition,
895                                  const std::string& error_code, const std::string& text)
896 {
897   this->xmpp.send_presence_error(std::to_string(iid), nick, this->user_jid, type, condition, error_code, text);
898 }
899 
send_muc_leave(const Iid & iid,const IrcUser & user,const std::string & message,const bool self,const bool user_requested,const std::string & resource,const IrcClient * client)900 void Bridge::send_muc_leave(const Iid& iid, const IrcUser& user,
901                             const std::string& message, const bool self,
902                             const bool user_requested,
903                             const std::string& resource,
904                             const IrcClient* client)
905 {
906   std::string affiliation;
907   std::string role;
908   std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user.get_most_significant_mode(client->get_sorted_user_modes()));
909 
910   if (!resource.empty())
911     this->xmpp.send_muc_leave(std::to_string(iid), user.nick, this->make_xmpp_body(message),
912                               this->user_jid + "/" + resource, self, user_requested, affiliation, role);
913   else
914     {
915       for (const auto &res: this->resources_in_chan[iid.to_tuple()])
916         this->xmpp.send_muc_leave(std::to_string(iid), user.nick, this->make_xmpp_body(message),
917                                   this->user_jid + "/" + res, self, user_requested, affiliation, role);
918       if (self)
919         {
920           // Copy the resources currently in that channel
921           const auto resources_in_chan = this->resources_in_chan[iid.to_tuple()];
922 
923           this->remove_all_resources_from_chan(iid.to_tuple());
924 
925           // Now, for each resource that was in that channel, remove it from the server if it’s
926           // not in any other channel
927           for (const auto& r: resources_in_chan)
928           if (this->number_of_channels_the_resource_is_in(iid.get_server(), r) == 0)
929             this->remove_resource_from_server(iid.get_server(), r);
930         }
931     }
932   IrcClient* irc = this->find_irc_client(iid.get_server());
933   if (self && irc && irc->number_of_joined_channels() == 0)
934     irc->send_quit_command("");
935 }
936 
send_nick_change(Iid && iid,const std::string & old_nick,const std::string & new_nick,const char user_mode,const bool self)937 void Bridge::send_nick_change(Iid&& iid,
938                               const std::string& old_nick,
939                               const std::string& new_nick,
940                               const char user_mode,
941                               const bool self)
942 {
943   std::string affiliation;
944   std::string role;
945   std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);
946 
947   for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
948     this->xmpp.send_nick_change(std::to_string(iid),
949                                 old_nick, new_nick, affiliation, role, this->user_jid + "/" + resource, self);
950 }
951 
send_xmpp_message(const std::string & from,const std::string & author,const std::string & msg)952 void Bridge::send_xmpp_message(const std::string& from, const std::string& author, const std::string& msg)
953 {
954   std::string body;
955   if (!author.empty())
956     {
957       IrcUser user(author);
958       body = "\u000303" + user.nick + (user.host.empty()?
959                                         "\u0003: ":
960                                         (" (\u000310" + user.host + "\u000303)\u0003: ")) + msg;
961     }
962   else
963     body = msg;
964 
965   const auto encoding = in_encoding_for(*this, {from, this});
966   for (const auto& resource: this->resources_in_server[from])
967     {
968       if (Config::get("fixed_irc_server", "").empty())
969         this->xmpp.send_message(from, this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true);
970       else
971         this->xmpp.send_message("", this->make_xmpp_body(body, encoding), this->user_jid + "/" + resource, "chat", false, true);
972     }
973 }
974 
send_user_join(const std::string & hostname,const std::string & chan_name,const IrcUser * user,const char user_mode,const bool self)975 void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
976                             const IrcUser* user, const char user_mode, const bool self)
977 {
978   const auto resources = this->resources_in_chan[ChannelKey{chan_name, hostname}];
979   if (self && resources.empty())
980     { // This was a forced join: no client ever asked to join this room,
981       // but the server tells us we are in that room anyway.  XMPP can’t
982       // do that, so we invite all the resources to join that channel.
983       const Iid iid(chan_name, hostname, Iid::Type::Channel);
984       this->send_xmpp_invitation(iid, "");
985     }
986   else
987     {
988       for (const auto& resource: resources)
989         this->send_user_join(hostname, chan_name, user, user_mode, self, resource);
990     }
991 }
992 
send_user_join(const std::string & hostname,const std::string & chan_name,const IrcUser * user,const char user_mode,const bool self,const std::string & resource)993 void Bridge::send_user_join(const std::string& hostname, const std::string& chan_name,
994                        const IrcUser* user, const char user_mode,
995                        const bool self, const std::string& resource)
996 {
997   std::string affiliation;
998   std::string role;
999   std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(user_mode);
1000 
1001   std::string encoded_chan_name(chan_name);
1002   xep0106::encode(encoded_chan_name);
1003 
1004   std::string encoded_nick_name(user->nick);
1005   xep0106::encode(encoded_nick_name);
1006 
1007   std::string full_jid =
1008       encoded_nick_name + utils::empty_if_fixed_server("%" + hostname)
1009       + "@" + this->xmpp.get_served_hostname();
1010   if (!user->host.empty())
1011     full_jid += "/" + user->host;
1012 
1013   this->xmpp.send_user_join(encoded_chan_name + utils::empty_if_fixed_server("%" + hostname),
1014                             user->nick, full_jid, affiliation, role,
1015                             this->user_jid + "/" + resource, self);
1016 }
1017 
send_topic(const std::string & hostname,const std::string & chan_name,const std::string & topic,const std::string & who)1018 void Bridge::send_topic(const std::string& hostname, const std::string& chan_name, const std::string& topic,
1019                         const std::string& who)
1020 {
1021   for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
1022     {
1023       this->send_topic(hostname, chan_name, topic, who, resource);
1024     }
1025 }
1026 
send_topic(const std::string & hostname,const std::string & chan_name,const std::string & topic,const std::string & who,const std::string & resource)1027 void Bridge::send_topic(const std::string& hostname, const std::string& chan_name,
1028                         const std::string& topic, const std::string& who,
1029                         const std::string& resource)
1030 {
1031   std::string encoded_chan_name(chan_name);
1032   xep0106::encode(encoded_chan_name);
1033   const auto encoding = in_encoding_for(*this, {encoded_chan_name, hostname, Iid::Type::Channel});
1034   this->xmpp.send_topic(encoded_chan_name + utils::empty_if_fixed_server(
1035       "%" + hostname), this->make_xmpp_body(topic, encoding), this->user_jid + "/" + resource, who);
1036 
1037 }
1038 
send_room_history(const std::string & hostname,const std::string & chan_name,const HistoryLimit & history_limit)1039 void Bridge::send_room_history(const std::string& hostname, const std::string& chan_name, const HistoryLimit& history_limit)
1040 {
1041   for (const auto& resource: this->resources_in_chan[ChannelKey{chan_name, hostname}])
1042     this->send_room_history(hostname, chan_name, resource, history_limit);
1043 }
1044 
send_room_history(const std::string & hostname,std::string chan_name,const std::string & resource,const HistoryLimit & history_limit)1045 void Bridge::send_room_history(const std::string& hostname, std::string chan_name, const std::string& resource, const HistoryLimit& history_limit)
1046 {
1047 #ifdef USE_DATABASE
1048   const auto goptions = Database::get_global_options(this->user_jid);
1049   auto limit = goptions.col<Database::MaxHistoryLength>();
1050   if (limit < 0)
1051     limit = 20;
1052   if (history_limit.stanzas >= 0 && history_limit.stanzas < limit)
1053     limit = history_limit.stanzas;
1054   const auto result = Database::get_muc_logs(this->user_jid, chan_name, hostname, static_cast<std::size_t>(limit), history_limit.since, {}, Id::unset_value, Database::Paging::last);
1055   const auto& lines = std::get<1>(result);
1056   chan_name.append(utils::empty_if_fixed_server("%" + hostname));
1057   for (const auto& line: lines)
1058     {
1059       const auto seconds = line.col<Database::Date>();
1060       this->xmpp.send_history_message(chan_name, line.col<Database::Nick>(), line.col<Database::Body>(),
1061                                       this->user_jid + "/" + resource, seconds);
1062     }
1063 #else
1064   (void)hostname;
1065   (void)chan_name;
1066   (void)resource;
1067   (void)history_limit;
1068 #endif
1069 }
1070 
get_own_nick(const Iid & iid)1071 std::string Bridge::get_own_nick(const Iid& iid)
1072 {
1073   IrcClient* irc = this->find_irc_client(iid.get_server());
1074   if (irc)
1075     return irc->get_own_nick();
1076   return {};
1077 }
1078 
active_clients() const1079 size_t Bridge::active_clients() const
1080 {
1081   return this->irc_clients.size();
1082 }
1083 
kick_muc_user(Iid && iid,const std::string & target,const std::string & reason,const std::string & author,const bool self)1084 void Bridge::kick_muc_user(Iid&& iid, const std::string& target, const std::string& reason, const std::string& author,
1085                            const bool self)
1086 {
1087   for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
1088       this->xmpp.kick_user(std::to_string(iid), target, reason, author, this->user_jid + "/" + resource, self);
1089 }
1090 
send_nickname_conflict_error(const Iid & iid,const std::string & nickname)1091 void Bridge::send_nickname_conflict_error(const Iid& iid, const std::string& nickname)
1092 {
1093     for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
1094         this->xmpp.send_presence_error(std::to_string(iid), nickname, this->user_jid + "/" + resource,
1095                                        "cancel", "conflict", "409", "");
1096 }
1097 
send_affiliation_role_change(const Iid & iid,const std::string & target,const char mode)1098 void Bridge::send_affiliation_role_change(const Iid& iid, const std::string& target, const char mode)
1099 {
1100   std::string role;
1101   std::string affiliation;
1102 
1103   std::tie(role, affiliation) = get_role_affiliation_from_irc_mode(mode);
1104   for (const auto& resource: this->resources_in_chan[iid.to_tuple()])
1105     this->xmpp.send_affiliation_role_change(std::to_string(iid), target, affiliation, role,
1106                                             this->user_jid + "/" + resource);
1107 }
1108 
send_iq_version_request(const std::string & nick,const std::string & hostname)1109 void Bridge::send_iq_version_request(const std::string& nick, const std::string& hostname)
1110 {
1111   const auto resources = this->resources_in_server[hostname];
1112   if (resources.begin() != resources.end())
1113     this->xmpp.send_iq_version_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname),
1114                                        this->user_jid + "/" + *resources.begin());
1115 }
1116 
send_xmpp_ping_request(const std::string & nick,const std::string & hostname,const std::string & id)1117 void Bridge::send_xmpp_ping_request(const std::string& nick, const std::string& hostname,
1118                                     const std::string& id)
1119 {
1120   // Use revstr because the forwarded ping to target XMPP user must not be
1121   // the same as the request iq, but we also need to get it back easily
1122   // (revstr again)
1123   // Forward to the first resource (arbitrary, based on the “order” of the std::set) only
1124   const auto resources = this->resources_in_server[hostname];
1125   if (resources.begin() != resources.end())
1126     this->xmpp.send_ping_request(utils::tolower(nick) + utils::empty_if_fixed_server("%" + hostname),
1127                                  this->user_jid + "/" + *resources.begin(), utils::revstr(id));
1128 }
1129 
send_xmpp_invitation(const Iid & iid,const std::string & author)1130 void Bridge::send_xmpp_invitation(const Iid& iid, const std::string& author)
1131 {
1132   for (const auto& resource: this->resources_in_server[iid.get_server()])
1133     this->xmpp.send_invitation(std::to_string(iid), this->user_jid + "/" + resource, author);
1134 }
1135 
on_irc_client_connected(const std::string & hostname)1136 void Bridge::on_irc_client_connected(const std::string& hostname)
1137 {
1138   this->xmpp.on_irc_client_connected(hostname, this->user_jid);
1139 }
1140 
on_irc_client_disconnected(const std::string & hostname)1141 void Bridge::on_irc_client_disconnected(const std::string& hostname)
1142 {
1143   this->xmpp.on_irc_client_disconnected(hostname, this->user_jid);
1144 }
1145 
set_preferred_from_jid(const std::string & nick,const std::string & full_jid)1146 void Bridge::set_preferred_from_jid(const std::string& nick, const std::string& full_jid)
1147 {
1148   auto it = this->preferred_user_from.find(nick);
1149   if (it == this->preferred_user_from.end())
1150     this->preferred_user_from.emplace(nick, full_jid);
1151   else
1152     this->preferred_user_from[nick] = full_jid;
1153 }
1154 
remove_preferred_from_jid(const std::string & nick)1155 void Bridge::remove_preferred_from_jid(const std::string& nick)
1156 {
1157   auto it = this->preferred_user_from.find(nick);
1158   if (it != this->preferred_user_from.end())
1159     this->preferred_user_from.erase(it);
1160 }
1161 
remove_all_preferred_from_jid_of_room(const std::string & channel_name)1162 void Bridge::remove_all_preferred_from_jid_of_room(const std::string& channel_name)
1163 {
1164   for (auto it = this->preferred_user_from.begin(); it != this->preferred_user_from.end();)
1165     {
1166       Iid iid(Jid(it->second).local, {});
1167       if (iid.get_local() == channel_name)
1168         it = this->preferred_user_from.erase(it);
1169       else
1170         ++it;
1171     }
1172 }
1173 
add_waiting_irc(irc_responder_callback_t && callback)1174 void Bridge::add_waiting_irc(irc_responder_callback_t&& callback)
1175 {
1176   this->waiting_irc.emplace_back(std::move(callback));
1177 }
1178 
trigger_on_irc_message(const std::string & irc_hostname,const IrcMessage & message)1179 void Bridge::trigger_on_irc_message(const std::string& irc_hostname, const IrcMessage& message)
1180 {
1181   auto it = this->waiting_irc.begin();
1182   while (it != this->waiting_irc.end())
1183     {
1184       if ((*it)(irc_hostname, message) == true)
1185         it = this->waiting_irc.erase(it);
1186       else
1187         ++it;
1188     }
1189 }
1190 
get_irc_clients()1191 std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients()
1192 {
1193   return this->irc_clients;
1194 }
1195 
get_irc_clients() const1196 const std::unordered_map<std::string, std::unique_ptr<IrcClient>>& Bridge::get_irc_clients() const
1197 {
1198   return this->irc_clients;
1199 }
1200 
get_chantypes(const std::string & hostname) const1201 std::set<char> Bridge::get_chantypes(const std::string& hostname) const
1202 {
1203   IrcClient* irc = this->find_irc_client(hostname);
1204   if (!irc)
1205     return {'#', '&'};
1206   return irc->get_chantypes();
1207 }
1208 
add_resource_to_chan(const Bridge::ChannelKey & channel,const std::string & resource)1209 void Bridge::add_resource_to_chan(const Bridge::ChannelKey& channel, const std::string& resource)
1210 {
1211   auto it = this->resources_in_chan.find(channel);
1212   if (it == this->resources_in_chan.end())
1213     this->resources_in_chan[channel] = {resource};
1214   else
1215     it->second.insert(resource);
1216 }
1217 
remove_resource_from_chan(const Bridge::ChannelKey & channel,const std::string & resource)1218 void Bridge::remove_resource_from_chan(const Bridge::ChannelKey& channel, const std::string& resource)
1219 {
1220   auto it = this->resources_in_chan.find(channel);
1221   if (it != this->resources_in_chan.end())
1222     {
1223       it->second.erase(resource);
1224       if (it->second.empty())
1225         this->resources_in_chan.erase(it);
1226     }
1227 }
1228 
is_resource_in_chan(const Bridge::ChannelKey & channel,const std::string & resource) const1229 bool Bridge::is_resource_in_chan(const Bridge::ChannelKey& channel, const std::string& resource) const
1230 {
1231   auto it = this->resources_in_chan.find(channel);
1232   if (it != this->resources_in_chan.end())
1233     if (it->second.count(resource) == 1)
1234       return true;
1235   return false;
1236 }
1237 
remove_all_resources_from_chan(const Bridge::ChannelKey & channel)1238 void Bridge::remove_all_resources_from_chan(const Bridge::ChannelKey& channel)
1239 {
1240   this->resources_in_chan.erase(channel);
1241 }
1242 
add_resource_to_server(const Bridge::IrcHostname & irc_hostname,const std::string & resource)1243 void Bridge::add_resource_to_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
1244 {
1245   auto it = this->resources_in_server.find(irc_hostname);
1246   if (it == this->resources_in_server.end())
1247     this->resources_in_server[irc_hostname] = {resource};
1248   else
1249     it->second.insert(resource);
1250 }
1251 
remove_resource_from_server(const Bridge::IrcHostname & irc_hostname,const std::string & resource)1252 void Bridge::remove_resource_from_server(const Bridge::IrcHostname& irc_hostname, const std::string& resource)
1253 {
1254   auto it = this->resources_in_server.find(irc_hostname);
1255   if (it != this->resources_in_server.end())
1256     {
1257       it->second.erase(resource);
1258       if (it->second.empty())
1259         this->resources_in_server.erase(it);
1260     }
1261 }
1262 
number_of_resources_in_chan(const Bridge::ChannelKey & channel) const1263 std::size_t Bridge::number_of_resources_in_chan(const Bridge::ChannelKey& channel) const
1264 {
1265   auto it = this->resources_in_chan.find(channel);
1266   if (it == this->resources_in_chan.end())
1267     return 0;
1268   return it->second.size();
1269 }
1270 
number_of_channels_the_resource_is_in(const std::string & irc_hostname,const std::string & resource) const1271 std::size_t Bridge::number_of_channels_the_resource_is_in(const std::string& irc_hostname, const std::string& resource) const
1272 {
1273   std::size_t res = 0;
1274   for (auto pair: this->resources_in_chan)
1275     {
1276       if (std::get<1>(pair.first) == irc_hostname && pair.second.count(resource) != 0)
1277         res++;
1278     }
1279 
1280   return res;
1281 }
1282 
generate_channel_join_for_resource(const Iid & iid,const std::string & resource)1283 void Bridge::generate_channel_join_for_resource(const Iid& iid, const std::string& resource)
1284 {
1285   IrcClient* irc = this->get_irc_client(iid.get_server());
1286   IrcChannel* channel = irc->get_channel(iid.get_local());
1287   const auto self = channel->get_self();
1288 
1289   // Send the occupant list
1290   for (const auto& user: channel->get_users())
1291     {
1292       if (user->nick != self->nick)
1293         {
1294           this->send_user_join(iid.get_server(), iid.get_encoded_local(),
1295                                user.get(), user->get_most_significant_mode(irc->get_sorted_user_modes()),
1296                                false, resource);
1297         }
1298     }
1299   this->send_user_join(iid.get_server(), iid.get_encoded_local(),
1300                        self, self->get_most_significant_mode(irc->get_sorted_user_modes()),
1301                        true, resource);
1302   this->send_room_history(iid.get_server(), iid.get_local(), resource, irc->history_limit);
1303   this->send_topic(iid.get_server(), iid.get_encoded_local(), channel->topic, channel->topic_author, resource);
1304 }
1305 
1306 #ifdef USE_DATABASE
set_record_history(const bool val)1307 void Bridge::set_record_history(const bool val)
1308 {
1309   this->record_history = val;
1310 }
1311 #endif
1312