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