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