irc_client.hpp 15.8 KB
Newer Older
1 2 3
#ifndef IRC_CLIENT_INCLUDED
# define IRC_CLIENT_INCLUDED

4
#include <irc/irc_message.hpp>
5 6
#include <irc/irc_channel.hpp>
#include <irc/iid.hpp>
louiz’'s avatar
louiz’ committed
7

8
#include <network/tcp_socket_handler.hpp>
louiz’'s avatar
louiz’ committed
9
#include <network/resolver.hpp>
10

11
#include <unordered_map>
12
#include <utility>
13
#include <memory>
louiz’'s avatar
louiz’ committed
14
#include <vector>
15
#include <string>
louiz’'s avatar
louiz’ committed
16
#include <stack>
louiz’'s avatar
louiz’ committed
17
#include <map>
18
#include <set>
19

20 21
class Bridge;

22 23 24 25
/**
 * Represent one IRC client, i.e. an endpoint connected to a single IRC
 * server, through a TCP socket, receiving and sending commands to it.
 */
26
class IrcClient: public TCPSocketHandler
27 28
{
public:
29 30
  explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname,
                     const std::string& nickname, const std::string& username,
louiz’'s avatar
louiz’ committed
31 32
                     const std::string& realname, const std::string& user_hostname,
                     Bridge* bridge);
33
  ~IrcClient();
34 35 36 37
  /**
   * Connect to the IRC server
   */
  void start();
38 39 40 41
  /**
   * Called when the connection to the server cannot be established
   */
  void on_connection_failed(const std::string& reason) override final;
louiz’'s avatar
louiz’ committed
42 43 44
  /**
   * Called when successfully connected to the server
   */
louiz’'s avatar
louiz’ committed
45
  void on_connected() override final;
46 47 48
  /**
   * Close the connection, remove us from the poller
   */
49
  void on_connection_close(const std::string& error) override final;
50 51 52 53
  /**
   * Parse the data we have received so far and try to get one or more
   * complete messages from it.
   */
54
  void parse_in_buffer(const size_t) override final;
55 56 57 58
  /**
   * Return the channel with this name, create it if it does not yet exist
   */
  IrcChannel* get_channel(const std::string& name);
louiz’'s avatar
louiz’ committed
59 60 61 62
  /**
   * Returns true if the channel is joined
   */
  bool is_channel_joined(const std::string& name);
louiz’'s avatar
louiz’ committed
63 64 65 66
  /**
   * Return our own nick
   */
  std::string get_own_nick() const;
louiz’'s avatar
louiz’ committed
67 68 69 70 71 72
  /**
   * Serialize the given message into a line, and send that into the socket
   * (actually, into our out_buf and signal the poller that we want to wach
   * for send events to be ready)
   */
  void send_message(IrcMessage&& message);
louiz’'s avatar
louiz’ committed
73
  void send_raw(const std::string& txt);
74 75 76
  /**
   * Send the PONG irc command
   */
louiz’'s avatar
louiz’ committed
77
  void send_pong_command(const IrcMessage& message);
78 79 80 81 82
  /**
   * Do nothing when we receive a PONG command (but also do not log that no
   * handler exist)
   */
  void on_pong(const IrcMessage& message);
83
  void send_ping_command();
louiz’'s avatar
louiz’ committed
84 85 86 87 88 89 90 91
  /**
   * Send the USER irc command
   */
  void send_user_command(const std::string& username, const std::string& realname);
  /**
   * Send the NICK irc command
   */
  void send_nick_command(const std::string& username);
92
  void send_pass_command(const std::string& password);
louiz’'s avatar
louiz’ committed
93
  void send_webirc_command(const std::string& password, const std::string& user_ip);
louiz’'s avatar
louiz’ committed
94
  /**
louiz’'s avatar
louiz’ committed
95
   * Send the JOIN irc command.
louiz’'s avatar
louiz’ committed
96
   */
97
  void send_join_command(const std::string& chan_name, const std::string& password);
louiz’'s avatar
louiz’ committed
98 99 100 101 102
  /**
   * Send a PRIVMSG command for a channel
   * Return true if the message was actually sent
   */
  bool send_channel_message(const std::string& chan_name, const std::string& body);
