irc_client.hpp 12.5 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>
9

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

18 19
class Bridge;

20 21 22 23
/**
 * Represent one IRC client, i.e. an endpoint connected to a single IRC
 * server, through a TCP socket, receiving and sending commands to it.
 */
24
class IrcClient: public TCPSocketHandler
25 26
{
public:
27
  explicit IrcClient(std::shared_ptr<Poller> poller, const std::string& hostname, const std::string& username, Bridge* bridge);
28
  ~IrcClient();
29 30 31 32
  /**
   * Connect to the IRC server
   */
  void start();
33 34 35 36
  /**
   * 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
37 38 39
  /**
   * Called when successfully connected to the server
   */
louiz’'s avatar
louiz’ committed
40
  void on_connected() override final;
41 42 43
  /**
   * Close the connection, remove us from the poller
   */
louiz’'s avatar
louiz’ committed
44
  void on_connection_close() override final;
45 46 47 48
  /**
   * Parse the data we have received so far and try to get one or more
   * complete messages from it.
   */
49
  void parse_in_buffer(const size_t) override final;
50 51 52 53
  /**
   * 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
54 55 56 57
  /**
   * Returns true if the channel is joined
   */
  bool is_channel_joined(const std::string& name);
louiz’'s avatar
louiz’ committed
58 59 60 61
  /**
   * Return our own nick
   */
  std::string get_own_nick() const;
louiz’'s avatar
louiz’ committed
62 63 64 65 66 67
  /**
   * 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);
68 69 70
  /**
   * Send the PONG irc command
   */
louiz’'s avatar
louiz’ committed
71
  void send_pong_command(const IrcMessage& message);
72
  void send_ping_command();
louiz’'s avatar
louiz’ committed
73 74 75 76 77 78 79 80 81
  /**
   * 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);
  /**
louiz’'s avatar
louiz’ committed
82
   * Send the JOIN irc command.
louiz’'s avatar
louiz’ committed
83 84
   */
  void send_join_command(const std::string& chan_name);
louiz’'s avatar
louiz’ committed
85 86 87 88 89
  /**
   * 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);
louiz’'s avatar
louiz’ committed
90 91 92
  /**
   * Send a PRIVMSG command for an user
   */
louiz’'s avatar
louiz’ committed
93
  void send_private_message(const std::string& username, const std::string& body, const std::string& type);
louiz’'s avatar
louiz’ committed
94 95 96 97
  /**
   * Send the PART irc command
   */
  void send_part_command(const std::string& chan_name, const std::string& status_message);
louiz’'s avatar
louiz’ committed
98 99 100 101
  /**
   * Send the MODE irc command
   */
  void send_mode_command(const std::string& chan_name, const std::vector<std::string>& arguments);
102 103 104 105
  /**
   * Send the KICK irc command
   */
  void send_kick_command(const std::string& chan_name, const std::string& target, const std::string& reason);
106
  void send_topic_command(const std::string& chan_name, const std::string& topic);
louiz’'s avatar
louiz’ committed
107 108 109
  /**
   * Send the QUIT irc command
   */
110
  void send_quit_command(const std::string& reason);
111 112 113 114 115 116
  /**
   * 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="");
117 118 119 120
  /**
   * Forward the server message received from IRC to the XMPP component
   */
  void forward_server_message(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
121 122 123 124 125
  /**
   * 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);
126 127 128 129 130 131 132 133 134 135 136 137
  /**
   * 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);
138 139 140 141 142 143 144 145 146
  /**
   * 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);
  /**
   * 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.
   */
louiz’'s avatar
louiz’ committed
147
  void on_channel_join(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
148 149 150 151
  /**
   * When a channel message is received
   */
  void on_channel_message(const IrcMessage& message);
152 153 154 155
  /**
   * A notice is received
   */
  void on_notice(const IrcMessage& message);
156 157 158 159 160 161 162 163 164
  /**
   * 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);
165 166 167 168
  /**
   * We tried to set an invalid nickname
   */
  void on_erroneous_nickname(const IrcMessage& message);
169 170 171 172 173
  /**
   * 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);
174 175 176 177
  /**
   * 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
178 179 180 181
  /**
   * 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);
louiz’'s avatar
louiz’ committed
182
  void on_part(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
183
  void on_error(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
184
  void on_nick(const IrcMessage& message);
185
  void on_kick(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
186
  void on_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
187
  /**
louiz’'s avatar
louiz’ committed
188 189 190
   * 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
191
   */
louiz’'s avatar
louiz’ committed
192
  void on_user_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
193
  /**
louiz’'s avatar
louiz’ committed
194 195
   * 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
196
   */
louiz’'s avatar
louiz’ committed
197
  void on_channel_mode(const IrcMessage& message);
louiz’'s avatar
louiz’ committed
198
  void on_quit(const IrcMessage& message);
199 200 201 202
  /**
   * Return the number of joined channels
   */
  size_t number_of_joined_channels() const;
louiz’'s avatar
louiz’ committed
203 204 205 206
  /**
   * Get a reference to the unique dummy channel
   */
  DummyIrcChannel& get_dummy_channel();
louiz’'s avatar
louiz’ committed
207 208 209 210 211
  /**
   * 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
212 213 214 215

  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; }
216 217

private:
218 219 220 221 222 223 224 225
  /**
   * The hostname of the server we are connected to.
   */
  const std::string hostname;
  /**
   * The user name used in the USER irc command
   */
  const std::string username;
louiz’'s avatar
louiz’ committed
226 227 228 229
  /**
   * Our current nickname on the server
   */
  std::string current_nick;
230 231 232 233 234 235 236 237
  /**
   * 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
238 239 240 241 242
  /**
   * 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
243 244 245 246 247 248 249
  /**
   * A list of chan we want to join, 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.
   */
  std::vector<std::string> channels_to_join;
louiz’'s avatar
louiz’ committed
250 251 252 253
  /**
   * 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
254
  bool welcomed;
louiz’'s avatar
louiz’ committed
255 256 257 258 259 260
  /**
   * 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;
261 262 263 264 265
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
   * section 3.5
   */
  std::set<char> chantypes;
266 267 268 269 270
  /**
   * 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
271 272 273
  /**
   * See http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt section 3.14
   * The example given would be transformed into
274
   * modes_to_prefix = {{'&', 'a'}, {'*', 'b'}}
louiz’'s avatar
louiz’ committed
275 276
   */
  std::map<char, char> prefix_to_mode;
277 278 279 280 281
  /**
   * 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
282 283 284 285 286 287
  /**
   * 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;
louiz’'s avatar
louiz’ committed
288

289 290 291 292 293 294
  IrcClient(const IrcClient&) = delete;
  IrcClient(IrcClient&&) = delete;
  IrcClient& operator=(const IrcClient&) = delete;
  IrcClient& operator=(IrcClient&&) = delete;
};

295 296 297 298 299 300 301
/**
 * Define a map of functions to be called for each IRC command we can
 * handle.
 */
typedef void (IrcClient::*irc_callback_t)(const IrcMessage&);

static const std::unordered_map<std::string, irc_callback_t> irc_callbacks = {
302
  {"NOTICE", &IrcClient::on_notice},
303 304
  {"002", &IrcClient::forward_server_message},
  {"003", &IrcClient::forward_server_message},
louiz’'s avatar
louiz’ committed
305
  {"005", &IrcClient::on_isupport_message},
306 307 308 309 310 311
  {"RPL_MOTDSTART", &IrcClient::empty_motd},
  {"375", &IrcClient::empty_motd},
  {"RPL_MOTD", &IrcClient::on_motd_line},
  {"372", &IrcClient::on_motd_line},
  {"RPL_MOTDEND", &IrcClient::send_motd},
  {"376", &IrcClient::send_motd},
312 313 314 315
  {"JOIN", &IrcClient::on_channel_join},
  {"PRIVMSG", &IrcClient::on_channel_message},
  {"353", &IrcClient::set_and_forward_user_list},
  {"332", &IrcClient::on_topic_received},
louiz’'s avatar
louiz’ committed
316
  {"TOPIC", &IrcClient::on_topic_received},
317
  {"366", &IrcClient::on_channel_completely_joined},
318
  {"432", &IrcClient::on_erroneous_nickname},
319
  {"433", &IrcClient::on_nickname_conflict},
320 321
  {"001", &IrcClient::on_welcome_message},
  {"PART", &IrcClient::on_part},
louiz’'s avatar
louiz’ committed
322
  {"ERROR", &IrcClient::on_error},
323 324 325 326
  {"QUIT", &IrcClient::on_quit},
  {"NICK", &IrcClient::on_nick},
  {"MODE", &IrcClient::on_mode},
  {"PING", &IrcClient::send_pong_command},
327
  {"KICK", &IrcClient::on_kick},
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376

  {"401", &IrcClient::on_generic_error},
  {"402", &IrcClient::on_generic_error},
  {"403", &IrcClient::on_generic_error},
  {"404", &IrcClient::on_generic_error},
  {"405", &IrcClient::on_generic_error},
  {"406", &IrcClient::on_generic_error},
  {"407", &IrcClient::on_generic_error},
  {"408", &IrcClient::on_generic_error},
  {"409", &IrcClient::on_generic_error},
  {"410", &IrcClient::on_generic_error},
  {"411", &IrcClient::on_generic_error},
  {"412", &IrcClient::on_generic_error},
  {"414", &IrcClient::on_generic_error},
  {"421", &IrcClient::on_generic_error},
  {"422", &IrcClient::on_generic_error},
  {"423", &IrcClient::on_generic_error},
  {"424", &IrcClient::on_generic_error},
  {"431", &IrcClient::on_generic_error},
  {"436", &IrcClient::on_generic_error},
  {"441", &IrcClient::on_generic_error},
  {"442", &IrcClient::on_generic_error},
  {"443", &IrcClient::on_generic_error},
  {"444", &IrcClient::on_generic_error},
  {"446", &IrcClient::on_generic_error},
  {"451", &IrcClient::on_generic_error},
  {"461", &IrcClient::on_generic_error},
  {"462", &IrcClient::on_generic_error},
  {"463", &IrcClient::on_generic_error},
  {"464", &IrcClient::on_generic_error},
  {"465", &IrcClient::on_generic_error},
  {"467", &IrcClient::on_generic_error},
  {"470", &IrcClient::on_generic_error},
  {"471", &IrcClient::on_generic_error},
  {"472", &IrcClient::on_generic_error},
  {"473", &IrcClient::on_generic_error},
  {"474", &IrcClient::on_generic_error},
  {"475", &IrcClient::on_generic_error},
  {"476", &IrcClient::on_generic_error},
  {"477", &IrcClient::on_generic_error},
  {"481", &IrcClient::on_generic_error},
  {"482", &IrcClient::on_generic_error},
  {"483", &IrcClient::on_generic_error},
  {"484", &IrcClient::on_generic_error},
  {"485", &IrcClient::on_generic_error},
  {"487", &IrcClient::on_generic_error},
  {"491", &IrcClient::on_generic_error},
  {"501", &IrcClient::on_generic_error},
  {"502", &IrcClient::on_generic_error},
377 378
};

379
#endif // IRC_CLIENT_INCLUDED