1 #include <utility>
2 #include <utils/timed_events.hpp>
3 #include <database/database.hpp>
4 #include <irc/irc_message.hpp>
5 #include <irc/irc_client.hpp>
6 #include <bridge/bridge.hpp>
7 #include <irc/irc_user.hpp>
8 #include <utils/base64.hpp>
9
10 #include <logger/logger.hpp>
11 #include <config/config.hpp>
12 #include <utils/tolower.hpp>
13 #include <utils/split.hpp>
14 #include <utils/string.hpp>
15
16 #include <sstream>
17 #include <iostream>
18 #include <stdexcept>
19 #include <algorithm>
20 #include <cstring>
21
22 #include <chrono>
23 #include <string>
24
25 #include "biboumi.h"
26
27 using namespace std::string_literals;
28 using namespace std::chrono_literals;
29
30 /**
31 * Define a map of functions to be called for each IRC command we can
32 * handle.
33 */
34 using IrcCallback = void (IrcClient::*)(const IrcMessage&);
35
36 static const std::unordered_map<std::string,
37 std::pair<IrcCallback, std::pair<std::size_t, std::size_t>>> irc_callbacks = {
38 {"NOTICE", {&IrcClient::on_notice, {2, 0}}},
39 {"002", {&IrcClient::forward_server_message, {2, 0}}},
40 {"003", {&IrcClient::forward_server_message, {2, 0}}},
41 {"004", {&IrcClient::on_server_myinfo, {4, 0}}},
42 {"005", {&IrcClient::on_isupport_message, {0, 0}}},
43 {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}},
44 {"321", {&IrcClient::on_rpl_liststart, {0, 0}}},
45 {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}},
46 {"322", {&IrcClient::on_rpl_list, {0, 0}}},
47 {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}},
48 {"323", {&IrcClient::on_rpl_listend, {0, 0}}},
49 {"RPL_NOTOPIC", {&IrcClient::on_empty_topic, {0, 0}}},
50 {"331", {&IrcClient::on_empty_topic, {0, 0}}},
51 {"341", {&IrcClient::on_invited, {3, 0}}},
52 {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}},
53 {"375", {&IrcClient::empty_motd, {0, 0}}},
54 {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}},
55 {"372", {&IrcClient::on_motd_line, {2, 0}}},
56 {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}},
57 {"376", {&IrcClient::send_motd, {0, 0}}},
58 {"JOIN", {&IrcClient::on_channel_join, {1, 0}}},
59 {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}},
60 {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}},
61 {"332", {&IrcClient::on_topic_received, {2, 0}}},
62 {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}},
63 {"333", {&IrcClient::on_topic_who_time_received, {4, 0}}},
64 {"RPL_TOPICWHOTIME", {&IrcClient::on_topic_who_time_received, {4, 0}}},
65 {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
66 {"367", {&IrcClient::on_banlist, {3, 0}}},
67 {"368", {&IrcClient::on_banlist_end, {3, 0}}},
68 {"396", {&IrcClient::on_own_host_received, {2, 0}}},
69 {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
70 {"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
71 {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
72 {"443", {&IrcClient::on_useronchannel, {3, 0}}},
73 {"475", {&IrcClient::on_channel_bad_key, {3, 0}}},
74 {"ERR_USERONCHANNEL", {&IrcClient::on_useronchannel, {3, 0}}},
75 {"001", {&IrcClient::on_welcome_message, {1, 0}}},
76 {"PART", {&IrcClient::on_part, {1, 0}}},
77 {"ERROR", {&IrcClient::on_error, {1, 0}}},
78 {"QUIT", {&IrcClient::on_quit, {0, 0}}},
79 {"NICK", {&IrcClient::on_nick, {1, 0}}},
80 {"MODE", {&IrcClient::on_mode, {1, 0}}},
81 {"PING", {&IrcClient::send_pong_command, {1, 0}}},
82 {"PONG", {&IrcClient::on_pong, {0, 0}}},
83 {"KICK", {&IrcClient::on_kick, {3, 0}}},
84 {"INVITE", {&IrcClient::on_invite, {2, 0}}},
85 {"CAP", {&IrcClient::on_cap, {3, 0}}},
86 #ifdef WITH_SASL
87 {"AUTHENTICATE", {&IrcClient::on_authenticate, {1, 0}}},
88 {"900", {&IrcClient::on_sasl_login, {3, 0}}},
89 {"902", {&IrcClient::on_sasl_failure, {2, 0}}},
90 {"903", {&IrcClient::on_sasl_success, {0, 0}}},
91 {"904", {&IrcClient::on_sasl_failure, {2, 0}}},
92 {"905", {&IrcClient::on_sasl_failure, {2, 0}}},
93 {"906", {&IrcClient::on_sasl_failure, {2, 0}}},
94 {"907", {&IrcClient::on_sasl_failure, {2, 0}}},
95 {"908", {&IrcClient::on_sasl_failure, {2, 0}}},
96 #endif
97 {"401", {&IrcClient::on_generic_error, {2, 0}}},
98 {"402", {&IrcClient::on_generic_error, {2, 0}}},
99 {"403", {&IrcClient::on_generic_error, {2, 0}}},
100 {"404", {&IrcClient::on_generic_error, {2, 0}}},
101 {"405", {&IrcClient::on_generic_error, {2, 0}}},
102 {"406", {&IrcClient::on_generic_error, {2, 0}}},
103 {"407", {&IrcClient::on_generic_error, {2, 0}}},
104 {"408", {&IrcClient::on_generic_error, {2, 0}}},
105 {"409", {&IrcClient::on_generic_error, {2, 0}}},
106 {"410", {&IrcClient::on_generic_error, {2, 0}}},
107 {"411", {&IrcClient::on_generic_error, {2, 0}}},
108 {"412", {&IrcClient::on_generic_error, {2, 0}}},
109 {"414", {&IrcClient::on_generic_error, {2, 0}}},
110 {"421", {&IrcClient::on_generic_error, {2, 0}}},
111 {"422", {&IrcClient::on_generic_error, {2, 0}}},
112 {"423", {&IrcClient::on_generic_error, {2, 0}}},
113 {"424", {&IrcClient::on_generic_error, {2, 0}}},
114 {"431", {&IrcClient::on_generic_error, {2, 0}}},
115 {"436", {&IrcClient::on_generic_error, {2, 0}}},
116 {"441", {&IrcClient::on_generic_error, {2, 0}}},
117 {"442", {&IrcClient::on_generic_error, {2, 0}}},
118 {"444", {&IrcClient::on_generic_error, {2, 0}}},
119 {"446", {&IrcClient::on_generic_error, {2, 0}}},
120 {"451", {&IrcClient::on_generic_error, {2, 0}}},
121 {"461", {&IrcClient::on_generic_error, {2, 0}}},
122 {"462", {&IrcClient::on_generic_error, {2, 0}}},
123 {"463", {&IrcClient::on_generic_error, {2, 0}}},
124 {"464", {&IrcClient::on_generic_error, {2, 0}}},
125 {"465", {&IrcClient::on_generic_error, {2, 0}}},
126 {"467", {&IrcClient::on_generic_error, {2, 0}}},
127 {"470", {&IrcClient::on_generic_error, {2, 0}}},
128 {"471", {&IrcClient::on_generic_error, {2, 0}}},
129 {"472", {&IrcClient::on_generic_error, {2, 0}}},
130 {"473", {&IrcClient::on_generic_error, {2, 0}}},
131 {"474", {&IrcClient::on_generic_error, {2, 0}}},
132 {"476", {&IrcClient::on_generic_error, {2, 0}}},
133 {"477", {&IrcClient::on_generic_error, {2, 0}}},
134 {"481", {&IrcClient::on_generic_error, {2, 0}}},
135 {"482", {&IrcClient::on_generic_error, {2, 0}}},
136 {"483", {&IrcClient::on_generic_error, {2, 0}}},
137 {"484", {&IrcClient::on_generic_error, {2, 0}}},
138 {"485", {&IrcClient::on_generic_error, {2, 0}}},
139 {"487", {&IrcClient::on_generic_error, {2, 0}}},
140 {"491", {&IrcClient::on_generic_error, {2, 0}}},
141 {"501", {&IrcClient::on_generic_error, {2, 0}}},
142 {"502", {&IrcClient::on_generic_error, {2, 0}}},
143 };
144
IrcClient(std::shared_ptr<Poller> & poller,std::string hostname,std::string nickname,std::string username,std::string realname,std::string user_hostname,Bridge & bridge)145 IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
146 std::string nickname, std::string username,
147 std::string realname, std::string user_hostname,
148 Bridge& bridge):
149 TCPClientSocketHandler(poller),
150 hostname(hostname),
151 user_hostname(std::move(user_hostname)),
152 username(std::move(username)),
153 realname(std::move(realname)),
154 current_nick(std::move(nickname)),
155 bridge(bridge),
156 welcomed(false),
157 chanmodes({"", "", "", ""}),
158 chantypes({'#', '&'}),
__anon56f2db640102() 159 tokens_bucket(this->get_throttle_limit(), 1s, [this]() {
160 if (message_queue.empty())
161 return true;
162 this->actual_send(std::move(this->message_queue.front()));
163 this->message_queue.pop_front();
164 return false;
165 }, "TokensBucket" + this->hostname + this->bridge.get_jid())
166 {
167 #ifdef USE_DATABASE
168 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
169 this->get_hostname());
170 std::vector<std::string> ports = utils::split(options.col<Database::Ports>(), ';', false);
171 for (auto it = ports.rbegin(); it != ports.rend(); ++it)
172 this->ports_to_try.emplace(*it, false);
173 # ifdef BOTAN_FOUND
174 ports = utils::split(options.col<Database::TlsPorts>(), ';', false);
175 for (auto it = ports.rbegin(); it != ports.rend(); ++it)
176 this->ports_to_try.emplace(*it, true);
177 # endif // BOTAN_FOUND
178
179 #else // not USE_DATABASE
180 this->ports_to_try.emplace("6667", false); // standard non-encrypted port
181 # ifdef BOTAN_FOUND
182 this->ports_to_try.emplace("6670", true); // non-standard but I want it for some servers
183 this->ports_to_try.emplace("6697", true); // standard encrypted port
184 # endif // BOTAN_FOUND
185 #endif // USE_DATABASE
186 }
187
~IrcClient()188 IrcClient::~IrcClient()
189 {
190 // This event may or may not exist (if we never got connected, it
191 // doesn't), but it's ok
192 TimedEventsManager::instance().cancel("PING" + this->hostname + this->bridge.get_jid());
193 TimedEventsManager::instance().cancel("TokensBucket" + this->hostname + this->bridge.get_jid());
194 }
195
start()196 void IrcClient::start()
197 {
198 if (this->is_connecting() || this->is_connected())
199 return;
200 if (this->ports_to_try.empty())
201 {
202 this->bridge.send_xmpp_message(this->hostname, "", "Can not connect to IRC server: no port specified.");
203 return;
204 }
205 std::string port;
206 bool tls;
207 std::tie(port, tls) = this->ports_to_try.top();
208 this->ports_to_try.pop();
209 this->bind_addr = Config::get("outgoing_bind", "");
210 std::string address = this->hostname;
211
212 #ifdef USE_DATABASE
213 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
214 this->get_hostname());
215 # ifdef BOTAN_FOUND
216 this->credential_manager.set_trusted_fingerprint(options.col<Database::TrustedFingerprint>());
217 # endif
218 if (Config::get("fixed_irc_server", "").empty() &&
219 !options.col<Database::Address>().empty())
220 address = options.col<Database::Address>();
221 #endif
222 this->bridge.send_xmpp_message(this->hostname, "", "Connecting to " +
223 address + ":" + port + " (" +
224 (tls ? "encrypted" : "not encrypted") + ")");
225 this->connect(address, port, tls);
226 }
227
on_connection_failed(const std::string & reason)228 void IrcClient::on_connection_failed(const std::string& reason)
229 {
230 this->bridge.send_xmpp_message(this->hostname, "",
231 "Connection failed: " + reason);
232
233 if (this->hostname_resolution_failed)
234 while (!this->ports_to_try.empty())
235 this->ports_to_try.pop();
236
237 if (this->ports_to_try.empty())
238 {
239 // Send an error message for all room that the user wanted to join
240 for (const auto& tuple: this->channels_to_join)
241 {
242 Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
243 this->bridge.send_presence_error(iid, this->current_nick,
244 "cancel", "item-not-found",
245 "", reason);
246 }
247 this->channels_to_join.clear();
248 }
249 else // try the next port
250 this->start();
251 }
252
on_connected()253 void IrcClient::on_connected()
254 {
255 const auto webirc_password = Config::get("webirc_password", "");
256 static std::string resolved_ip;
257
258 if (!webirc_password.empty())
259 {
260 if (!resolved_ip.empty())
261 this->send_webirc_command(webirc_password, resolved_ip);
262 else
263 { // Start resolving the hostname of the user, and call
264 // on_connected again when it’s done
265 this->dns_resolver.resolve(this->user_hostname, "5222",
266 [this](const struct addrinfo* addr)
267 {
268 resolved_ip = addr_to_string(addr);
269 // Only continue the process if we
270 // didn’t get connected while we were
271 // resolving
272 if (this->is_connected())
273 this->on_connected();
274 },
275 [this](const char* error_msg)
276 {
277 if (this->is_connected())
278 {
279 this->on_connection_close("Could not resolve hostname " + this->user_hostname +
280 ": " + error_msg);
281 this->send_quit_command("");
282 }
283 });
284 return;
285 }
286 }
287
288 this->send_gateway_message("Connected to IRC server"s + (this->use_tls ? " (encrypted)": "") + ".");
289
290 this->capabilities["multi-prefix"] = {[]{}, []{}};
291
292 #ifdef USE_DATABASE
293 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
294 this->get_hostname());
295
296 const auto& server_password = options.col<Database::Pass>();
297
298 if (!server_password.empty())
299 this->send_pass_command(options.col<Database::Pass>());
300 #endif
301
302 #ifdef WITH_SASL
303 const auto& sasl_password = options.col<Database::SaslPassword>();
304 if (!sasl_password.empty())
305 {
306 this->capabilities["sasl"] = {
307 [this]
308 {
309 this->send_message({"AUTHENTICATE", {"PLAIN"}});
310 log_warning("negociating SASL now...");
311 },
312 []
313 {
314 log_warning("SASL not supported by the server, disconnecting.");
315 }
316 };
317 this->sasl_state = SaslState::needed;
318 }
319 #endif
320
321 {
322 for (const auto &pair : this->capabilities)
323 this->send_message({ "CAP", {"REQ", pair.first}});
324 }
325
326 this->send_nick_command(this->current_nick);
327 #ifdef USE_DATABASE
328 if (Config::get("realname_customization", "true") == "true")
329 {
330 if (!options.col<Database::Username>().empty())
331 this->username = options.col<Database::Username>();
332 if (!options.col<Database::Realname>().empty())
333 this->realname = options.col<Database::Realname>();
334 this->send_user_command(username, realname);
335 }
336 else
337 #endif
338 this->send_user_command(this->username, this->realname);
339 }
340
on_connection_close(const std::string & error_msg)341 void IrcClient::on_connection_close(const std::string& error_msg)
342 {
343 std::string message = "Connection closed";
344 if (!error_msg.empty())
345 message += ": " + error_msg;
346 else
347 message += ".";
348 const IrcMessage error{"ERROR", {message}};
349 this->on_error(error);
350 log_warning(message);
351 this->bridge.on_irc_client_disconnected(this->get_hostname());
352 }
353
get_channel(const std::string & n)354 IrcChannel* IrcClient::get_channel(const std::string& n)
355 {
356 const std::string name = utils::tolower(n);
357 try
358 {
359 return this->channels.at(name).get();
360 }
361 catch (const std::out_of_range& exception)
362 {
363 return this->channels.emplace(name, std::make_unique<IrcChannel>()).first->second.get();
364 }
365 }
366
find_channel(const std::string & n) const367 const IrcChannel* IrcClient::find_channel(const std::string& n) const
368 {
369 const std::string name = utils::tolower(n);
370 try
371 {
372 return this->channels.at(name).get();
373 }
374 catch (const std::out_of_range& exception)
375 {
376 return nullptr;
377 }
378 }
379
is_channel_joined(const std::string & name)380 bool IrcClient::is_channel_joined(const std::string& name)
381 {
382 IrcChannel* channel = this->get_channel(name);
383 return channel->joined;
384 }
385
get_own_nick() const386 std::string IrcClient::get_own_nick() const
387 {
388 return this->current_nick;
389 }
390
parse_in_buffer(const size_t)391 void IrcClient::parse_in_buffer(const size_t)
392 {
393 while (true)
394 {
395 auto pos = this->in_buf.find("\r\n");
396 if (pos == std::string::npos)
397 break ;
398 IrcMessage message(this->in_buf.substr(0, pos));
399 this->consume_in_buffer(pos + 2);
400 log_debug("IRC RECEIVING: (", this->get_hostname(), ") ", message);
401
402 // Call the standard callback (if any), associated with the command
403 // name that we just received.
404 auto it = irc_callbacks.find(message.command);
405 if (it != irc_callbacks.end())
406 {
407 const auto& limits = it->second.second;
408 // Check that the Message is well formed before actually calling
409 // the callback.
410 const auto args_size = message.arguments.size();
411 const auto min = limits.first;
412 const auto max = limits.second;
413 if (args_size < min ||
414 (max > 0 && args_size > max))
415 log_warning("Invalid number of arguments for IRC command “", message.command,
416 "”: ", args_size);
417 else
418 {
419 const auto& cb = it->second.first;
420 try {
421 (this->*(cb))(message);
422 } catch (const std::exception& e) {
423 log_error("Unhandled exception: ", e.what());
424 }
425 }
426 }
427 else
428 {
429 log_info("No handler for command ", message.command,
430 ", forwarding the arguments to the user");
431 this->on_unknown_message(message);
432 }
433 // Try to find a waiting_iq, which response will be triggered by this IrcMessage
434 this->bridge.trigger_on_irc_message(this->hostname, message);
435 }
436 }
437
actual_send(std::pair<IrcMessage,MessageCallback> && message_pair)438 void IrcClient::actual_send(std::pair<IrcMessage, MessageCallback>&& message_pair)
439 {
440 const IrcMessage& message = message_pair.first;
441 const MessageCallback& callback = message_pair.second;
442 log_debug("IRC SENDING: (", this->get_hostname(), ") ", message);
443 std::string res;
444 if (!message.prefix.empty())
445 res += ":" + message.prefix + " ";
446 res += message.command;
447 for (const std::string& arg: message.arguments)
448 {
449 if (arg.find(' ') != std::string::npos
450 || (!arg.empty() && arg[0] == ':'))
451 {
452 res += " :" + arg;
453 break;
454 }
455 res += " " + arg;
456 }
457 res += "\r\n";
458 this->send_data(std::move(res));
459
460 if (callback)
461 callback(this, message);
462 }
463
send_message(IrcMessage message,MessageCallback callback,bool throttle)464 void IrcClient::send_message(IrcMessage message, MessageCallback callback, bool throttle)
465 {
466 auto message_pair = std::make_pair(std::move(message), std::move(callback));
467 if (this->tokens_bucket.use_token() || !throttle)
468 this->actual_send(std::move(message_pair));
469 else
470 message_queue.push_back(std::move(message_pair));
471 }
472
send_raw(const std::string & txt)473 void IrcClient::send_raw(const std::string& txt)
474 {
475 log_debug("IRC SENDING (raw): (", this->get_hostname(), ") ", txt);
476 this->send_data(txt + "\r\n");
477 }
478
send_user_command(const std::string & username,const std::string & realname)479 void IrcClient::send_user_command(const std::string& username, const std::string& realname)
480 {
481 this->send_message(IrcMessage("USER", {username, this->user_hostname, "ignored", realname}));
482 }
483
send_nick_command(const std::string & nick)484 void IrcClient::send_nick_command(const std::string& nick)
485 {
486 this->send_message(IrcMessage("NICK", {nick}));
487 }
488
send_pass_command(const std::string & password)489 void IrcClient::send_pass_command(const std::string& password)
490 {
491 this->send_message(IrcMessage("PASS", {password}));
492 }
493
send_webirc_command(const std::string & password,const std::string & user_ip)494 void IrcClient::send_webirc_command(const std::string& password, const std::string& user_ip)
495 {
496 this->send_message(IrcMessage("WEBIRC", {password, "biboumi", this->user_hostname, user_ip}));
497 }
498
send_kick_command(const std::string & chan_name,const std::string & target,const std::string & reason)499 void IrcClient::send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason)
500 {
501 this->send_message(IrcMessage("KICK", {chan_name, target, reason}));
502 }
503
send_list_command()504 void IrcClient::send_list_command()
505 {
506 this->send_message(IrcMessage("LIST", {"*"}));
507 }
508
send_invitation(const std::string & chan_name,const std::string & nick)509 void IrcClient::send_invitation(const std::string& chan_name, const std::string& nick)
510 {
511 this->send_message(IrcMessage("INVITE", {nick, chan_name}));
512 }
513
send_topic_command(const std::string & chan_name,const std::string & topic)514 void IrcClient::send_topic_command(const std::string& chan_name, const std::string& topic)
515 {
516 this->send_message(IrcMessage("TOPIC", {chan_name, topic}));
517 }
518
send_quit_command(const std::string & reason)519 void IrcClient::send_quit_command(const std::string& reason)
520 {
521 this->send_message(IrcMessage("QUIT", {reason}), {}, false);
522 }
523
send_join_command(const std::string & chan_name,const std::string & password)524 void IrcClient::send_join_command(const std::string& chan_name, const std::string& password)
525 {
526 if (!this->welcomed)
527 {
528 const auto it = std::find_if(begin(this->channels_to_join), end(this->channels_to_join),
529 [&chan_name](const auto& pair) { return std::get<0>(pair) == chan_name; });
530 if (it == end(this->channels_to_join))
531 this->channels_to_join.emplace_back(chan_name, password);
532 }
533 else if (password.empty())
534 this->send_message(IrcMessage("JOIN", {chan_name}));
535 else
536 this->send_message(IrcMessage("JOIN", {chan_name, password}));
537 this->start();
538 }
539
send_channel_message(const std::string & chan_name,const std::string & body,MessageCallback callback)540 bool IrcClient::send_channel_message(const std::string& chan_name, const std::string& body,
541 MessageCallback callback)
542 {
543 IrcChannel* channel = this->get_channel(chan_name);
544 if (!channel->joined)
545 {
546 log_warning("Cannot send message to channel ", chan_name, ", it is not joined");
547 return false;
548 }
549 // The max size is 512, taking into account the whole message, not just
550 // the text we send.
551 // This includes our own nick, constants for username and host (because these
552 // are notoriously hard to know what the server will use), in addition to the basic
553 // components of the message we send (command name, chan name, \r\n etc.)
554 // : + NICK + ! + USER + @ + HOST + <space> + PRIVMSG + <space> + CHAN + <space> + : + \r\n
555 // 63 is the maximum hostname length defined by the protocol. 10 seems to be
556 // the username limit.
557 constexpr auto max_username_size = 10;
558 constexpr auto max_hostname_size = 63;
559 const auto line_size = 512 -
560 this->current_nick.size() - max_username_size - max_hostname_size -
561 ::strlen(":!@ PRIVMSG ") - chan_name.length() - ::strlen(" :\r\n");
562 const auto lines = cut(body, line_size);
563 for (const auto& line: lines)
564 this->send_message(IrcMessage("PRIVMSG", {chan_name, line}), callback);
565 return true;
566 }
567
send_private_message(const std::string & username,const std::string & body,const std::string & type)568 void IrcClient::send_private_message(const std::string& username, const std::string& body, const std::string& type)
569 {
570 std::string::size_type pos = 0;
571 while (pos < body.size())
572 {
573 this->send_message(IrcMessage(std::string(type), {username, body.substr(pos, 400)}));
574 pos += 400;
575 }
576 // We always try to insert and we don't care if the username was already
577 // in the set.
578 this->nicks_to_treat_as_private.insert(username);
579 }
580
send_part_command(const std::string & chan_name,const std::string & status_message)581 void IrcClient::send_part_command(const std::string& chan_name, const std::string& status_message)
582 {
583 this->send_message(IrcMessage("PART", {chan_name, status_message}));
584 }
585
send_mode_command(const std::string & chan_name,const std::vector<std::string> & arguments)586 void IrcClient::send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments)
587 {
588 std::vector<std::string> args(arguments);
589 args.insert(args.begin(), chan_name);
590 IrcMessage m("MODE", std::move(args));
591 this->send_message(std::move(m));
592 }
593
send_pong_command(const IrcMessage & message)594 void IrcClient::send_pong_command(const IrcMessage& message)
595 {
596 const std::string id = message.arguments[0];
597 this->send_message(IrcMessage("PONG", {id}));
598 }
599
on_pong(const IrcMessage &)600 void IrcClient::on_pong(const IrcMessage&)
601 {
602 }
603
send_ping_command()604 void IrcClient::send_ping_command()
605 {
606 this->send_message(IrcMessage("PING", {"biboumi"}));
607 }
608
forward_server_message(const IrcMessage & message)609 void IrcClient::forward_server_message(const IrcMessage& message)
610 {
611 const std::string from = message.prefix;
612 std::string body;
613 for (auto it = std::next(message.arguments.begin()); it != message.arguments.end(); ++it)
614 body += *it + ' ';
615
616 this->bridge.send_xmpp_message(this->hostname, from, body);
617 }
618
on_notice(const IrcMessage & message)619 void IrcClient::on_notice(const IrcMessage& message)
620 {
621 std::string from = message.prefix;
622 std::string to = message.arguments[0];
623 const std::string body = message.arguments[1];
624
625 // Handle notices starting with [#channame] as if they were sent to that channel
626 if (body.size() > 3 && body[0] == '[')
627 {
628 const auto chan_prefix = body[1];
629 auto end = body.find(']');
630 if (this->chantypes.find(chan_prefix) != this->chantypes.end() && end != std::string::npos)
631 to = body.substr(1, end - 1);
632 }
633
634 if (!body.empty() && body[0] == '\01' && body[body.size() - 1] == '\01')
635 // Do not forward the notice to the user if it's a CTCP command
636 return ;
637
638 if (!to.empty() && this->chantypes.find(to[0]) == this->chantypes.end())
639 {
640 // The notice is for us precisely.
641
642 // Find out if we already sent a private message to this user. If yes
643 // we treat that message as a private message coming from
644 // it. Otherwise we treat it as a notice coming from the server.
645 IrcUser user(from);
646 std::string nick = utils::tolower(user.nick);
647 if (this->nicks_to_treat_as_private.find(nick) !=
648 this->nicks_to_treat_as_private.end())
649 { // We previously sent a message to that nick)
650 this->bridge.send_message({nick, this->hostname, Iid::Type::User}, nick, body,
651 false);
652 }
653 else
654 this->bridge.send_xmpp_message(this->hostname, from, body);
655 }
656 else
657 {
658 // The notice was directed at a channel we are in. Modify the message
659 // to indicate that it is a notice, and make it a MUC message coming
660 // from the MUC JID
661 IrcMessage modified_message(std::move(from), "PRIVMSG", {to, "\u000303[notice]\u0003 " + body});
662 this->on_channel_message(modified_message);
663 }
664 }
665
on_isupport_message(const IrcMessage & message)666 void IrcClient::on_isupport_message(const IrcMessage& message)
667 {
668 const size_t len = message.arguments.size();
669 for (size_t i = 1; i < len; ++i)
670 {
671 const std::string token = message.arguments[i];
672 if (token.substr(0, 10) == "CHANMODES=")
673 {
674 this->chanmodes = utils::split(token.substr(11), ',');
675 // make sure we have 4 strings
676 this->chanmodes.resize(4);
677 }
678 else if (token.substr(0, 7) == "PREFIX=")
679 {
680 size_t i = 8; // jump PREFIX=(
681 size_t j = 9;
682 // Find the ) char
683 while (j < token.size() && token[j] != ')')
684 j++;
685 j++;
686 while (j < token.size() && token[i] != ')')
687 {
688 this->sorted_user_modes.push_back(token[i]);
689 this->prefix_to_mode[token[j++]] = token[i++];
690 }
691 }
692 else if (token.substr(0, 10) == "CHANTYPES=")
693 {
694 // Remove the default types, they apply only if no other value is
695 // specified.
696 this->chantypes.clear();
697 size_t i = 10;
698 while (i < token.size())
699 this->chantypes.insert(token[i++]);
700 }
701 }
702 }
703
on_server_myinfo(const IrcMessage &)704 void IrcClient::on_server_myinfo(const IrcMessage&)
705 {
706 }
707
send_gateway_message(const std::string & message,const std::string & from)708 void IrcClient::send_gateway_message(const std::string& message, const std::string& from)
709 {
710 this->bridge.send_xmpp_message(this->hostname, from, message);
711 }
712
set_and_forward_user_list(const IrcMessage & message)713 void IrcClient::set_and_forward_user_list(const IrcMessage& message)
714 {
715 const std::string chan_name = utils::tolower(message.arguments[2]);
716 IrcChannel* channel = this->get_channel(chan_name);
717 if (channel->joined)
718 {
719 this->forward_server_message(message);
720 return;
721 }
722 std::vector<std::string> nicks = utils::split(message.arguments[3], ' ');
723 for (const std::string& nick: nicks)
724 {
725 // Just create this dummy user to parse and get its modes
726 IrcUser tmp_user{nick, this->prefix_to_mode};
727 // Does this concern ourself
728 if (channel->get_self() && channel->find_user(tmp_user.nick) == channel->get_self())
729 {
730 // We now know our own modes, that’s all.
731 channel->get_self()->modes = tmp_user.modes;
732 }
733 else
734 { // Otherwise this is a new user
735 const IrcUser *user = channel->add_user(nick, this->prefix_to_mode);
736 this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
737 }
738 }
739 }
740
on_channel_join(const IrcMessage & message)741 void IrcClient::on_channel_join(const IrcMessage& message)
742 {
743 const std::string chan_name = utils::tolower(message.arguments[0]);
744 IrcChannel* channel;
745 channel = this->get_channel(chan_name);
746 const std::string nick = message.prefix;
747 IrcUser* user = channel->add_user(nick, this->prefix_to_mode);
748 if (channel->joined == false)
749 channel->set_self(user);
750 else
751 this->bridge.send_user_join(this->hostname, chan_name, user, user->get_most_significant_mode(this->sorted_user_modes), false);
752 }
753
on_channel_message(const IrcMessage & message)754 void IrcClient::on_channel_message(const IrcMessage& message)
755 {
756 const IrcUser user(message.prefix);
757 const std::string nick = user.nick;
758 Iid iid;
759 iid.set_local(message.arguments[0]);
760 iid.set_server(this->hostname);
761 const std::string body = message.arguments[1];
762 bool muc = true;
763 if (!this->get_channel(iid.get_local())->joined)
764 {
765 iid.type = Iid::Type::User;
766 iid.set_local(nick);
767 muc = false;
768 }
769 else
770 iid.type = Iid::Type::Channel;
771 if (!body.empty() && body[0] == '\01')
772 {
773 if (body.substr(1, 6) == "ACTION")
774 this->bridge.send_message(iid, nick,
775 "/me" + body.substr(7, body.size() - 8), muc);
776 else if (body.substr(1, 8) == "VERSION\01")
777 this->bridge.send_iq_version_request(nick, this->hostname);
778 else if (body.substr(1, 5) == "PING ")
779 this->bridge.send_xmpp_ping_request(utils::tolower(nick), this->hostname,
780 body.substr(6, body.size() - 7));
781 }
782 else
783 this->bridge.send_message(iid, nick, body, muc);
784 }
785
on_rpl_liststart(const IrcMessage &)786 void IrcClient::on_rpl_liststart(const IrcMessage&)
787 {
788 }
789
on_rpl_list(const IrcMessage &)790 void IrcClient::on_rpl_list(const IrcMessage&)
791 {
792 }
793
on_rpl_listend(const IrcMessage &)794 void IrcClient::on_rpl_listend(const IrcMessage&)
795 {
796 }
797
empty_motd(const IrcMessage &)798 void IrcClient::empty_motd(const IrcMessage&)
799 {
800 this->motd.erase();
801 }
802
on_invited(const IrcMessage & message)803 void IrcClient::on_invited(const IrcMessage& message)
804 {
805 const std::string& chan_name = message.arguments[2];
806 const std::string& invited_nick = message.arguments[1];
807
808 this->bridge.send_xmpp_message(this->hostname, "", invited_nick + " has been invited to " + chan_name);
809 }
810
on_empty_topic(const IrcMessage & message)811 void IrcClient::on_empty_topic(const IrcMessage& message)
812 {
813 const std::string chan_name = utils::tolower(message.arguments[1]);
814 log_debug("empty topic for ", chan_name);
815 IrcChannel* channel = this->get_channel(chan_name);
816 if (channel)
817 channel->topic.clear();
818 }
819
on_motd_line(const IrcMessage & message)820 void IrcClient::on_motd_line(const IrcMessage& message)
821 {
822 const std::string body = message.arguments[1];
823 // We could send the MOTD without a line break between each IRC-message,
824 // but sometimes it contains some ASCII art, we use line breaks to keep
825 // them intact.
826 this->motd += body+"\n";
827 }
828
send_motd(const IrcMessage &)829 void IrcClient::send_motd(const IrcMessage&)
830 {
831 this->bridge.send_xmpp_message(this->hostname, "", this->motd);
832 }
833
on_topic_received(const IrcMessage & message)834 void IrcClient::on_topic_received(const IrcMessage& message)
835 {
836 const std::string chan_name = utils::tolower(message.arguments[message.arguments.size() - 2]);
837 IrcUser author(message.prefix);
838 IrcChannel* channel = this->get_channel(chan_name);
839 channel->topic = message.arguments[message.arguments.size() - 1];
840 channel->topic_author = author.nick;
841 if (channel->joined)
842 this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
843 }
844
on_topic_who_time_received(const IrcMessage & message)845 void IrcClient::on_topic_who_time_received(const IrcMessage& message)
846 {
847 IrcUser author(message.arguments[2]);
848 const std::string chan_name = utils::tolower(message.arguments[1]);
849 IrcChannel* channel = this->get_channel(chan_name);
850 channel->topic_author = author.nick;
851 }
852
on_channel_completely_joined(const IrcMessage & message)853 void IrcClient::on_channel_completely_joined(const IrcMessage& message)
854 {
855 const std::string chan_name = utils::tolower(message.arguments[1]);
856 IrcChannel* channel = this->get_channel(chan_name);
857 if (chan_name == "*" || channel->joined)
858 {
859 this->forward_server_message(message);
860 return;
861 }
862 if (!channel->get_self())
863 {
864 log_error("End of NAMES list but we never received our own nick.");
865 return;
866 }
867 channel->joined = true;
868 this->bridge.send_user_join(this->hostname, chan_name, channel->get_self(),
869 channel->get_self()->get_most_significant_mode(this->sorted_user_modes), true);
870 this->bridge.send_room_history(this->hostname, chan_name, this->history_limit);
871 this->bridge.send_topic(this->hostname, chan_name, channel->topic, channel->topic_author);
872 }
873
on_banlist(const IrcMessage & message)874 void IrcClient::on_banlist(const IrcMessage& message)
875 {
876 const std::string chan_name = utils::tolower(message.arguments[1]);
877 IrcChannel* channel = this->get_channel(chan_name);
878 if (channel->joined)
879 {
880 Iid iid;
881 iid.set_local(chan_name);
882 iid.set_server(this->hostname);
883 iid.type = Iid::Type::Channel;
884 std::string body{message.arguments[2] + " banned"};
885 if (message.arguments.size() >= 4)
886 {
887 IrcUser by(message.arguments[3], this->prefix_to_mode);
888 body += " by " + by.nick;
889 }
890 if (message.arguments.size() >= 5)
891 body += " on " + message.arguments[4];
892
893 this->bridge.send_message(iid, "", body, true);
894 }
895 }
896
on_banlist_end(const IrcMessage & message)897 void IrcClient::on_banlist_end(const IrcMessage& message)
898 {
899 const std::string chan_name = utils::tolower(message.arguments[1]);
900 IrcChannel* channel = this->get_channel(chan_name);
901 if (channel->joined)
902 {
903 Iid iid;
904 iid.set_local(chan_name);
905 iid.set_server(this->hostname);
906 iid.type = Iid::Type::Channel;
907 this->bridge.send_message(iid, "", message.arguments[2], true);
908 }
909 }
910
on_own_host_received(const IrcMessage & message)911 void IrcClient::on_own_host_received(const IrcMessage& message)
912 {
913 this->own_host = message.arguments[1];
914 const std::string from = message.prefix;
915 if (message.arguments.size() >= 3)
916 this->bridge.send_xmpp_message(this->hostname, from,
917 this->own_host + " " + message.arguments[2]);
918 else
919 this->bridge.send_xmpp_message(this->hostname, from, this->own_host +
920 " is now your displayed host");
921 }
922
on_erroneous_nickname(const IrcMessage & message)923 void IrcClient::on_erroneous_nickname(const IrcMessage& message)
924 {
925 const std::string error_msg = message.arguments.size() >= 3 ?
926 message.arguments[2]: "Erroneous nickname";
927 this->send_gateway_message(error_msg + ": " + message.arguments[1], message.prefix);
928 }
929
on_nickname_conflict(const IrcMessage & message)930 void IrcClient::on_nickname_conflict(const IrcMessage& message)
931 {
932 const std::string nickname = message.arguments[1];
933 this->on_generic_error(message);
934 for (const auto& pair: this->channels)
935 {
936 Iid iid;
937 iid.set_local(pair.first);
938 iid.set_server(this->hostname);
939 iid.type = Iid::Type::Channel;
940 this->bridge.send_nickname_conflict_error(iid, nickname);
941 }
942 }
943
on_nickname_change_too_fast(const IrcMessage & message)944 void IrcClient::on_nickname_change_too_fast(const IrcMessage& message)
945 {
946 const std::string nickname = message.arguments[1];
947 std::string txt;
948 if (message.arguments.size() >= 3)
949 txt = message.arguments[2];
950 this->on_generic_error(message);
951 for (const auto& pair: this->channels)
952 {
953 Iid iid;
954 iid.set_local(pair.first);
955 iid.set_server(this->hostname);
956 iid.type = Iid::Type::Channel;
957 this->bridge.send_presence_error(iid, nickname,
958 "cancel", "not-acceptable",
959 "", txt);
960 }
961 }
on_generic_error(const IrcMessage & message)962 void IrcClient::on_generic_error(const IrcMessage& message)
963 {
964 const std::string error_msg = message.arguments.size() >= 3 ?
965 message.arguments[2]: "Unspecified error";
966 this->send_gateway_message(message.arguments[1] + ": " + error_msg, message.prefix);
967 }
968
on_useronchannel(const IrcMessage & message)969 void IrcClient::on_useronchannel(const IrcMessage& message)
970 {
971 this->send_gateway_message(message.arguments[1] + " " + message.arguments[3] + " "
972 + message.arguments[2]);
973 }
974
on_welcome_message(const IrcMessage & message)975 void IrcClient::on_welcome_message(const IrcMessage& message)
976 {
977 this->current_nick = message.arguments[0];
978 this->welcomed = true;
979 #ifdef USE_DATABASE
980 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
981 this->get_hostname());
982 const auto commands = Database::get_after_connection_commands(options);
983 for (const auto& command: commands)
984 this->send_raw(command.col<Database::AfterConnectionCommand>());
985 #endif
986 // Install a repeated events to regularly send a PING
987 TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
988 "PING" + this->hostname + this->bridge.get_jid()));
989 std::string channels{};
990 std::string channels_with_key{};
991 std::string keys{};
992
993 for (const auto& tuple: this->channels_to_join)
994 {
995 const auto& chan = std::get<0>(tuple);
996 const auto& key = std::get<1>(tuple);
997 if (chan.empty())
998 continue;
999 if (!key.empty())
1000 {
1001 if (keys.size() + channels_with_key.size() >= 300)
1002 { // Arbitrary size, to make sure we never send more than 512
1003 this->send_join_command(channels_with_key, keys);
1004 channels_with_key.clear();
1005 keys.clear();
1006 }
1007 if (!keys.empty())
1008 keys += ",";
1009 keys += key;
1010 if (!channels_with_key.empty())
1011 channels_with_key += ",";
1012 channels_with_key += chan;
1013 }
1014 else
1015 {
1016 if (channels.size() >= 300)
1017 { // Arbitrary size, to make sure we never send more than 512
1018 this->send_join_command(channels, {});
1019 channels.clear();
1020 }
1021 if (!channels.empty())
1022 channels += ",";
1023 channels += chan;
1024 }
1025 }
1026 if (!channels.empty())
1027 this->send_join_command(channels, {});
1028 if (!channels_with_key.empty())
1029 this->send_join_command(channels_with_key, keys);
1030 this->channels_to_join.clear();
1031 }
1032
on_part(const IrcMessage & message)1033 void IrcClient::on_part(const IrcMessage& message)
1034 {
1035 const std::string chan_name = message.arguments[0];
1036 IrcChannel* channel = this->get_channel(chan_name);
1037 if (!channel->joined)
1038 return ;
1039 std::string txt;
1040 if (message.arguments.size() >= 2)
1041 txt = message.arguments[1];
1042 const IrcUser* user = channel->find_user(message.prefix);
1043 if (user)
1044 {
1045 std::string nick = user->nick;
1046 bool self = channel->get_self() && channel->get_self()->nick == nick;
1047 auto user_ptr = channel->remove_user(user);
1048 if (self)
1049 {
1050 this->channels.erase(utils::tolower(chan_name));
1051 // channel pointer is now invalid
1052 channel = nullptr;
1053 }
1054 Iid iid;
1055 iid.set_local(chan_name);
1056 iid.set_server(this->hostname);
1057 iid.type = Iid::Type::Channel;
1058 this->bridge.send_muc_leave(iid, *user_ptr, txt, self, true, {}, this);
1059 }
1060 }
1061
on_error(const IrcMessage & message)1062 void IrcClient::on_error(const IrcMessage& message)
1063 {
1064 const std::string leave_message = message.arguments[0];
1065 // The user is out of all the channels
1066 for (const auto& pair: this->channels)
1067 {
1068 Iid iid;
1069 iid.set_local(pair.first);
1070 iid.set_server(this->hostname);
1071 iid.type = Iid::Type::Channel;
1072 IrcChannel* channel = pair.second.get();
1073 if (!channel->joined)
1074 continue;
1075 this->bridge.send_muc_leave(iid, *channel->get_self(), leave_message, true, false, {}, this);
1076 }
1077 this->channels.clear();
1078 this->send_gateway_message("ERROR: " + leave_message);
1079 }
1080
on_quit(const IrcMessage & message)1081 void IrcClient::on_quit(const IrcMessage& message)
1082 {
1083 std::string txt;
1084 if (message.arguments.size() >= 1)
1085 txt = message.arguments[0];
1086 for (const auto& pair: this->channels)
1087 {
1088 const std::string& chan_name = pair.first;
1089 IrcChannel* channel = pair.second.get();
1090 const IrcUser* user = channel->find_user(message.prefix);
1091 if (!user)
1092 continue;
1093 bool self = false;
1094 if (user == channel->get_self())
1095 self = true;
1096 Iid iid;
1097 iid.set_local(chan_name);
1098 iid.set_server(this->hostname);
1099 iid.type = Iid::Type::Channel;
1100 this->bridge.send_muc_leave(iid, *user, txt, self, false, {}, this);
1101 channel->remove_user(user);
1102 }
1103 }
1104
on_nick(const IrcMessage & message)1105 void IrcClient::on_nick(const IrcMessage& message)
1106 {
1107 const std::string new_nick = IrcUser(message.arguments[0]).nick;
1108 const std::string current_nick = IrcUser(message.prefix).nick;
1109 const auto change_nick_func = [this, &new_nick, ¤t_nick](const std::string& chan_name, const IrcChannel* channel)
1110 {
1111 IrcUser* user;
1112 if (channel->get_self() && channel->get_self()->nick == current_nick)
1113 user = channel->get_self();
1114 else
1115 user = channel->find_user(current_nick);
1116 if (user)
1117 {
1118 std::string old_nick = user->nick;
1119 Iid iid(chan_name, this->hostname, Iid::Type::Channel);
1120 const bool self = channel->get_self()->nick == old_nick;
1121 const char user_mode = user->get_most_significant_mode(this->sorted_user_modes);
1122 this->bridge.send_nick_change(std::move(iid), old_nick, new_nick, user_mode, self);
1123 user->nick = new_nick;
1124 if (self)
1125 {
1126 channel->get_self()->nick = new_nick;
1127 this->current_nick = new_nick;
1128 }
1129 }
1130 };
1131
1132 for (const auto& pair: this->channels)
1133 {
1134 change_nick_func(pair.first, pair.second.get());
1135 }
1136 }
1137
on_kick(const IrcMessage & message)1138 void IrcClient::on_kick(const IrcMessage& message)
1139 {
1140 const std::string chan_name = utils::tolower(message.arguments[0]);
1141 const std::string target_nick = message.arguments[1];
1142 const std::string reason = message.arguments[2];
1143 IrcChannel* channel = this->get_channel(chan_name);
1144 if (!channel->joined)
1145 return ;
1146 const IrcUser* target = channel->find_user(target_nick);
1147 if (!target)
1148 {
1149 log_warning("Received a KICK command from a nick absent from the channel.");
1150 return;
1151 }
1152 const bool self = channel->get_self() == target;
1153 if (self)
1154 channel->joined = false;
1155 IrcUser author(message.prefix);
1156 Iid iid;
1157 iid.set_local(chan_name);
1158 iid.set_server(this->hostname);
1159 iid.type = Iid::Type::Channel;
1160 this->bridge.kick_muc_user(std::move(iid), target_nick, reason, author.nick, self);
1161 channel->remove_user(target);
1162 }
1163
on_invite(const IrcMessage & message)1164 void IrcClient::on_invite(const IrcMessage& message)
1165 {
1166 IrcUser author(message.prefix);
1167 Iid iid;
1168 iid.set_local(message.arguments[1]);
1169 iid.set_server(this->hostname);
1170 iid.type = Iid::Type::Channel;
1171
1172 this->bridge.send_xmpp_invitation(iid, author.nick);
1173 }
1174
on_mode(const IrcMessage & message)1175 void IrcClient::on_mode(const IrcMessage& message)
1176 {
1177 const std::string target = message.arguments[0];
1178 if (this->chantypes.find(target[0]) != this->chantypes.end())
1179 this->on_channel_mode(message);
1180 else
1181 this->on_user_mode(message);
1182 }
1183
on_channel_bad_key(const IrcMessage & message)1184 void IrcClient::on_channel_bad_key(const IrcMessage& message)
1185 {
1186 this->on_generic_error(message);
1187 const std::string& nickname = message.arguments[0];
1188 const std::string& channel = message.arguments[1];
1189 std::string text;
1190 if (message.arguments.size() > 2)
1191 text = message.arguments[2];
1192
1193 this->bridge.send_presence_error({channel, this->hostname, Iid::Type::Channel}, nickname, "auth", "not-authorized", "", text);
1194 }
1195
on_channel_mode(const IrcMessage & message)1196 void IrcClient::on_channel_mode(const IrcMessage& message)
1197 {
1198 Iid iid;
1199 iid.set_local(message.arguments[0]);
1200 iid.set_server(this->hostname);
1201 iid.type = Iid::Type::Channel;
1202 IrcUser user(message.prefix);
1203 std::string mode_arguments;
1204 for (size_t i = 1; i < message.arguments.size(); ++i)
1205 {
1206 if (!message.arguments[i].empty())
1207 {
1208 if (i != 1)
1209 mode_arguments += " ";
1210 mode_arguments += message.arguments[i];
1211 }
1212 }
1213 this->bridge.send_message(iid, "", "Mode " + iid.get_local() +
1214 " [" + mode_arguments + "] by " + user.nick,
1215 true, this->is_channel_joined(iid.get_local()));
1216 const IrcChannel* channel = this->get_channel(iid.get_local());
1217 if (!channel)
1218 return;
1219
1220 // parse the received modes, we need to handle things like "+m-oo coucou toutou"
1221 const std::string modes = message.arguments[1];
1222 // a list of modified IrcUsers. When we applied all modes, we check the
1223 // modes that now applies to each of them, and send a notification for
1224 // each one. This is to disallow sending two notifications or more when a
1225 // single MODE command changes two or more modes on the same participant
1226 std::set<const IrcUser*> modified_users;
1227 // If it is true, the modes are added, if it’s false they are
1228 // removed. When we encounter the '+' char, the value is changed to true,
1229 // and with '-' it is changed to false.
1230 bool add = true;
1231 bool use_arg;
1232 size_t arg_pos = 2;
1233 for (const char c: modes)
1234 {
1235 if (c == '+')
1236 add = true;
1237 else if (c == '-')
1238 add = false;
1239 else
1240 { // lookup the mode symbol in the 4 chanmodes lists, depending on
1241 // the list where it is found, it takes an argument or not
1242 size_t type;
1243 for (type = 0; type < 4; ++type)
1244 if (this->chanmodes[type].find(c) != std::string::npos)
1245 break;
1246 if (type == 4) // if mode was not found
1247 {
1248 // That mode can also be of type B if it is present in the
1249 // prefix_to_mode map
1250 for (const auto& pair: this->prefix_to_mode)
1251 if (pair.second == c)
1252 {
1253 type = 1;
1254 break;
1255 }
1256 }
1257 // modes of type A, B or C (but only with add == true)
1258 if (type == 0 || type == 1 ||
1259 (type == 2 && add == true))
1260 use_arg = true;
1261 else // modes of type C (but only with add == false), D, or unknown
1262 use_arg = false;
1263 if (use_arg == true && message.arguments.size() > arg_pos)
1264 {
1265 const std::string target = message.arguments[arg_pos++];
1266 IrcUser* user = channel->find_user(target);
1267 if (!user)
1268 {
1269 log_warning("Trying to set mode for non-existing user '", target
1270 , "' in channel", iid.get_local());
1271 return;
1272 }
1273 if (add)
1274 user->add_mode(c);
1275 else
1276 user->remove_mode(c);
1277 modified_users.insert(user);
1278 }
1279 }
1280 }
1281 for (const IrcUser* u: modified_users)
1282 {
1283 char most_significant_mode = u->get_most_significant_mode(this->sorted_user_modes);
1284 this->bridge.send_affiliation_role_change(iid, u->nick, most_significant_mode);
1285 }
1286 }
1287
set_throttle_limit(long int limit)1288 void IrcClient::set_throttle_limit(long int limit)
1289 {
1290 this->tokens_bucket.set_limit(limit);
1291 }
1292
on_user_mode(const IrcMessage & message)1293 void IrcClient::on_user_mode(const IrcMessage& message)
1294 {
1295 this->bridge.send_xmpp_message(this->hostname, "",
1296 "User mode for " + message.arguments[0] +
1297 " is [" + message.arguments[1] + "]");
1298 }
1299
on_unknown_message(const IrcMessage & message)1300 void IrcClient::on_unknown_message(const IrcMessage& message)
1301 {
1302 if (message.arguments.size() < 2)
1303 return ;
1304 std::string from = message.prefix;
1305 std::stringstream ss;
1306 for (auto it = std::next(message.arguments.begin()); it != message.arguments.end(); ++it)
1307 {
1308 ss << *it;
1309 if (it + 1 != message.arguments.end())
1310 ss << " ";
1311 }
1312 this->bridge.send_xmpp_message(this->hostname, from, ss.str());
1313 }
1314
number_of_joined_channels() const1315 size_t IrcClient::number_of_joined_channels() const
1316 {
1317 return this->channels.size();
1318 }
1319
1320 #ifdef BOTAN_FOUND
abort_on_invalid_cert() const1321 bool IrcClient::abort_on_invalid_cert() const
1322 {
1323 #ifdef USE_DATABASE
1324 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname);
1325 return options.col<Database::VerifyCert>();
1326 #endif
1327 return true;
1328 }
1329 #endif
1330
get_throttle_limit() const1331 long int IrcClient::get_throttle_limit() const
1332 {
1333 #ifdef USE_DATABASE
1334 return Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname).col<Database::ThrottleLimit>();
1335 #else
1336 return 10;
1337 #endif
1338 }
1339
on_cap(const IrcMessage & message)1340 void IrcClient::on_cap(const IrcMessage &message)
1341 {
1342 const auto& sub_command = message.arguments[1];
1343 const auto& caps = utils::split(message.arguments[2], ' ', false);
1344 for (const auto& cap: caps)
1345 {
1346 auto it = this->capabilities.find(cap);
1347 if (it == this->capabilities.end())
1348 {
1349 log_warning("Received a CAP message for something we didn’t ask, or that we already handled: [", cap, "]");
1350 return;
1351 }
1352 Capability& capability = it->second;
1353 if (sub_command == "ACK")
1354 capability.on_ack();
1355 else if (sub_command == "NACK")
1356 capability.on_nack();
1357 this->capabilities.erase(it);
1358 }
1359 if (this->capabilities.empty())
1360 this->cap_end();
1361 }
1362
1363 #ifdef WITH_SASL
on_authenticate(const IrcMessage &)1364 void IrcClient::on_authenticate(const IrcMessage &)
1365 {
1366 if (this->sasl_state == SaslState::unneeded)
1367 {
1368 log_warning("Received an AUTHENTICATE command but we don’t intend to authenticate…");
1369 return;
1370 }
1371
1372 auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
1373 this->get_hostname());
1374 const auto auth_string = '\0' + options.col<Database::Nick>() + '\0' + options.col<Database::SaslPassword>();
1375 const auto base64_auth_string = base64::encode(auth_string);
1376 this->send_message({"AUTHENTICATE", {base64_auth_string}});
1377 }
1378
on_sasl_success(const IrcMessage &)1379 void IrcClient::on_sasl_success(const IrcMessage &)
1380 {
1381 this->sasl_state = SaslState::success;
1382 this->cap_end();
1383 }
1384
on_sasl_failure(const IrcMessage & message)1385 void IrcClient::on_sasl_failure(const IrcMessage& message)
1386 {
1387 this->sasl_state = SaslState::failure;
1388 const auto reason = message.arguments[1];
1389 // Send an error message for all room that the user wanted to join
1390 for (const auto& tuple: this->channels_to_join)
1391 {
1392 Iid iid(std::get<0>(tuple) + "%" + this->hostname, this->chantypes);
1393 this->bridge.send_presence_error(iid, this->current_nick,
1394 "cancel", "item-not-found",
1395 "", reason);
1396 }
1397 this->channels_to_join.clear();
1398 this->send_quit_command(reason);
1399 }
1400
on_sasl_login(const IrcMessage & message)1401 void IrcClient::on_sasl_login(const IrcMessage &message)
1402 {
1403 const auto& login = message.arguments[2];
1404 std::string text = "Your are now logged in as " + login;
1405 if (message.arguments.size() > 3)
1406 text = message.arguments[3];
1407 this->bridge.send_xmpp_message(this->hostname, message.prefix, text);
1408 }
1409 #endif
1410
cap_end()1411 void IrcClient::cap_end()
1412 {
1413 #ifdef WITH_SASL
1414 // If we are currently authenticating through sasl, finish that before sending CAP END
1415 if (this->sasl_state == SaslState::needed)
1416 return;
1417 #endif
1418 this->send_message({"CAP", {"END"}});
1419 this->bridge.on_irc_client_connected(this->get_hostname());
1420 }
1421