103 104 105
  /**
   * Send a PRIVMSG command for an user
   */
louiz’'s avatar
louiz’ committed
106
  void send_private_message(const std::string& username, const std::string& body, const std::string& type);
107 108 109 110
  /**
   * Send the PART irc command
   */
  void send_part_command(const std::string& chan_name, const std::string& status_message);
111 112 113 114
  /**
   * Send the MODE irc command
   */
  void send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments);
115 116 117 118
  /**
   * Send the KICK irc command
   */
  void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason);
119 120 121 122
  /**
   * Send the LIST irc command
   */
  void send_list_command();
123
  void send_topic_command(const std::string& chan_name, const std::string& topic);
louiz’'s avatar
louiz’ committed
124 125 126
  /**
   * Send the QUIT irc command
   */
127
  void send_quit_command(const std::string& reason);
128 129 130 131 132 133
  /**
   * Send a message to the gateway user, not generated by the IRC server,
   * but that might be useful because we want to be verbose (for example we
   * might want to notify the user about the connexion state)
   */
  void send_gateway_message(const std::string& message, const std::string& from="");
134 135 136 137
  /**
   * Forward the server message received from IRC to the XMPP component
   */
  void forward_server_message(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
138 139 140 141 142
  /**
   * When receiving the isupport informations.  See
   * http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
   */
  void on_isupport_message(const IrcMessage& message);
143 144 145 146 147 148 149 150 151 152 153 154
  /**
   * Just empty the motd we kept as a string
   */
  void empty_motd(const IrcMessage& message);
  /**
   * Send the MOTD string as one single "big" message
   */
  void send_motd(const IrcMessage& message);
  /**
   * Append this line to the MOTD
   */
  void on_motd_line(const IrcMessage& message);
155 156 157 158 159
  /**
   * Forward the join of an other user into an IRC channel, and save the
   * IrcUsers in the IrcChannel
   */
  void set_and_forward_user_list(const IrcMessage& message);
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
  /**
   * Signal the start of the LIST response. The RFC says its obsolete and
   * “not used”, but I we receive it on some servers, so just ignore it.
   */
  void on_rpl_liststart(const IrcMessage& message);
  /**
   * A single LIST response line (one channel)
   *
   * The command is handled in a wait_irc callback. This general handler is
   * empty and just used to avoid sending a message stanza for each received
   * channel.
   */
  void on_rpl_list(const IrcMessage& message);
  /**
   * Signal the end of the LIST response, ignore.
   */
  void on_rpl_listend(const IrcMessage& message);
177 178 179 180
  /**
   * Remember our nick and host, when we are joined to the channel. The list
   * of user comes after so we do not send the self-presence over XMPP yet.
   */
181
  void on_channel_join(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
182 183 184 185
  /**
   * When a channel message is received
   */
  void on_channel_message(const IrcMessage& message);
186 187 188 189
  /**
   * A notice is received
   */
  void on_notice(const IrcMessage& message);
190 191 192 193 194 195 196 197 198
  /**
   * Save the topic in the IrcChannel
   */
  void on_topic_received(const IrcMessage& message);
  /**
   * The channel has been completely joined (self presence, topic, all names
   * received etc), send the self presence and topic to the XMPP user.
   */
  void on_channel_completely_joined(const IrcMessage& message);
199 200 201 202
  /**
   * We tried to set an invalid nickname
   */
  void on_erroneous_nickname(const IrcMessage& message);
203 204 205 206 207
  /**
   * When the IRC servers denies our nickname because of a conflict.  Send a
   * presence conflict from all channels, because the name is server-wide.
   */
  void on_nickname_conflict(const IrcMessage& message);
208 209 210 211
  /**
   * Idem, but for when the user changes their nickname too quickly
   */
  void on_nickname_change_too_fast(const IrcMessage& message);
212 213 214 215
  /**
   * Handles most errors from the server by just forwarding the message to the user.
   */
  void on_generic_error(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
216 217 218 219
  /**
   * When a message 001 is received, join the rooms we wanted to join, and set our actual nickname
   */
  void on_welcome_message(const IrcMessage& message);
220
  void on_part(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
221
  void on_error(const IrcMessage& message);
222
  void on_nick(const IrcMessage& message);
223
  void on_kick(const IrcMessage& message);
224
  void on_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
225
  /**
226 227 228
   * A mode towards our own user is received (note, that is different from a
   * channel mode towards or own nick, see
   * http://tools.ietf.org/html/rfc2812#section-3.1.5 VS #section-3.2.3)
louiz’'s avatar
louiz’ committed
229
   */
230
  void on_user_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
231
  /**
232 233
   * A mode towards a channel. Note that this can change the mode of the
   * channel itself or an IrcUser in it.
louiz’'s avatar
louiz’ committed
234
   */
235
  void on_channel_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
236
  void on_quit(const IrcMessage& message);
237
  void on_unknown_message(const IrcMessage& message);
238 239 240 241
  /**
   * Return the number of joined channels
   */
  size_t number_of_joined_channels() const;
louiz’'s avatar
louiz’ committed
242 243 244 245
  /**
   * Get a reference to the unique dummy channel
   */
  DummyIrcChannel& get_dummy_channel();
246 247 248 249 250
  /**
   * Leave the dummy channel: forward a message to the user to indicate that
   * he left it, and mark it as not joined.
   */
  void leave_dummy_channel(const std::string& exit_message);
louiz’'s avatar
louiz’ committed
251 252 253 254

  const std::string& get_hostname() const { return this->hostname; }
  std::string get_nick() const { return this->current_nick; }
  bool is_welcomed() const { return this->welcomed; }
255

louiz’'s avatar
louiz’ committed
256 257
  const Resolver& get_resolver() const;

258
private:
259 260 261 262
  /**
   * The hostname of the server we are connected to.
   */
  const std::string hostname;
louiz’'s avatar
louiz’ committed
263 264 265 266 267
  /**
   * The hostname of the user.  This is used in the USER and the WEBIRC
   * commands, but only the one in WEBIRC will be used by the IRC server.
   */
  const std::string user_hostname;
268
  /**
269
   * The username used in the USER irc command
270
   */
271 272 273 274 275
  std::string username;
  /**
   * The realname used in the USER irc command
   */
  std::string realname;
louiz’'s avatar
louiz’ committed
276 277 278 279
  /**
   * Our current nickname on the server
   */
  std::string current_nick;
280 281 282 283 284 285 286 287
  /**
   * Raw pointer because the bridge owns us.
   */
  Bridge* bridge;
  /**
   * The list of joined channels, indexed by name
   */
  std::unordered_map<std::string, std::unique_ptr<IrcChannel>> channels;
louiz’'s avatar
louiz’ committed
288 289 290 291 292
  /**
   * A single channel with a iid of the form "hostname" (normal channel have
   * an iid of the form "chan%hostname".
   */
  DummyIrcChannel dummy_channel;
louiz’'s avatar
louiz’ committed
293
  /**
294 295 296 297 298
   * A list of chan we want to join (tuples with the channel name and the
   * password, if any), but we need a response 001 from the server before
   * sending the actual JOIN commands. So we just keep the channel names in
   * a list, and send the JOIN commands for each of them whenever the
   * WELCOME message is received.
louiz’'s avatar
louiz’ committed
299
   */
300
  std::vector<std::tuple<std::string, std::string>> channels_to_join;
louiz’'s avatar
louiz’ committed
301 302 303 304
  /**
   * This flag indicates that the server is completely joined (connection
   * has been established, we are authentified and we have a nick)
   */
louiz’'s avatar
louiz’ committed
305
  bool welcomed;
louiz’'s avatar
louiz’ committed
306 307 308 309 310 311
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.3
   * We store the possible chanmodes in this object.
   * chanmodes[0] contains modes of type A, [1] of type B etc
   */
  std::vector<std::string> chanmodes;
312 313 314 315 316
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
   * section 3.5
   */
  std::set<char> chantypes;
317 318 319 320 321
  /**
   * Each motd line received is appended to this string, which we send when
   * the motd is completely received
   */
  std::string motd;
louiz’'s avatar
louiz’ committed
322 323 324
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14
   * The example given would be transformed into
325
   * modes_to_prefix = {{'&', 'a'}, {'*', 'b'}}
louiz’'s avatar
louiz’ committed
326 327
   */
  std::map<char, char> prefix_to_mode;
328 329 330 331 332
  /**
   * Available user modes, sorted from most significant to least significant
   * (for example 'ahov' is a common order).
   */
  std::vector<char> sorted_user_modes;
louiz’'s avatar
louiz’ committed
333 334 335 336 337 338
  /**
   * A list of ports to which we will try to connect, in reverse. Each port
   * is associated with a boolean telling if we should use TLS or not if the
   * connection succeeds on that port.
   */
  std::stack<std::pair<std::string, bool>> ports_to_try;
339 340 341 342
  /**
   * A set of (lowercase) nicknames to which we sent a private message.
   */
  std::set<std::string> nicks_to_treat_as_private;
louiz’'s avatar
louiz’ committed
343 344 345 346 347
  /**
   * DNS resolver, used to resolve the hostname of the user if we are using
   * the WebIRC protocole.
   */
  Resolver dns_resolver;
louiz’'s avatar
louiz’ committed
348

349 350 351 352 353 354
  IrcClient(const IrcClient&) = delete;
  IrcClient(IrcClient&&) = delete;
  IrcClient& operator=(const IrcClient&) = delete;
  IrcClient& operator=(IrcClient&&) = delete;
};

355 356 357 358 359 360
/**
 * Define a map of functions to be called for each IRC command we can
 * handle.
 */
typedef void (IrcClient::*irc_callback_t)(const IrcMessage&);

361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
static const std::unordered_map<std::string,
                                std::pair<irc_callback_t, std::pair<std::size_t, std::size_t>>> irc_callbacks = {
  {"NOTICE", {&IrcClient::on_notice, {2, 0}}},
  {"002", {&IrcClient::forward_server_message, {2, 0}}},
  {"003", {&IrcClient::forward_server_message, {2, 0}}},
  {"005", {&IrcClient::on_isupport_message, {0, 0}}},
  {"RPL_LISTSTART", {&IrcClient::on_rpl_liststart, {0, 0}}},
  {"321", {&IrcClient::on_rpl_liststart, {0, 0}}},
  {"RPL_LIST", {&IrcClient::on_rpl_list, {0, 0}}},
  {"322", {&IrcClient::on_rpl_list, {0, 0}}},
  {"RPL_LISTEND", {&IrcClient::on_rpl_listend, {0, 0}}},
  {"323", {&IrcClient::on_rpl_listend, {0, 0}}},
  {"RPL_MOTDSTART", {&IrcClient::empty_motd, {0, 0}}},
  {"375", {&IrcClient::empty_motd, {0, 0}}},
  {"RPL_MOTD", {&IrcClient::on_motd_line, {2, 0}}},
  {"372", {&IrcClient::on_motd_line, {2, 0}}},
  {"RPL_MOTDEND", {&IrcClient::send_motd, {0, 0}}},
  {"376", {&IrcClient::send_motd, {0, 0}}},
  {"JOIN", {&IrcClient::on_channel_join, {1, 0}}},
  {"PRIVMSG", {&IrcClient::on_channel_message, {2, 0}}},
  {"353", {&IrcClient::set_and_forward_user_list, {4, 0}}},
  {"332", {&IrcClient::on_topic_received, {2, 0}}},
  {"TOPIC", {&IrcClient::on_topic_received, {2, 0}}},
  {"366", {&IrcClient::on_channel_completely_joined, {2, 0}}},
  {"432", {&IrcClient::on_erroneous_nickname, {2, 0}}},
  {"433", {&IrcClient::on_nickname_conflict, {2, 0}}},
  {"438", {&IrcClient::on_nickname_change_too_fast, {2, 0}}},
  {"001", {&IrcClient::on_welcome_message, {1, 0}}},
  {"PART", {&IrcClient::on_part, {1, 0}}},
  {"ERROR", {&IrcClient::on_error, {1, 0}}},
  {"QUIT", {&IrcClient::on_quit, {0, 0}}},
  {"NICK", {&IrcClient::on_nick, {1, 0}}},
  {"MODE", {&IrcClient::on_mode, {1, 0}}},
  {"PING", {&IrcClient::send_pong_command, {1, 0}}},
  {"PONG", {&IrcClient::on_pong, {0, 0}}},
  {"KICK", {&IrcClient::on_kick, {3, 0}}},
397

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
  {"401", {&IrcClient::on_generic_error, {2, 0}}},
  {"402", {&IrcClient::on_generic_error, {2, 0}}},
  {"403", {&IrcClient::on_generic_error, {2, 0}}},
  {"404", {&IrcClient::on_generic_error, {2, 0}}},
  {"405", {&IrcClient::on_generic_error, {2, 0}}},
  {"406", {&IrcClient::on_generic_error, {2, 0}}},
  {"407", {&IrcClient::on_generic_error, {2, 0}}},
  {"408", {&IrcClient::on_generic_error, {2, 0}}},
  {"409", {&IrcClient::on_generic_error, {2, 0}}},
  {"410", {&IrcClient::on_generic_error, {2, 0}}},
  {"411", {&IrcClient::on_generic_error, {2, 0}}},
  {"412", {&IrcClient::on_generic_error, {2, 0}}},
  {"414", {&IrcClient::on_generic_error, {2, 0}}},
  {"421", {&IrcClient::on_generic_error, {2, 0}}},
  {"422", {&IrcClient::on_generic_error, {2, 0}}},
  {"423", {&IrcClient::on_generic_error, {2, 0}}},
  {"424", {&IrcClient::on_generic_error, {2, 0}}},
  {"431", {&IrcClient::on_generic_error, {2, 0}}},
  {"436", {&IrcClient::on_generic_error, {2, 0}}},
  {"441", {&IrcClient::on_generic_error, {2, 0}}},
  {"442", {&IrcClient::on_generic_error, {2, 0}}},
  {"443", {&IrcClient::on_generic_error, {2, 0}}},
  {"444", {&IrcClient::on_generic_error, {2, 0}}},
  {"446", {&IrcClient::on_generic_error, {2, 0}}},
  {"451", {&IrcClient::on_generic_error, {2, 0}}},
  {"461", {&IrcClient::on_generic_error, {2, 0}}},
  {"462", {&IrcClient::on_generic_error, {2, 0}}},
  {"463", {&IrcClient::on_generic_error, {2, 0}}},
  {"464", {&IrcClient::on_generic_error, {2, 0}}},
  {"465", {&IrcClient::on_generic_error, {2, 0}}},
  {"467", {&IrcClient::on_generic_error, {2, 0}}},
  {"470", {&IrcClient::on_generic_error, {2, 0}}},
  {"471", {&IrcClient::on_generic_error, {2, 0}}},
  {"472", {&IrcClient::on_generic_error, {2, 0}}},
  {"473", {&IrcClient::on_generic_error, {2, 0}}},
  {"474", {&IrcClient::on_generic_error, {2, 0}}},
  {"475", {&IrcClient::on_generic_error, {2, 0}}},
  {"476", {&IrcClient::on_generic_error, {2, 0}}},
  {"477", {&IrcClient::on_generic_error, {2, 0}}},
  {"481", {&IrcClient::on_generic_error, {2, 0}}},
  {"482", {&IrcClient::on_generic_error, {2, 0}}},
  {"483", {&IrcClient::on_generic_error, {2, 0}}},
  {"484", {&IrcClient::on_generic_error, {2, 0}}},
  {"485", {&IrcClient::on_generic_error, {2, 0}}},
  {"487", {&IrcClient::on_generic_error, {2, 0}}},
  {"491", {&IrcClient::on_generic_error, {2, 0}}},
  {"501", {&IrcClient::on_generic_error, {2, 0}}},
  {"502", {&IrcClient::on_generic_error, {2, 0}}},
446 447
};

448
#endif // IRC_CLIENT_INCLUDED