1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 Copyright (C) 2010 COR Entertainment, LLC.
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the 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.
13
14 See the GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 */
21 // cl_irc.c -- irc client
22
23
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include "client.h"
30 #include "qcommon/htable.h"
31
32 #if defined WIN32_VARIANT
33 # include <winsock.h>
34 # include <process.h>
35 typedef SOCKET irc_socket_t;
36 #else
37 # if defined HAVE_UNISTD_H
38 # include <unistd.h>
39 # endif
40 # include <sys/socket.h>
41 # include <sys/time.h>
42 # include <netinet/in.h>
43 # include <netdb.h>
44 # include <sys/param.h>
45 # include <sys/ioctl.h>
46 # include <sys/uio.h>
47 # include <errno.h>
48 # include <pthread.h>
49 typedef int irc_socket_t;
50 # if !defined HAVE_CLOSESOCKET
51 # define closesocket close
52 # endif
53 # if !defined INVALID_SOCKET
54 # define INVALID_SOCKET -1
55 # endif
56 #endif
57
58
59 /* IRC control cvars */
60 cvar_t * cl_IRC_connect_at_startup;
61 cvar_t * cl_IRC_server;
62 cvar_t * cl_IRC_channel;
63 cvar_t * cl_IRC_port;
64 cvar_t * cl_IRC_override_nickname;
65 cvar_t * cl_IRC_nickname;
66 cvar_t * cl_IRC_kick_rejoin;
67 cvar_t * cl_IRC_reconnect_delay;
68
69
70 /*
71 * Timing controls
72 *
73 * In order to avoid actively waiting like crazy, there are many parts of the
74 * IRC client code that need to sleep or wait for a timeout. However, if the
75 * wait is too long, it makes the whole thing unreactive to e.g. the irc_say
76 * command; if the wait is too shot, it starts using CPU time.
77 *
78 * The constants below control the timeouts.
79 */
80
81 #define IRC_TIMEOUT_MS 250
82 #define IRC_TIMEOUT_US ( IRC_TIMEOUT_MS * 1000 )
83 #define IRC_TIMEOUTS_PER_SEC ( 1000 / IRC_TIMEOUT_MS)
84
85
86
87 /* Ctype-like macros */
88 #define IS_UPPER(c) ( (c) >= 'A' && (c) <= 'Z' )
89 #define IS_LOWER(c) ( (c) >= 'a' && (c) <= 'z' )
90 #define IS_DIGIT(c) ( (c) >= '0' && (c) <= '9' )
91 #define IS_CNTRL(c) ( (c) >= 0 && (c) <= 31 )
92 #define IS_ALPHA(c) ( IS_UPPER(c) || IS_LOWER(c) )
93 #define IS_ALNUM(c) ( IS_ALPHA(c) || IS_DIGIT(c) )
94
95
96
97 /* IRC command status; used to determine if connection should be re-attempted or not */
98 #define IRC_CMD_SUCCESS 0 // Success
99 #define IRC_CMD_FATAL 1 // Fatal error, don't bother retrying
100 #define IRC_CMD_RETRY 2 // Recoverable error, command should be attempted again
101
102
103 /* Constants that indicate the state of the IRC thread. */
104 #define IRC_THREAD_DEAD 0 // Thread is dead or hasn't been started
105 #define IRC_THREAD_INITIALISING 1 // Thread is being initialised
106 #define IRC_THREAD_CONNECTING 2 // Thread is attempting to connect
107 #define IRC_THREAD_SETNICK 3 // Thread is trying to set the player's
108 // nick
109 #define IRC_THREAD_CONNECTED 4 // Thread established a connection to
110 // the server and will attempt to join
111 // the channel
112 #define IRC_THREAD_JOINED 5 // Channel joined, ready to send or
113 // receive messages
114 #define IRC_THREAD_QUITTING 6 // The thread is being killed
115
116
117 /* Function that sets the thread status when the thread dies. Since that is
118 * system-dependent, it can't be done in the thread's main code.
119 */
120 static void IRC_SetThreadDead( );
121
122
123 /* Status of the IRC thread */
124 static int IRC_ThreadStatus = IRC_THREAD_DEAD;
125
126 /* Quit requested? */
127 static qboolean IRC_QuitRequested;
128
129 /* Socket handler */
130 static irc_socket_t IRC_Socket; // Socket
131
132
133 /*
134 * The protocol parser uses a finite state machine, here are the various
135 * states' definitions as well as a variable containing the current state
136 * and various other variables for message building.
137 */
138 #define IRC_PARSER_RECOVERY (-1) // Error recovery
139 #define IRC_PARSER_START 0 // Start of a message
140 #define IRC_PARSER_PFX_NOS_START 1 // Prefix start
141 #define IRC_PARSER_PFX_NOS 2 // Prefix, server or nick name
142 #define IRC_PARSER_PFX_USER_START 3 // Prefix, start of user name
143 #define IRC_PARSER_PFX_USER 4 // Prefix, user name
144 #define IRC_PARSER_PFX_HOST_START 5 // Prefix, start of host name
145 #define IRC_PARSER_PFX_HOST 6 // Prefix, host name
146 #define IRC_PARSER_COMMAND_START 7 // Start of command after a prefix
147 #define IRC_PARSER_STR_COMMAND 8 // String command
148 #define IRC_PARSER_NUM_COMMAND_2 9 // Numeric command, second character
149 #define IRC_PARSER_NUM_COMMAND_3 10 // Numeric command, third character
150 #define IRC_PARSER_NUM_COMMAND_4 11 // Numeric command end
151 #define IRC_PARSER_PARAM_START 12 // Parameter start
152 #define IRC_PARSER_MID_PARAM 13 // "Middle" parameter
153 #define IRC_PARSER_TRAILING_PARAM 14 // Trailing parameter
154 #define IRC_PARSER_LF 15 // End of line
155
156 static int IRC_ParserState;
157 static qboolean IRC_ParserInMessage;
158 static qboolean IRC_ParserError;
159
160
161 /*
162 * According to RFC 1459, maximal message size is 512 bytes, including trailing
163 * CRLF.
164 */
165
166 #define IRC_MESSAGE_SIZE 512
167 #define IRC_SEND_BUF_SIZE IRC_MESSAGE_SIZE
168 #define IRC_RECV_BUF_SIZE (IRC_MESSAGE_SIZE * 2)
169
170
171 /*
172 * IRC messages consist in:
173 * 1) an optional prefix, which contains either a server name or a nickname,
174 * 2) a command, which may be either a word or 3 numbers,
175 * 3) any number of arguments.
176 *
177 * RFC 2812 says that there are at most 14 "middle" parameters and a trailing
178 * parameter. However, UnrealIRCd does not respect this, and sends messages
179 * that contain an extra parameter. While the message in question could be
180 * ignored, it's better to avoid entering the error recovery state.
181 *
182 * Since we won't be handling messages in parallel, we will create a
183 * static record and use that to store everything, as we can definitely
184 * spare 130k of memory (note: we could have something smaller but it'd
185 * probably be a pointless exercise).
186 */
187
188
189 #define irc_string_t(len) struct { \
190 unsigned int length; \
191 char string[ len ]; \
192 }
193
194
195 #define IRC_MAX_NICK_LEN 64
196 #define IRC_MAX_ARG_LEN 509
197 #define IRC_MAX_PARAMS 16
198
199 struct irc_message_t {
200 // Prefix
201 irc_string_t(IRC_MAX_NICK_LEN) pfx_nickOrServer;
202 irc_string_t(IRC_MAX_NICK_LEN) pfx_user;
203 irc_string_t(IRC_MAX_NICK_LEN) pfx_host;
204
205 // Command
206 irc_string_t(32) cmd_string;
207
208 // Arguments
209 irc_string_t(IRC_MAX_ARG_LEN) arg_values[IRC_MAX_PARAMS];
210 unsigned int arg_count;
211 };
212
213 static struct irc_message_t IRC_ReceivedMessage;
214
215
216 // Macros to access the message's various fields
217 #define IRC_String(N) (IRC_ReceivedMessage.N.string)
218 #define IRC_Length(N) (IRC_ReceivedMessage.N.length)
219
220
221 /*
222 * IRC command handlers are called when some command is received;
223 * they are stored in hash tables.
224 */
225
226 typedef int (*irc_handler_func_t)( );
227 typedef int (*ctcp_handler_func_t)( qboolean is_channel , const char * message );
228
229 struct irc_handler_t {
230 char cmd_string[33];
231 void * handler;
232 };
233
234 static hashtable_t IRC_Handlers;
235 static hashtable_t IRC_CTCPHandlers;
236
237
238 /*
239 * Username, nickname, etc...
240 */
241
242 struct irc_user_t {
243 char nick[16];
244 int nicklen;
245 int nickattempts;
246 char username[16];
247 char email[100];
248 };
249
250 static struct irc_user_t IRC_User;
251
252
253 /*
254 * Events that can be displayed and flags that apply to them.
255 */
256
257 #define IRC_EVT_SAY 0x00000000 // Standard message
258 #define IRC_EVT_ACT 0x00000001 // /me message
259 #define IRC_EVT_JOIN 0x00000002 // Join
260 #define IRC_EVT_PART 0x00000003 // Part
261 #define IRC_EVT_QUIT 0x00000004 // Quit
262 #define IRC_EVT_KICK 0x00000005 // Kick
263 #define IRC_EVT_NICK_CHANGE 0x00000006 // Nick change
264 #define IRC_EVTF_SELF 0x00000100 // Event applies to current user
265
266
267 #define IRC_EventType(evt) ( evt & 0xff )
268 #define IRC_EventIsSelf(evt) ( ( evt & IRC_EVTF_SELF ) == IRC_EVTF_SELF )
269 #define IRC_MakeEvent(type,isself) ( IRC_EVT_##type | ( (isself) ? IRC_EVTF_SELF : 0 ) )
270
271
272
273 /*
274 * Rate limiters for various events.
275 *
276 * The rate limiter works on a per-event basis, it doesn't know nor care
277 * about users.
278 * Its threshold and increase constants (which will be scaled depending on
279 * the timing controls) determine the amount of responses per second, while
280 * also allowing "bursts".
281 */
282
283 /* Rate limiter threshold - above that, no response */
284 #define IRC_LIMIT_THRESHOLD 3
285
286 /* Rate limiter increase per check */
287 #define IRC_LIMIT_INCREASE 1
288
289 #define IRC_RL_MESSAGE 0
290 #define IRC_RL_PING 1
291 #define IRC_RL_VERSION 2
292
293 static unsigned int IRC_RateLimiter[ 3 ];
294
295
296
297
298 /*--------------------------------------------------------------------------*/
299 /* FUNCTIONS THAT MANAGE IRC COMMAND HANDLERS */
300 /*--------------------------------------------------------------------------*/
301
302
303 /*
304 * Initialises the handler tables
305 */
IRC_InitHandlers()306 static inline void IRC_InitHandlers( )
307 {
308 IRC_Handlers = HT_Create( 100 , HT_FLAG_INTABLE | HT_FLAG_CASE ,
309 sizeof( struct irc_handler_t ) ,
310 HT_OffsetOfField( struct irc_handler_t , cmd_string ) ,
311 32 );
312 IRC_CTCPHandlers = HT_Create( 100 , HT_FLAG_INTABLE | HT_FLAG_CASE ,
313 sizeof( struct irc_handler_t ) ,
314 HT_OffsetOfField( struct irc_handler_t , cmd_string ) ,
315 32 );
316 }
317
318
319 /*
320 * Frees the list of handlers (used when the IRC thread dies).
321 */
IRC_FreeHandlers()322 static void IRC_FreeHandlers( )
323 {
324 HT_Destroy( IRC_Handlers );
325 HT_Destroy( IRC_CTCPHandlers );
326 }
327
328
329 /*
330 * Registers a new IRC command handler.
331 */
IRC_AddHandler(const char * command,irc_handler_func_t handler)332 static inline void IRC_AddHandler( const char * command , irc_handler_func_t handler )
333 {
334 qboolean created;
335 struct irc_handler_t * rv;
336 rv = HT_GetItem( IRC_Handlers , command , &created );
337 assert( created );
338 rv->handler = handler;
339 }
340
341
342 /*
343 * Registers a new CTCP command handler.
344 */
IRC_AddCTCPHandler(const char * command,ctcp_handler_func_t handler)345 static void IRC_AddCTCPHandler( const char * command , ctcp_handler_func_t handler )
346 {
347 qboolean created;
348 struct irc_handler_t * rv;
349 rv = HT_GetItem( IRC_CTCPHandlers , command , &created );
350 assert( created );
351 rv->handler = handler;
352 }
353
354
355 /*
356 * Executes the command handler for the currently stored command. If there is
357 * no registered handler matching the command, ignore it.
358 */
IRC_ExecuteHandler()359 static int IRC_ExecuteHandler( )
360 {
361 struct irc_handler_t * handler;
362 handler = HT_GetItem( IRC_Handlers , IRC_String(cmd_string) , NULL );
363 if ( handler == NULL )
364 return IRC_CMD_SUCCESS;
365 return ((irc_handler_func_t)(handler->handler))( );
366 }
367
368
369 /*
370 * Executes a CTCP command handler.
371 */
IRC_ExecuteCTCPHandler(const char * command,qboolean is_channel,const char * argument)372 static int IRC_ExecuteCTCPHandler( const char * command , qboolean is_channel , const char *argument )
373 {
374 struct irc_handler_t * handler;
375 handler = HT_GetItem( IRC_CTCPHandlers , command , NULL );
376 if ( handler == NULL )
377 return IRC_CMD_SUCCESS;
378 return ((ctcp_handler_func_t)(handler->handler))( is_channel , argument );
379 }
380
381
382
383 /*--------------------------------------------------------------------------*/
384 /* IRC DELAYED EXECUTION */
385 /*--------------------------------------------------------------------------*/
386
387 /* Structure for the delayed execution queue */
388 struct irc_delayed_t
389 {
390 irc_handler_func_t handler; // Handler to call
391 int time_left; // "Time" left before call
392 struct irc_delayed_t * next; // Next record
393 };
394
395 /* Delayed execution queue head & tail */
396 static struct irc_delayed_t * IRC_DEQueue = NULL;
397
398
399 /*
400 * This function sets an IRC handler function to be executed after some time.
401 */
IRC_SetTimeout(irc_handler_func_t function,int time)402 static void IRC_SetTimeout( irc_handler_func_t function , int time )
403 {
404 struct irc_delayed_t * qe , * find;
405 assert( time > 0 );
406
407 // Create entry
408 qe = (struct irc_delayed_t *) malloc( sizeof( struct irc_delayed_t ) );
409 qe->handler = function;
410 qe->time_left = time * IRC_TIMEOUTS_PER_SEC;
411
412 // Find insert location
413 if ( IRC_DEQueue ) {
414 if ( IRC_DEQueue->time_left >= time ) {
415 qe->next = IRC_DEQueue;
416 IRC_DEQueue = qe;
417 } else {
418 find = IRC_DEQueue;
419 while ( find->next && find->next->time_left < time )
420 find = find->next;
421 qe->next = find->next;
422 find->next = qe;
423 }
424 } else {
425 qe->next = NULL;
426 IRC_DEQueue = qe;
427 }
428 }
429
430
431 /*
432 * This function dequeues an entry from the delayed execution queue.
433 */
IRC_DequeueDelayed()434 static qboolean IRC_DequeueDelayed( )
435 {
436 struct irc_delayed_t * found;
437
438 if ( ! IRC_DEQueue )
439 return false;
440
441 found = IRC_DEQueue;
442 IRC_DEQueue = found->next;
443 free( found );
444 return true;
445 }
446
447
448 /*
449 * This function deletes all remaining entries from the delayed execution
450 * queue.
451 */
IRC_FlushDEQueue()452 static void IRC_FlushDEQueue( )
453 {
454 while ( IRC_DequeueDelayed( ) ) {
455 // PURPOSEDLY EMPTY
456 }
457 }
458
459
460 /*
461 * This function processes the delayed execution queue.
462 */
IRC_ProcessDEQueue()463 static int IRC_ProcessDEQueue( )
464 {
465 struct irc_delayed_t * iter;
466 int err_code;
467
468 iter = IRC_DEQueue;
469 while ( iter ) {
470 if ( iter->time_left == 1 ) {
471 err_code = (iter->handler)( );
472 IRC_DequeueDelayed( );
473 if ( err_code != IRC_CMD_SUCCESS )
474 return err_code;
475 iter = IRC_DEQueue;
476 } else {
477 iter->time_left --;
478 iter = iter->next;
479 }
480 }
481
482 return IRC_CMD_SUCCESS;
483 }
484
485
486
487 /*--------------------------------------------------------------------------*/
488 /* IRC MESSAGE PARSER */
489 /*--------------------------------------------------------------------------*/
490
491
492 /* Parser macros, 'cause I'm lazy */
493 #define P_SET_STATE(S) IRC_ParserState = IRC_PARSER_##S
494 #define P_INIT_MESSAGE(S) { \
495 P_SET_STATE(S); \
496 IRC_ParserInMessage = true; \
497 memset( &IRC_ReceivedMessage , 0 , sizeof( struct irc_message_t ) ); \
498 }
499 #if defined DEBUG_DUMP_IRC
500 #define P_ERROR(S) { \
501 if ( ! IRC_ParserError ) { \
502 Com_Printf( "IRC PARSER ERROR (state: %d , received: %d)\n" , IRC_ParserState , next ); \
503 } \
504 P_SET_STATE(S); \
505 IRC_ParserError = true; \
506 }
507 #else // defined DEBUG_DUMP_IRC
508 #define P_ERROR(S) { \
509 P_SET_STATE(S); \
510 IRC_ParserError = true; \
511 }
512 #endif // defined DEBUG_DUMP_IRC
513 #define P_AUTO_ERROR { \
514 if ( next == '\r' ) { \
515 P_ERROR(LF); \
516 } else { \
517 P_ERROR(RECOVERY); \
518 } \
519 }
520 #define P_INIT_STRING(S) { \
521 IRC_ReceivedMessage.S.string[0] = next; \
522 IRC_ReceivedMessage.S.length = 1; \
523 }
524 #define P_ADD_STRING(S) { \
525 if ( IRC_ReceivedMessage.S.length == sizeof( IRC_ReceivedMessage.S.string ) - 1 ) { \
526 P_ERROR(RECOVERY); \
527 } else { \
528 IRC_ReceivedMessage.S.string[IRC_ReceivedMessage.S.length ++] = next; \
529 } \
530 }
531 #define P_NEXT_PARAM { \
532 if ( ( ++ IRC_ReceivedMessage.arg_count ) == IRC_MAX_PARAMS ) { \
533 P_ERROR(RECOVERY); \
534 } \
535 }
536 #define P_START_PARAM { \
537 if ( ( ++ IRC_ReceivedMessage.arg_count ) == IRC_MAX_PARAMS ) { \
538 P_ERROR(RECOVERY); \
539 } else \
540 P_INIT_STRING(arg_values[IRC_ReceivedMessage.arg_count - 1]) \
541 }
542 #define P_ADD_PARAM P_ADD_STRING(arg_values[IRC_ReceivedMessage.arg_count - 1])
543
544
545
546 /*
547 * Main parsing function that uses a FSM to parse one character at a time.
548 * Returns true when a full message is read and no error has occured.
549 */
550
IRC_Parser(char next)551 static qboolean IRC_Parser( char next )
552 {
553 qboolean has_msg = false;
554
555 switch ( IRC_ParserState ) {
556
557 /* Initial state; clear the message, then check input. ':'
558 * indicates there is a prefix, a digit indicates a numeric
559 * command, an upper-case letter indicates a string command.
560 * It's also possible we received an empty line - just skip
561 * it. Anything else is an error.
562 */
563 case IRC_PARSER_START:
564 IRC_ParserError = false;
565 IRC_ParserInMessage = false;
566 if ( next == ':' ) {
567 P_INIT_MESSAGE(PFX_NOS_START);
568 } else if ( next == '\r' ) {
569 P_SET_STATE(LF);
570 } else if ( IS_DIGIT( next ) ) {
571 P_INIT_MESSAGE(NUM_COMMAND_2);
572 P_INIT_STRING(cmd_string);
573 } else if ( IS_UPPER( next ) ) {
574 P_INIT_MESSAGE(STR_COMMAND);
575 P_INIT_STRING(cmd_string);
576 } else {
577 P_ERROR(RECOVERY);
578 }
579 break;
580
581 /*
582 * Start of prefix; anything is accepted, except for '!', '@', ' '
583 * and control characters which all cause an error recovery.
584 */
585 case IRC_PARSER_PFX_NOS_START:
586 if ( next == '!' || next == '@' || next == ' ' || IS_CNTRL( next ) ) {
587 P_AUTO_ERROR;
588 } else {
589 P_SET_STATE(PFX_NOS);
590 P_INIT_STRING(pfx_nickOrServer);
591 }
592 break;
593
594 /*
595 * Prefix, server or nick name. Control characters cause an error,
596 * ' ', '!' and '@' cause state changes.
597 */
598 case IRC_PARSER_PFX_NOS:
599 if ( next == '!' ) {
600 P_SET_STATE(PFX_USER_START);
601 } else if ( next == '@' ) {
602 P_SET_STATE(PFX_HOST_START);
603 } else if ( next == ' ' ) {
604 P_SET_STATE(COMMAND_START);
605 } else if IS_CNTRL( next ) {
606 P_AUTO_ERROR;
607 } else {
608 P_ADD_STRING(pfx_nickOrServer);
609 }
610 break;
611
612 /*
613 * Start of user name; anything goes, except for '!', '@', ' '
614 * and control characters which cause an error.
615 */
616 case IRC_PARSER_PFX_USER_START:
617 if ( next == '!' || next == '@' || next == ' ' || IS_CNTRL( next ) ) {
618 P_AUTO_ERROR;
619 } else {
620 P_SET_STATE(PFX_USER);
621 P_INIT_STRING(pfx_user);
622 }
623 break;
624
625 /*
626 * User name; '@' will cause state changes, '!' , ' ' and
627 * control characters will cause errors.
628 */
629 case IRC_PARSER_PFX_USER:
630 if ( next == '@' ) {
631 P_SET_STATE(PFX_HOST_START);
632 } else if ( next == '!' || next == ' ' || IS_CNTRL( next ) ) {
633 P_AUTO_ERROR;
634 } else {
635 P_ADD_STRING(pfx_user);
636 }
637 break;
638
639 /*
640 * Start of host name; anything goes, except for '!', '@', ' '
641 * and control characters which cause an error.
642 */
643 case IRC_PARSER_PFX_HOST_START:
644 if ( next == '!' || next == '@' || next == ' ' || IS_CNTRL( next ) ) {
645 P_AUTO_ERROR;
646 } else {
647 P_SET_STATE(PFX_HOST);
648 P_INIT_STRING(pfx_host);
649 }
650 break;
651
652 /*
653 * Host name; ' ' will cause state changes, '!' and control
654 * characters will cause errors.
655 */
656 case IRC_PARSER_PFX_HOST:
657 if ( next == ' ' ) {
658 P_SET_STATE(COMMAND_START);
659 } else if ( next == '!' || next == '@' || IS_CNTRL( next ) ) {
660 P_AUTO_ERROR;
661 } else {
662 P_ADD_STRING(pfx_host);
663 }
664 break;
665
666 /*
667 * Start of command, will accept start of numeric and string
668 * commands; anything else is an error.
669 */
670 case IRC_PARSER_COMMAND_START:
671 if ( IS_DIGIT( next ) ) {
672 P_SET_STATE(NUM_COMMAND_2);
673 P_INIT_STRING(cmd_string);
674 } else if ( IS_UPPER( next ) ) {
675 P_SET_STATE(STR_COMMAND);
676 P_INIT_STRING(cmd_string);
677 } else {
678 P_AUTO_ERROR;
679 }
680 break;
681
682 /*
683 * String command. Uppercase letters will cause the parser
684 * to continue on string commands, ' ' indicates a parameter
685 * is expected, '\r' means we're done. Anything else is an
686 * error.
687 */
688 case IRC_PARSER_STR_COMMAND:
689 if ( next == ' ' ) {
690 P_SET_STATE(PARAM_START);
691 } else if ( next == '\r' ) {
692 P_SET_STATE(LF);
693 } else if ( IS_UPPER( next ) ) {
694 P_ADD_STRING(cmd_string);
695 } else {
696 P_ERROR(RECOVERY);
697 }
698 break;
699
700 /*
701 * Second/third digit of numeric command; anything but a digit
702 * is an error.
703 */
704 case IRC_PARSER_NUM_COMMAND_2:
705 case IRC_PARSER_NUM_COMMAND_3:
706 if ( IS_DIGIT( next ) ) {
707 IRC_ParserState ++;
708 P_ADD_STRING(cmd_string);
709 } else {
710 P_AUTO_ERROR;
711 }
712 break;
713
714 /*
715 * End of numeric command, could be a ' ' or a '\r'.
716 */
717 case IRC_PARSER_NUM_COMMAND_4:
718 if ( next == ' ' ) {
719 P_SET_STATE(PARAM_START);
720 } else if ( next == '\r' ) {
721 P_SET_STATE(LF);
722 } else {
723 P_ERROR(RECOVERY);
724 }
725 break;
726
727 /*
728 * Start of parameter. ':' means it's a trailing parameter,
729 * spaces and control characters shouldn't be here, and
730 * anything else is a "middle" parameter.
731 */
732 case IRC_PARSER_PARAM_START:
733 if ( next == ':' ) {
734 P_SET_STATE(TRAILING_PARAM);
735 P_NEXT_PARAM;
736 } else if ( next == '\r' ) {
737 P_SET_STATE(LF);
738 } else if ( IS_CNTRL( next ) ) {
739 P_AUTO_ERROR;
740 } else if ( next != ' ' ) {
741 if ( next & 0x80 )
742 next = '?';
743 P_SET_STATE(MID_PARAM);
744 P_START_PARAM;
745 }
746 break;
747
748 /*
749 * "Middle" parameter; ' ' means there's another parameter coming,
750 * '\r' means the end of the message, control characters are not
751 * accepted, anything else is part of the parameter.
752 */
753 case IRC_PARSER_MID_PARAM:
754 if ( next == ' ' ) {
755 P_SET_STATE(PARAM_START);
756 } else if ( next == '\r' ) {
757 P_SET_STATE(LF);
758 } else if ( IS_CNTRL( next ) ) {
759 P_ERROR(RECOVERY);
760 } else {
761 if ( next & 0x80 )
762 next = '?';
763 P_ADD_PARAM;
764 }
765 break;
766
767 /*
768 * Trailing parameter; '\r' means the end of the command,
769 * and anything else is just added to the string.
770 */
771 case IRC_PARSER_TRAILING_PARAM:
772 if ( next == '\r' ) {
773 P_SET_STATE(LF);
774 } else {
775 if ( next & 0x80 ) {
776 next = '?';
777 }
778 P_ADD_PARAM;
779 }
780 break;
781
782 /*
783 * End of line, expect '\n'. If found, we may have a message
784 * to handle (unless there were errors). Anything else is an
785 * error.
786 */
787 case IRC_PARSER_LF:
788 if ( next == '\n' ) {
789 has_msg = IRC_ParserInMessage;
790 P_SET_STATE(START);
791 } else {
792 P_AUTO_ERROR;
793 }
794 break;
795
796 /*
797 * Error recovery: wait for an '\r'.
798 */
799 case IRC_PARSER_RECOVERY:
800 if ( next == '\r' )
801 P_SET_STATE(LF);
802 break;
803 }
804
805 return has_msg && !IRC_ParserError;
806 }
807
808
809 /*
810 * Debugging function that dumps the IRC message.
811 */
812 #ifdef DEBUG_DUMP_IRC
IRC_DumpMessage()813 static void IRC_DumpMessage( )
814 {
815 int i;
816
817 Com_Printf( "----------- IRC MESSAGE RECEIVED -----------\n" );
818 Com_Printf( " (pfx) nick/server .... [%.3d]%s\n" , IRC_Length( pfx_nickOrServer ) , IRC_String( pfx_nickOrServer ) );
819 Com_Printf( " (pfx) user ........... [%.3d]%s\n" , IRC_Length( pfx_user ) , IRC_String( pfx_user ) );
820 Com_Printf( " (pfx) host ........... [%.3d]%s\n" , IRC_Length( pfx_host ) , IRC_String( pfx_host ) );
821 Com_Printf( " command string ....... [%.3d]%s\n" , IRC_Length( cmd_string ) , IRC_String( cmd_string ) );
822 Com_Printf( " arguments ............ %.3d\n" , IRC_ReceivedMessage.arg_count );
823 for ( i = 0 ; i < IRC_ReceivedMessage.arg_count ; i ++ ) {
824 Com_Printf( " ARG %d = [%.3d]%s\n" , i + 1 , IRC_Length( arg_values[ i ] ) , IRC_String( arg_values[ i ] ) );
825 }
826 }
827 #endif // DEBUG_DUMP_IRC
828
829
830
831 /*--------------------------------------------------------------------------*/
832 /* "SYSTEM" FUNCTIONS */
833 /*--------------------------------------------------------------------------*/
834
835
836 #if defined WIN32_VARIANT
IRC_HandleError(void)837 static void IRC_HandleError(void)
838 {
839 switch ( WSAGetLastError() )
840 {
841 case 0: // No error
842 return;
843
844 case WSANOTINITIALISED :
845 Com_Printf("Unable to initialise socket.\n");
846 break;
847 case WSAEAFNOSUPPORT :
848 Com_Printf("The specified address family is not supported.\n");
849 break;
850 case WSAEADDRNOTAVAIL :
851 Com_Printf("Specified address is not available from the local machine.\n");
852 break;
853 case WSAECONNREFUSED :
854 Com_Printf("The attempt to connect was forcefully rejected.\n");
855 break;
856 case WSAEDESTADDRREQ :
857 Com_Printf("address destination address is required.\n");
858 break;
859 case WSAEFAULT :
860 Com_Printf("The namelen argument is incorrect.\n");
861 break;
862 case WSAEINVAL :
863 Com_Printf("The socket is not already bound to an address.\n");
864 break;
865 case WSAEISCONN :
866 Com_Printf("The socket is already connected.\n");
867 break;
868 case WSAEADDRINUSE :
869 Com_Printf("The specified address is already in use.\n");
870 break;
871 case WSAEMFILE :
872 Com_Printf("No more file descriptors are available.\n");
873 break;
874 case WSAENOBUFS :
875 Com_Printf("No buffer space available. The socket cannot be created.\n");
876 break;
877 case WSAEPROTONOSUPPORT :
878 Com_Printf("The specified protocol is not supported.\n");
879 break;
880 case WSAEPROTOTYPE :
881 Com_Printf("The specified protocol is the wrong type for this socket.\n");
882 break;
883 case WSAENETUNREACH :
884 Com_Printf("The network can't be reached from this host at this time.\n");
885 break;
886 case WSAENOTSOCK :
887 Com_Printf("The descriptor is not a socket.\n");
888 break;
889 case WSAETIMEDOUT :
890 Com_Printf("Attempt timed out without establishing a connection.\n");
891 break;
892 case WSAESOCKTNOSUPPORT :
893 Com_Printf("Socket type is not supported in this address family.\n");
894 break;
895 case WSAENETDOWN :
896 Com_Printf("Network subsystem failure.\n");
897 break;
898 case WSAHOST_NOT_FOUND :
899 Com_Printf("Authoritative Answer Host not found.\n");
900 break;
901 case WSATRY_AGAIN :
902 Com_Printf("Non-Authoritative Host not found or SERVERFAIL.\n");
903 break;
904 case WSANO_RECOVERY :
905 Com_Printf("Non recoverable errors, FORMERR, REFUSED, NOTIMP.\n");
906 break;
907 case WSANO_DATA :
908 Com_Printf("Valid name, no data record of requested type.\n");
909 break;
910 case WSAEINPROGRESS :
911 Com_Printf("address blocking Windows Sockets operation is in progress.\n");
912 break;
913 default :
914 Com_Printf("Unknown connection error.\n");
915 break;
916 }
917
918 WSASetLastError( 0 );
919 }
920 #elif defined UNIX_VARIANT
IRC_HandleError(void)921 static void IRC_HandleError( void )
922 {
923 Com_Printf( "IRC socket connection error: %s\n" , strerror( errno ) );
924 }
925 #endif
926
927
928 #if defined MSG_NOSIGNAL
929 # define IRC_SEND_FLAGS MSG_NOSIGNAL
930 #else
931 # define IRC_SEND_FLAGS 0
932 #endif
933
934 /*
935 * Attempt to format then send a message to the IRC server. Will return
936 * true on success, and false if an overflow occurred or if send() failed.
937 */
IRC_Send(const char * format,...)938 static int IRC_Send( const char * format , ... )
939 {
940 char buffer[ IRC_SEND_BUF_SIZE + 1 ];
941 va_list args;
942 int len , sent;
943
944 // Format message
945 va_start( args , format );
946 len = vsnprintf( buffer , IRC_SEND_BUF_SIZE - 1 , format , args );
947 va_end( args );
948 if ( len >= IRC_SEND_BUF_SIZE - 1 ) {
949 // This is a bug, return w/ a fatal error
950 Com_Printf( "...IRC: send buffer overflow (%d characters)\n" , len );
951 return IRC_CMD_FATAL;
952 }
953
954 // Add CRLF terminator
955 #if defined DEBUG_DUMP_IRC
956 Com_Printf( "SENDING IRC MESSAGE: %s\n" , buffer );
957 #endif
958 buffer[ len++ ] = '\r';
959 buffer[ len++ ] = '\n';
960
961 // Send message
962 sent = send(IRC_Socket, buffer , len , IRC_SEND_FLAGS );
963 if ( sent < len ) {
964 IRC_HandleError( );
965 return IRC_CMD_RETRY;
966 }
967
968 return IRC_CMD_SUCCESS;
969 }
970
971
972 /*
973 * This function is used to prevent the IRC thread from turning the CPU into
974 * a piece of molten silicium while it waits for the server to send data.
975 *
976 * If data is received, SUCCESS is returned; otherwise, RETRY will be returned
977 * on timeout and FATAL on error.
978 */
979
980 #if defined WIN32_VARIANT
981 # define SELECT_ARG 0
982 # define SELECT_CHECK ( rv == -1 && WSAGetLastError() == WSAEINTR )
983 #elif defined UNIX_VARIANT
984 # define SELECT_ARG ( IRC_Socket + 1 )
985 # define SELECT_CHECK ( rv == -1 && errno == EINTR )
986 #endif
987
IRC_Wait()988 static int IRC_Wait( )
989 {
990 struct timeval timeout;
991 fd_set read_set;
992 int rv;
993
994 // Wait for data to be available
995 do {
996 FD_ZERO( &read_set );
997 FD_SET( IRC_Socket, &read_set );
998 timeout.tv_sec = 0;
999 timeout.tv_usec = IRC_TIMEOUT_US;
1000 rv = select( SELECT_ARG , &read_set , NULL , NULL , &timeout );
1001 } while ( SELECT_CHECK );
1002
1003 // Something wrong happened
1004 if ( rv < 0 ) {
1005 IRC_HandleError( );
1006 return IRC_CMD_FATAL;
1007 }
1008
1009 return ( rv == 0 ) ? IRC_CMD_RETRY : IRC_CMD_SUCCESS;
1010 }
1011
1012
1013 /*
1014 * Wait for some seconds.
1015 */
1016
IRC_Sleep(int seconds)1017 static void IRC_Sleep( int seconds )
1018 {
1019 int i;
1020 assert( seconds > 0 );
1021 for ( i = 0 ; i < seconds * IRC_TIMEOUTS_PER_SEC && !IRC_QuitRequested ; i ++ ) {
1022 #if defined WIN32_VARIANT
1023 Sleep( IRC_TIMEOUT_MS );
1024 #elif defined UNIX_VARIANT
1025 usleep( IRC_TIMEOUT_US );
1026 #endif
1027 }
1028 }
1029
1030
1031
1032 /*--------------------------------------------------------------------------*/
1033 /* RATE LIMITS */
1034 /*--------------------------------------------------------------------------*/
1035
1036
1037 /*
1038 * Checks if some action can be effected using the rate limiter. If it can,
1039 * the rate limiter's status will be updated.
1040 */
IRC_CheckEventRate(int event_type)1041 static inline qboolean IRC_CheckEventRate( int event_type )
1042 {
1043 if ( IRC_RateLimiter[ event_type ] >= IRC_LIMIT_THRESHOLD * IRC_TIMEOUTS_PER_SEC )
1044 return false;
1045 IRC_RateLimiter[ event_type ] += IRC_LIMIT_INCREASE * IRC_TIMEOUTS_PER_SEC;
1046 return true;
1047 }
1048
1049
1050 /*
1051 * Decrease all non-zero rate limiter entries.
1052 */
IRC_UpdateRateLimiter()1053 static inline void IRC_UpdateRateLimiter( )
1054 {
1055 int i;
1056 for ( i = 0 ; i < sizeof( IRC_RateLimiter ) / sizeof( unsigned int ) ; i ++ )
1057 if ( IRC_RateLimiter[ i ] ) {
1058 IRC_RateLimiter[ i ] --;
1059 }
1060 }
1061
1062
1063 /*
1064 * Initialise the rate limiter.
1065 */
IRC_InitRateLimiter()1066 static inline void IRC_InitRateLimiter( )
1067 {
1068 int i;
1069 for ( i = 0 ; i < sizeof( IRC_RateLimiter ) / sizeof( unsigned int ) ; i ++ )
1070 IRC_RateLimiter[ i ] = 0;
1071 }
1072
1073
1074
1075 /*--------------------------------------------------------------------------*/
1076 /* DISPLAY CODE */
1077 /*--------------------------------------------------------------------------*/
1078
1079
IRC_NeutraliseString(char * buffer,const char * source)1080 static void IRC_NeutraliseString( char * buffer , const char * source )
1081 {
1082 while ( *source ) {
1083 char c = *source;
1084 if ( IS_CNTRL( c ) ) {
1085 *( buffer ++ ) = ' ';
1086 } else if ( c & 0x80 ) {
1087 *( buffer ++ ) = '?';
1088 } else if ( c == Q_COLOR_ESCAPE ) {
1089 *( buffer ++ ) = Q_COLOR_ESCAPE;
1090 *( buffer ++ ) = Q_COLOR_ESCAPE;
1091 } else {
1092 *( buffer ++ ) = c;
1093 }
1094 source ++;
1095 }
1096 *buffer = 0;
1097 }
1098
1099
IRC_Display(int event,const char * nick,const char * message)1100 static void IRC_Display( int event , const char * nick , const char *message )
1101 {
1102 char buffer[ IRC_RECV_BUF_SIZE * 2 ];
1103 char nick_copy[ IRC_MAX_NICK_LEN * 2 ];
1104 char message_copy[ IRC_MAX_ARG_LEN * 2 ];
1105 const char *fmt_string;
1106 qboolean has_nick;
1107 qboolean has_message;
1108
1109 // If we're quitting, just skip this
1110 if ( IRC_QuitRequested )
1111 return;
1112
1113 // Determine message format
1114 switch ( IRC_EventType( event ) ) {
1115 case IRC_EVT_SAY:
1116 has_nick = has_message = true;
1117 if ( IRC_EventIsSelf( event ) ) {
1118 fmt_string = "^2<^7%s^2> %s";
1119 } else if ( strstr( message , IRC_User.nick ) ) {
1120 fmt_string = "^3<^7%s^3> %s";
1121 } else {
1122 fmt_string = "^1<^7%s^1> %s";
1123 }
1124 break;
1125 case IRC_EVT_ACT:
1126 has_nick = has_message = true;
1127 if ( IRC_EventIsSelf( event ) ) {
1128 fmt_string = "^2* ^7%s^2 %s";
1129 } else if ( strstr( message , IRC_User.nick ) ) {
1130 fmt_string = "^3* ^7%s^3 %s";
1131 } else {
1132 fmt_string = "^1* ^7%s^1 %s";
1133 }
1134 break;
1135 case IRC_EVT_JOIN:
1136 has_message = false;
1137 has_nick = !IRC_EventIsSelf( event );
1138 if ( has_nick ) {
1139 fmt_string = "^5-> ^7%s^5 has entered the channel.";
1140 } else {
1141 fmt_string = "^2Joined IRC chat.";
1142 }
1143 break;
1144 case IRC_EVT_PART:
1145 // The AlienArena IRC client never parts, so it's
1146 // someone else.
1147 has_nick = true;
1148 has_message = ( message[0] != 0 );
1149 if ( has_message ) {
1150 fmt_string = "^5<- ^7%s^5 has left the channel: %s.";
1151 } else {
1152 fmt_string = "^5<- ^7%s^5 has left the channel.";
1153 }
1154 break;
1155 case IRC_EVT_QUIT:
1156 has_nick = !IRC_EventIsSelf( event );
1157 if ( has_nick ) {
1158 has_message = ( message[0] != 0 );
1159 if ( has_message ) {
1160 fmt_string = "^5<- ^7%s^5 has quit: %s.";
1161 } else {
1162 fmt_string = "^5<- ^7%s^5 has quit.";
1163 }
1164 } else {
1165 has_message = true;
1166 fmt_string = "^2Quit IRC chat: %s.";
1167 }
1168 break;
1169 case IRC_EVT_KICK:
1170 has_nick = has_message = true;
1171 if ( IRC_EventIsSelf( event ) ) {
1172 fmt_string = "^2Kicked by ^7%s^2: %s.";
1173 } else {
1174 fmt_string = "^5<- ^7%s^5 has been kicked: %s.";
1175 }
1176 break;
1177 case IRC_EVT_NICK_CHANGE:
1178 has_nick = has_message = true;
1179 if ( IRC_EventIsSelf( event ) ) {
1180 fmt_string = "^2** ^7%s^2 is now known as ^7%s^2.";
1181 } else {
1182 fmt_string = "^5** ^7%s^5 is now known as ^7%s^5.";
1183 }
1184 break;
1185 default:
1186 has_nick = has_message = false;
1187 fmt_string = "unknown message received";
1188 break;
1189 }
1190
1191 // Neutralise required strings
1192 if ( has_nick )
1193 IRC_NeutraliseString( nick_copy , nick );
1194 if ( has_message )
1195 IRC_NeutraliseString( message_copy , message );
1196
1197 // Format message
1198 if ( has_nick && has_message ) {
1199 sprintf( buffer , fmt_string , nick_copy , message_copy );
1200 } else if ( has_nick ) {
1201 sprintf( buffer , fmt_string , nick_copy );
1202 } else if ( has_message ) {
1203 sprintf( buffer , fmt_string , message_copy );
1204 } else {
1205 strncpy( buffer , fmt_string , IRC_RECV_BUF_SIZE * 2 - 1 );
1206 }
1207 buffer[ IRC_RECV_BUF_SIZE * 2 - 1 ] = 0;
1208
1209 SCR_IRCPrintf( "^1IRC: %s", buffer );
1210 }
1211
1212
1213
1214 /*--------------------------------------------------------------------------*/
1215 /* IRC MESSAGE HANDLERS */
1216 /*--------------------------------------------------------------------------*/
1217
1218
1219 /*
1220 * Send the user's nickname.
1221 */
IRC_SendNickname()1222 static int IRC_SendNickname( )
1223 {
1224 return IRC_Send( "NICK %s" , IRC_User.nick );
1225 }
1226
1227
1228 /*
1229 * Join the channel
1230 */
IRC_JoinChannel()1231 static int IRC_JoinChannel( )
1232 {
1233 return IRC_Send( "JOIN #%s" , cl_IRC_channel->string );
1234 }
1235
1236
1237 /*
1238 * Handles a PING by replying with a PONG.
1239 */
IRCH_Ping()1240 static int IRCH_Ping( )
1241 {
1242 if ( IRC_ReceivedMessage.arg_count == 1 )
1243 return IRC_Send( "PONG :%s" , IRC_String( arg_values[ 0 ] ) );
1244 return IRC_CMD_SUCCESS;
1245 }
1246
1247
1248 /*
1249 * Handles server errors
1250 */
IRCH_ServerError()1251 static int IRCH_ServerError( )
1252 {
1253 if ( IRC_ThreadStatus == IRC_THREAD_QUITTING ) {
1254 return IRC_CMD_SUCCESS;
1255 }
1256
1257 if ( IRC_ReceivedMessage.arg_count == 1 ) {
1258 Com_Printf( "IRC: server error - %s\n" , IRC_String( arg_values[ 0 ] ) );
1259 } else {
1260 Com_Printf( "IRC: server error\n" );
1261 }
1262 return IRC_CMD_RETRY;
1263 }
1264
1265
1266 /*
1267 * Some fatal error was received, the IRC thread must die.
1268 */
IRCH_FatalError()1269 static int IRCH_FatalError( )
1270 {
1271 IRC_Display( IRC_MakeEvent(QUIT,1) , "" , "fatal error" );
1272 IRC_Send( "QUIT :Something went wrong" );
1273 return IRC_CMD_RETRY;
1274 }
1275
1276
1277 /*
1278 * Nickname error. If received while the thread is in the SETNICK state,
1279 * we might want to try again. Otherwise, we ignore the error as it should
1280 * not have been received anyway.
1281 */
1282 #define RANDOM_NUMBER_CHAR ( '0' + rand() % 10 )
IRCH_NickError()1283 static int IRCH_NickError( )
1284 {
1285 int i;
1286
1287 if ( IRC_ThreadStatus == IRC_THREAD_SETNICK ) {
1288 if ( ++ IRC_User.nickattempts == 4 ) {
1289 IRC_Send( "QUIT :Could not set nickname" );
1290 return IRC_CMD_FATAL;
1291 }
1292
1293 if ( IRC_User.nicklen < 15 ) {
1294 IRC_User.nick[ IRC_User.nicklen ++ ] = RANDOM_NUMBER_CHAR;
1295 } else {
1296 for ( i = IRC_User.nicklen - 3 ; i < IRC_User.nicklen ; i ++ ) {
1297 IRC_User.nick[ i ] = RANDOM_NUMBER_CHAR;
1298 }
1299 }
1300
1301 IRC_SetTimeout( IRC_SendNickname , 2 );
1302 } else {
1303 Com_Printf( "...IRC: got spurious nickname error\n" );
1304 }
1305
1306 return IRC_CMD_SUCCESS;
1307 }
1308
1309
1310 /*
1311 * Connection established, we will be able to join a channel
1312 */
IRCH_Connected()1313 static int IRCH_Connected( )
1314 {
1315 if ( IRC_ThreadStatus != IRC_THREAD_SETNICK ) {
1316 IRC_Display( IRC_MakeEvent(QUIT,1) , "" , "IRC client bug" );
1317 IRC_Send( "QUIT :AlienArena IRC bug!" );
1318 return IRC_CMD_RETRY;
1319 }
1320 IRC_ThreadStatus = IRC_THREAD_CONNECTED;
1321 IRC_SetTimeout( &IRC_JoinChannel , 1 );
1322 return IRC_CMD_SUCCESS;
1323 }
1324
1325
1326 /*
1327 * Received JOIN
1328 */
IRCH_Joined()1329 static int IRCH_Joined( )
1330 {
1331 int event;
1332
1333 if ( IRC_ThreadStatus < IRC_THREAD_CONNECTED ) {
1334 IRC_Display( IRC_MakeEvent(QUIT,1) , "" , "IRC client bug" );
1335 IRC_Send( "QUIT :AlienArena IRC bug!" );
1336 return IRC_CMD_RETRY;
1337 }
1338
1339 if ( !strcmp( IRC_String( pfx_nickOrServer ) , IRC_User.nick ) ) {
1340 IRC_ThreadStatus = IRC_THREAD_JOINED;
1341 event = IRC_MakeEvent(JOIN,1);
1342 } else {
1343 event = IRC_MakeEvent(JOIN,0);
1344 }
1345 IRC_Display( event , IRC_String( pfx_nickOrServer ) , NULL );
1346 return IRC_CMD_SUCCESS;
1347 }
1348
1349
1350 /*
1351 * Received PART
1352 */
IRCH_Part()1353 static int IRCH_Part( )
1354 {
1355 IRC_Display( IRC_MakeEvent(PART, 0) , IRC_String( pfx_nickOrServer ) , IRC_String( arg_values[ 1 ] ) );
1356 return IRC_CMD_SUCCESS;
1357 }
1358
1359
1360 /*
1361 * Received QUIT
1362 */
IRCH_Quit()1363 static int IRCH_Quit( )
1364 {
1365 IRC_Display( IRC_MakeEvent(QUIT, 0) , IRC_String( pfx_nickOrServer ) , IRC_String( arg_values[ 0 ] ) );
1366 return IRC_CMD_SUCCESS;
1367 }
1368
1369
1370 /*
1371 * Received KICK
1372 */
IRCH_Kick()1373 static int IRCH_Kick( )
1374 {
1375 if ( !strcmp( IRC_String( arg_values[ 1 ] ) , IRC_User.nick ) ) {
1376 IRC_Display( IRC_MakeEvent(KICK, 1) , IRC_String( pfx_nickOrServer ) , IRC_String( arg_values[ 2 ] ) );
1377 if ( cl_IRC_kick_rejoin->integer > 0 ) {
1378 IRC_ThreadStatus = IRC_THREAD_CONNECTED;
1379 IRC_SetTimeout( &IRC_JoinChannel , cl_IRC_kick_rejoin->integer );
1380 } else {
1381 IRC_Display( IRC_MakeEvent(QUIT, 1) , "" , "kicked from channel.." );
1382 IRC_Send( "QUIT :b&!" );
1383 return IRC_CMD_FATAL;
1384 }
1385 } else {
1386 IRC_Display( IRC_MakeEvent(KICK, 0) , IRC_String( arg_values[ 1 ] ) , IRC_String( arg_values[ 2 ] ) );
1387 }
1388 return IRC_CMD_SUCCESS;
1389 }
1390
1391
1392 /*
1393 * Received NICK
1394 *
1395 * While the AA client does not support changing the current nickname,
1396 * it is still possible to receive a NICK applying to the connected user
1397 * because of e.g. OperServ's SVSNICK command.
1398 */
IRCH_Nick()1399 static int IRCH_Nick( )
1400 {
1401 int event;
1402
1403 if ( IRC_ReceivedMessage.arg_count != 1 )
1404 return IRC_CMD_SUCCESS;
1405
1406 if ( !strcmp( IRC_String( pfx_nickOrServer ) , IRC_User.nick ) ) {
1407 strncpy( IRC_User.nick , IRC_String( arg_values[ 0 ] ) , 15 );
1408 Com_Printf( "%s\n", IRC_User.nick );
1409 event = IRC_MakeEvent(NICK_CHANGE, 1);
1410 } else {
1411 event = IRC_MakeEvent(NICK_CHANGE, 0);
1412 }
1413 IRC_Display( event , IRC_String( pfx_nickOrServer ) , IRC_String( arg_values[ 0 ] ) );
1414 return IRC_CMD_SUCCESS;
1415 }
1416
1417
1418 /*
1419 * Handles an actual message.
1420 */
IRC_HandleMessage(qboolean is_channel,const char * string)1421 static int IRC_HandleMessage( qboolean is_channel , const char * string )
1422 {
1423 if ( is_channel ) {
1424 IRC_Display( IRC_MakeEvent(SAY, 0) , IRC_String( pfx_nickOrServer ) , string );
1425 return IRC_CMD_SUCCESS;
1426 }
1427
1428 if ( IRC_CheckEventRate( IRC_RL_MESSAGE ) )
1429 return IRC_Send( "PRIVMSG %s :Sorry, AlienArena's IRC client does not support private messages" , IRC_String( pfx_nickOrServer ) );
1430 return IRC_CMD_SUCCESS;
1431 }
1432
1433
1434 /*
1435 * Splits a CTCP message into action and argument, then call
1436 * its handler (if there is one).
1437 */
IRC_HandleCTCP(qboolean is_channel,char * string,int string_len)1438 static int IRC_HandleCTCP( qboolean is_channel , char * string , int string_len )
1439 {
1440 char * end_of_action;
1441
1442 end_of_action = strchr( string , ' ' );
1443 if ( end_of_action == NULL ) {
1444 end_of_action = string + string_len - 1;
1445 *end_of_action = 0;
1446 } else {
1447 *( string + string_len - 1 ) = 0;
1448 *end_of_action = 0;
1449 end_of_action ++;
1450 }
1451
1452 #if defined DEBUG_DUMP_IRC
1453 Com_Printf( "--- IRC/CTCP ---\n" );
1454 Com_Printf( " Command: %s\n Argument(s): %s\n" , string , end_of_action );
1455 #endif
1456
1457 return IRC_ExecuteCTCPHandler( string , is_channel , end_of_action );
1458 }
1459
1460
1461
1462 /*
1463 * Received PRIVMSG.
1464 *
1465 * This is either an actual message (to the channel or to the user) or a
1466 * CTCP command (action, version, etc...)
1467 */
IRCH_PrivMsg()1468 static int IRCH_PrivMsg( )
1469 {
1470 qboolean is_channel;
1471
1472 if ( IRC_ReceivedMessage.arg_count != 2 ) {
1473 return IRC_CMD_SUCCESS;
1474 }
1475
1476 // Check message to channel (bail out if it isn't our channel)
1477 is_channel = IRC_String( arg_values[ 0 ] )[ 0 ] == '#';
1478 if ( is_channel && strcmp( &( IRC_String( arg_values[ 0 ] )[ 1 ] ) , cl_IRC_channel->string ) )
1479 return IRC_CMD_SUCCESS;
1480
1481 if ( IRC_Length( arg_values[ 1 ] ) > 2
1482 && IRC_String( arg_values[ 1 ] )[ 0 ] == 1
1483 && IRC_String( arg_values[ 1 ] )[ IRC_Length( arg_values[ 1 ] ) - 1 ] == 1 ) {
1484 return IRC_HandleCTCP( is_channel , IRC_String( arg_values[ 1 ] ) + 1 , IRC_Length( arg_values[ 1 ] ) - 1 );
1485 }
1486
1487 return IRC_HandleMessage( is_channel , IRC_String( arg_values[ 1 ] ) );
1488 }
1489
1490
1491 /*
1492 * User is banned. Leave and do not come back.
1493 */
IRCH_Banned()1494 static int IRCH_Banned( )
1495 {
1496 IRC_Display( IRC_MakeEvent(QUIT, 1) , "" , "banned from channel.." );
1497 IRC_Send( "QUIT :b&!" );
1498 return IRC_CMD_FATAL;
1499 }
1500
1501
1502
1503 /*--------------------------------------------------------------------------*/
1504 /* CTCP COMMAND HANDLERS */
1505 /*--------------------------------------------------------------------------*/
1506
1507 /*
1508 * Action command aka "/me"
1509 */
CTCP_Action(qboolean is_channel,const char * argument)1510 static int CTCP_Action( qboolean is_channel , const char * argument )
1511 {
1512 if ( !*argument )
1513 return IRC_CMD_SUCCESS;
1514
1515 if ( is_channel ) {
1516 IRC_Display( IRC_MakeEvent(ACT, 0) , IRC_String( pfx_nickOrServer ) , argument );
1517 return IRC_CMD_SUCCESS;
1518 }
1519
1520 if ( IRC_CheckEventRate( IRC_RL_MESSAGE ) )
1521 return IRC_Send( "PRIVMSG %s :Sorry, AlienArena's IRC client does not support private messages" , IRC_String( pfx_nickOrServer ) );
1522 return IRC_CMD_SUCCESS;
1523 }
1524
1525
1526 /*
1527 * PING requests
1528 */
CTCP_Ping(qboolean is_channel,const char * argument)1529 static int CTCP_Ping( qboolean is_channel , const char * argument )
1530 {
1531 if ( is_channel || !IRC_CheckEventRate( IRC_RL_PING ) )
1532 return IRC_CMD_SUCCESS;
1533
1534 if ( *argument )
1535 return IRC_Send( "NOTICE %s :\001PING %s\001" , IRC_String( pfx_nickOrServer ) , argument );
1536
1537 return IRC_Send( "NOTICE %s :\001PING\001" , IRC_String( pfx_nickOrServer ) );
1538 }
1539
1540
1541 /*
1542 * VERSION requests, let's advertise AA a lil'.
1543 */
CTCP_Version(qboolean is_channel,const char * argument)1544 static int CTCP_Version( qboolean is_channel , const char * argument )
1545 {
1546 if ( is_channel || !IRC_CheckEventRate( IRC_RL_VERSION ) )
1547 return IRC_CMD_SUCCESS;
1548
1549 return IRC_Send( "NOTICE %s :\001VERSION AlienArena IRC client - v" VERSION "\001" , IRC_String( pfx_nickOrServer ) );
1550 }
1551
1552
1553
1554 /*--------------------------------------------------------------------------*/
1555 /* MESSAGE SENDING */
1556 /*--------------------------------------------------------------------------*/
1557
1558 /* Maximal message length */
1559 #define IRC_MAX_SEND_LEN 400
1560
1561 /*
1562 * The message sending queue is used to avoid having to send stuff from the
1563 * game's main thread, as it could block or cause mix-ups in the printing
1564 * function.
1565 */
1566
1567 struct irc_sendqueue_t
1568 {
1569 qboolean has_content;
1570 qboolean is_action;
1571 char string[IRC_MAX_SEND_LEN];
1572 };
1573
1574 /* Length of the IRC send queue */
1575 #define IRC_SENDQUEUE_SIZE 16
1576
1577 /* Index of the next message to process */
1578 static int IRC_SendQueue_Process = 0;
1579 /* Index of the next message to write */
1580 static int IRC_SendQueue_Write = 0;
1581
1582 /* The queue */
1583 static struct irc_sendqueue_t IRC_SendQueue[ IRC_SENDQUEUE_SIZE ];
1584
1585
1586 /*
1587 * Initialise the send queue.
1588 */
IRC_InitSendQueue()1589 static inline void IRC_InitSendQueue( )
1590 {
1591 memset( &IRC_SendQueue , 0 , sizeof( IRC_SendQueue ) );
1592 }
1593
1594
1595 /*
1596 * Writes an entry to the send queue.
1597 */
IRC_AddSendItem(qboolean is_action,const char * string)1598 static qboolean IRC_AddSendItem( qboolean is_action , const char * string )
1599 {
1600 if ( IRC_SendQueue[ IRC_SendQueue_Write ].has_content )
1601 return false;
1602
1603 strcpy( IRC_SendQueue[ IRC_SendQueue_Write ].string , string );
1604 IRC_SendQueue[ IRC_SendQueue_Write ].is_action = is_action;
1605 IRC_SendQueue[ IRC_SendQueue_Write ].has_content = true;
1606 IRC_SendQueue_Write = ( IRC_SendQueue_Write + 1 ) % IRC_SENDQUEUE_SIZE;
1607 return true;
1608 }
1609
1610
1611 /*
1612 * Sends an IRC message (console command).
1613 */
CL_IRCSay()1614 void CL_IRCSay( )
1615 {
1616 char m_sendstring[480];
1617 qboolean send_result;
1618
1619 if (Cmd_Argc() != 2) {
1620 Com_Printf ("usage: irc_say <text>\n");
1621 return;
1622 }
1623
1624 if ( IRC_ThreadStatus != IRC_THREAD_JOINED ) {
1625 Com_Printf("IRC: Not connected\n");
1626 return;
1627 }
1628
1629 memset( m_sendstring , 0 , sizeof( m_sendstring ) );
1630 strncpy( m_sendstring , Cmd_Argv(1) , 479 );
1631 if ( m_sendstring[ 0 ] == 0 )
1632 return;
1633
1634 if ( ( m_sendstring[ 0 ] == '/' || m_sendstring[ 0 ] == '.' ) && !Q_strnicmp( m_sendstring + 1 , "me " , 3 ) && m_sendstring[ 4 ] != 0 ) {
1635 send_result = IRC_AddSendItem( true , m_sendstring + 4 );
1636 } else {
1637 send_result = IRC_AddSendItem( false , m_sendstring );
1638 }
1639
1640 if ( !send_result )
1641 Com_Printf( "IRC: flood detected, message not sent\n" );
1642 }
1643
1644
1645 /*
1646 * Processes the next item on the send queue, if any.
1647 */
IRC_ProcessSendQueue()1648 static qboolean IRC_ProcessSendQueue( )
1649 {
1650 const char * fmt_string;
1651 int event , rv;
1652
1653 if ( !IRC_SendQueue[ IRC_SendQueue_Process ].has_content )
1654 return true;
1655
1656 if ( IRC_SendQueue[ IRC_SendQueue_Process ].is_action ) {
1657 fmt_string = "PRIVMSG #%s :\001ACTION %s\001";
1658 event = IRC_MakeEvent(ACT, 1);
1659 } else {
1660 fmt_string = "PRIVMSG #%s :%s";
1661 event = IRC_MakeEvent(SAY, 1);
1662 }
1663
1664 rv = IRC_Send( fmt_string , cl_IRC_channel->string , IRC_SendQueue[ IRC_SendQueue_Process ].string );
1665 if ( rv == IRC_CMD_SUCCESS ) {
1666 IRC_Display( event , IRC_User.nick , IRC_SendQueue[ IRC_SendQueue_Process ].string );
1667 }
1668 IRC_SendQueue[ IRC_SendQueue_Process ].has_content = false;
1669 IRC_SendQueue_Process = ( IRC_SendQueue_Process + 1 ) % IRC_SENDQUEUE_SIZE;
1670 return ( rv == IRC_CMD_SUCCESS );
1671 }
1672
1673
1674
1675 /*
1676 * Attempts to receive data from the server. If data is received, parse it
1677 * and attempt to execute a handler for each complete message.
1678 */
IRC_ProcessData(void)1679 static int IRC_ProcessData(void)
1680 {
1681 char buffer[ IRC_RECV_BUF_SIZE ];
1682 int i , len , err_code;
1683
1684 len = recv( IRC_Socket, buffer, IRC_RECV_BUF_SIZE, 0 );
1685
1686 // Handle errors / remote disconnects
1687 if ( len <= 0 ) {
1688 if ( len < 0 )
1689 IRC_HandleError( );
1690 IRC_ThreadStatus = IRC_THREAD_QUITTING;
1691 return IRC_CMD_RETRY;
1692 }
1693
1694 for ( i = 0 ; i < len ; i ++ ) {
1695 if ( IRC_Parser( buffer[ i ] ) ) {
1696 #ifdef DEBUG_DUMP_IRC
1697 IRC_DumpMessage( );
1698 #endif // DEBUG_DUMP_IRC
1699 err_code = IRC_ExecuteHandler( );
1700 if ( err_code != IRC_CMD_SUCCESS )
1701 return err_code;
1702 }
1703 }
1704
1705 return IRC_CMD_SUCCESS;
1706 }
1707
1708
1709 /*
1710 * Prepares the user record which is used when issuing the USER command.
1711 */
IRC_InitialiseUser(const char * name)1712 static qboolean IRC_InitialiseUser( const char * name )
1713 {
1714 qboolean ovrnn;
1715 const char * source;
1716 int i = 0, j = 0;
1717 int replaced = 0;
1718 char c;
1719
1720 ovrnn = cl_IRC_override_nickname->integer && strlen( cl_IRC_nickname->name );
1721 source = ovrnn ? cl_IRC_nickname->string : name;
1722
1723 // Strip color chars for the player's name, and remove special
1724 // characters
1725 IRC_User.nicklen = 0;
1726 IRC_User.nickattempts = 1;
1727 while ( j < 15 ) {
1728 if ( !ovrnn ) {
1729 // Only process color escape codes if the nickname
1730 // is being computed from the player source
1731 if ( i == 32 || !source[i] ) {
1732 IRC_User.nick[j ++] = 0;
1733 continue;
1734 }
1735 if ( source[i] == Q_COLOR_ESCAPE ) {
1736 i ++;
1737 if ( source[i] != Q_COLOR_ESCAPE ) {
1738 if ( source[i] )
1739 i ++;
1740 continue;
1741 }
1742 }
1743 }
1744
1745 c = source[i ++];
1746 if ( j == 0 && !( IS_ALPHA( c ) || strchr( "[]\\`_^{|}" , c ) ) ) {
1747 c = '_';
1748 replaced ++;
1749 } else if ( j > 0 && !( IS_ALNUM( c ) || strchr( "-[]\\`_^{|}" , c ) ) ) {
1750 c = '_';
1751 replaced ++;
1752 }
1753 IRC_User.nick[j] = c;
1754
1755 // User names are even more sensitive
1756 if ( ! ( c == '-' || c == '.' || c == '_' || IS_ALNUM( c ) ) )
1757 c = '_';
1758 IRC_User.username[j] = c;
1759
1760 IRC_User.nicklen = ++j;
1761 }
1762
1763 // If the nickname is overriden and its modified value differs,
1764 // then it is invalid
1765 if ( ovrnn && strcmp( source , IRC_User.nick ) )
1766 return false;
1767
1768 // Set static address
1769 strcpy( IRC_User.email, "mymail@mail.com" );
1770
1771 return ( IRC_User.nicklen > 0 && replaced < IRC_User.nicklen / 2 );
1772 }
1773
1774
1775 /*
1776 * Establishes the IRC connection, sets the nick, etc...
1777 */
1778 #define CHECK_SHUTDOWN { if ( IRC_QuitRequested ) return IRC_CMD_FATAL; }
1779 #define CHECK_SHUTDOWN_CLOSE { if ( IRC_QuitRequested ) { closesocket( IRC_Socket ); return IRC_CMD_FATAL; } }
IRC_AttemptConnection()1780 static int IRC_AttemptConnection( )
1781 {
1782 struct sockaddr_in address; // socket address
1783 struct hostent * host; // host lookup
1784 char host_name[100]; // host name
1785 char name[32]; // player's name
1786 int err_code;
1787 int port;
1788
1789 CHECK_SHUTDOWN;
1790 Com_Printf("...IRC: connecting to server\n");
1791
1792 // Force players to use a non-default name
1793 strcpy( name, Cvar_VariableString( "name" ) );
1794 if (! Q_strnicmp( name , "player" , 7 ) ) {
1795 Com_Printf("...IRC: rejected due to unset player name\n");
1796 return IRC_CMD_FATAL;
1797 }
1798
1799 // Prepare USER record
1800 if (! IRC_InitialiseUser( name ) ) {
1801 Com_Printf("...IRC: rejected due to mostly unusable player name\n");
1802 return IRC_CMD_FATAL;
1803 }
1804
1805 // Find server address
1806 Q_strncpyz2( host_name, cl_IRC_server->string, sizeof(host_name) );
1807 if ( (host=gethostbyname(host_name)) == NULL ) {
1808 Com_Printf("...IRC: unknown server\n");
1809 return IRC_CMD_FATAL;
1810 }
1811
1812 // Create socket
1813 CHECK_SHUTDOWN;
1814 if ( (IRC_Socket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET ) {
1815 IRC_HandleError( );
1816 return IRC_CMD_FATAL;
1817 }
1818
1819 // Initialise socket address
1820 port = cl_IRC_port->integer;
1821 if ( port <= 0 || port >= 65536 ) {
1822 Com_Printf("IRC: invalid port number, defaulting to 6667\n");
1823 port = 6667;
1824 }
1825 address.sin_family = AF_INET;
1826 address.sin_port = htons( port );
1827 address.sin_addr.s_addr = *((unsigned long *) host->h_addr);
1828
1829 // Attempt connection
1830 if ( (connect(IRC_Socket,(struct sockaddr *) &address, sizeof(address))) != 0) {
1831 closesocket(IRC_Socket);
1832 Com_Printf("...IRC connection refused.\n");
1833 return IRC_CMD_RETRY;
1834 }
1835
1836 // Send username and nick name
1837 CHECK_SHUTDOWN_CLOSE;
1838 err_code = IRC_Send( "USER %s %s %s :%s" , IRC_User.username , IRC_User.email , host_name , IRC_User.nick );
1839 if ( err_code == IRC_CMD_SUCCESS )
1840 err_code = IRC_SendNickname( );
1841 if ( err_code != IRC_CMD_SUCCESS ) {
1842 closesocket(IRC_Socket);
1843 return err_code;
1844 }
1845
1846 // Initialise parser and set thread state
1847 IRC_ParserState = IRC_PARSER_START;
1848 IRC_ThreadStatus = IRC_THREAD_SETNICK;
1849
1850 CHECK_SHUTDOWN_CLOSE;
1851 Com_Printf("...Connected to IRC server\n");
1852 return IRC_CMD_SUCCESS;
1853 }
1854
1855
1856
1857 /*
1858 * Attempt to connect to the IRC server for the first time.
1859 * Only retry a few times and assume the server's dead/does not exist if
1860 * connection can't be established.
1861 */
IRC_InitialConnect()1862 static qboolean IRC_InitialConnect( )
1863 {
1864 int err_code , retries = 3;
1865 int rc_delay = cl_IRC_reconnect_delay->integer;
1866 if ( rc_delay < 5 )
1867 rc_delay = 5;
1868
1869 err_code = IRC_CMD_SUCCESS;
1870 IRC_ThreadStatus = IRC_THREAD_CONNECTING;
1871 do {
1872 // If we're re-attempting a connection, wait a little bit,
1873 // or we might just piss the server off.
1874 if ( err_code == IRC_CMD_RETRY ) {
1875 IRC_Sleep( rc_delay );
1876 } else if ( IRC_QuitRequested ) {
1877 return false;
1878 }
1879
1880 err_code = IRC_AttemptConnection( );
1881 } while ( err_code == IRC_CMD_RETRY && --retries > 0 );
1882
1883 return ( err_code == IRC_CMD_SUCCESS );
1884 }
1885
1886
1887
1888 /*
1889 * Attempt to reconnect to the IRC server. Only stop trying on fatal errors
1890 * or if the thread's status is set to QUITTING.
1891 */
IRC_Reconnect()1892 static int IRC_Reconnect( )
1893 {
1894 int err_code;
1895 int rc_delay = cl_IRC_reconnect_delay->integer;
1896 if ( rc_delay < 5 )
1897 rc_delay = 5;
1898
1899 err_code = IRC_CMD_SUCCESS;
1900 IRC_ThreadStatus = IRC_THREAD_CONNECTING;
1901 do {
1902 IRC_Sleep( ( err_code == IRC_CMD_SUCCESS ) ? ( rc_delay >> 1 ) : rc_delay );
1903 if ( IRC_QuitRequested ) {
1904 return IRC_CMD_FATAL;
1905 }
1906 err_code = IRC_AttemptConnection( );
1907 } while ( err_code == IRC_CMD_RETRY );
1908
1909 return err_code;
1910 }
1911
1912
1913
1914
1915
1916 /*
1917 * IRC main loop. Once the initial connection has been established, either
1918 * 1) pump messages or 2) handle delayed functions. Try re-connecting if
1919 * connection is lost.
1920 */
IRC_MainLoop()1921 static void IRC_MainLoop()
1922 {
1923 int err_code;
1924
1925 // Connect to server
1926 if (! IRC_InitialConnect() )
1927 return;
1928
1929 do {
1930 do {
1931 // If we must quit, send the command.
1932 if ( IRC_QuitRequested && IRC_ThreadStatus != IRC_THREAD_QUITTING ) {
1933 IRC_ThreadStatus = IRC_THREAD_QUITTING;
1934 IRC_Display( IRC_MakeEvent(QUIT,1) , "" , "quit from menu" );
1935 err_code = IRC_Send( "QUIT :AlienArena IRC %s" , VERSION );
1936 } else {
1937 // Wait for data or 1s timeout
1938 err_code = IRC_Wait( );
1939 if ( err_code == IRC_CMD_SUCCESS ) {
1940 // We have some data, process it
1941 err_code = IRC_ProcessData( );
1942 } else if ( err_code == IRC_CMD_RETRY ) {
1943 // Timed out, handle timers and update rate limiter
1944 err_code = IRC_ProcessDEQueue();
1945 IRC_UpdateRateLimiter( );
1946 } else {
1947 // Disconnected, but reconnection should be attempted
1948 err_code = IRC_CMD_RETRY;
1949 }
1950
1951 if ( err_code == IRC_CMD_SUCCESS && ! IRC_QuitRequested )
1952 err_code = IRC_ProcessSendQueue( ) ? IRC_CMD_SUCCESS : IRC_CMD_RETRY;
1953 }
1954 } while ( err_code == IRC_CMD_SUCCESS );
1955 closesocket( IRC_Socket );
1956
1957 // If we must quit, let's skip trying to reconnect
1958 if ( IRC_QuitRequested || err_code == IRC_CMD_FATAL )
1959 return;
1960
1961 // Reconnect to server
1962 do {
1963 err_code = IRC_Reconnect( );
1964 } while ( err_code == IRC_CMD_RETRY );
1965 } while ( err_code != IRC_CMD_FATAL );
1966 }
1967
1968
1969
1970 /*
1971 * Main function of the IRC thread: initialise command handlers,
1972 * start the main loop, and uninitialise handlers after the loop
1973 * exits.
1974 */
IRC_Thread()1975 static void IRC_Thread( )
1976 {
1977 // Init. send queue & rate limiter
1978 IRC_InitSendQueue( );
1979 IRC_InitRateLimiter( );
1980 IRC_InitHandlers( );
1981
1982 // Init. IRC handlers
1983 IRC_AddHandler( "PING" , &IRCH_Ping ); // Ping request
1984 IRC_AddHandler( "ERROR" , &IRCH_ServerError ); // Server error
1985 IRC_AddHandler( "JOIN" , &IRCH_Joined ); // Channel join
1986 IRC_AddHandler( "PART" , &IRCH_Part ); // Channel part
1987 IRC_AddHandler( "QUIT" , &IRCH_Quit ); // Client quit
1988 IRC_AddHandler( "PRIVMSG" , &IRCH_PrivMsg ); // Message or CTCP
1989 IRC_AddHandler( "KICK" , &IRCH_Kick ); // Kick
1990 IRC_AddHandler( "NICK" , &IRCH_Nick ); // Nick change
1991 IRC_AddHandler( "001" , &IRCH_Connected ); // Connection established
1992 IRC_AddHandler( "404" , &IRCH_Banned ); // Banned (when sending message)
1993 IRC_AddHandler( "432" , &IRCH_FatalError ); // Erroneous nick name
1994 IRC_AddHandler( "433" , &IRCH_NickError ); // Nick name in use
1995 IRC_AddHandler( "474" , &IRCH_Banned ); // Banned (when joining)
1996
1997 // Init. CTCP handlers
1998 IRC_AddCTCPHandler( "ACTION" , &CTCP_Action ); // "/me"
1999 IRC_AddCTCPHandler( "PING" , &CTCP_Ping );
2000 IRC_AddCTCPHandler( "VERSION" , &CTCP_Version );
2001
2002 // Enter loop
2003 IRC_MainLoop( );
2004
2005 // Clean up
2006 Com_Printf( "...IRC: disconnected from server\n" );
2007 IRC_FlushDEQueue( );
2008 IRC_FreeHandlers( );
2009 IRC_SetThreadDead( );
2010 }
2011
2012
2013
2014 /*
2015 * Caution: IRC_SystemThreadProc(), IRC_StartThread() and IRC_WaitThread()
2016 * have separate "VARIANTS".
2017 *
2018 * Note different prototypes for IRC_SystemThreadProc() and completely
2019 * different IRC_StartThread()/IRC_WaitThread() implementations.
2020 */
2021 #if defined WIN32_VARIANT
2022
2023 /****** THREAD HANDLING - WINDOWS VARIANT ******/
2024
2025 static HANDLE IRC_ThreadHandle = NULL;
2026
IRC_SystemThreadProc(LPVOID dummy)2027 static DWORD WINAPI IRC_SystemThreadProc( LPVOID dummy)
2028 {
2029 IRC_Thread( );
2030 return 0;
2031 }
2032
2033
IRC_StartThread()2034 static void IRC_StartThread()
2035 {
2036 if ( IRC_ThreadHandle == NULL )
2037 IRC_ThreadHandle = CreateThread( NULL , 0 , IRC_SystemThreadProc , NULL , 0 , NULL );
2038 }
2039
2040
IRC_SetThreadDead()2041 static void IRC_SetThreadDead( )
2042 {
2043 IRC_ThreadStatus = IRC_THREAD_DEAD;
2044 IRC_ThreadHandle = NULL;
2045 }
2046
2047
IRC_WaitThread()2048 static void IRC_WaitThread()
2049 {
2050 if ( IRC_ThreadHandle != NULL ) {
2051 if ( IRC_ThreadStatus != IRC_THREAD_DEAD ) {
2052 WaitForSingleObject( IRC_ThreadHandle , 10000 );
2053 CloseHandle( IRC_ThreadHandle );
2054 }
2055 IRC_ThreadHandle = NULL;
2056 }
2057 }
2058
2059 #elif defined UNIX_VARIANT
2060
2061 /****** THREAD HANDLING - UNIX VARIANT ******/
2062
2063 static pthread_t IRC_ThreadHandle = (pthread_t) NULL;
2064
2065
IRC_SystemThreadProc(void * dummy)2066 static void *IRC_SystemThreadProc(void *dummy)
2067 {
2068 IRC_Thread( );
2069 return NULL;
2070 }
2071
2072
IRC_StartThread(void)2073 static void IRC_StartThread(void)
2074 {
2075 if ( IRC_ThreadHandle == (pthread_t) NULL )
2076 pthread_create( &IRC_ThreadHandle , NULL , IRC_SystemThreadProc , NULL );
2077 }
2078
2079
IRC_SetThreadDead()2080 static void IRC_SetThreadDead( )
2081 {
2082 IRC_ThreadStatus = IRC_THREAD_DEAD;
2083 IRC_ThreadHandle = (pthread_t) NULL;
2084 }
2085
2086
IRC_WaitThread()2087 static void IRC_WaitThread()
2088 {
2089 if ( IRC_ThreadHandle != (pthread_t) NULL ) {
2090 if ( IRC_ThreadStatus != IRC_THREAD_DEAD )
2091 pthread_join( IRC_ThreadHandle , NULL );
2092 IRC_ThreadHandle = (pthread_t) NULL;
2093 }
2094 }
2095
2096 #endif
2097
2098
CL_IRCSetup(void)2099 void CL_IRCSetup(void)
2100 {
2101 cl_IRC_connect_at_startup = Cvar_Get( "cl_IRC_connect_at_startup" , "1" , CVAR_ARCHIVE );
2102 cl_IRC_server = Cvar_Get( "cl_IRC_server" , "irc.planetarena.org" , CVAR_ARCHIVE );
2103 cl_IRC_channel = Cvar_Get( "cl_IRC_channel" , "alienarena" , CVAR_ARCHIVE );
2104 cl_IRC_port = Cvar_Get( "cl_IRC_port" , "6667" , CVAR_ARCHIVE );
2105 cl_IRC_override_nickname = Cvar_Get( "cl_IRC_override_nickname" , "0" , CVAR_ARCHIVE );
2106 cl_IRC_nickname = Cvar_Get( "cl_IRC_nickname" , "" , CVAR_ARCHIVE );
2107 cl_IRC_kick_rejoin = Cvar_Get( "cl_IRC_kick_rejoin" , "0" , CVAR_ARCHIVE );
2108 cl_IRC_reconnect_delay = Cvar_Get( "cl_IRC_reconnect_delay" , "100" , CVAR_ARCHIVE );
2109
2110 if ( cl_IRC_connect_at_startup->value )
2111 CL_InitIRC( );
2112 }
2113
2114
CL_InitIRC(void)2115 void CL_InitIRC(void)
2116 {
2117 if ( IRC_ThreadStatus != IRC_THREAD_DEAD ) {
2118 Com_Printf( "...IRC thread is already running\n" );
2119 return;
2120 }
2121 IRC_QuitRequested = false;
2122 IRC_ThreadStatus = IRC_THREAD_INITIALISING;
2123 IRC_StartThread( );
2124 }
2125
2126
CL_IRCInitiateShutdown(void)2127 void CL_IRCInitiateShutdown(void)
2128 {
2129 IRC_QuitRequested = true;
2130 }
2131
2132
CL_IRCWaitShutdown(void)2133 void CL_IRCWaitShutdown( void )
2134 {
2135 IRC_WaitThread( );
2136 }
2137
2138
CL_IRCIsConnected(void)2139 qboolean CL_IRCIsConnected(void)
2140 {
2141 return ( IRC_ThreadStatus == IRC_THREAD_JOINED );
2142 }
2143
2144
CL_IRCIsRunning(void)2145 qboolean CL_IRCIsRunning(void)
2146 {
2147 return ( IRC_ThreadStatus != IRC_THREAD_DEAD );
2148 }
2149