1 /**
2  * @file
3  * @brief IRC client implementation for UFO:AI
4  */
5 
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 Most of this stuff comes from Warsow
10 
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License
13 as published by the Free Software Foundation; either version 2
14 of the License, or (at your option) any later version.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 
20 See the GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 
26 */
27 
28 #include "cl_irc.h"
29 #include "client.h"
30 #include "cl_language.h"
31 #include "ui/ui_main.h"
32 #include "ui/ui_nodes.h"
33 #include "ui/ui_popup.h"
34 #include "battlescape/cl_hud.h"
35 #include "cgame/cl_game.h"
36 
37 #ifdef _WIN32
38 #	include <winerror.h>
39 #else
40 #	include <netinet/in.h>
41 #	include <arpa/inet.h>
42 #	include <netdb.h>
43 #	include <fcntl.h>
44 #endif
45 
46 static cvar_t* irc_server;
47 static cvar_t* irc_port;
48 static cvar_t* irc_channel;
49 static cvar_t* irc_nick;
50 static cvar_t* irc_user;
51 static cvar_t* irc_password;
52 static cvar_t* irc_topic;
53 static cvar_t* irc_defaultChannel;
54 static cvar_t* irc_logConsole;
55 static cvar_t* irc_showIfNotInMenu;
56 /* menu cvar */
57 static cvar_t* irc_send_buffer;
58 static memPool_t* cl_ircSysPool;
59 
60 static bool irc_connected;
61 
62 #define IRC_SEND_BUF_SIZE 512
63 #define IRC_RECV_BUF_SIZE 1024
64 
65 typedef struct irc_user_s {
66 	char key[MAX_VAR];
67 	struct irc_user_s* next;
68 } irc_user_t;
69 
70 typedef struct irc_channel_s {
71 	char name[MAX_VAR];
72 	char topic[256];
73 	int users;
74 	irc_user_t* user;
75 } irc_channel_t;
76 
77 /* numeric commands as specified by RFC 1459 - Internet Relay Chat Protocol */
78 typedef enum irc_numeric_e {
79 	/* command replies */
80 	RPL_WELCOME = 1,				/* ":Welcome to the Internet Relay Network <nick>!<user>@<host>" */
81 	RPL_YOURHOST = 2,				/* ":Your host is <servername>, running version <ver>" */
82 	RPL_CREATED = 3,				/* ":This server was created <date>" */
83 	RPL_MYINFO = 4,					/* "<servername> <version> <available user modes> <available channel modes>" */
84 	RPL_ISUPPORT = 5,				/* "<nick> <parameter> * :are supported by this server" */
85 	RPL_HELLO = 20,					/* ":Please wait while we process your connection" */
86 	RPL_NONE = 300,
87 	RPL_USERHOST = 302,				/* ":[<reply>{<space><reply>}]" */
88 	RPL_ISON = 303,					/* ":[<nick> {<space><nick>}]" */
89 	RPL_AWAY = 301,					/* "<nick> :<away message>" */
90 	RPL_UNAWAY = 305,				/* ":You are no longer marked as being away" */
91 	RPL_NOWAWAY = 306,				/* ":You have been marked as being away" */
92 	RPL_WHOISUSER = 311,			/* "<nick> <user> <host> * :<real name>" */
93 	RPL_WHOISSERVER = 312,			/* "<nick> <server> :<server info>" */
94 	RPL_WHOISOPERATOR = 313,		/* "<nick> :is an IRC operator" */
95 	RPL_WHOISIDLE = 317,			/* "<nick> <integer> :seconds idle" */
96 	RPL_ENDOFWHOIS = 318,			/* "<nick> :End of /WHOIS list" */
97 	RPL_WHOISCHANNELS = 319,		/* "<nick> :{[@|+]<channel><space>}" */
98 	RPL_WHOWASUSER = 314,			/* "<nick> <user> <host> * :<real name>" */
99 	RPL_ENDOFWHOWAS = 369,			/* "<nick> :End of WHOWAS" */
100 	RPL_WHOISACCOUNT = 330,			/* "<nick> <account> :is logged in as" */
101 
102 	RPL_LISTSTART = 321,			/* "Channel :Users  Name" */
103 	RPL_LIST = 322,					/* "<channel> <# visible> :<topic>" */
104 	RPL_LISTEND = 323,				/* ":End of /LIST" */
105 	RPL_CHANNELMODEIS = 324,		/* "<channel> <mode> <mode params>" */
106 	RPL_NOTOPIC = 331,				/* "<channel> :No topic is set" */
107 	RPL_TOPIC = 332,				/* "<channel> :<topic>" */
108 	RPL_TOPICWHOTIME = 333,			/* "<channel> <nick> <time>" */
109 	RPL_INVITING = 341,				/* "<channel> <nick>" */
110 	RPL_SUMMONING = 342,			/* "<user> :Summoning user to IRC" */
111 	RPL_VERSION = 351,				/* "<version>.<debuglevel> <server> :<comments>" */
112 	RPL_WHOREPLY = 352,				/* "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
113 	RPL_ENDOFWHO = 315,				/* "<name> :End of /WHO list" */
114 	RPL_NAMREPLY = 353,				/* "<channel> :[[@|+]<nick> [[@|+]<nick> [...]]]" */
115 	RPL_ENDOFNAMES = 366,			/* "<channel> :End of /NAMES list" */
116 	RPL_LINKS = 364,				/* "<mask> <server> :<hopcount> <server info>" */
117 	RPL_ENDOFLINKS = 365,			/* "<mask> :End of /LINKS list" */
118 	RPL_BANLIST = 367,				/* "<channel> <banid>" */
119 	RPL_ENDOFBANLIST = 368,			/* "<channel> :End of channel ban list" */
120 	RPL_INFO = 371,					/* ":<string>" */
121 	RPL_ENDOFINFO = 374,			/* ":End of /INFO list" */
122 	RPL_MOTDSTART = 375,			/* ":- <server> Message of the day - " */
123 	RPL_MOTD = 372,					/* ":- <text>" */
124 	RPL_ENDOFMOTD = 376,			/* ":End of /MOTD command" */
125 	RPL_YOUREOPER = 381,			/* ":You are now an IRC operator" */
126 	RPL_REHASHING = 382,			/* "<config file> :Rehashing" */
127 	RPL_TIME = 391,					/* "<server> :<string showing server's local time>" */
128 	RPL_USERSSTART = 392,			/* ":UserID   Terminal  Host" */
129 	RPL_USERS = 393,				/* ":%-8s %-9s %-8s" */
130 	RPL_ENDOFUSERS = 394,			/* ":End of users" */
131 	RPL_NOUSERS = 395,				/* ":Nobody logged in" */
132 	RPL_TRACELINK = 200,			/* "Link <version & debug level> <destination> <next server>" */
133 	RPL_TRACECONNECTING = 201,		/* "Try. <class> <server>" */
134 	RPL_TRACEHANDSHAKE = 202,		/* "H.S. <class> <server>" */
135 	RPL_TRACEUNKNOWN = 203,			/* "???? <class> [<client IP address in dot form>]" */
136 	RPL_TRACEOPERATOR = 204,		/* "Oper <class> <nick>" */
137 	RPL_TRACEUSER = 205,			/* "User <class> <nick>" */
138 	RPL_TRACESERVER = 206,			/* "Serv <class> <int>S <int>C <server> <nick!user|*!*>@<host|server>" */
139 	RPL_TRACENEWTYPE = 208,			/* "<newtype> 0 <client name>" */
140 	RPL_TRACELOG = 261,				/* "File <logfile> <debug level>" */
141 	RPL_STATSLINKINFO = 211,		/* "<linkname> <sendq> <sent messages> <sent bytes> <received messages> <received bytes> <time open>" */
142 	RPL_STATSCOMMANDS = 212,		/* "<command> <count>" */
143 	RPL_STATSCLINE = 213,			/* "C <host> * <name> <port> <class>" */
144 	RPL_STATSNLINE = 214,			/* "N <host> * <name> <port> <class>" */
145 	RPL_STATSILINE = 215,			/* "I <host> * <host> <port> <class>" */
146 	RPL_STATSKLINE = 216,			/* "K <host> * <username> <port> <class>" */
147 	RPL_STATSYLINE = 218,			/* "Y <class> <ping frequency> <connectfrequency> <max sendq>" */
148 	RPL_ENDOFSTATS = 219,			/* "<stats letter> :End of /STATS report" */
149 	RPL_STATSLLINE = 241,			/* "L <hostmask> * <servername> <maxdepth>" */
150 	RPL_STATSUPTIME = 242,			/* ":Server Up %d days %d:%02d:%02d" */
151 	RPL_STATSOLINE = 243,			/* "O <hostmask> * <name>" */
152 	RPL_STATSHLINE = 244,			/* "H <hostmask> * <servername>" */
153 	RPL_UMODEIS = 221,				/* "<user mode string>" */
154 	RPL_LUSERCLIENT = 251,			/* ":There are <integer> users and <integer> invisible on <integer> servers" */
155 	RPL_LUSEROP = 252,				/* "<integer> :operator(s) online" */
156 	RPL_LUSERUNKNOWN = 253,			/* "<integer> :unknown connection(s)" */
157 	RPL_LUSERCHANNELS = 254,		/* "<integer> :channels formed" */
158 	RPL_LUSERME = 255,				/* ":I have <integer> clients and <integer> servers" */
159 	RPL_ADMINME = 256,				/* "<server> :Administrative info" */
160 	RPL_ADMINLOC1 = 257,			/* ":<admin info>" */
161 	RPL_ADMINLOC2 = 258,			/* ":<admin info>" */
162 	RPL_ADMINEMAIL = 259,			/* ":<admin info>" */
163 	RPL_LOCALUSERS = 265,
164 	RPL_GLOBALUSERS = 266,
165 
166 	/* error codes */
167 	ERR_NOSUCHNICK = 401,			/* "<nickname> :No such nick/channel" */
168 	ERR_NOSUCHSERVER = 402,			/* "<server name> :No such server" */
169 	ERR_NOSUCHCHANNEL = 403,		/* "<channel name> :No such channel" */
170 	ERR_CANNOTSENDTOCHAN = 404,		/* "<channel name> :Cannot send to channel" */
171 	ERR_TOOMANYCHANNELS = 405,		/* "<channel name> :You have joined too many channels" */
172 	ERR_WASNOSUCHNICK = 406,		/* "<nickname> :There was no such nickname" */
173 	ERR_TOOMANYTARGETS = 407,		/* "<target> :Duplicate recipients. No message delivered" */
174 	ERR_NOORIGIN = 409,				/* ":No origin specified" */
175 	ERR_NORECIPIENT = 411,			/* ":No recipient given (<command>)" */
176 	ERR_NOTEXTTOSEND = 412,			/* ":No text to send" */
177 	ERR_NOTOPLEVEL = 413,			/* "<mask> :No toplevel domain specified" */
178 	ERR_WILDTOPLEVEL = 414,			/* "<mask> :Wildcard in toplevel domain" */
179 	ERR_UNKNOWNCOMMAND = 421,		/* "<command> :Unknown command" */
180 	ERR_NOMOTD = 422,				/* ":MOTD File is missing" */
181 	ERR_NOADMININFO = 423,			/* "<server> :No administrative info available" */
182 	ERR_FILEERROR = 424,			/* ":File error doing <file op> on <file>" */
183 	ERR_NONICKNAMEGIVEN = 431,		/* ":No nickname given" */
184 	ERR_ERRONEUSNICKNAME = 432,		/* "<nick> :Erroneus nickname" */
185 	ERR_NICKNAMEINUSE = 433,		/* "<nick> :Nickname is already in use" */
186 	ERR_NICKCOLLISION = 436,		/* "<nick> :Nickname collision KILL" */
187 	ERR_BANNICKCHANGE = 437,
188 	ERR_NCHANGETOOFAST = 438,
189 	ERR_USERNOTINCHANNEL = 441,		/* "<nick> <channel> :They aren't on that channel" */
190 	ERR_NOTONCHANNEL = 442,			/* "<channel> :You're not on that channel" */
191 	ERR_USERONCHANNEL = 443,		/* "<user> <channel> :is already on channel" */
192 	ERR_NOLOGIN = 444,				/* "<user> :User not logged in" */
193 	ERR_SUMMONDISABLED = 445,		/* ":SUMMON has been disabled" */
194 	ERR_USERSDISABLED = 446,		/* ":USERS has been disabled" */
195 	ERR_NOTREGISTERED = 451,		/* ":You have not registered" */
196 	ERR_NEEDMOREPARAMS = 461,		/* "<command> :Not enough parameters" */
197 	ERR_ALREADYREGISTRED = 462,		/* ":You may not reregister" */
198 	ERR_NOPERMFORHOST = 463,		/* ":Your host isn't among the privileged" */
199 	ERR_PASSWDMISMATCH = 464,		/* ":Password incorrect" */
200 	ERR_YOUREBANNEDCREEP = 465,		/* ":You are banned from this server" */
201 	ERR_BADNAME = 468,				/* ":Your username is invalid" */
202 	ERR_KEYSET = 467,				/* "<channel> :Channel key already set" */
203 	ERR_CHANNELISFULL = 471,		/* "<channel> :Cannot join channel (+l)" */
204 	ERR_UNKNOWNMODE = 472,			/* "<char> :is unknown mode char to me" */
205 	ERR_INVITEONLYCHAN = 473,		/* "<channel> :Cannot join channel (+i)" */
206 	ERR_BANNEDFROMCHAN = 474,		/* "<channel> :Cannot join channel (+b)" */
207 	ERR_BADCHANNELKEY = 475,		/* "<channel> :Cannot join channel (+k)" */
208 	ERR_NOPRIVILEGES = 481,			/* ":Permission Denied- You're not an IRC operator" */
209 	ERR_CHANOPRIVSNEEDED = 482,		/* "<channel> :You're not channel operator" */
210 	ERR_CANTKILLSERVER = 483,		/* ":You cant kill a server!" */
211 	ERR_NOOPERHOST = 491,			/* ":No O-lines for your host" */
212 	ERR_UMODEUNKNOWNFLAG = 501,		/* ":Unknown MODE flag" */
213 	ERR_USERSDONTMATCH = 502,		/* ":Cant change mode for other users" */
214 	ERR_GHOSTEDCLIENT = 503,
215 	ERR_LAST_ERR_MSG = 504,
216 	ERR_SILELISTFULL = 511,
217 	ERR_NOSUCHGLINE = 512,
218 /*	ERR_TOOMANYWATCH = 512,*/
219 	ERR_BADPING = 513,
220 	ERR_TOOMANYDCC = 514,
221 	ERR_LISTSYNTAX = 521,
222 	ERR_WHOSYNTAX = 522,
223 	ERR_WHOLIMEXCEED = 523
224 } irc_numeric_t;
225 
226 typedef enum irc_command_type_e {
227 	IRC_COMMAND_NUMERIC,
228 	IRC_COMMAND_STRING
229 } irc_command_type_t;
230 
231 typedef enum irc_nick_prefix_e {
232 	IRC_NICK_PREFIX_NONE = ' ',
233 	IRC_NICK_PREFIX_OP = '@',
234 	IRC_NICK_PREFIX_VOICE = '+'
235 } irc_nick_prefix_t;
236 
237 /* commands can be numeric's or string */
238 typedef struct irc_command_s {
239 	union {
240 		/* tagged union */
241 		irc_numeric_t	numeric;
242 		const char* 	string;
243 	} id;
244 	irc_command_type_t	type;
245 } irc_command_t;
246 
247 /* server <- client messages */
248 typedef struct irc_server_msg_s {
249 	union {
250 		char string[IRC_SEND_BUF_SIZE];
251 		irc_numeric_t numeric;
252 	} id;
253 	irc_command_type_t type;
254 	char prefix[IRC_SEND_BUF_SIZE];
255 	char params[IRC_SEND_BUF_SIZE];
256 	char trailing[IRC_SEND_BUF_SIZE];
257 } irc_server_msg_t;
258 
259 static struct net_stream *irc_stream;
260 
261 static const char IRC_INVITE_FOR_A_GAME[] = "UFOAIINVITE;";
262 
263 static irc_channel_t ircChan;
264 static irc_channel_t* chan;
265 
266 static char irc_buffer[4096];
267 
268 static void Irc_Logic_RemoveChannelName(irc_channel_t* channel, const char* nick);
269 static void Irc_Logic_AddChannelName(irc_channel_t* channel, irc_nick_prefix_t prefix, const char* nick);
270 static void Irc_Client_Names_f(void);
271 static bool Irc_Client_Join(const char* channel, const char* password);
272 static void Irc_Logic_Disconnect(const char* reason);
273 
274 static bool Irc_AppendToBuffer(const char* const msg, ...) __attribute__((format(__printf__, 1, 2)));
275 static bool Irc_Proto_ParseServerMsg(const char* txt, size_t txt_len, irc_server_msg_t* msg);
276 static bool Irc_Proto_Enqueue(const char* msg, size_t msg_len);
277 
278 static bool Irc_Net_Connect(const char* host, const char* port);
279 static bool Irc_Net_Disconnect(void);
280 static void Irc_Net_Send(const char* msg, size_t msg_len);
281 
282 static void Irc_Connect_f(void);
283 static void Irc_Disconnect_f(void);
284 static void Irc_Input_Deactivate_f(void);
285 
286 /*
287 ===============================================================
288 Common functions
289 ===============================================================
290 */
291 
Irc_IsChannel(const char * target)292 static inline bool Irc_IsChannel (const char* target)
293 {
294 	assert(target);
295 	return (target[0] == '#' || target[0] == '&');
296 }
297 
Irc_ParseName(const char * mask,char * nick,size_t size,irc_nick_prefix_t * prefix)298 static void Irc_ParseName (const char* mask, char* nick, size_t size, irc_nick_prefix_t* prefix)
299 {
300 	const char* emph;
301 	if (mask[0] == IRC_NICK_PREFIX_OP || mask[0] == IRC_NICK_PREFIX_VOICE) {
302 		*prefix = (irc_nick_prefix_t) *mask;	/* read prefix */
303 		++mask;									/* crop prefix from mask */
304 	} else {
305 		*prefix = IRC_NICK_PREFIX_NONE;
306 	}
307 	emph = strchr(mask, '!');
308 	if (emph) {
309 		size_t length = emph - mask;
310 		if (length >= size - 1)
311 			length = size - 1;
312 		/* complete hostmask, crop anything after ! */
313 		memcpy(nick, mask, length);
314 		nick[length] = '\0';
315 	} else {
316 		/* just the nickname, use as is */
317 		Q_strncpyz(nick, mask, size);
318 	}
319 }
320 
321 /*
322 ===============================================================
323 Protocol functions
324 ===============================================================
325 */
326 
327 static cvar_t* irc_messageBucketSize;
328 static cvar_t* irc_messageBucketBurst;
329 static cvar_t* irc_characterBucketSize;
330 static cvar_t* irc_characterBucketBurst;
331 static cvar_t* irc_characterBucketRate;
332 
333 typedef struct irc_bucket_message_s {
334 	char* msg;
335 	size_t msg_len;
336 	struct irc_bucket_message_s* next;
337 } irc_bucket_message_t;
338 
339 typedef struct irc_bucket_s {
340 	irc_bucket_message_t* first_msg;	/**< pointer to first message in queue */
341 	unsigned int message_size;			/**< number of messages in bucket */
342 	unsigned int character_size;		/**< number of characters in bucket */
343 	int last_refill;				/**< last refill timestamp */
344 	double character_token;
345 } irc_bucket_t;
346 
347 static irc_bucket_t irc_bucket;
348 
349 /**
350  * @sa Irc_Proto_Disconnect
351  */
Irc_Proto_Connect(const char * host,const char * port)352 static bool Irc_Proto_Connect (const char* host, const char* port)
353 {
354 	const bool status = Irc_Net_Connect(host, port);
355 	if (!status) {
356 		irc_bucket.first_msg = nullptr;
357 		irc_bucket.message_size = 0;
358 		irc_bucket.character_size = 0;
359 		irc_bucket.last_refill = CL_Milliseconds();
360 		irc_bucket.character_token = (double)irc_characterBucketBurst->value;
361 	}
362 	return status;
363 }
364 
365 /**
366  * @sa Irc_Proto_Connect
367  */
Irc_Proto_Disconnect(void)368 static bool Irc_Proto_Disconnect (void)
369 {
370 	const bool status = Irc_Net_Disconnect();
371 	if (!status) {
372 		irc_bucket_message_t* msg = irc_bucket.first_msg;
373 		while (msg) {
374 			irc_bucket_message_t* prev = msg;
375 			msg = msg->next;
376 			Mem_Free(prev->msg);
377 			Mem_Free(prev);
378 		}
379 		irc_bucket.first_msg = nullptr;
380 		irc_bucket.message_size = 0;
381 		irc_bucket.character_size = 0;
382 	}
383 	return status;
384 }
385 
386 /**
387  * @sa Irc_Net_Send
388  */
Irc_Proto_Quit(const char * quitmsg)389 static bool Irc_Proto_Quit (const char* quitmsg)
390 {
391 	char msg[IRC_SEND_BUF_SIZE];
392 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "QUIT %s\r\n", quitmsg);
393 	msg[sizeof(msg) - 1] = '\0';
394 	Irc_Net_Send(msg, msg_len);	/* send immediately */
395 	return false;
396 }
397 
398 /**
399  * @sa Irc_Proto_Enqueue
400  */
Irc_Proto_Nick(const char * nick)401 static bool Irc_Proto_Nick (const char* nick)
402 {
403 	char msg[IRC_SEND_BUF_SIZE];
404 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "NICK %s\r\n", nick);
405 	msg[sizeof(msg) - 1] = '\0';
406 	return Irc_Proto_Enqueue(msg, msg_len);
407 }
408 
409 /**
410  * @sa Irc_Proto_Enqueue
411  */
Irc_Proto_User(const char * user,bool invisible,const char * name)412 static bool Irc_Proto_User (const char* user, bool invisible, const char* name)
413 {
414 	char msg[IRC_SEND_BUF_SIZE];
415 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "USER %s %c * :%s\r\n", user, invisible ? '8' : '0', name);
416 	msg[sizeof(msg) - 1] = '\0';
417 	return Irc_Proto_Enqueue(msg, msg_len);
418 }
419 
420 /**
421  * @sa Irc_Proto_Enqueue
422  */
Irc_Proto_Password(const char * password)423 static bool Irc_Proto_Password (const char* password)
424 {
425 	char msg[IRC_SEND_BUF_SIZE];
426 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "PASS %s\r\n", password);
427 	msg[sizeof(msg) - 1] = '\0';
428 	return Irc_Proto_Enqueue(msg, msg_len);
429 }
430 
431 /**
432  * @sa Irc_Proto_Enqueue
433  */
Irc_Proto_Join(const char * channel,const char * password)434 static bool Irc_Proto_Join (const char* channel, const char* password)
435 {
436 	char msg[IRC_SEND_BUF_SIZE];
437 	const int msg_len = password
438 		? snprintf(msg, sizeof(msg) - 1, "JOIN %s %s\r\n", channel, password)
439 		: snprintf(msg, sizeof(msg) - 1, "JOIN %s\r\n", channel);
440 	msg[sizeof(msg) - 1] = '\0';
441 
442 	/* only one channel allowed */
443 	if (chan) {
444 		Com_Printf("Already in a channel\n");
445 		return false;
446 	}
447 
448 	chan = &ircChan;
449 	OBJZERO(*chan);
450 	Q_strncpyz(chan->name, channel, sizeof(chan->name));
451 	return Irc_Proto_Enqueue(msg, msg_len);
452 }
453 
454 /**
455  * @sa Irc_Proto_Enqueue
456  */
Irc_Proto_Part(const char * channel)457 static bool Irc_Proto_Part (const char* channel)
458 {
459 	char msg[IRC_SEND_BUF_SIZE];
460 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "PART %s\r\n", channel);
461 	msg[sizeof(msg) - 1] = '\0';
462 	return Irc_Proto_Enqueue(msg, msg_len);
463 }
464 
465 /**
466  * @sa Irc_Proto_Enqueue
467  */
Irc_Proto_Mode(const char * target,const char * modes,const char * params)468 static bool Irc_Proto_Mode (const char* target, const char* modes, const char* params)
469 {
470 	char msg[IRC_SEND_BUF_SIZE];
471 	const int msg_len = params
472 		? snprintf(msg, sizeof(msg) - 1, "MODE %s %s %s\r\n", target, modes, params)
473 		: snprintf(msg, sizeof(msg) - 1, "MODE %s %s\r\n", target, modes);
474 	msg[sizeof(msg) - 1] = '\0';
475 	return Irc_Proto_Enqueue(msg, msg_len);
476 }
477 
478 /**
479  * @sa Irc_Proto_Enqueue
480  */
Irc_Proto_Topic(const char * channel,const char * topic)481 static bool Irc_Proto_Topic (const char* channel, const char* topic)
482 {
483 	char msg[IRC_SEND_BUF_SIZE];
484 	const int msg_len = topic
485 		? snprintf(msg, sizeof(msg) - 1, "TOPIC %s :%s\r\n", channel, topic)
486 		: snprintf(msg, sizeof(msg) - 1, "TOPIC %s\r\n", channel);
487 	msg[sizeof(msg) - 1] = '\0';
488 	return Irc_Proto_Enqueue(msg, msg_len);
489 }
490 
491 /**
492  * @sa Irc_Proto_Enqueue
493  * @return true on failure
494  * @sa Irc_Client_CmdPrivmsg
495  */
Irc_Proto_Msg(const char * target,const char * text)496 static bool Irc_Proto_Msg (const char* target, const char* text)
497 {
498 	if (*text == '/') {
499 		Com_DPrintf(DEBUG_CLIENT, "Don't send irc commands as PRIVMSG\n");
500 		Cbuf_AddText("%s\n", &text[1]);
501 		return true;
502 	} else {
503 		char msg[IRC_SEND_BUF_SIZE];
504 		const int msg_len = snprintf(msg, sizeof(msg) - 1, "PRIVMSG %s :%s\r\n", target, text);
505 		msg[sizeof(msg) - 1] = '\0';
506 		return Irc_Proto_Enqueue(msg, msg_len);
507 	}
508 }
509 
510 /**
511  * @sa Irc_Proto_Enqueue
512  */
Irc_Proto_Notice(const char * target,const char * text)513 static bool Irc_Proto_Notice (const char* target, const char* text)
514 {
515 	char msg[IRC_SEND_BUF_SIZE];
516 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "NOTICE %s :%s\r\n", target, text);
517 	msg[sizeof(msg) - 1] = '\0';
518 	return Irc_Proto_Enqueue(msg, msg_len);
519 }
520 
521 /**
522  * @sa Irc_Net_Send
523  */
Irc_Proto_Pong(const char * nick,const char * server,const char * cookie)524 static void Irc_Proto_Pong (const char* nick, const char* server, const char* cookie)
525 {
526 	char msg[IRC_SEND_BUF_SIZE];
527 	const int msg_len = cookie
528 		? snprintf(msg, sizeof(msg) - 1, "PONG %s %s :%s\r\n", nick, server, cookie)
529 		: snprintf(msg, sizeof(msg) - 1, "PONG %s %s\r\n", nick, server);
530 	msg[sizeof(msg) - 1] = '\0';
531 	Irc_Net_Send(msg, msg_len);	/* send immediately */
532 }
533 
534 /**
535  * @sa Irc_Proto_Enqueue
536  */
Irc_Proto_Kick(const char * channel,const char * nick,const char * reason)537 static bool Irc_Proto_Kick (const char* channel, const char* nick, const char* reason)
538 {
539 	char msg[IRC_SEND_BUF_SIZE];
540 	const int msg_len = reason
541 		? snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, reason)
542 		: snprintf(msg, sizeof(msg) - 1, "KICK %s %s :%s\r\n", channel, nick, nick);
543 	msg[sizeof(msg) - 1] = '\0';
544 	return Irc_Proto_Enqueue(msg, msg_len);
545 }
546 
547 /**
548  * @sa Irc_Proto_Enqueue
549  */
Irc_Proto_Who(const char * nick)550 static bool Irc_Proto_Who (const char* nick)
551 {
552 	char msg[IRC_SEND_BUF_SIZE];
553 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHO %s\r\n", nick);
554 	msg[sizeof(msg) - 1] = '\0';
555 	return Irc_Proto_Enqueue(msg, msg_len);
556 }
557 
558 /**
559  * @sa Irc_Proto_Enqueue
560  */
Irc_Proto_Whois(const char * nick)561 static bool Irc_Proto_Whois (const char* nick)
562 {
563 	char msg[IRC_SEND_BUF_SIZE];
564 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOIS %s\r\n", nick);
565 	msg[sizeof(msg) - 1] = '\0';
566 	return Irc_Proto_Enqueue(msg, msg_len);
567 }
568 
569 /**
570  * @sa Irc_Proto_Enqueue
571  */
Irc_Proto_Whowas(const char * nick)572 static bool Irc_Proto_Whowas (const char* nick)
573 {
574 	char msg[IRC_SEND_BUF_SIZE];
575 	const int msg_len = snprintf(msg, sizeof(msg) - 1, "WHOWAS %s\r\n", nick);
576 	msg[sizeof(msg) - 1] = '\0';
577 	return Irc_Proto_Enqueue(msg, msg_len);
578 }
579 
580 /**
581  * @sa Irc_Logic_ReadMessages
582  */
Irc_Proto_PollServerMsg(irc_server_msg_t * msg,bool * msg_complete)583 static bool Irc_Proto_PollServerMsg (irc_server_msg_t* msg, bool *msg_complete)
584 {
585 	static char buf[IRC_RECV_BUF_SIZE];
586 	static char* last = buf;
587 	*msg_complete = false;
588 	/* recv packet */
589 	const int recvd = NET_StreamDequeue(irc_stream, last, sizeof(buf) - (last - buf) - 1);
590 	if (recvd < 0)
591 		return true;
592 
593 	/* terminate buf string */
594 	const char*  const begin = buf;
595 	last += recvd;
596 	*last = '\0';
597 	if (last != begin) {
598 		/* buffer not empty; */
599 		const char*  const end = strstr(begin, "\r\n");
600 		if (end) {
601 			/* complete command in buffer, parse */
602 			const size_t cmd_len = end + 2 - begin;
603 			if (!Irc_Proto_ParseServerMsg(begin, cmd_len, msg)) {
604 				/* parsing successful */
605 				/* move succeeding commands to begin of buffer */
606 				memmove(buf, end + 2, sizeof(buf) - cmd_len);
607 				last -= cmd_len;
608 				*msg_complete = true;
609 			} else {
610 				/* parsing failure, fatal */
611 				Com_Printf("Received invalid packet from server\n");
612 				return true;
613 			}
614 		}
615 	} else {
616 		*msg_complete = false;
617 	}
618 	return false;
619 }
620 
621 /**
622  * @brief Append the irc message to the buffer
623  * @note the irc_buffer is linked to mn.menuText array to display in the menu
624  * @param[in] msg the complete irc message (without \n)
625  * @return true if the message was added to the chatbuffer, too
626  */
Irc_AppendToBuffer(const char * const msg,...)627 static bool Irc_AppendToBuffer (const char* const msg, ...)
628 {
629 	va_list ap;
630 	char appendString[2048];
631 
632 	va_start(ap, msg);
633 	Q_vsnprintf(appendString, sizeof(appendString), msg, ap);
634 	va_end(ap);
635 
636 	while (strlen(irc_buffer) + strlen(appendString) + 1 >= sizeof(irc_buffer)) {
637 		const char* n;
638 		if (!(n = strchr(irc_buffer, '\n'))) {
639 			irc_buffer[0] = '\0';
640 			break;
641 		}
642 		memmove(irc_buffer, n + 1, strlen(n));
643 	}
644 
645 	Q_strcat(irc_buffer, sizeof(irc_buffer), "%s\n", appendString);
646 	if (irc_logConsole->integer)
647 		Com_Printf("IRC: %s\n", appendString);
648 
649 	UI_RegisterText(TEXT_IRCCONTENT, irc_buffer);
650 	UI_TextScrollEnd("irc.irc_data");
651 
652 	if (irc_showIfNotInMenu->integer && !Q_streq(UI_GetActiveWindowName(), "irc")) {
653 		S_StartLocalSample("misc/talk", SND_VOLUME_DEFAULT);
654 		GAME_AddChatMessage(appendString);
655 		return true;
656 	}
657 	return false;
658 }
659 
Irc_Client_CmdRplWhowasuser(const char * params,const char * trailing)660 static void Irc_Client_CmdRplWhowasuser (const char* params, const char* trailing)
661 {
662 	char buf[IRC_SEND_BUF_SIZE];
663 	const char* nick = "", *user = "", *host = "", *real_name = trailing;
664 	char* p;
665 	unsigned int i = 0;
666 
667 	/* parse params "<nick> <user> <host> * :<real name>" */
668 	Q_strncpyz(buf, params, sizeof(buf));
669 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
670 		switch (i) {
671 		case 1:
672 			nick = p;
673 			break;
674 		case 2:
675 			user = p;
676 			break;
677 		case 3:
678 			host = p;
679 			break;
680 		}
681 	}
682 	Irc_AppendToBuffer("^B%s was %s@%s : %s", nick, user, host, real_name);
683 }
684 
Irc_Client_CmdTopic(const char * prefix,const char * trailing)685 static inline void Irc_Client_CmdTopic (const char* prefix, const char* trailing)
686 {
687 	Cvar_ForceSet("irc_topic", trailing);
688 }
689 
Irc_Client_CmdRplTopic(const char * params,const char * trailing)690 static void Irc_Client_CmdRplTopic (const char* params, const char* trailing)
691 {
692 	const char* channel = strchr(params, ' ');
693 	if (channel) {
694 		++channel;
695 		Irc_Client_CmdTopic(params, trailing);
696 	}
697 }
698 
Irc_Client_CmdRplWhoisuser(const char * params,const char * trailing)699 static void Irc_Client_CmdRplWhoisuser (const char* params, const char* trailing)
700 {
701 	char buf[IRC_SEND_BUF_SIZE];
702 	const char* nick = "", *user = "", *host = "", *real_name = trailing;
703 	char* p;
704 	unsigned int i = 0;
705 
706 	/* parse params "<nick> <user> <host> * :<real name>" */
707 	strcpy(buf, params);
708 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
709 		switch (i) {
710 		case 1:
711 			nick = p;
712 			break;
713 		case 2:
714 			user = p;
715 			break;
716 		case 3:
717 			host = p;
718 			break;
719 		}
720 	}
721 	Irc_AppendToBuffer("^B%s is %s@%s : %s", nick, user, host, real_name);
722 }
723 
Irc_Client_CmdRplWhoisserver(const char * params,const char * trailing)724 static void Irc_Client_CmdRplWhoisserver (const char* params, const char* trailing)
725 {
726 	char buf[IRC_SEND_BUF_SIZE];
727 	const char* nick = "", *server = "", *server_info = trailing;
728 	char* p;
729 	unsigned int i = 0;
730 
731 	/* parse params "<nick> <server> :<server info>" */
732 	strcpy(buf, params);
733 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
734 		switch (i) {
735 		case 1:
736 			nick = p;
737 			break;
738 		case 2:
739 			server = p;
740 			break;
741 		}
742 	}
743 	Irc_AppendToBuffer("^B%s using %s : %s", nick, server, server_info);
744 }
745 
Irc_Client_CmdRplWhoisaccount(const char * params,const char * trailing)746 static void Irc_Client_CmdRplWhoisaccount (const char* params, const char* trailing)
747 {
748 	char buf[IRC_SEND_BUF_SIZE];
749 	const char* nick = "", *account = "";
750 	char* p;
751 	unsigned int i = 0;
752 
753 	/* parse params "<nick> <account> :is logged in as" */
754 	strcpy(buf, params);
755 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
756 		switch (i) {
757 		case 1:
758 			nick = p;
759 			break;
760 		case 2:
761 			account = p;
762 			break;
763 		}
764 	}
765 	Irc_AppendToBuffer("^B%s %s %s", nick, trailing, account);
766 }
767 
Irc_Client_CmdRplWhoisidle(const char * params,const char * trailing)768 static void Irc_Client_CmdRplWhoisidle (const char* params, const char* trailing)
769 {
770 	char buf[IRC_SEND_BUF_SIZE];
771 	const char* nick = "", *idle = "";
772 	char* p;
773 	unsigned int i = 0;
774 
775 	/* parse params "<nick> <integer> :seconds idle" */
776 	strcpy(buf, params);
777 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
778 		switch (i) {
779 		case 1:
780 			nick = p;
781 			break;
782 		case 2:
783 			idle = p;
784 			break;
785 		}
786 	}
787 	Irc_AppendToBuffer("^B%s is %s %s", nick, idle, trailing);
788 }
789 
Irc_Client_CmdRplWhoreply(const char * params,const char * trailing)790 static void Irc_Client_CmdRplWhoreply (const char* params, const char* trailing)
791 {
792 	char buf[IRC_SEND_BUF_SIZE];
793 	const char* channel = "", *user = "", *host = "", *server = "", *nick = "", *hg = "";
794 	char* p;
795 	unsigned int i = 0;
796 
797 	/* parse params "<channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real name>" */
798 	strcpy(buf, params);
799 	for (p = strtok(buf, " "); p; p = strtok(nullptr, " "), ++i) {
800 		switch (i) {
801 		case 0:
802 			channel = p;
803 			break;
804 		case 1:
805 			user = p;
806 			break;
807 		case 2:
808 			host = p;
809 			break;
810 		case 3:
811 			server = p;
812 			break;
813 		case 4:
814 			nick = p;
815 			break;
816 		case 5:
817 			hg = p;
818 			break;
819 		}
820 	}
821 	Irc_AppendToBuffer("%s %s %s %s %s %s : %s", channel, user, host, server, nick, hg, trailing);
822 }
823 
Irc_Client_CmdMode(const char * prefix,const char * params,const char * trailing)824 static void Irc_Client_CmdMode (const char* prefix, const char* params, const char* trailing)
825 {
826 	char nick[MAX_VAR];
827 	irc_nick_prefix_t p;
828 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
829 	Irc_AppendToBuffer("^B%s sets mode %s", nick, params);
830 }
831 
Irc_Client_CmdJoin(const char * prefix,const char * params,const char * trailing)832 static void Irc_Client_CmdJoin (const char* prefix, const char* params, const char* trailing)
833 {
834 	char nick[MAX_VAR];
835 	irc_nick_prefix_t p;
836 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
837 	Irc_AppendToBuffer("^BJoined: %s", nick);
838 	Irc_Logic_AddChannelName(chan, p, nick);
839 }
840 
Irc_Client_CmdPart(const char * prefix,const char * trailing)841 static void Irc_Client_CmdPart (const char* prefix, const char* trailing)
842 {
843 	char nick[MAX_VAR];
844 	irc_nick_prefix_t p;
845 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
846 	Irc_AppendToBuffer("^BLeft: %s (%s)", nick, prefix);
847 	Irc_Logic_RemoveChannelName(chan, nick);
848 }
849 
Irc_Client_CmdQuit(const char * prefix,const char * params,const char * trailing)850 static void Irc_Client_CmdQuit (const char* prefix, const char* params, const char* trailing)
851 {
852 	char nick[MAX_VAR];
853 	irc_nick_prefix_t p;
854 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
855 	Irc_AppendToBuffer("^BQuits: %s (%s)", nick, trailing);
856 	Irc_Logic_RemoveChannelName(chan, nick);
857 }
858 
Irc_Client_CmdKill(const char * prefix,const char * params,const char * trailing)859 static void Irc_Client_CmdKill (const char* prefix, const char* params, const char* trailing)
860 {
861 	char nick[MAX_VAR];
862 	irc_nick_prefix_t p;
863 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
864 	Irc_AppendToBuffer("^BKilled: %s (%s)", nick, trailing);
865 	Irc_Logic_RemoveChannelName(chan, nick);
866 }
867 
Irc_Client_CmdKick(const char * prefix,const char * params,const char * trailing)868 static void Irc_Client_CmdKick (const char* prefix, const char* params, const char* trailing)
869 {
870 	char buf[IRC_SEND_BUF_SIZE];
871 	char nick[MAX_VAR];
872 	irc_nick_prefix_t p;
873 	const char* channel, *victim;
874 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
875 	strcpy(buf, params);
876 	channel = strtok(buf, " ");
877 	victim = strtok(nullptr, " ");
878 	if (Q_streq(victim, irc_nick->string)) {
879 		/* we have been kicked */
880 		Irc_AppendToBuffer("^BYou were kicked from %s by %s (%s)", channel, nick, trailing);
881 	} else {
882 		/* someone else was kicked */
883 		Irc_AppendToBuffer("^B%s kicked %s (%s)", nick, victim, trailing);
884 	}
885 	Irc_Logic_RemoveChannelName(chan, nick);
886 }
887 
888 /**
889  * @brief Changes the cvar 'name' with the new name you set
890  */
Irc_Client_CmdNick(const char * prefix,const char * params,const char * trailing)891 static void Irc_Client_CmdNick (const char* prefix, const char* params, const char* trailing)
892 {
893 	char nick[MAX_VAR];
894 	irc_nick_prefix_t p;
895 
896 	/* not connected */
897 	if (!chan)
898 		return;
899 
900 	Irc_ParseName(prefix, nick, sizeof(nick), &p);
901 	if (Q_streq(irc_nick->string, nick))
902 		Cvar_ForceSet("cl_name", trailing);
903 	Irc_AppendToBuffer("%s is now known as %s", nick, trailing);
904 	Irc_Logic_RemoveChannelName(chan, nick);
905 	Irc_Logic_AddChannelName(chan, p, trailing);
906 }
907 
908 #define IRC_CTCP_MARKER_CHR '\001'
909 #define IRC_CTCP_MARKER_STR "\001"
910 
911 /**
912  * @sa Irc_Client_Invite_f
913  */
Irc_Client_CmdPrivmsg(const char * prefix,const char * params,const char * trailing)914 static void Irc_Client_CmdPrivmsg (const char* prefix, const char* params, const char* trailing)
915 {
916 	char nick[MAX_VAR];
917 	const char*  const emph = strchr(prefix, '!');
918 	const char*  ctcp = strchr(trailing, IRC_CTCP_MARKER_CHR);
919 	OBJZERO(nick);
920 	if (emph)
921 		memcpy(nick, prefix, emph - prefix);
922 	else
923 		strcpy(nick, prefix);
924 
925 	if (ctcp) {
926 		if (Q_streq(trailing + 1, "VERSION" IRC_CTCP_MARKER_STR)) {
927 			/* response with the game version */
928 			Irc_Proto_Msg(irc_defaultChannel->string, Cvar_GetString("version"));
929 			/*Irc_Proto_Notice(nick, IRC_CTCP_MARKER_STR "VERSION " UFO_VERSION " " CPUSTRING " " __DATE__ " " BUILDSTRING);*/
930 			Com_DPrintf(DEBUG_CLIENT, "Irc_Client_CmdPrivmsg: Response to version query\n");
931 		} else if (!strncmp(trailing + 1, "PING", 4)) {
932 			/* Used to measure the delay of the IRC network between clients. */
933 			char response[IRC_SEND_BUF_SIZE];
934 			strcpy(response, trailing);
935 			response[2] = 'O'; /* PING => PONG */
936 			Irc_Proto_Notice(nick, response);
937 		} else if (Q_streq(trailing + 1, "TIME" IRC_CTCP_MARKER_STR)) {
938 			const time_t t = time(nullptr);
939 			char response[IRC_SEND_BUF_SIZE];
940 			const size_t response_len = sprintf(response, IRC_CTCP_MARKER_STR "TIME :%s" IRC_CTCP_MARKER_STR, ctime(&t));
941 			response[response_len - 1] = '\0';	/* remove trailing \n */
942 			Irc_Proto_Notice(nick, response);
943 		} else {
944 			Com_Printf("Irc_Client_CmdPrivmsg: Unknown ctcp command: '%s'\n", trailing);
945 		}
946 	} else {
947 		if (!strncmp(trailing, IRC_INVITE_FOR_A_GAME, strlen(IRC_INVITE_FOR_A_GAME))) {
948 			char serverIPAndPort[128];
949 			char* port;
950 			char* version;
951 			Q_strncpyz(serverIPAndPort, trailing + strlen(IRC_INVITE_FOR_A_GAME), sizeof(serverIPAndPort));
952 			/* values are splitted by ; */
953 			port = strstr(serverIPAndPort, ";");
954 			if (port == nullptr) {
955 				Com_DPrintf(DEBUG_CLIENT, "Invalid irc invite message received\n");
956 				return;
957 			}
958 
959 			/* split ip and port */
960 			*port++ = '\0';
961 
962 			/* the version is optional */
963 			version = strstr(port, ";");
964 			if (version != nullptr) {
965 				/* split port and version */
966 				*version++ = '\0';
967 				if (!Q_streq(version, UFO_VERSION)) {
968 					Com_DPrintf(DEBUG_CLIENT, "irc invite message from different game version received: %s (versus our version: " UFO_VERSION ")\n",
969 							version);
970 					return;
971 				}
972 			}
973 
974 			/** get the ip and port into the menu */
975 			UI_ExecuteConfunc("multiplayer_invite_server_info %s %s", serverIPAndPort, port);
976 
977 			UI_PushWindow("multiplayer_invite");
978 		} else if (!Irc_AppendToBuffer("<%s> %s", nick, trailing)) {
979 			/* check whether this is no message to the channel - but to the user */
980 			if (params && !Q_streq(params, irc_defaultChannel->string)) {
981 				S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
982 				GAME_AddChatMessage(va("<%s> %s\n", nick, trailing));
983 			} else if (strstr(trailing, irc_nick->string)) {
984 				S_StartLocalSample("misc/lobbyprivmsg", SND_VOLUME_DEFAULT);
985 				GAME_AddChatMessage(va("<%s> %s\n", nick, trailing));
986 			}
987 		}
988 
989 		if (UI_GetActiveWindow() && !Q_streq(UI_GetActiveWindowName(), "irc")) {
990 			Com_Printf(S_COLOR_GREEN "<%s@lobby> %s\n", nick, trailing);
991 		}
992 	}
993 }
994 
Irc_Client_CmdRplNamreply(const char * params,const char * trailing)995 static void Irc_Client_CmdRplNamreply (const char* params, const char* trailing)
996 {
997 	char* pos;
998 	char* space;
999 	char nick[MAX_VAR];
1000 	size_t len = strlen(trailing) + 1;
1001 	irc_nick_prefix_t p;
1002 
1003 	if (!chan)
1004 		return;
1005 
1006 	char* const parseBuf = Mem_PoolAllocTypeN(char, len, cl_ircSysPool);
1007 	if (!parseBuf)
1008 		return;
1009 
1010 	Q_strncpyz(parseBuf, trailing, len);
1011 	pos = parseBuf;
1012 
1013 	do {
1014 		/* names are space separated */
1015 		space = strstr(pos, " ");
1016 		if (space)
1017 			*space = '\0';
1018 		Irc_ParseName(pos, nick, sizeof(nick), &p);
1019 		Irc_Logic_AddChannelName(chan, p, nick);
1020 		if (space)
1021 			pos = space + 1;
1022 	} while (space && *pos);
1023 
1024 	Mem_Free(parseBuf);
1025 }
1026 
1027 /**
1028  * @todo Implement me
1029  */
Irc_Client_CmdRplEndofnames(const char * params,const char * trailing)1030 static void Irc_Client_CmdRplEndofnames (const char* params, const char* trailing)
1031 {
1032 }
1033 
1034 /**
1035  * @sa Irc_Logic_ReadMessages
1036  */
Irc_Proto_ProcessServerMsg(const irc_server_msg_t * msg)1037 static bool Irc_Proto_ProcessServerMsg (const irc_server_msg_t* msg)
1038 {
1039 	irc_command_t cmd;
1040 	const char* p = nullptr;
1041 	cmd.type = msg->type;
1042 
1043 	/** @todo Skip non printable chars here */
1044 
1045 	switch (cmd.type) {
1046 	case IRC_COMMAND_NUMERIC:
1047 		cmd.id.numeric = msg->id.numeric;
1048 		switch (cmd.id.numeric) {
1049 		case RPL_HELLO:
1050 		case RPL_WELCOME:
1051 		case RPL_YOURHOST:
1052 		case RPL_CREATED:
1053 		case RPL_MYINFO:
1054 		case RPL_MOTDSTART:
1055 		case RPL_MOTD:
1056 		case RPL_LOCALUSERS:
1057 		case RPL_GLOBALUSERS:
1058 		case RPL_ISUPPORT:
1059 		case RPL_LUSEROP:
1060 		case RPL_LUSERUNKNOWN:
1061 		case RPL_LUSERCHANNELS:
1062 		case RPL_LUSERCLIENT:
1063 		case RPL_LUSERME:
1064 			return true;
1065 
1066 		/* read our own motd */
1067 		case RPL_ENDOFMOTD:
1068 			Irc_AppendToBuffer("%s", CL_Translate("*msgid:irc_motd"));
1069 			return true;
1070 		case RPL_NAMREPLY:
1071 			Irc_Client_CmdRplNamreply(msg->params, msg->trailing);
1072 			return true;
1073 		case RPL_ENDOFNAMES:
1074 			Irc_Client_CmdRplEndofnames(msg->params, msg->trailing);
1075 			return true;
1076 		case RPL_TOPIC:
1077 			Irc_Client_CmdRplTopic(msg->params, msg->trailing);
1078 			return true;
1079 		case RPL_NOTOPIC:
1080 			return true;
1081 		case RPL_WHOISUSER:
1082 			Irc_Client_CmdRplWhoisuser(msg->params, msg->trailing);
1083 			return true;
1084 		case RPL_WHOISSERVER:
1085 			Irc_Client_CmdRplWhoisserver(msg->params, msg->trailing);
1086 			return true;
1087 		case RPL_WHOISIDLE:
1088 			Irc_Client_CmdRplWhoisidle(msg->params, msg->trailing);
1089 			return true;
1090 		case RPL_WHOISACCOUNT:
1091 			Irc_Client_CmdRplWhoisaccount(msg->params, msg->trailing);
1092 			return true;
1093 		case RPL_WHOREPLY:
1094 			Irc_Client_CmdRplWhoreply(msg->params, msg->trailing);
1095 			return true;
1096 		case RPL_WHOWASUSER:
1097 			Irc_Client_CmdRplWhowasuser(msg->params, msg->trailing);
1098 			return true;
1099 		case RPL_ENDOFWHO:
1100 		case RPL_WHOISCHANNELS:
1101 		case RPL_WHOISOPERATOR:
1102 		case RPL_ENDOFWHOIS:
1103 		case RPL_ENDOFWHOWAS:
1104 			p = strchr(msg->params, ' ');
1105 			if (p) {
1106 				++p;
1107 				Irc_AppendToBuffer("%s %s", p, msg->trailing);
1108 			}
1109 			return true;
1110 
1111 		case ERR_NICKNAMEINUSE:
1112 		case ERR_NOSUCHNICK:
1113 		case ERR_NONICKNAMEGIVEN:
1114 		case ERR_ERRONEUSNICKNAME:
1115 		case ERR_NICKCOLLISION:
1116 			Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
1117 			UI_PushWindow("irc_changename");
1118 			break;
1119 		/* error codes */
1120 		case ERR_NOSUCHSERVER:
1121 		case ERR_NOSUCHCHANNEL:
1122 		case ERR_CANNOTSENDTOCHAN:
1123 		case ERR_TOOMANYCHANNELS:
1124 		case ERR_WASNOSUCHNICK:
1125 		case ERR_TOOMANYTARGETS:
1126 		case ERR_NOORIGIN:
1127 		case ERR_NORECIPIENT:
1128 		case ERR_NOTEXTTOSEND:
1129 		case ERR_NOTOPLEVEL:
1130 		case ERR_WILDTOPLEVEL:
1131 		case ERR_UNKNOWNCOMMAND:
1132 		case ERR_NOMOTD:
1133 		case ERR_NOADMININFO:
1134 		case ERR_FILEERROR:
1135 		case ERR_BANNICKCHANGE:
1136 		case ERR_NCHANGETOOFAST:
1137 		case ERR_USERNOTINCHANNEL:
1138 		case ERR_NOTONCHANNEL:
1139 		case ERR_USERONCHANNEL:
1140 		case ERR_NOLOGIN:
1141 		case ERR_SUMMONDISABLED:
1142 		case ERR_USERSDISABLED:
1143 		case ERR_NOTREGISTERED:
1144 		case ERR_NEEDMOREPARAMS:
1145 		case ERR_ALREADYREGISTRED:
1146 		case ERR_NOPERMFORHOST:
1147 		case ERR_PASSWDMISMATCH:
1148 		case ERR_YOUREBANNEDCREEP:
1149 		case ERR_BADNAME:
1150 		case ERR_KEYSET:
1151 		case ERR_CHANNELISFULL:
1152 		case ERR_UNKNOWNMODE:
1153 		case ERR_INVITEONLYCHAN:
1154 		case ERR_BANNEDFROMCHAN:
1155 		case ERR_BADCHANNELKEY:
1156 		case ERR_NOPRIVILEGES:
1157 		case ERR_CHANOPRIVSNEEDED:
1158 		case ERR_CANTKILLSERVER:
1159 		case ERR_NOOPERHOST:
1160 		case ERR_UMODEUNKNOWNFLAG:
1161 		case ERR_USERSDONTMATCH:
1162 		case ERR_GHOSTEDCLIENT:
1163 		case ERR_LAST_ERR_MSG:
1164 		case ERR_SILELISTFULL:
1165 		case ERR_NOSUCHGLINE:
1166 		case ERR_BADPING:
1167 		case ERR_TOOMANYDCC:
1168 		case ERR_LISTSYNTAX:
1169 		case ERR_WHOSYNTAX:
1170 		case ERR_WHOLIMEXCEED:
1171 			Irc_AppendToBuffer("%s : %s", msg->params, msg->trailing);
1172 			return true;
1173 		default:
1174 			Com_DPrintf(DEBUG_CLIENT, "<%s> [%i] %s : %s\n", msg->prefix, cmd.id.numeric, msg->params, msg->trailing);
1175 			return true;
1176 		} /* switch */
1177 		break;
1178 	case IRC_COMMAND_STRING:
1179 		cmd.id.string = msg->id.string;
1180 #ifdef DEBUG
1181 		Com_DPrintf(DEBUG_CLIENT, "<%s> [%s] %s : %s\n", msg->prefix, cmd.id.string, msg->params, msg->trailing);
1182 #endif
1183 		if (!strncmp(cmd.id.string, "NICK", 4))
1184 			Irc_Client_CmdNick(msg->prefix, msg->params, msg->trailing);
1185 		else if (!strncmp(cmd.id.string, "QUIT", 4))
1186 			Irc_Client_CmdQuit(msg->prefix, msg->params, msg->trailing);
1187 		else if (!strncmp(cmd.id.string, "NOTICE", 6)) {
1188 			if (irc_logConsole->integer)
1189 				Com_Printf("%s\n", msg->trailing);
1190 		} else if (!strncmp(cmd.id.string, "PRIVMSG", 7))
1191 			Irc_Client_CmdPrivmsg(msg->prefix, msg->params, msg->trailing);
1192 		else if (!strncmp(cmd.id.string, "MODE", 4))
1193 			Irc_Client_CmdMode(msg->prefix, msg->params, msg->trailing);
1194 		else if (!strncmp(cmd.id.string, "JOIN", 4))
1195 			Irc_Client_CmdJoin(msg->prefix, msg->params, msg->trailing);
1196 		else if (!strncmp(cmd.id.string, "PART", 4))
1197 			Irc_Client_CmdPart(msg->prefix, msg->trailing);
1198 		else if (!strncmp(cmd.id.string, "TOPIC", 5))
1199 			Irc_Client_CmdTopic(msg->prefix, msg->trailing);
1200 		else if (!strncmp(cmd.id.string, "KILL", 4))
1201 			Irc_Client_CmdKill(msg->prefix, msg->params, msg->trailing);
1202 		else if (!strncmp(cmd.id.string, "KICK", 4))
1203 			Irc_Client_CmdKick(msg->prefix, msg->params, msg->trailing);
1204 		else if (!strncmp(cmd.id.string, "PING", 4))
1205 			Irc_Proto_Pong(irc_nick->string, msg->params, msg->trailing[0] ? msg->trailing : nullptr);
1206 		else if (!strncmp(cmd.id.string, "ERROR", 5)) {
1207 			Irc_Logic_Disconnect(msg->trailing);
1208 			Q_strncpyz(popupText, msg->trailing, sizeof(popupText));
1209 			UI_Popup(_("Error"), popupText);
1210 		} else
1211 			Irc_AppendToBuffer("%s", msg->trailing);
1212 		break;
1213 	} /* switch */
1214 	return false;
1215 }
1216 
Irc_Proto_ParseServerMsg(const char * txt,size_t txt_len,irc_server_msg_t * msg)1217 static bool Irc_Proto_ParseServerMsg (const char* txt, size_t txt_len, irc_server_msg_t* msg)
1218 {
1219 	const char* c = txt;
1220 	const char* end = txt + txt_len;
1221 	msg->prefix[0] = '\0';
1222 	msg->params[0] = '\0';
1223 	msg->trailing[0] = '\0';
1224 	if (c < end && *c == ':') {
1225 		/* parse prefix */
1226 		char* prefix = msg->prefix;
1227 		int i = 1;
1228 		const size_t size = sizeof(msg->prefix);
1229 		++c;
1230 		while (c < end && *c != '\r' && *c != ' ') {
1231 			if (i++ >= size)
1232 				return true;
1233 			*prefix++ = *c++;
1234 		}
1235 		*prefix = '\0';
1236 		++c;
1237 	}
1238 	if (c < end && *c != '\r') {
1239 		/* parse command */
1240 		if (c < end && *c >= '0' && *c <= '9') {
1241 			/* numeric command */
1242 			char command[4];
1243 			int i;
1244 			for (i = 0; i < 3; ++i) {
1245 				if (c < end && *c >= '0' && *c <= '9')
1246 					command[i] = *c++;
1247 				else
1248 					return true;
1249 			}
1250 			command[3] = '\0';
1251 			msg->type = IRC_COMMAND_NUMERIC;
1252 			msg->id.numeric = (irc_numeric_t)atoi(command);
1253 		} else if (c < end && *c != '\r') {
1254 			/* string command */
1255 			int i = 1;
1256 			const size_t size = sizeof(msg->id.string);
1257 			char* command = msg->id.string;
1258 			while (c < end && *c != '\r' && *c != ' ') {
1259 				if (i++ >= size)
1260 					return true;
1261 				*command++ = *c++;
1262 			}
1263 			*command = '\0';
1264 			msg->type = IRC_COMMAND_STRING;
1265 		} else
1266 			return true;
1267 		if (c < end && *c == ' ') {
1268 			/* parse params and trailing */
1269 			char* params = msg->params;
1270 			int i = 1;
1271 			const size_t size = sizeof(msg->params);
1272 			++c;
1273 
1274 			/* parse params */
1275 			while (c < end && *c != '\r' && *c != ':') {
1276 				/* parse single param */
1277 				while (c < end && *c != '\r' && *c != ' ') {
1278 					if (i++ >= size)
1279 						return true;
1280 					*params++ = *c++;
1281 				}
1282 				/* more params */
1283 				if (c + 1 < end && *c == ' ' && *(c + 1) != ':') {
1284 					if (i++ >= size)
1285 						return true;
1286 					*params++ = ' ';
1287 				}
1288 				if (*c == ' ')
1289 					++c;
1290 			}
1291 			*params = '\0';
1292 			/* parse trailing */
1293 			if (c < end && *c == ':') {
1294 				char* trailing = msg->trailing;
1295 				int i = 1;
1296 				const size_t size = sizeof(msg->trailing);
1297 				++c;
1298 				while (c < end && *c != '\r') {
1299 					if (i++ >= size)
1300 						return true;
1301 					*trailing++ = *c++;
1302 				}
1303 				*trailing = '\0';
1304 			}
1305 		}
1306 	}
1307 	return false;
1308 }
1309 
1310 /**
1311  * @sa Irc_Proto_DrainBucket
1312  * @sa Irc_Proto_RefillBucket
1313  */
Irc_Proto_Enqueue(const char * msg,size_t msg_len)1314 static bool Irc_Proto_Enqueue (const char* msg, size_t msg_len)
1315 {
1316 	const int messageBucketSize = irc_messageBucketSize->integer;
1317 	const int characterBucketSize = irc_characterBucketSize->integer;
1318 
1319 	if (!irc_connected) {
1320 		Com_Printf("Irc_Proto_Enqueue: not connected\n");
1321 		return true;
1322 	}
1323 
1324 	/* create message node */
1325 	if (irc_bucket.message_size + 1 <= messageBucketSize && irc_bucket.character_size + msg_len <= characterBucketSize) {
1326 		/** @todo strip high bits - or unprintable chars */
1327 		irc_bucket_message_t* const m = Mem_AllocType(irc_bucket_message_t);
1328 		m->msg = Mem_AllocTypeN(char, msg_len);
1329 		memcpy(m->msg, msg, msg_len);
1330 		m->msg_len = msg_len;
1331 		m->next = nullptr;
1332 		/* append message node */
1333 		irc_bucket_message_t** anchor = &irc_bucket.first_msg;
1334 		while (*anchor) anchor = &(*anchor)->next;
1335 		*anchor = m;
1336 		/* update bucket sizes */
1337 		++irc_bucket.message_size;
1338 		irc_bucket.character_size += msg_len;
1339 		return false;
1340 	} else {
1341 		Com_Printf("Bucket(s) full. Could not enqueue message\n");
1342 		return true;
1343 	}
1344 }
1345 
1346 /**
1347  * @sa Irc_Proto_Enqueue
1348  * @sa Irc_Proto_DrainBucket
1349  */
Irc_Proto_RefillBucket(void)1350 static void Irc_Proto_RefillBucket (void)
1351 {
1352 	/* calculate token refill */
1353 	const double characterBucketSize = irc_characterBucketSize->value;
1354 	const double characterBucketRate = irc_characterBucketRate->value;
1355 	const int ms = CL_Milliseconds();
1356 	const int ms_delta = ms - irc_bucket.last_refill;
1357 	const double char_delta = (ms_delta * characterBucketRate) / 1000;
1358 	const double char_new = irc_bucket.character_token + char_delta;
1359 	/* refill token (but do not exceed maximum) */
1360 	irc_bucket.character_token = std::min(char_new, characterBucketSize);
1361 	/* set timestamp so next refill can calculate delta */
1362 	irc_bucket.last_refill = ms;
1363 }
1364 
1365 /**
1366  * @brief Send all enqueued packets
1367  * @sa Irc_Proto_Enqueue
1368  * @sa Irc_Proto_RefillBucket
1369  */
Irc_Proto_DrainBucket(void)1370 static void Irc_Proto_DrainBucket (void)
1371 {
1372 	const double characterBucketBurst = irc_characterBucketBurst->value;
1373 	irc_bucket_message_t* msg;
1374 
1375 	/* remove messages whose size exceed our burst size (we can not send them) */
1376 	for (msg = irc_bucket.first_msg; msg && msg->msg_len > characterBucketBurst; msg = irc_bucket.first_msg) {
1377 		irc_bucket_message_t*  const next = msg->next;
1378 		/* update bucket sizes */
1379 		--irc_bucket.message_size;
1380 		irc_bucket.character_size -= msg->msg_len;
1381 		/* free message */
1382 		Mem_Free(msg->msg);
1383 		/* dequeue message */
1384 		irc_bucket.first_msg = next;
1385 	}
1386 	/* send burst of remaining messages */
1387 	for (msg = irc_bucket.first_msg; msg; msg = irc_bucket.first_msg) {
1388 		/* send message */
1389 		Irc_Net_Send(msg->msg, msg->msg_len);
1390 		irc_bucket.character_token -= msg->msg_len;
1391 		/* dequeue message */
1392 		irc_bucket.first_msg = msg->next;
1393 		/* update bucket sizes */
1394 		--irc_bucket.message_size;
1395 		irc_bucket.character_size -= msg->msg_len;
1396 		/* free message */
1397 		Mem_Free(msg->msg);
1398 		Mem_Free(msg);
1399 	}
1400 }
1401 
1402 /*
1403 ===============================================================
1404 Logic functions
1405 ===============================================================
1406 */
1407 
1408 /**
1409  * @sa Irc_Logic_Frame
1410  */
Irc_Logic_SendMessages(void)1411 static void Irc_Logic_SendMessages (void)
1412 {
1413 	Irc_Proto_RefillBucket();		/* first refill token */
1414 	Irc_Proto_DrainBucket();	/* then send messages (if allowed) */
1415 }
1416 
1417 /**
1418  * @sa Irc_Logic_Frame
1419  * @sa Irc_Logic_Disconnect
1420  * @sa Irc_Proto_ProcessServerMsg
1421  * @sa Irc_Proto_PollServerMsg
1422  */
Irc_Logic_ReadMessages(void)1423 static void Irc_Logic_ReadMessages (void)
1424 {
1425 	bool msg_complete;
1426 	do {
1427 		irc_server_msg_t msg;
1428 		if (!Irc_Proto_PollServerMsg(&msg, &msg_complete)) {
1429 			/* success */
1430 			if (msg_complete)
1431 				Irc_Proto_ProcessServerMsg(&msg);
1432 		} else {
1433 			/* failure */
1434 			UI_Popup(_("Error"), _("Server closed connection"));
1435 			Irc_Logic_Disconnect("Server closed connection");
1436 		}
1437 	} while (msg_complete);
1438 }
1439 
Irc_Logic_Connect(const char * server,const char * port)1440 static void Irc_Logic_Connect (const char* server, const char* port)
1441 {
1442 	if (!Irc_Proto_Connect(server, port)) {
1443 		/* connected to server, send NICK and USER commands */
1444 		const char*  const pass = irc_password->string;
1445 		const char*  const user = irc_user->string;
1446 		irc_connected = true;
1447 		if (pass[0] != '\0')
1448 			Irc_Proto_Password(pass);
1449 		Irc_Proto_Nick(irc_nick->string);
1450 		Irc_Proto_User(user, true, user);
1451 	} else {
1452 		Com_Printf("Could not connect to the irc server %s:%s\n", server, port);
1453 	}
1454 }
1455 
Irc_Logic_Disconnect(const char * reason)1456 static void Irc_Logic_Disconnect (const char* reason)
1457 {
1458 	if (!irc_connected) {
1459 		Com_Printf("Irc_Disconnect: not connected\n");
1460 		return;
1461 	}
1462 
1463 	Com_Printf("Irc_Disconnect: %s\n", reason);
1464 	Irc_Proto_Quit(reason);
1465 	Irc_Proto_Disconnect();
1466 	irc_connected = false;
1467 	chan = nullptr;
1468 	Cvar_ForceSet("irc_defaultChannel", "");
1469 	Cvar_ForceSet("irc_topic", "Connecting (please wait)...");
1470 	UI_ResetData(TEXT_IRCUSERS);
1471 	Irc_Input_Deactivate_f();
1472 }
1473 
1474 /**
1475  * @sa Irc_Logic_ReadMessages
1476  * @sa Irc_Logic_SendMessages
1477  */
Irc_Logic_Frame(void)1478 void Irc_Logic_Frame (void)
1479 {
1480 	if (irc_connected) {
1481 		if (irc_channel->modified) {
1482 			/** @todo do this without disconnect, connect */
1483 			Irc_Logic_Disconnect("Switched to another channel");
1484 			Irc_Logic_Connect(irc_server->string, irc_port->string);
1485 			if (irc_connected)
1486 				Irc_Client_Join(irc_channel->string, nullptr);
1487 		}
1488 		/* could have changed in the meantime */
1489 		if (irc_connected) {
1490 			Irc_Logic_SendMessages();
1491 			Irc_Logic_ReadMessages();
1492 		}
1493 	}
1494 	irc_channel->modified = false;
1495 }
1496 
Irc_Logic_GetChannelTopic(const irc_channel_t * channel)1497 static const char* Irc_Logic_GetChannelTopic (const irc_channel_t* channel)
1498 {
1499 	assert(channel);
1500 	return channel->topic;
1501 }
1502 
1503 /**
1504  * @brief Adds a new username to the channel username list
1505  * @sa Irc_Logic_RemoveChannelName
1506  */
Irc_Logic_AddChannelName(irc_channel_t * channel,irc_nick_prefix_t prefix,const char * nick)1507 static void Irc_Logic_AddChannelName (irc_channel_t* channel, irc_nick_prefix_t prefix, const char* nick)
1508 {
1509 	int i;
1510 	/* first one */
1511 	irc_user_t* user = channel->user;
1512 	for (i = 0; user && i < channel->users; i++, user = user->next) {
1513 		if (!strncmp(&(user->key[1]), nick, MAX_VAR - 1))
1514 			return;
1515 	}
1516 	user = Mem_PoolAllocType(irc_user_t, cl_ircSysPool);
1517 	user->next = channel->user;
1518 	channel->user = user;
1519 
1520 	Com_sprintf(user->key, sizeof(user->key), "%c%s", prefix, nick);
1521 	channel->users++;
1522 	Com_DPrintf(DEBUG_CLIENT, "Add user '%s' to userlist at pos %i\n", user->key, channel->users);
1523 	Irc_Client_Names_f();
1524 }
1525 
1526 /**
1527  * @brief Removes a username from the channel username list
1528  * @sa Irc_Logic_AddChannelName
1529  */
Irc_Logic_RemoveChannelName(irc_channel_t * channel,const char * nick)1530 static void Irc_Logic_RemoveChannelName (irc_channel_t* channel, const char* nick)
1531 {
1532 	int i;
1533 	/* first one */
1534 	irc_user_t* user = channel->user;
1535 	irc_user_t* predecessor = nullptr;
1536 	for (i = 0; user && i < channel->users; i++, user = user->next) {
1537 		if (!strncmp(&(user->key[1]), nick, sizeof(user->key))) {
1538 			/* delete the first user from the list */
1539 			if (!predecessor)
1540 				channel->user = user->next;
1541 			/* point to the descendant */
1542 			else
1543 				predecessor->next = user->next;
1544 			Mem_Free(user);
1545 			chan->users--;
1546 			Irc_Client_Names_f();
1547 			return;
1548 		}
1549 		predecessor = user;
1550 	}
1551 }
1552 
1553 /*
1554 ===============================================================
1555 Network functions
1556 ===============================================================
1557 */
1558 
Irc_Net_StreamClose(void)1559 static void Irc_Net_StreamClose (void)
1560 {
1561 	irc_stream = nullptr;
1562 }
1563 
1564 /**
1565  * @return true if successful - false otherwise
1566  * @sa Irc_Net_Disconnect
1567  */
Irc_Net_Connect(const char * host,const char * port)1568 static bool Irc_Net_Connect (const char* host, const char* port)
1569 {
1570 	if (irc_stream)
1571 		NET_StreamFree(irc_stream);
1572 	irc_stream = NET_Connect(host, port, Irc_Net_StreamClose);
1573 	return irc_stream ? false : true;
1574 }
1575 
1576 /**
1577  * @sa Irc_Net_Connect
1578  */
Irc_Net_Disconnect(void)1579 static bool Irc_Net_Disconnect (void)
1580 {
1581 	NET_StreamFree(irc_stream);
1582 	return true;
1583 }
1584 
Irc_Net_Send(const char * msg,size_t msg_len)1585 static void Irc_Net_Send (const char* msg, size_t msg_len)
1586 {
1587 	assert(msg);
1588 	NET_StreamEnqueue(irc_stream, msg, msg_len);
1589 }
1590 
1591 /*
1592 ===============================================================
1593 Bindings
1594 ===============================================================
1595 */
1596 
Irc_Connect_f(void)1597 static void Irc_Connect_f (void)
1598 {
1599 	const int argc = Cmd_Argc();
1600 	if (!irc_port || !irc_server)
1601 		return;
1602 	if (argc >= 2 && argc <= 4) {
1603 #if 0
1604 		if (irc_connected)
1605 			Irc_Logic_Disconnect("reconnect");
1606 #endif
1607 		if (!irc_connected) {
1608 			/* not connected yet */
1609 			if (argc >= 2)
1610 				Cvar_Set("irc_server", "%s", Cmd_Argv(1));
1611 			if (argc >= 3)
1612 				Cvar_Set("irc_port", "%s", Cmd_Argv(2));
1613 			Com_Printf("Connecting to %s:%s\n", irc_server->string, irc_port->string);
1614 			Irc_Logic_Connect(irc_server->string, irc_port->string);
1615 			if (irc_connected && argc >= 4)
1616 				Irc_Client_Join(Cmd_Argv(3), nullptr);
1617 		} else {
1618 			Com_Printf("Already connected.\n");
1619 		}
1620 	} else if (irc_server->string[0] != '\0' && irc_port->integer) {
1621 		if (!irc_connected)
1622 			Cbuf_AddText("irc_connect %s %i %s\n", irc_server->string, irc_port->integer, irc_channel->string);
1623 		else
1624 			Com_Printf("Already connected.\n");
1625 	} else {
1626 		Com_Printf("Usage: %s [<server>] [<port>] [<channel>]\n", Cmd_Argv(0));
1627 	}
1628 }
1629 
Irc_Disconnect_f(void)1630 static void Irc_Disconnect_f (void)
1631 {
1632 	Irc_Logic_Disconnect("normal exit");
1633 }
1634 
Irc_Client_Join(const char * channel,const char * password)1635 static bool Irc_Client_Join (const char* channel, const char* password)
1636 {
1637 	if (!Irc_IsChannel(channel)) {
1638 		Com_Printf("No valid channel name\n");
1639 		return false;
1640 	}
1641 	/* join desired channel */
1642 	if (!Irc_Proto_Join(channel, password)) {
1643 		Cvar_ForceSet("irc_defaultChannel", channel);
1644 		Com_Printf("Joined channel: '%s'\n", channel);
1645 		return true;
1646 	} else {
1647 		Com_Printf("Could not join channel: '%s'\n", channel);
1648 		return false;
1649 	}
1650 }
1651 
Irc_Client_Join_f(void)1652 static void Irc_Client_Join_f (void)
1653 {
1654 	const int argc = Cmd_Argc();
1655 	if (argc >= 2 && argc <= 3) {
1656 		const char*  const channel = Cmd_Argv(1);
1657 		/* password is optional */
1658 		const char*  const channel_pass = (argc == 3) ? Cmd_Argv(2) : nullptr;
1659 		Irc_Client_Join(channel, channel_pass);
1660 	} else {
1661 		Com_Printf("Usage: %s <channel> [<password>]\n", Cmd_Argv(0));
1662 	}
1663 }
1664 
Irc_Client_Part_f(void)1665 static void Irc_Client_Part_f (void)
1666 {
1667 	const int argc = Cmd_Argc();
1668 	if (argc == 2) {
1669 		const char*  const channel = Cmd_Argv(1);
1670 		Irc_Proto_Part(channel);
1671 	} else {
1672 		Com_Printf("Usage: %s <channel>\n", Cmd_Argv(0));
1673 	}
1674 }
1675 
1676 /**
1677  * @brief Send a message from menu or commandline
1678  * @note This function uses the irc_send_buffer cvar to handle the menu input for irc messages
1679  * See menu_irc.ufo for more information
1680  */
Irc_Client_Msg_f(void)1681 static void Irc_Client_Msg_f (void)
1682 {
1683 	char cropped_msg[IRC_SEND_BUF_SIZE];
1684 	const char* msg;
1685 
1686 	if (Cmd_Argc() >= 2)
1687 		msg = Cmd_Args();
1688 	else if (irc_send_buffer->string[0] != '\0')
1689 		msg = irc_send_buffer->string;
1690 	else
1691 		return;
1692 
1693 	if (msg[0] != '\0') {
1694 		if (irc_defaultChannel->string[0] != '\0') {
1695 			if (msg[0] == '"') {
1696 				const size_t msg_len = strlen(msg);
1697 				memcpy(cropped_msg, msg + 1, msg_len - 2);
1698 				cropped_msg[msg_len - 2] = '\0';
1699 				msg = cropped_msg;
1700 			}
1701 			if (!Irc_Proto_Msg(irc_defaultChannel->string, msg)) {
1702 				/* local echo */
1703 				Irc_AppendToBuffer("<%s> %s", irc_nick->string, msg);
1704 			}
1705 			Cvar_ForceSet("irc_send_buffer", "");
1706 		} else
1707 			Com_Printf("Join a channel first.\n");
1708 	}
1709 }
1710 
Irc_Client_PrivMsg_f(void)1711 static void Irc_Client_PrivMsg_f (void)
1712 {
1713 	if (Cmd_Argc() >= 3) {
1714 		char cropped_msg[IRC_SEND_BUF_SIZE];
1715 		const char*  const target = Cmd_Argv(1);
1716 		const char* msg = Cmd_Args() + strlen(target) + 1;
1717 		if (*msg == '"') {
1718 			size_t msg_len = strlen(msg);
1719 			memcpy(cropped_msg, msg + 1, msg_len - 2);
1720 			cropped_msg[msg_len - 2] = '\0';
1721 			msg = cropped_msg;
1722 		}
1723 		Irc_Proto_Msg(target, msg);
1724 	} else {
1725 		Com_Printf("Usage: %s <target> {<msg>}\n", Cmd_Argv(0));
1726 	}
1727 }
1728 
Irc_Client_Mode_f(void)1729 static void Irc_Client_Mode_f (void)
1730 {
1731 	const int argc = Cmd_Argc();
1732 	if (argc >= 3) {
1733 		const char*  const target = Cmd_Argv(1);
1734 		const char*  const modes = Cmd_Argv(2);
1735 		const char*  const params = argc >= 4
1736 			? Cmd_Args() + strlen(target) + strlen(modes) + 2
1737 			: nullptr;
1738 		Irc_Proto_Mode(target, modes, params);
1739 	} else
1740 		Com_Printf("Usage: %s <target> <modes> {<param>}\n", Cmd_Argv(0));
1741 }
1742 
Irc_Client_Topic_f(void)1743 static void Irc_Client_Topic_f (void)
1744 {
1745 	const int argc = Cmd_Argc();
1746 	if (argc >= 2) {
1747 		const char*  const channel = Cmd_Argv(1);
1748 		if (chan) {
1749 			if (argc >= 3) {
1750 				char buf[1024];
1751 				const char* in = Cmd_Args();
1752 				char* out = buf;
1753 				if (*in == '"')
1754 					in += 2;
1755 				in += strlen(channel) + 1;
1756 				Q_strncpyz(out, in, sizeof(out));
1757 				if (*out == '"') {
1758 					size_t out_len;
1759 					++out;
1760 					out_len = strlen(out);
1761 					assert(out_len >= 1);
1762 					out[out_len - 1] = '\0';
1763 				}
1764 				Irc_Proto_Topic(channel, out);
1765 			} else {
1766 				Com_Printf("%s topic: \"%s\"\n", channel, Irc_Logic_GetChannelTopic(chan));
1767 			}
1768 		} else {
1769 			Com_Printf("Not joined: %s\n", channel);
1770 		}
1771 	} else {
1772 		Com_Printf("Usage: %s <channel> [<topic>]\n", Cmd_Argv(0));
1773 	}
1774 }
1775 
1776 #define IRC_MAX_USERLIST 512
1777 static char irc_userListOrdered[IRC_MAX_USERLIST][MAX_VAR];
1778 
Irc_Client_Names_f(void)1779 static void Irc_Client_Names_f (void)
1780 {
1781 	if (!chan) {
1782 		Com_Printf("Not joined\n");
1783 		return;
1784 	}
1785 	linkedList_t* irc_names_buffer = nullptr;
1786 	int i;
1787 	irc_user_t* user = chan->user;
1788 	for (i = 0; i < chan->users; i++) {
1789 		if (i >= IRC_MAX_USERLIST)
1790 			break;
1791 		Q_strncpyz(irc_userListOrdered[i], user->key, MAX_VAR);
1792 		user = user->next;
1793 	}
1794 	if (i > 0) {
1795 		qsort((void*)irc_userListOrdered, i, MAX_VAR, Q_StringSort);
1796 		while (i--)
1797 			LIST_AddString(&irc_names_buffer, irc_userListOrdered[i]);
1798 	}
1799 	UI_RegisterLinkedListText(TEXT_IRCUSERS, irc_names_buffer);
1800 }
1801 
Irc_Client_Kick_f(void)1802 static void Irc_Client_Kick_f (void)
1803 {
1804 	const int argc = Cmd_Argc();
1805 	if (argc < 3) {
1806 		Com_Printf("Usage: %s <channel> <nick> [<reason>]\n", Cmd_Argv(0));
1807 		return;
1808 	}
1809 	const char* channel = Cmd_Argv(1);
1810 	if (!chan) {
1811 		Com_Printf("Not joined: %s.\n", channel);
1812 		return;
1813 	}
1814 	const char*  const nick = Cmd_Argv(2);
1815 	const char* reason;
1816 	if (argc >= 4)
1817 		reason = Cmd_Args() + strlen(nick) + strlen(channel) + 2;
1818 	else
1819 		reason = nullptr;
1820 	Irc_Proto_Kick(channel, nick, reason);
1821 }
1822 
Irc_GetExternalIP(const char * externalIP,void * userdata)1823 static void Irc_GetExternalIP (const char* externalIP, void* userdata)
1824 {
1825 	const irc_user_t* user;
1826 	char buf[128];
1827 
1828 	if (!externalIP) {
1829 		Com_Printf("Could not query masterserver\n");
1830 		return;
1831 	}
1832 	Com_sprintf(buf, sizeof(buf), "%s%s;%s;%s", IRC_INVITE_FOR_A_GAME, externalIP, port->string, UFO_VERSION);
1833 
1834 	user = chan->user;
1835 	while (user) {
1836 		/** @todo Maybe somehow check the version of the client with ctcp VERSION and only send
1837 		 * to those, that are connected with ufoai and have a correct version */
1838 		/* the first character is a prefix, skip it when checking
1839 		 * against our own nickname */
1840 		const char* name = &(user->key[1]);
1841 		if (!Q_streq(name, irc_nick->string))
1842 			Irc_Proto_Msg(name, buf);
1843 		user = user->next;
1844 	}
1845 }
1846 
1847 /**
1848  * @sa Irc_Client_CmdPrivmsg
1849  */
Irc_Client_Invite_f(void)1850 static void Irc_Client_Invite_f (void)
1851 {
1852 	if (!CL_OnBattlescape()) {
1853 		Com_Printf("You must be connected to a running server to invite others\n");
1854 		return;
1855 	}
1856 
1857 	if (!chan) {
1858 		UI_PushWindow("irc_popup");
1859 		return;
1860 	}
1861 
1862 	HTTP_GetURL(va("%s/ufo/masterserver.php?ip", masterserver_url->string), Irc_GetExternalIP);
1863 }
1864 
Irc_Client_Who_f(void)1865 static void Irc_Client_Who_f (void)
1866 {
1867 	if (Cmd_Argc() != 2) {
1868 		Com_Printf("Usage: %s <usermask>\n", Cmd_Argv(0));
1869 		return;
1870 	}
1871 	Irc_Proto_Who(Cmd_Argv(1));
1872 }
1873 
Irc_Client_Whois_f(void)1874 static void Irc_Client_Whois_f (void)
1875 {
1876 	if (Cmd_Argc() != 2) {
1877 		Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
1878 		return;
1879 	}
1880 	Irc_Proto_Whois(Cmd_Argv(1));
1881 }
1882 
Irc_Client_Whowas_f(void)1883 static void Irc_Client_Whowas_f (void)
1884 {
1885 	if (Cmd_Argc() != 2) {
1886 		Com_Printf("Usage: %s <nick>\n", Cmd_Argv(0));
1887 		return;
1888 	}
1889 	Irc_Proto_Whowas(Cmd_Argv(1));
1890 }
1891 
1892 /*
1893 ===============================================================
1894 Menu functions
1895 ===============================================================
1896 */
1897 
1898 /**
1899  * @brief Adds the username you clicked to your input buffer
1900  * @sa Irc_UserRightClick_f
1901  */
Irc_UserClick_f(void)1902 static void Irc_UserClick_f (void)
1903 {
1904 	const char* name;
1905 	int num, cnt;
1906 
1907 	if (Cmd_Argc() != 2)
1908 		return;
1909 
1910 	if (!chan)
1911 		return;
1912 
1913 	num = atoi(Cmd_Argv(1));
1914 	if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
1915 		return;
1916 
1917 	cnt = std::min(chan->users, IRC_MAX_USERLIST);
1918 	cnt -= num + 1;
1919 
1920 	name = irc_userListOrdered[cnt];
1921 	Cvar_Set("irc_send_buffer", "%s%s: ", irc_send_buffer->string, &name[1]);
1922 }
1923 
1924 /**
1925  * @brief Performs a whois query for the username you clicked
1926  * @sa Irc_UserClick_f
1927  */
Irc_UserRightClick_f(void)1928 static void Irc_UserRightClick_f (void)
1929 {
1930 	const char* name;
1931 	int num, cnt;
1932 
1933 	if (Cmd_Argc() != 2)
1934 		return;
1935 
1936 	if (!chan)
1937 		return;
1938 
1939 	num = atoi(Cmd_Argv(1));
1940 	if (num < 0 || num >= chan->users || num >= IRC_MAX_USERLIST)
1941 		return;
1942 
1943 	cnt = std::min(chan->users, IRC_MAX_USERLIST);
1944 	cnt -= num + 1;
1945 
1946 	name = irc_userListOrdered[cnt];
1947 	Irc_Proto_Whois(&name[1]);
1948 }
1949 
1950 /**
1951  * @sa Irc_Input_Deactivate
1952  */
Irc_Input_Activate_f(void)1953 static void Irc_Input_Activate_f (void)
1954 {
1955 	/* in case of a failure we need this in UI_PopWindow */
1956 	if (irc_connected && irc_defaultChannel->string[0] != '\0') {
1957 		UI_RegisterText(TEXT_IRCCONTENT, irc_buffer);
1958 	} else {
1959 		Com_DPrintf(DEBUG_CLIENT, "Irc_Input_Activate: Warning - IRC not connected\n");
1960 		UI_PopWindow();
1961 		UI_PushWindow("irc_popup");
1962 	}
1963 }
1964 
1965 /**
1966  * @sa Irc_Input_Activate
1967  */
Irc_Input_Deactivate_f(void)1968 static void Irc_Input_Deactivate_f (void)
1969 {
1970 	irc_send_buffer->modified = false;
1971 
1972 	UI_ResetData(TEXT_IRCCONTENT);
1973 }
1974 
1975 /*
1976 ===============================================================
1977 Init and Shutdown functions
1978 ===============================================================
1979 */
1980 
Irc_Init(void)1981 void Irc_Init (void)
1982 {
1983 	cl_ircSysPool = Mem_CreatePool("Client: IRC system");
1984 
1985 	/* commands */
1986 	Cmd_AddCommand("irc_join", Irc_Client_Join_f, "Join an irc channel");
1987 	Cmd_AddCommand("irc_connect", Irc_Connect_f, "Connect to the irc network");
1988 	Cmd_AddCommand("irc_disconnect", Irc_Disconnect_f, "Disconnect from the irc network");
1989 	Cmd_AddCommand("irc_part", Irc_Client_Part_f);
1990 	Cmd_AddCommand("irc_privmsg", Irc_Client_PrivMsg_f);
1991 	Cmd_AddCommand("irc_mode", Irc_Client_Mode_f);
1992 	Cmd_AddCommand("irc_who", Irc_Client_Who_f);
1993 	Cmd_AddCommand("irc_whois", Irc_Client_Whois_f);
1994 	Cmd_AddCommand("irc_whowas", Irc_Client_Whowas_f);
1995 	Cmd_AddCommand("irc_chanmsg", Irc_Client_Msg_f);
1996 	Cmd_AddCommand("irc_topic", Irc_Client_Topic_f);
1997 	Cmd_AddCommand("irc_names", Irc_Client_Names_f);
1998 	Cmd_AddCommand("irc_kick", Irc_Client_Kick_f);
1999 	Cmd_AddCommand("irc_invite", Irc_Client_Invite_f, "Invite other players for a game");
2000 
2001 	Cmd_AddCommand("irc_userlist_click", Irc_UserClick_f, "Menu function for clicking a user from the list");
2002 	Cmd_AddCommand("irc_userlist_rclick", Irc_UserRightClick_f, "Menu function for clicking a user from the list");
2003 
2004 	Cmd_AddCommand("irc_activate", Irc_Input_Activate_f, "IRC init when entering the menu");
2005 	Cmd_AddCommand("irc_deactivate", Irc_Input_Deactivate_f, "IRC deactivate when leaving the irc menu");
2006 
2007 	/* cvars */
2008 	irc_server = Cvar_Get("irc_server", "irc.freenode.org", CVAR_ARCHIVE, "IRC server to connect to");
2009 	irc_channel = Cvar_Get("irc_channel", "#ufoai-gamer", CVAR_ARCHIVE, "IRC channel to join into");
2010 	irc_channel->modified = false;
2011 	irc_port = Cvar_Get("irc_port", "6667", CVAR_ARCHIVE, "IRC port to connect to");
2012 	irc_user = Cvar_Get("irc_user", "UFOAIPlayer", CVAR_ARCHIVE);
2013 	irc_password = Cvar_Get("irc_password", "", CVAR_ARCHIVE);
2014 	irc_topic = Cvar_Get("irc_topic", "Connecting (please wait)...", CVAR_NOSET);
2015 	irc_defaultChannel = Cvar_Get("irc_defaultChannel", "", CVAR_NOSET);
2016 	irc_logConsole = Cvar_Get("irc_logConsole", "0", CVAR_ARCHIVE, "Log all irc conversations to game console, too");
2017 	irc_showIfNotInMenu = Cvar_Get("irc_showIfNotInMenu", "0", CVAR_ARCHIVE, "Show chat messages on top of the menu stack if we are not in the irc menu");
2018 	irc_send_buffer = Cvar_Get("irc_send_buffer");
2019 	irc_nick = Cvar_Get("cl_name");
2020 
2021 	irc_messageBucketSize = Cvar_Get("irc_messageBucketSize", "100", CVAR_ARCHIVE, "max 100 messages in bucket");
2022 	irc_messageBucketBurst = Cvar_Get("irc_messageBucketBurst", "5", CVAR_ARCHIVE, "max burst size is 5 messages");
2023 	irc_characterBucketSize = Cvar_Get("irc_characterBucketSize", "2500", CVAR_ARCHIVE, "max 2,500 characters in bucket");
2024 	irc_characterBucketBurst = Cvar_Get("irc_characterBucketBurst", "250", CVAR_ARCHIVE, "max burst size is 250 characters");
2025 	irc_characterBucketRate = Cvar_Get("irc_characterBucketRate", "10", CVAR_ARCHIVE, "per second (100 characters in 10 seconds)");
2026 }
2027 
Irc_Shutdown(void)2028 void Irc_Shutdown (void)
2029 {
2030 	if (irc_connected)
2031 		Irc_Logic_Disconnect("shutdown");
2032 
2033 	Mem_DeletePool(cl_ircSysPool);
2034 }
2035