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