1 /*****************************************************************************
2 * PokerTH - The open source texas holdem engine *
3 * Copyright (C) 2006-2012 Felix Hammer, Florian Thauer, Lothar May *
4 * *
5 * This program is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU Affero General Public License as *
7 * published by the Free Software Foundation, either version 3 of the *
8 * License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU Affero General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Affero General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 * *
18 * *
19 * Additional permission under GNU AGPL version 3 section 7 *
20 * *
21 * If you modify this program, or any covered work, by linking or *
22 * combining it with the OpenSSL project's OpenSSL library (or a *
23 * modified version of that library), containing parts covered by the *
24 * terms of the OpenSSL or SSLeay licenses, the authors of PokerTH *
25 * (Felix Hammer, Florian Thauer, Lothar May) grant you additional *
26 * permission to convey the resulting work. *
27 * Corresponding Source for a non-source form of such a combination *
28 * shall include the source code for the parts of OpenSSL used as well *
29 * as that of the covered work. *
30 *****************************************************************************/
31
32 #include <net/socket_helper.h>
33 #include <net/ircthread.h>
34 #include <net/socket_msg.h>
35 #ifdef _WIN32
36 #include <libircclient/libircclient.h>
37 #else
38 #include <libircclient.h>
39 #endif
40
41 // We need to do the following to handle different versions of libircclient.
42 // Sadly, libircclient doesn't have actual definitions for its versions in its headers.
43 // However, we can use a definition that appeared in the same version we need
44 // to check for. Hacky, but hey, it works.
45 #ifdef LIBIRC_OPTION_SSL_NO_VERIFY
46 #ifdef _WIN32
47 #include <libircclient/libirc_rfcnumeric.h>
48 #else
49 #include <libirc_rfcnumeric.h>
50 #endif
51 #endif
52
53 #include <boost/algorithm/string/predicate.hpp>
54 #include <queue>
55 #include <sstream>
56 #include <cctype>
57 #include <cstring>
58
59 using namespace std;
60
61 #define IRC_WAIT_TERMINATION_MSEC 500
62 #define IRC_RECV_TIMEOUT_MSEC 50
63 #define IRC_MAX_RENAME_TRIES 5
64 #define IRC_MIN_RECONNECT_INTERVAL_SEC 60
65 #define IRC_SEND_LIMIT_BYTES 1024
66
67 #define IRC_RENAME_ATTACH "|Lobby"
68 #define IRC_MAX_NICK_LEN 16
69
70
71 struct IrcContext {
IrcContextIrcContext72 IrcContext(IrcThread &t) : ircThread(t), session(NULL), serverPort(0), useIPv6(false), renameTries(0), sendingBlocked(false), sendCounter(0) {}
73 IrcThread &ircThread;
74 irc_session_t *session;
75 string serverAddress;
76 unsigned serverPort;
77 bool useIPv6;
78 string origNick;
79 string nick;
80 string channel;
81 string channelPassword;
82 unsigned renameTries;
83 bool sendingBlocked;
84 size_t sendCounter;
85 queue<string> sendQueue;
86 };
87
irc_auto_rename_nick(irc_session_t * session)88 void irc_auto_rename_nick(irc_session_t *session)
89 {
90 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
91
92 if (context->renameTries <= IRC_MAX_RENAME_TRIES) { // Limit number of rename tries.
93 // Automatically rename the nick on collision.
94 // First: Try to append the string "Lobby".
95 if (context->nick.find(IRC_RENAME_ATTACH) == string::npos) {
96 if (context->nick.length() + (sizeof(IRC_RENAME_ATTACH)) > IRC_MAX_NICK_LEN)
97 context->nick = context->nick.substr(0, IRC_MAX_NICK_LEN - (sizeof(IRC_RENAME_ATTACH)));
98 context->nick = context->nick + IRC_RENAME_ATTACH;
99 } else {
100 // This didn't work out. Append a number or increment it.
101 string::reverse_iterator end = context->nick.rbegin();
102 if (!context->nick.empty() && isdigit(*end)) {
103 if (*end != '9')
104 *end = (*end) + 1;
105 else
106 context->nick = context->nick + "0";
107 } else
108 context->nick = context->nick + "1";
109 }
110 irc_cmd_nick(session, context->nick.c_str());
111 context->renameTries++;
112 } else
113 irc_cmd_quit(session, NULL);
114 }
115
irc_notify_player_list(irc_session_t * session,const char * players)116 void irc_notify_player_list(irc_session_t *session, const char *players)
117 {
118 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
119
120 istringstream input(players);
121 string name;
122 input >> name;
123 while (!input.fail() && !input.eof()) {
124 context->ircThread.GetCallback().SignalIrcPlayerJoined(name);
125 input >> name;
126 }
127 }
128
irc_handle_server_error(irc_session_t * session,unsigned irc_error_code)129 void irc_handle_server_error(irc_session_t *session, unsigned irc_error_code)
130 {
131 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
132
133 context->ircThread.GetCallback().SignalIrcServerError(irc_error_code);
134 }
135
136 void
irc_event_connect(irc_session_t * session,const char *,const char * origin,const char **,unsigned)137 irc_event_connect(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char ** /*params*/, unsigned /*count*/)
138 {
139 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
140
141 context->ircThread.GetCallback().SignalIrcConnect(origin);
142 irc_cmd_join(session, context->channel.c_str(), context->channelPassword.c_str());
143 }
144
145 void
irc_event_join(irc_session_t * session,const char *,const char * origin,const char **,unsigned)146 irc_event_join(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char ** /*params*/, unsigned /*count*/)
147 {
148 // someone joined the channel.
149 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
150
151 if (context->nick == origin)
152 context->ircThread.GetCallback().SignalIrcSelfJoined(context->nick, context->channel);
153 else
154 context->ircThread.GetCallback().SignalIrcPlayerJoined(origin);
155 }
156
157 void
irc_event_nick(irc_session_t * session,const char *,const char * origin,const char ** params,unsigned count)158 irc_event_nick(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char **params, unsigned count)
159 {
160 // someone changed his/her nick
161 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
162
163 if (count && context->nick != params[0]) { // only act if this was not an auto-rename
164 if (context->nick == origin)
165 context->nick = params[0];
166 context->ircThread.GetCallback().SignalIrcPlayerChanged(origin, params[0]);
167 }
168 }
169
170 void
irc_event_kick(irc_session_t * session,const char *,const char * origin,const char ** params,unsigned count)171 irc_event_kick(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char **params, unsigned count)
172 {
173 // someone got kicked
174 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
175
176 string byWhom(origin);
177 string who;
178 string reason;
179 if (count >= 2)
180 who = params[1];
181 if (count >= 3)
182 reason = params[2];
183 context->ircThread.GetCallback().SignalIrcPlayerKicked(who, byWhom, reason);
184 if (!who.empty())
185 context->ircThread.GetCallback().SignalIrcPlayerLeft(who);
186 }
187
188 void
irc_event_leave(irc_session_t * session,const char *,const char * origin,const char **,unsigned)189 irc_event_leave(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char ** /*params*/, unsigned /*count*/)
190 {
191 // someone left the channel.
192 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
193
194 context->ircThread.GetCallback().SignalIrcPlayerLeft(origin);
195 }
196
197 void
irc_event_channel(irc_session_t * session,const char *,const char * origin,const char ** params,unsigned count)198 irc_event_channel(irc_session_t *session, const char * /*irc_event*/, const char *origin, const char **params, unsigned count)
199 {
200 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
201
202 if (count >= 2 && boost::algorithm::iequals(context->channel, params[0])) { // check whether this message is for our channel
203 // Signal the message (if any) to GUI.
204 if (*params[1] != 0)
205 context->ircThread.GetCallback().SignalIrcChatMsg(origin, params[1]);
206 }
207 }
208
209 void
irc_event_unknown(irc_session_t * session,const char * irc_event,const char *,const char **,unsigned)210 irc_event_unknown(irc_session_t *session, const char * irc_event, const char * /*origin*/, const char ** /*params*/, unsigned /*count*/)
211 {
212 IrcContext *context = static_cast<IrcContext *>(irc_get_ctx(session));
213
214 if (boost::algorithm::iequals(irc_event, "PONG")) {
215 context->sendingBlocked = false;
216 context->ircThread.FlushQueue();
217 }
218 }
219
220 void
irc_event_numeric(irc_session_t * session,unsigned irc_event,const char *,const char ** params,unsigned count)221 irc_event_numeric(irc_session_t * session, unsigned irc_event, const char * /*origin*/, const char **params, unsigned count)
222 {
223 switch (irc_event) {
224 case LIBIRC_RFC_ERR_NICKNAMEINUSE :
225 case LIBIRC_RFC_ERR_NICKCOLLISION :
226 irc_auto_rename_nick(session);
227 break;
228 case LIBIRC_RFC_RPL_TOPIC :
229 break;
230 case LIBIRC_RFC_RPL_NAMREPLY :
231 if (count >= 4)
232 irc_notify_player_list(session, params[3]);
233 break;
234 case LIBIRC_RFC_ERR_NOSUCHNICK :
235 case LIBIRC_RFC_ERR_NOSUCHCHANNEL :
236 case LIBIRC_RFC_ERR_CANNOTSENDTOCHAN :
237 case LIBIRC_RFC_ERR_TOOMANYCHANNELS :
238 case LIBIRC_RFC_ERR_WASNOSUCHNICK :
239 case LIBIRC_RFC_ERR_TOOMANYTARGETS :
240 case LIBIRC_RFC_ERR_NOSUCHSERVICE :
241 case LIBIRC_RFC_ERR_NOORIGIN :
242 case LIBIRC_RFC_ERR_NORECIPIENT :
243 case LIBIRC_RFC_ERR_NOTEXTTOSEND :
244 case LIBIRC_RFC_ERR_NOTOPLEVEL :
245 case LIBIRC_RFC_ERR_WILDTOPLEVEL :
246 case LIBIRC_RFC_ERR_BADMASK :
247 case LIBIRC_RFC_ERR_UNKNOWNCOMMAND :
248 case LIBIRC_RFC_ERR_NOMOTD :
249 case LIBIRC_RFC_ERR_NOADMININFO :
250 case LIBIRC_RFC_ERR_FILEERROR :
251 case LIBIRC_RFC_ERR_NONICKNAMEGIVEN :
252 case LIBIRC_RFC_ERR_ERRONEUSNICKNAME :
253 case LIBIRC_RFC_ERR_UNAVAILRESOURCE :
254 case LIBIRC_RFC_ERR_USERNOTINCHANNEL :
255 case LIBIRC_RFC_ERR_NOTONCHANNEL :
256 case LIBIRC_RFC_ERR_USERONCHANNEL :
257 case LIBIRC_RFC_ERR_NOLOGIN :
258 case LIBIRC_RFC_ERR_SUMMONDISABLED :
259 case LIBIRC_RFC_ERR_USERSDISABLED :
260 case LIBIRC_RFC_ERR_NOTREGISTERED :
261 case LIBIRC_RFC_ERR_NEEDMOREPARAMS :
262 case LIBIRC_RFC_ERR_ALREADYREGISTRED :
263 case LIBIRC_RFC_ERR_NOPERMFORHOST :
264 case LIBIRC_RFC_ERR_PASSWDMISMATCH :
265 case LIBIRC_RFC_ERR_YOUREBANNEDCREEP :
266 case LIBIRC_RFC_ERR_YOUWILLBEBANNED :
267 case LIBIRC_RFC_ERR_KEYSET :
268 case LIBIRC_RFC_ERR_CHANNELISFULL :
269 case LIBIRC_RFC_ERR_UNKNOWNMODE :
270 case LIBIRC_RFC_ERR_INVITEONLYCHAN :
271 case LIBIRC_RFC_ERR_BANNEDFROMCHAN :
272 case LIBIRC_RFC_ERR_BADCHANNELKEY :
273 case LIBIRC_RFC_ERR_BADCHANMASK :
274 case LIBIRC_RFC_ERR_NOCHANMODES :
275 case LIBIRC_RFC_ERR_BANLISTFULL :
276 case LIBIRC_RFC_ERR_NOPRIVILEGES :
277 case LIBIRC_RFC_ERR_CHANOPRIVSNEEDED :
278 case LIBIRC_RFC_ERR_CANTKILLSERVER :
279 case LIBIRC_RFC_ERR_RESTRICTED :
280 case LIBIRC_RFC_ERR_UNIQOPPRIVSNEEDED :
281 case LIBIRC_RFC_ERR_NOOPERHOST :
282 case LIBIRC_RFC_ERR_UMODEUNKNOWNFLAG :
283 case LIBIRC_RFC_ERR_USERSDONTMATCH :
284 irc_handle_server_error(session, irc_event);
285 break;
286 }
287 }
288
IrcThread(const IrcThread & other)289 IrcThread::IrcThread(const IrcThread &other)
290 : Thread(),
291 m_terminationTimer(boost::posix_time::time_duration(0, 0, 0), boost::timers::portable::microsec_timer::manual_start),
292 m_lastConnectTimer(boost::posix_time::time_duration(0, 0, 0), boost::timers::portable::microsec_timer::manual_start)
293 {
294 m_callback = other.m_callback;
295 m_context.reset(new IrcContext(*this));
296
297 IrcContext &context = GetContext();
298 const IrcContext &otherContext = other.GetContext();
299
300 context.serverAddress = otherContext.serverAddress;
301 context.serverPort = otherContext.serverPort;
302 context.useIPv6 = otherContext.useIPv6;
303 context.origNick = otherContext.origNick;
304 context.nick = otherContext.origNick; // do not use changed nick.
305 context.channel = otherContext.channel;
306 context.channelPassword = otherContext.channelPassword;
307 }
308
IrcThread(IrcCallback * callback)309 IrcThread::IrcThread(IrcCallback *callback)
310 : m_callback(callback),
311 m_terminationTimer(boost::posix_time::time_duration(0, 0, 0), boost::timers::portable::microsec_timer::manual_start),
312 m_lastConnectTimer(boost::posix_time::time_duration(0, 0, 0), boost::timers::portable::microsec_timer::manual_start)
313 {
314 assert(callback);
315 m_context.reset(new IrcContext(*this));
316 }
317
~IrcThread()318 IrcThread::~IrcThread()
319 {
320 IrcContext &context = GetContext();
321 if (context.session)
322 irc_destroy_session(context.session);
323 }
324
325 void
Init(const std::string & serverAddress,unsigned serverPort,bool ipv6,const std::string & nick,const std::string & channel,const std::string & channelPassword)326 IrcThread::Init(const std::string &serverAddress, unsigned serverPort, bool ipv6, const std::string &nick, const std::string &channel, const std::string &channelPassword)
327 {
328 if (IsRunning() || serverAddress.empty() || nick.empty() || channel.empty())
329 return; // TODO: throw exception
330
331 IrcContext &context = GetContext();
332
333 context.serverAddress = serverAddress;
334 context.serverPort = serverPort;
335 context.useIPv6 = ipv6;
336 context.origNick = nick;
337 context.nick = nick;
338 context.channel = channel;
339 context.channelPassword = channelPassword;
340 }
341
342 void
SendChatMessage(const std::string & msg)343 IrcThread::SendChatMessage(const std::string &msg)
344 {
345 IrcContext &context = GetContext();
346 if (!context.sendingBlocked) {
347 irc_cmd_msg(context.session, context.channel.c_str(), msg.c_str());
348 context.sendCounter += msg.size();
349
350 if (context.sendCounter >= IRC_SEND_LIMIT_BYTES) {
351 context.sendingBlocked = true;
352 context.sendCounter = 0;
353 SendPing();
354 }
355 } else
356 context.sendQueue.push(msg);
357 }
358
359 void
SendPing()360 IrcThread::SendPing()
361 {
362 IrcContext &context = GetContext();
363 irc_send_raw(context.session, "PING %s", context.serverAddress.c_str());
364 }
365
366 void
SignalTermination()367 IrcThread::SignalTermination()
368 {
369 Thread::SignalTermination();
370 irc_cmd_quit(GetContext().session, NULL);
371 }
372
373 void
Main()374 IrcThread::Main()
375 {
376 do {
377 if (IrcInit())
378 IrcMain(); // Will loop until terminated.
379 } while (!ShouldTerminate() && GetContext().session && !irc_is_connected(GetContext().session) && m_lastConnectTimer.elapsed().total_seconds() > IRC_MIN_RECONNECT_INTERVAL_SEC);
380 }
381
382 bool
IrcInit()383 IrcThread::IrcInit()
384 {
385 bool retVal = false;
386
387 IrcContext &context = GetContext();
388 if (context.session) {
389 irc_destroy_session(context.session);
390 context.session = NULL;
391 }
392 // Initialize libirc stuff.
393 irc_callbacks_t callbacks;
394 memset (&callbacks, 0, sizeof(callbacks));
395
396 callbacks.event_connect = irc_event_connect;
397 callbacks.event_join = irc_event_join;
398 callbacks.event_nick = irc_event_nick;
399 callbacks.event_quit = irc_event_leave;
400 callbacks.event_part = irc_event_leave;
401 //callbacks.event_mode
402 //callbacks.event_topic
403 callbacks.event_kick = irc_event_kick;
404 callbacks.event_channel = irc_event_channel;
405 //callbacks.event_privmsg
406 //callbacks.event_notice
407 //callbacks.event_invite
408 //callbacks.event_umode
409 //callbacks.event_ctcp_rep
410 //callbacks.event_ctcp_action
411 callbacks.event_unknown = irc_event_unknown;
412 callbacks.event_numeric = irc_event_numeric;
413
414 //callbacks.event_dcc_chat_req
415 //callbacks.event_dcc_send_req
416
417 context.session = irc_create_session(&callbacks);
418
419 if (context.session) {
420 irc_set_ctx(context.session, &context);
421 // We want nicknames only, strip them from nick!host.
422 irc_option_set(context.session, LIBIRC_OPTION_STRIPNICKS);
423 retVal = true;
424 }
425 return retVal;
426 }
427
428 void
IrcMain()429 IrcThread::IrcMain()
430 {
431 m_lastConnectTimer.restart();
432 IrcContext &context = GetContext();
433
434 irc_session_t *s = context.session;
435
436 bool connected = false;
437 if (context.useIPv6)
438 connected = irc_connect6(s, context.serverAddress.c_str(), context.serverPort, 0, context.nick.c_str(), 0, 0) == 0;
439 else
440 connected = irc_connect(s, context.serverAddress.c_str(), context.serverPort, 0, context.nick.c_str(), 0, 0) == 0;
441
442 if (!connected)
443 HandleIrcError(irc_errno(s));
444 else {
445 // Main loop.
446 while (irc_is_connected(s)) {
447 // Handle thread termination - gracefully.
448 if (!m_terminationTimer.is_running()) {
449 if (ShouldTerminate())
450 m_terminationTimer.start();
451 } else {
452 if (m_terminationTimer.elapsed().total_milliseconds() > IRC_WAIT_TERMINATION_MSEC)
453 break;
454 }
455
456 struct timeval timeout;
457 fd_set readSet, writeSet;
458 int maxfd = 0;
459
460
461 FD_ZERO(&readSet);
462 FD_ZERO(&writeSet);
463 timeout.tv_sec = 0;
464 timeout.tv_usec = IRC_RECV_TIMEOUT_MSEC * 1000;
465
466 irc_add_select_descriptors(s, &readSet, &writeSet, &maxfd);
467
468 int selectResult = select(maxfd + 1, &readSet, &writeSet, 0, &timeout);
469 if (selectResult == -1) {
470 GetCallback().SignalIrcError(ERR_IRC_SELECT_FAILED);
471 break;
472 }
473
474 if (irc_process_select_descriptors(s, &readSet, &writeSet) != 0) {
475 int errorCode = irc_errno(s);
476 if (errorCode) {
477 HandleIrcError(errorCode);
478 break;
479 }
480 }
481 Msleep(10); // paranoia
482 }
483 }
484 }
485
486 void
FlushQueue()487 IrcThread::FlushQueue()
488 {
489 IrcContext &context = GetContext();
490
491 while (!context.sendingBlocked && !context.sendQueue.empty()) {
492 string msg(context.sendQueue.front());
493 context.sendQueue.pop();
494 SendChatMessage(msg);
495 }
496 }
497
498 void
HandleIrcError(int errorCode)499 IrcThread::HandleIrcError(int errorCode)
500 {
501 int internalErrorCode = ERR_IRC_INTERNAL;
502 switch(errorCode) {
503 case LIBIRC_ERR_RESOLV:
504 case LIBIRC_ERR_SOCKET:
505 case LIBIRC_ERR_CONNECT:
506 internalErrorCode = ERR_IRC_CONNECT_FAILED;
507 break;
508 case LIBIRC_ERR_INVAL:
509 case LIBIRC_ERR_STATE:
510 internalErrorCode = ERR_IRC_INVALID_PARAM;
511 break;
512 case LIBIRC_ERR_CLOSED:
513 case LIBIRC_ERR_TERMINATED:
514 internalErrorCode = ERR_IRC_TERMINATED;
515 break;
516 case LIBIRC_ERR_READ:
517 internalErrorCode = ERR_IRC_RECV_FAILED;
518 break;
519 case LIBIRC_ERR_WRITE:
520 internalErrorCode = ERR_IRC_SEND_FAILED;
521 break;
522 case LIBIRC_ERR_TIMEOUT:
523 internalErrorCode = ERR_IRC_TIMEOUT;
524 break;
525 }
526 GetCallback().SignalIrcError(internalErrorCode);
527 }
528
529 const IrcContext &
GetContext() const530 IrcThread::GetContext() const
531 {
532 assert(m_context.get());
533 return *m_context;
534 }
535
536 IrcContext &
GetContext()537 IrcThread::GetContext()
538 {
539 assert(m_context.get());
540 return *m_context;
541 }
542
543 IrcCallback &
GetCallback()544 IrcThread::GetCallback()
545 {
546 assert(m_callback);
547 return *m_callback;
548 }
549
550