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