1 /********************************************************************\
2   * BitlBee -- An IRC to other IM-networks gateway                     *
3   *                                                                    *
4   * Copyright 2002-2012 Wilmer van der Gaast and others                *
5   \********************************************************************/
6 
7 /* IRC commands                                                         */
8 
9 /*
10   This program is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 2 of the License, or
13   (at your option) any later version.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License with
21   the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22   if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23   Fifth Floor, Boston, MA  02110-1301  USA
24 */
25 
26 #define BITLBEE_CORE
27 #include "bitlbee.h"
28 #include "canohost.h"
29 #include "help.h"
30 #include "ipc.h"
31 #include "base64.h"
32 
irc_cmd_pass(irc_t * irc,char ** cmd)33 static void irc_cmd_pass(irc_t *irc, char **cmd)
34 {
35 	if (irc->status & USTATUS_LOGGED_IN) {
36 		char *send_cmd[] = { "identify", cmd[1], NULL };
37 
38 		/* We're already logged in, this client seems to send the PASS
39 		   command last. (Possibly it won't send it at all if it turns
40 		   out we don't require it, which will break this feature.)
41 		   Try to identify using the given password. */
42 		root_command(irc, send_cmd);
43 		return;
44 	}
45 	/* Handling in pre-logged-in state, first see if this server is
46 	   password-protected: */
47 	else if (global.conf->auth_pass &&
48 	         (strncmp(global.conf->auth_pass, "md5:", 4) == 0 ?
49 	          md5_verify_password(cmd[1], global.conf->auth_pass + 4) == 0 :
50 	          strcmp(cmd[1], global.conf->auth_pass) == 0)) {
51 		irc->status |= USTATUS_AUTHORIZED;
52 		irc_check_login(irc);
53 	} else if (global.conf->auth_pass) {
54 		irc_send_num(irc, 464, ":Incorrect password");
55 	} else {
56 		/* Remember the password and try to identify after USER/NICK. */
57 		irc_setpass(irc, cmd[1]);
58 		irc_check_login(irc);
59 	}
60 }
61 
62 /* http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
63 
64    This isn't actually IRC, it's used by for example stunnel4 to indicate
65    the origin of the secured counterpart of the connection. It'll go wrong
66    with arguments starting with : like for example "::1" but I guess I'm
67    okay with that. */
irc_cmd_proxy(irc_t * irc,char ** cmd)68 static void irc_cmd_proxy(irc_t *irc, char **cmd)
69 {
70 	struct addrinfo hints, *ai;
71 	struct sockaddr_storage sock;
72 	socklen_t socklen = sizeof(sock);
73 
74 	if (getpeername(irc->fd, (struct sockaddr*) &sock, &socklen) != 0) {
75 		return;
76 	}
77 
78 	ipv64_normalise_mapped(&sock, &socklen);
79 
80 	/* Only accept PROXY "command" on localhost sockets. */
81 	if (!((sock.ss_family == AF_INET &&
82 	       ntohl(((struct sockaddr_in*)&sock)->sin_addr.s_addr) == INADDR_LOOPBACK) ||
83 	      (sock.ss_family == AF_INET6 &&
84 	       IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6*)&sock)->sin6_addr)))) {
85 		return;
86 	}
87 
88 	/* And only once. Do this with a pretty dumb regex-match for
89 	   now, maybe better to use some sort of flag.. */
90 	if (!g_regex_match_simple("^(ip6-)?localhost(.(localdomain.?)?)?$", irc->user->host, 0, 0)) {
91 		return;
92 	}
93 
94 	memset(&hints, 0, sizeof(hints));
95 	hints.ai_flags = AI_NUMERICHOST;
96 	if (getaddrinfo(cmd[2], NULL, &hints, &ai) != 0) {
97 		return;
98 	}
99 
100 	irc_set_hosts(irc, ai->ai_addr, ai->ai_addrlen);
101 	freeaddrinfo(ai);
102 }
103 
irc_sasl_plain_parse(char * input,char ** user,char ** pass)104 static gboolean irc_sasl_plain_parse(char *input, char **user, char **pass)
105 {
106 	int i, part, len;
107 	guint8 *decoded;
108 	char *parts[3];
109 
110 	/* bitlbee's base64_decode wrapper adds an extra null terminator at the end */
111 	len = base64_decode(input, &decoded);
112 
113 	/* this loop splits the decoded string into the parts array, like this:
114 	   "username\0username\0password" -> {"username", "username", "password"} */
115 
116 	for (i = 0, part = 0; i < len && part < 3; part++) {
117 		/* set each of parts[] to point to the beginning of a string */
118 		parts[part] = (char *) decoded + i;
119 
120 		/* move the cursor forward to the next null terminator*/
121 		i += strlen(parts[part]) + 1;
122 	}
123 
124 	/* sanity checks */
125 	if (part != 3 || i != (len + 1) || (parts[0][0] && strcmp(parts[0], parts[1]) != 0)) {
126 		g_free(decoded);
127 		return FALSE;
128 	} else {
129 		*user = g_strdup(parts[1]);
130 		*pass = g_strdup(parts[2]);
131 		g_free(decoded);
132 		return TRUE;
133 	}
134 }
135 
irc_sasl_check_pass(irc_t * irc,char * user,char * pass)136 static gboolean irc_sasl_check_pass(irc_t *irc, char *user, char *pass)
137 {
138 	storage_status_t status;
139 
140 	/* just check the password here to be able to reply with useful numerics
141 	 * the actual identification will be handled later */
142 	status = auth_check_pass(irc, user, pass);
143 
144 	if (status == STORAGE_OK) {
145 		if (!irc->user->nick) {
146 			/* set the nick here so we have it for the following numeric */
147 			irc->user->nick = g_strdup(user);
148 		}
149 		irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
150 		             irc->user->nick, irc->user->user, irc->user->host,
151 			     irc->user->nick, irc->user->nick);
152 		irc_send_num(irc, 903, ":Password accepted");
153 		return TRUE;
154 
155 	} else if (status == STORAGE_INVALID_PASSWORD) {
156 		irc_send_num(irc, 904, ":Incorrect password");
157 	} else if (status == STORAGE_NO_SUCH_USER) {
158 		irc_send_num(irc, 904, ":The nick is (probably) not registered");
159 	} else {
160 		irc_send_num(irc, 904, ":Unknown SASL authentication error");
161 	}
162 
163 	return FALSE;
164 }
165 
irc_cmd_authenticate(irc_t * irc,char ** cmd)166 static void irc_cmd_authenticate(irc_t *irc, char **cmd)
167 {
168 	/* require the CAP to be enabled, and don't allow authentication before server password */
169 	if (!(irc->caps & CAP_SASL) ||
170 	    (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED))) {
171 		return;
172 	}
173 
174 	if (irc->status & USTATUS_SASL_PLAIN_PENDING) {
175 		char *user, *pass;
176 
177 		irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
178 
179 		if (!irc_sasl_plain_parse(cmd[1], &user, &pass)) {
180 			irc_send_num(irc, 904, ":SASL authentication failed");
181 			return;
182 		}
183 
184 		/* let's not support the nick != user case
185 		 * if NICK is received after SASL, it will just fail after registration */
186 		if (user && irc->user->nick && strcmp(user, irc->user->nick) != 0) {
187 			irc_send_num(irc, 902, ":Your SASL username does not match your nickname");
188 
189 		} else if (irc_sasl_check_pass(irc, user, pass)) {
190 			/* and here we do the same thing as the PASS command*/
191 			if (irc->status & USTATUS_LOGGED_IN) {
192 				char *send_cmd[] = { "identify", pass, NULL };
193 				root_command(irc, send_cmd);
194 			} else {
195 				/* no check_login here - wait for CAP END */
196 				irc_setpass(irc, pass);
197 			}
198 		}
199 
200 		g_free(user);
201 		g_free(pass);
202 
203 	} else if (irc->status & USTATUS_IDENTIFIED) {
204 		irc_send_num(irc, 907, ":You have already authenticated");
205 
206 	} else if (strcmp(cmd[1], "*") == 0) {
207 		irc_send_num(irc, 906, ":SASL authentication aborted");
208 		irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
209 
210 	} else if (g_strcasecmp(cmd[1], "PLAIN") == 0) {
211 		irc_write(irc, "AUTHENTICATE +");
212 		irc->status |= USTATUS_SASL_PLAIN_PENDING;
213 
214 	} else {
215 		irc_send_num(irc, 908, "PLAIN :is the available SASL mechanism");
216 		irc_send_num(irc, 904, ":SASL authentication failed");
217 		irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
218 	}
219 }
220 
irc_cmd_user(irc_t * irc,char ** cmd)221 static void irc_cmd_user(irc_t *irc, char **cmd)
222 {
223 	irc->user->user = g_strdup(cmd[1]);
224 	irc->user->fullname = g_strdup(cmd[4]);
225 
226 	irc_check_login(irc);
227 }
228 
irc_cmd_nick(irc_t * irc,char ** cmd)229 static void irc_cmd_nick(irc_t *irc, char **cmd)
230 {
231 	irc_user_t *iu;
232 
233 	if ((iu = irc_user_by_name(irc, cmd[1])) && iu != irc->user) {
234 		irc_send_num(irc, 433, "%s :This nick is already in use", cmd[1]);
235 	} else if (!nick_ok(NULL, cmd[1])) {
236 		/* [SH] Invalid characters. */
237 		irc_send_num(irc, 432, "%s :This nick contains invalid characters", cmd[1]);
238 	} else if (irc->status & USTATUS_LOGGED_IN) {
239 		/* WATCH OUT: iu from the first if reused here to check if the
240 		   new nickname is the same (other than case, possibly). If it
241 		   is, no need to reset identify-status. */
242 		if ((irc->status & USTATUS_IDENTIFIED) && iu != irc->user) {
243 			irc_setpass(irc, NULL);
244 			irc->status &= ~USTATUS_IDENTIFIED;
245 			irc_umode_set(irc, "-R", 1);
246 
247 			if (irc->caps & CAP_SASL) {
248 				irc_send_num(irc, 901, "%s!%s@%s :You are now logged out",
249 					irc->user->nick, irc->user->user, irc->user->host);
250 			}
251 
252 			irc_rootmsg(irc, "Changing nicks resets your identify status. "
253 			            "Re-identify or register a new account if you want "
254 			            "your configuration to be saved. See \x02help "
255 			            "nick_changes\x02.");
256 		}
257 
258 		if (strcmp(cmd[1], irc->user->nick) != 0) {
259 			irc_user_set_nick(irc->user, cmd[1]);
260 		}
261 	} else {
262 		g_free(irc->user->nick);
263 		irc->user->nick = g_strdup(cmd[1]);
264 
265 		irc_check_login(irc);
266 	}
267 }
268 
irc_cmd_quit(irc_t * irc,char ** cmd)269 static void irc_cmd_quit(irc_t *irc, char **cmd)
270 {
271 	if (cmd[1] && *cmd[1]) {
272 		irc_abort(irc, 0, "Quit: %s", cmd[1]);
273 	} else {
274 		irc_abort(irc, 0, "Leaving...");
275 	}
276 }
277 
irc_cmd_ping(irc_t * irc,char ** cmd)278 static void irc_cmd_ping(irc_t *irc, char **cmd)
279 {
280 	irc_write(irc, ":%s PONG %s :%s", irc->root->host,
281 	          irc->root->host, cmd[1] ? cmd[1] : irc->root->host);
282 }
283 
irc_cmd_pong(irc_t * irc,char ** cmd)284 static void irc_cmd_pong(irc_t *irc, char **cmd)
285 {
286 	/* We could check the value we get back from the user, but in
287 	   fact we don't care, we're just happy s/he's still alive. */
288 	irc->last_pong = gettime();
289 	irc->pinging = 0;
290 }
291 
irc_cmd_join(irc_t * irc,char ** cmd)292 static void irc_cmd_join(irc_t *irc, char **cmd)
293 {
294 	char *comma, *s = cmd[1];
295 
296 	while (s) {
297 		irc_channel_t *ic;
298 
299 		if ((comma = strchr(s, ','))) {
300 			*comma = '\0';
301 		}
302 
303 		if ((ic = irc_channel_by_name(irc, s)) == NULL &&
304 		    (ic = irc_channel_new(irc, s))) {
305 			if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
306 				/* Autoconfiguration is for control channels only ATM. */
307 			} else if (bee_group_by_name(ic->irc->b, ic->name + 1, FALSE)) {
308 				set_setstr(&ic->set, "group", ic->name + 1);
309 				set_setstr(&ic->set, "fill_by", "group");
310 			} else if (set_setstr(&ic->set, "protocol", ic->name + 1)) {
311 				set_setstr(&ic->set, "fill_by", "protocol");
312 			} else if (set_setstr(&ic->set, "account", ic->name + 1)) {
313 				set_setstr(&ic->set, "fill_by", "account");
314 			} else {
315 				/* The set commands above will run this already,
316 				   but if we didn't hit any, we have to fill the
317 				   channel with the default population. */
318 				bee_irc_channel_update(ic->irc, ic, NULL);
319 			}
320 		} else if (ic == NULL) {
321 			irc_send_num(irc, 479, "%s :Invalid channel name", s);
322 			goto next;
323 		}
324 
325 		if (ic->flags & IRC_CHANNEL_JOINED) {
326 			/* Dude, you're already there...
327 			   RFC doesn't have any reply for that though? */
328 			goto next;
329 		}
330 
331 		if (ic->f->join && !ic->f->join(ic)) {
332 			/* The story is: FALSE either means the handler
333 			   showed an error message, or is doing some work
334 			   before the join should be confirmed. (In the
335 			   latter case, the caller should take care of that
336 			   confirmation.) TRUE means all's good, let the
337 			   user join the channel right away. */
338 			goto next;
339 		}
340 
341 		irc_channel_add_user(ic, irc->user);
342 
343 next:
344 		if (comma) {
345 			s = comma + 1;
346 			*comma = ',';
347 		} else {
348 			break;
349 		}
350 	}
351 }
352 
irc_cmd_names(irc_t * irc,char ** cmd)353 static void irc_cmd_names(irc_t *irc, char **cmd)
354 {
355 	irc_channel_t *ic;
356 
357 	if (cmd[1] && (ic = irc_channel_by_name(irc, cmd[1]))) {
358 		irc_send_names(ic);
359 	}
360 	/* With no args, we should show /names of all chans. Make the code
361 	   below work well if necessary.
362 	else
363 	{
364 	        GSList *l;
365 
366 	        for( l = irc->channels; l; l = l->next )
367 	                irc_send_names( l->data );
368 	}
369 	*/
370 }
371 
irc_cmd_part(irc_t * irc,char ** cmd)372 static void irc_cmd_part(irc_t *irc, char **cmd)
373 {
374 	irc_channel_t *ic;
375 
376 	if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
377 		irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
378 	} else if (irc_channel_del_user(ic, irc->user, IRC_CDU_PART, cmd[2])) {
379 		if (ic->f->part) {
380 			ic->f->part(ic, NULL);
381 		}
382 	} else {
383 		irc_send_num(irc, 442, "%s :You're not on that channel", cmd[1]);
384 	}
385 }
386 
irc_cmd_whois(irc_t * irc,char ** cmd)387 static void irc_cmd_whois(irc_t *irc, char **cmd)
388 {
389 	char *nick = cmd[1];
390 	irc_user_t *iu = irc_user_by_name(irc, nick);
391 
392 	if (iu) {
393 		irc_send_whois(iu);
394 	} else {
395 		irc_send_num(irc, 401, "%s :Nick does not exist", nick);
396 	}
397 }
398 
irc_cmd_whowas(irc_t * irc,char ** cmd)399 static void irc_cmd_whowas(irc_t *irc, char **cmd)
400 {
401 	/* For some reason irssi tries a whowas when whois fails. We can
402 	   ignore this, but then the user never gets a "user not found"
403 	   message from irssi which is a bit annoying. So just respond
404 	   with not-found and irssi users will get better error messages */
405 
406 	irc_send_num(irc, 406, "%s :Nick does not exist", cmd[1]);
407 	irc_send_num(irc, 369, "%s :End of WHOWAS", cmd[1]);
408 }
409 
irc_cmd_motd(irc_t * irc,char ** cmd)410 static void irc_cmd_motd(irc_t *irc, char **cmd)
411 {
412 	irc_send_motd(irc);
413 }
414 
irc_cmd_mode(irc_t * irc,char ** cmd)415 static void irc_cmd_mode(irc_t *irc, char **cmd)
416 {
417 	if (irc_channel_name_ok(cmd[1])) {
418 		irc_channel_t *ic;
419 
420 		if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
421 			irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
422 		} else if (cmd[2]) {
423 			if (*cmd[2] == '+' || *cmd[2] == '-') {
424 				irc_send_num(irc, 477, "%s :Can't change channel modes", cmd[1]);
425 			} else if (*cmd[2] == 'b') {
426 				irc_send_num(irc, 368, "%s :No bans possible", cmd[1]);
427 			}
428 		} else {
429 			irc_send_num(irc, 324, "%s +%s", cmd[1], ic->mode);
430 		}
431 	} else {
432 		if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
433 			if (cmd[2]) {
434 				irc_umode_set(irc, cmd[2], 0);
435 			} else {
436 				irc_send_num(irc, 221, "+%s", irc->umode);
437 			}
438 		} else {
439 			irc_send_num(irc, 502, ":Don't touch their modes");
440 		}
441 	}
442 }
443 
irc_cmd_who(irc_t * irc,char ** cmd)444 static void irc_cmd_who(irc_t *irc, char **cmd)
445 {
446 	char *channel = cmd[1];
447 	irc_channel_t *ic;
448 	irc_user_t *iu;
449 
450 	if (!channel || *channel == '0' || *channel == '*' || !*channel) {
451 		GList *all_users = g_hash_table_get_values(irc->nick_user_hash);
452 		irc_send_who(irc, (GSList *) all_users, "**");
453 		g_list_free(all_users);
454 	} else if ((ic = irc_channel_by_name(irc, channel))) {
455 		irc_send_who(irc, ic->users, channel);
456 	} else if ((iu = irc_user_by_name(irc, channel))) {
457 		/* Tiny hack! */
458 		GSList *l = g_slist_append(NULL, iu);
459 		irc_send_who(irc, l, channel);
460 		g_slist_free(l);
461 	} else {
462 		irc_send_num(irc, 403, "%s :No such channel", channel);
463 	}
464 }
465 
irc_cmd_privmsg(irc_t * irc,char ** cmd)466 static void irc_cmd_privmsg(irc_t *irc, char **cmd)
467 {
468 	irc_channel_t *ic;
469 	irc_user_t *iu;
470 
471 	if (!cmd[2]) {
472 		irc_send_num(irc, 412, ":No text to send");
473 		return;
474 	}
475 
476 	/* Don't treat CTCP actions as real CTCPs, just convert them right now. */
477 	if (g_strncasecmp(cmd[2], "\001ACTION", 7) == 0) {
478 		cmd[2] += 4;
479 		memcpy(cmd[2], "/me", 3);
480 		if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
481 			cmd[2][strlen(cmd[2]) - 1] = '\0';
482 		}
483 	}
484 
485 	if (irc_channel_name_ok(cmd[1]) &&
486 	    (ic = irc_channel_by_name(irc, cmd[1]))) {
487 		if (cmd[2][0] == '\001') {
488 			/* CTCPs to channels? Nah. Maybe later. */
489 		} else if (ic->f->privmsg) {
490 			ic->f->privmsg(ic, cmd[2]);
491 		}
492 	} else if ((iu = irc_user_by_name(irc, cmd[1]))) {
493 		if (cmd[2][0] == '\001') {
494 			char **ctcp;
495 
496 			if (iu->f->ctcp == NULL) {
497 				return;
498 			}
499 			if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
500 				cmd[2][strlen(cmd[2]) - 1] = '\0';
501 			}
502 
503 			ctcp = split_command_parts(cmd[2] + 1, 0);
504 			iu->f->ctcp(iu, ctcp);
505 		} else if (iu->f->privmsg) {
506 			iu->last_channel = NULL;
507 			iu->f->privmsg(iu, cmd[2]);
508 		}
509 	} else {
510 		irc_send_num(irc, 401, "%s :No such nick/channel", cmd[1]);
511 	}
512 }
513 
irc_cmd_notice(irc_t * irc,char ** cmd)514 static void irc_cmd_notice(irc_t *irc, char **cmd)
515 {
516 	irc_user_t *iu;
517 
518 	if (!cmd[2]) {
519 		irc_send_num(irc, 412, ":No text to send");
520 		return;
521 	}
522 
523 	/* At least for now just echo. IIRC some IRC clients use self-notices
524 	   for lag checks, so try to support that. */
525 	if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
526 		irc_send_msg(irc->user, "NOTICE", irc->user->nick, cmd[2], NULL);
527 	} else if ((iu = irc_user_by_name(irc, cmd[1]))) {
528 		iu->f->privmsg(iu, cmd[2]);
529 	}
530 }
531 
irc_cmd_nickserv(irc_t * irc,char ** cmd)532 static void irc_cmd_nickserv(irc_t *irc, char **cmd)
533 {
534 	/* [SH] This aliases the NickServ command to PRIVMSG root */
535 	/* [TV] This aliases the NS command to PRIVMSG root as well */
536 	root_command(irc, cmd + 1);
537 }
538 
539 static void irc_cmd_oper_hack(irc_t *irc, char **cmd);
540 
irc_cmd_oper(irc_t * irc,char ** cmd)541 static void irc_cmd_oper(irc_t *irc, char **cmd)
542 {
543 	/* Very non-standard evil but useful/secure hack, see below. */
544 	if (irc->status & OPER_HACK_ANY) {
545 		return irc_cmd_oper_hack(irc, cmd);
546 	}
547 
548 	if (global.conf->oper_pass &&
549 	    (strncmp(global.conf->oper_pass, "md5:", 4) == 0 ?
550 	     md5_verify_password(cmd[2], global.conf->oper_pass + 4) == 0 :
551 	     strcmp(cmd[2], global.conf->oper_pass) == 0)) {
552 		irc_umode_set(irc, "+o", 1);
553 		irc_send_num(irc, 381, ":Password accepted");
554 	} else {
555 		irc_send_num(irc, 491, ":Incorrect password");
556 	}
557 }
558 
irc_cmd_oper_hack(irc_t * irc,char ** cmd)559 static void irc_cmd_oper_hack(irc_t *irc, char **cmd)
560 {
561 	char *password = g_strjoinv(" ", cmd + 2);
562 
563 	/* /OPER can now also be used to enter IM/identify passwords without
564 	   echoing. It's a hack but the extra password security is worth it. */
565 	if (irc->status & OPER_HACK_ACCOUNT_PASSWORD) {
566 		account_t *a;
567 
568 		for (a = irc->b->accounts; a; a = a->next) {
569 			if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
570 				set_setstr(&a->set, "password", password);
571 				irc_rootmsg(irc, "Password added to IM account "
572 				            "%s", a->tag);
573 				/* The IRC client may expect this. 491 suggests the OPER
574 				   password was wrong, so the client won't expect a +o.
575 				   It may however repeat the password prompt. We'll see. */
576 				irc_send_num(irc, 491, ":Password added to IM account "
577 				             "%s", a->tag);
578 			}
579 		}
580 	} else if (irc->status & OPER_HACK_IDENTIFY) {
581 		char *send_cmd[] = { "identify", password, NULL, NULL };
582 		irc->status &= ~OPER_HACK_IDENTIFY;
583 		if (irc->status & OPER_HACK_IDENTIFY_NOLOAD) {
584 			send_cmd[1] = "-noload";
585 			send_cmd[2] = password;
586 		} else if (irc->status & OPER_HACK_IDENTIFY_FORCE) {
587 			send_cmd[1] = "-force";
588 			send_cmd[2] = password;
589 		}
590 		irc_send_num(irc, 491, ":Trying to identify");
591 		root_command(irc, send_cmd);
592 	} else if (irc->status & OPER_HACK_REGISTER) {
593 		char *send_cmd[] = { "register", password, NULL };
594 		irc_send_num(irc, 491, ":Trying to identify");
595 		root_command(irc, send_cmd);
596 	}
597 
598 	irc->status &= ~OPER_HACK_ANY;
599 	g_free(password);
600 }
601 
irc_cmd_invite(irc_t * irc,char ** cmd)602 static void irc_cmd_invite(irc_t *irc, char **cmd)
603 {
604 	irc_channel_t *ic;
605 	irc_user_t *iu;
606 
607 	if ((iu = irc_user_by_name(irc, cmd[1])) == NULL) {
608 		irc_send_num(irc, 401, "%s :No such nick", cmd[1]);
609 		return;
610 	} else if ((ic = irc_channel_by_name(irc, cmd[2])) == NULL) {
611 		irc_send_num(irc, 403, "%s :No such channel", cmd[2]);
612 		return;
613 	}
614 
615 	if (!ic->f->invite) {
616 		irc_send_num(irc, 482, "%s :Can't invite people here", cmd[2]);
617 	} else if (ic->f->invite(ic, iu)) {
618 		irc_send_num(irc, 341, "%s %s", iu->nick, ic->name);
619 	}
620 }
621 
irc_cmd_kick(irc_t * irc,char ** cmd)622 static void irc_cmd_kick(irc_t *irc, char **cmd)
623 {
624 	irc_channel_t *ic;
625 	irc_user_t *iu;
626 
627 	if ((iu = irc_user_by_name(irc, cmd[2])) == NULL) {
628 		irc_send_num(irc, 401, "%s :No such nick", cmd[2]);
629 		return;
630 	} else if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
631 		irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
632 		return;
633 	} else if (!ic->f->kick) {
634 		irc_send_num(irc, 482, "%s :Can't kick people here", cmd[1]);
635 		return;
636 	}
637 
638 	ic->f->kick(ic, iu, cmd[3] ? cmd[3] : NULL);
639 }
640 
irc_cmd_userhost(irc_t * irc,char ** cmd)641 static void irc_cmd_userhost(irc_t *irc, char **cmd)
642 {
643 	int i;
644 
645 	/* [TV] Usable USERHOST-implementation according to
646 	        RFC1459. Without this, mIRC shows an error
647 	        while connecting, and the used way of rejecting
648 	        breaks standards.
649 	*/
650 
651 	for (i = 1; cmd[i]; i++) {
652 		irc_user_t *iu = irc_user_by_name(irc, cmd[i]);
653 
654 		if (iu) {
655 			irc_send_num(irc, 302, ":%s=%c%s@%s", iu->nick,
656 			             irc_user_get_away(iu) ? '-' : '+',
657 			             iu->user, iu->host);
658 		}
659 	}
660 }
661 
irc_cmd_ison(irc_t * irc,char ** cmd)662 static void irc_cmd_ison(irc_t *irc, char **cmd)
663 {
664 	char buff[IRC_MAX_LINE];
665 	int lenleft, i;
666 
667 	buff[0] = '\0';
668 
669 	/* [SH] Leave room for : and \0 */
670 	lenleft = IRC_MAX_LINE - 2;
671 
672 	for (i = 1; cmd[i]; i++) {
673 		char *this, *next;
674 
675 		this = cmd[i];
676 		while (*this) {
677 			irc_user_t *iu;
678 
679 			if ((next = strchr(this, ' '))) {
680 				*next = 0;
681 			}
682 
683 			if ((iu = irc_user_by_name(irc, this)) &&
684 			    iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
685 				lenleft -= strlen(iu->nick) + 1;
686 
687 				if (lenleft < 0) {
688 					break;
689 				}
690 
691 				strcat(buff, iu->nick);
692 				strcat(buff, " ");
693 			}
694 
695 			if (next) {
696 				*next = ' ';
697 				this = next + 1;
698 			} else {
699 				break;
700 			}
701 		}
702 
703 		/* *sigh* */
704 		if (lenleft < 0) {
705 			break;
706 		}
707 	}
708 
709 	if (strlen(buff) > 0) {
710 		buff[strlen(buff) - 1] = '\0';
711 	}
712 
713 	irc_send_num(irc, 303, ":%s", buff);
714 }
715 
irc_cmd_watch(irc_t * irc,char ** cmd)716 static void irc_cmd_watch(irc_t *irc, char **cmd)
717 {
718 	int i;
719 
720 	/* Obviously we could also mark a user structure as being
721 	   watched, but what if the WATCH command is sent right
722 	   after connecting? The user won't exist yet then... */
723 	for (i = 1; cmd[i]; i++) {
724 		char *nick;
725 		irc_user_t *iu;
726 
727 		if (!cmd[i][0] || !cmd[i][1]) {
728 			break;
729 		}
730 
731 		nick = g_strdup(cmd[i] + 1);
732 		nick_lc(irc, nick);
733 
734 		iu = irc_user_by_name(irc, nick);
735 
736 		if (cmd[i][0] == '+') {
737 			if (!g_hash_table_lookup(irc->watches, nick)) {
738 				g_hash_table_insert(irc->watches, nick, nick);
739 			}
740 
741 			if (iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
742 				irc_send_num(irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
743 				             iu->host, (int) time(NULL), "is online");
744 			} else {
745 				irc_send_num(irc, 605, "%s %s %s %d :%s", nick, "*", "*",
746 				             (int) time(NULL), "is offline");
747 			}
748 		} else if (cmd[i][0] == '-') {
749 			gpointer okey, ovalue;
750 
751 			if (g_hash_table_lookup_extended(irc->watches, nick, &okey, &ovalue)) {
752 				g_hash_table_remove(irc->watches, okey);
753 				g_free(okey);
754 
755 				irc_send_num(irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching");
756 			}
757 		}
758 	}
759 }
760 
irc_cmd_topic(irc_t * irc,char ** cmd)761 static void irc_cmd_topic(irc_t *irc, char **cmd)
762 {
763 	irc_channel_t *ic = irc_channel_by_name(irc, cmd[1]);
764 	const char *new = cmd[2];
765 
766 	if (ic == NULL) {
767 		irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
768 	} else if (new) {
769 		if (ic->f->topic == NULL) {
770 			irc_send_num(irc, 482, "%s :Can't change this channel's topic", ic->name);
771 		} else if (ic->f->topic(ic, new)) {
772 			irc_send_topic(ic, TRUE);
773 		}
774 	} else {
775 		irc_send_topic(ic, FALSE);
776 	}
777 }
778 
irc_cmd_away(irc_t * irc,char ** cmd)779 static void irc_cmd_away(irc_t *irc, char **cmd)
780 {
781 	if (cmd[1] && *cmd[1]) {
782 		char away[strlen(cmd[1]) + 1];
783 		int i, j;
784 
785 		/* Copy away string, but skip control chars. Mainly because
786 		   Jabber really doesn't like them. */
787 		for (i = j = 0; cmd[1][i]; i++) {
788 			if ((unsigned char) (away[j] = cmd[1][i]) >= ' ') {
789 				j++;
790 			}
791 		}
792 		away[j] = '\0';
793 
794 		irc_send_num(irc, 306, ":You're now away: %s", away);
795 		set_setstr(&irc->b->set, "away", away);
796 	} else {
797 		irc_send_num(irc, 305, ":Welcome back");
798 		set_setstr(&irc->b->set, "away", NULL);
799 	}
800 }
801 
irc_cmd_list(irc_t * irc,char ** cmd)802 static void irc_cmd_list(irc_t *irc, char **cmd)
803 {
804 	GSList *l;
805 
806 	for (l = irc->channels; l; l = l->next) {
807 		irc_channel_t *ic = l->data;
808 
809 		irc_send_num(irc, 322, "%s %d :%s",
810 		             ic->name, g_slist_length(ic->users), ic->topic ? : "");
811 	}
812 	irc_send_num(irc, 323, ":%s", "End of /LIST");
813 }
814 
irc_cmd_version(irc_t * irc,char ** cmd)815 static void irc_cmd_version(irc_t *irc, char **cmd)
816 {
817 	irc_send_num(irc, 351, "%s-%s. %s :",
818 	             PACKAGE, BITLBEE_VERSION, irc->root->host);
819 }
820 
irc_cmd_completions(irc_t * irc,char ** cmd)821 static void irc_cmd_completions(irc_t *irc, char **cmd)
822 {
823 	help_t *h;
824 	set_t *s;
825 	int i;
826 
827 	irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK");
828 
829 	for (i = 0; root_commands[i].command; i++) {
830 		irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command);
831 	}
832 
833 	for (h = global.help; h; h = h->next) {
834 		irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title);
835 	}
836 
837 	for (s = irc->b->set; s; s = s->next) {
838 		irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key);
839 	}
840 
841 	irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END");
842 }
843 
irc_cmd_rehash(irc_t * irc,char ** cmd)844 static void irc_cmd_rehash(irc_t *irc, char **cmd)
845 {
846 	if (global.conf->runmode == RUNMODE_INETD) {
847 		ipc_master_cmd_rehash(NULL, NULL);
848 	} else {
849 		ipc_to_master(cmd);
850 	}
851 
852 	irc_send_num(irc, 382, "%s :Rehashing", global.conf_file);
853 }
854 
855 static const command_t irc_commands[] = {
856 	{ "cap",         1, irc_cmd_cap,         0 },
857 	{ "pass",        1, irc_cmd_pass,        0 },
858 	{ "proxy",       5, irc_cmd_proxy,       IRC_CMD_PRE_LOGIN },
859 	{ "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
860 	{ "nick",        1, irc_cmd_nick,        0 },
861 	{ "quit",        0, irc_cmd_quit,        0 },
862 	{ "ping",        0, irc_cmd_ping,        0 },
863 	{ "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
864 	{ "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
865 	{ "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
866 	{ "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
867 	{ "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
868 	{ "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
869 	{ "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
870 	{ "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
871 	{ "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
872 	{ "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
873 	{ "notice",      1, irc_cmd_notice,      IRC_CMD_LOGGED_IN },
874 	{ "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
875 	{ "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
876 	{ "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
877 	{ "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
878 	{ "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
879 	{ "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
880 	{ "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
881 	{ "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
882 	{ "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
883 	{ "kick",        2, irc_cmd_kick,        IRC_CMD_LOGGED_IN },
884 	{ "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
885 	{ "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
886 	{ "list",        0, irc_cmd_list,        IRC_CMD_LOGGED_IN },
887 	{ "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
888 	{ "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
889 	{ "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
890 	{ "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
891 	{ "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
892 	{ "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
893 	{ "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
894 	{ "authenticate", 1, irc_cmd_authenticate, 0 },
895 	{ NULL }
896 };
897 
irc_exec(irc_t * irc,char * cmd[])898 void irc_exec(irc_t *irc, char *cmd[])
899 {
900 	int i, n_arg;
901 
902 	if (!cmd[0]) {
903 		return;
904 	}
905 
906 	for (i = 0; irc_commands[i].command; i++) {
907 		if (g_strcasecmp(irc_commands[i].command, cmd[0]) == 0) {
908 			/* There should be no typo in the next line: */
909 			for (n_arg = 0; cmd[n_arg]; n_arg++) {
910 				;
911 			}
912 			n_arg--;
913 
914 			if (irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN) {
915 				irc_send_num(irc, 462, ":Only allowed before logging in");
916 			} else if (irc_commands[i].flags & IRC_CMD_LOGGED_IN && !(irc->status & USTATUS_LOGGED_IN)) {
917 				irc_send_num(irc, 451, ":Register first");
918 			} else if (irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr(irc->umode, 'o')) {
919 				irc_send_num(irc, 481, ":Permission denied - You're not an IRC operator");
920 			} else if (n_arg < irc_commands[i].required_parameters) {
921 				irc_send_num(irc, 461, "%s :Need more parameters", cmd[0]);
922 			} else if (irc_commands[i].flags & IRC_CMD_TO_MASTER) {
923 				/* IPC doesn't make sense in inetd mode,
924 				    but the function will catch that. */
925 				ipc_to_master(cmd);
926 			} else {
927 				irc_commands[i].execute(irc, cmd);
928 			}
929 
930 			return;
931 		}
932 	}
933 
934 	if (irc->status & USTATUS_LOGGED_IN) {
935 		irc_send_num(irc, 421, "%s :Unknown command", cmd[0]);
936 	}
937 }
938