source: irc_commands.c @ 4528a52

Last change on this file since 4528a52 was 4528a52, checked in by dequis <dx@…>, at 2018-03-19T03:27:19Z

irc_commands: send numeric 900 RPL_SASLSUCCESS too, some clients need it

The nodejs irc-framework lib used by thelounge and kiwiirc seems to
expect 900 on successful login, instead of just 903, so they hang on
sasl auth from bitlbee.

The sasl spec isn't very specific on which should be used (IMO clients
should handle both), but it seems to point at the direction of sending
both, so both it is.

  • Property mode set to 100644
File size: 26.9 KB
Line 
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
33static 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. */
68static 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
104static 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
136static 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
166static 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
221static 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
229static 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
269static 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
278static 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
284static 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
292static 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
343next:
344                if (comma) {
345                        s = comma + 1;
346                        *comma = ',';
347                } else {
348                        break;
349                }
350        }
351}
352
353static 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
372static 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
387static 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
399static 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
410static void irc_cmd_motd(irc_t *irc, char **cmd)
411{
412        irc_send_motd(irc);
413}
414
415static 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
444static 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                irc_send_who(irc, irc->users, "**");
452        } else if ((ic = irc_channel_by_name(irc, channel))) {
453                irc_send_who(irc, ic->users, channel);
454        } else if ((iu = irc_user_by_name(irc, channel))) {
455                /* Tiny hack! */
456                GSList *l = g_slist_append(NULL, iu);
457                irc_send_who(irc, l, channel);
458                g_slist_free(l);
459        } else {
460                irc_send_num(irc, 403, "%s :No such channel", channel);
461        }
462}
463
464static void irc_cmd_privmsg(irc_t *irc, char **cmd)
465{
466        irc_channel_t *ic;
467        irc_user_t *iu;
468
469        if (!cmd[2]) {
470                irc_send_num(irc, 412, ":No text to send");
471                return;
472        }
473
474        /* Don't treat CTCP actions as real CTCPs, just convert them right now. */
475        if (g_strncasecmp(cmd[2], "\001ACTION", 7) == 0) {
476                cmd[2] += 4;
477                memcpy(cmd[2], "/me", 3);
478                if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
479                        cmd[2][strlen(cmd[2]) - 1] = '\0';
480                }
481        }
482
483        if (irc_channel_name_ok(cmd[1]) &&
484            (ic = irc_channel_by_name(irc, cmd[1]))) {
485                if (cmd[2][0] == '\001') {
486                        /* CTCPs to channels? Nah. Maybe later. */
487                } else if (ic->f->privmsg) {
488                        ic->f->privmsg(ic, cmd[2]);
489                }
490        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
491                if (cmd[2][0] == '\001') {
492                        char **ctcp;
493
494                        if (iu->f->ctcp == NULL) {
495                                return;
496                        }
497                        if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
498                                cmd[2][strlen(cmd[2]) - 1] = '\0';
499                        }
500
501                        ctcp = split_command_parts(cmd[2] + 1, 0);
502                        iu->f->ctcp(iu, ctcp);
503                } else if (iu->f->privmsg) {
504                        iu->last_channel = NULL;
505                        iu->f->privmsg(iu, cmd[2]);
506                }
507        } else {
508                irc_send_num(irc, 401, "%s :No such nick/channel", cmd[1]);
509        }
510}
511
512static void irc_cmd_notice(irc_t *irc, char **cmd)
513{
514        irc_user_t *iu;
515
516        if (!cmd[2]) {
517                irc_send_num(irc, 412, ":No text to send");
518                return;
519        }
520
521        /* At least for now just echo. IIRC some IRC clients use self-notices
522           for lag checks, so try to support that. */
523        if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
524                irc_send_msg(irc->user, "NOTICE", irc->user->nick, cmd[2], NULL);
525        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
526                iu->f->privmsg(iu, cmd[2]);
527        }
528}
529
530static void irc_cmd_nickserv(irc_t *irc, char **cmd)
531{
532        /* [SH] This aliases the NickServ command to PRIVMSG root */
533        /* [TV] This aliases the NS command to PRIVMSG root as well */
534        root_command(irc, cmd + 1);
535}
536
537static void irc_cmd_oper_hack(irc_t *irc, char **cmd);
538
539static void irc_cmd_oper(irc_t *irc, char **cmd)
540{
541        /* Very non-standard evil but useful/secure hack, see below. */
542        if (irc->status & OPER_HACK_ANY) {
543                return irc_cmd_oper_hack(irc, cmd);
544        }
545
546        if (global.conf->oper_pass &&
547            (strncmp(global.conf->oper_pass, "md5:", 4) == 0 ?
548             md5_verify_password(cmd[2], global.conf->oper_pass + 4) == 0 :
549             strcmp(cmd[2], global.conf->oper_pass) == 0)) {
550                irc_umode_set(irc, "+o", 1);
551                irc_send_num(irc, 381, ":Password accepted");
552        } else {
553                irc_send_num(irc, 491, ":Incorrect password");
554        }
555}
556
557static void irc_cmd_oper_hack(irc_t *irc, char **cmd)
558{
559        char *password = g_strjoinv(" ", cmd + 2);
560
561        /* /OPER can now also be used to enter IM/identify passwords without
562           echoing. It's a hack but the extra password security is worth it. */
563        if (irc->status & OPER_HACK_ACCOUNT_PASSWORD) {
564                account_t *a;
565
566                for (a = irc->b->accounts; a; a = a->next) {
567                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
568                                set_setstr(&a->set, "password", password);
569                                irc_rootmsg(irc, "Password added to IM account "
570                                            "%s", a->tag);
571                                /* The IRC client may expect this. 491 suggests the OPER
572                                   password was wrong, so the client won't expect a +o.
573                                   It may however repeat the password prompt. We'll see. */
574                                irc_send_num(irc, 491, ":Password added to IM account "
575                                             "%s", a->tag);
576                        }
577                }
578        } else if (irc->status & OPER_HACK_IDENTIFY) {
579                char *send_cmd[] = { "identify", password, NULL, NULL };
580                irc->status &= ~OPER_HACK_IDENTIFY;
581                if (irc->status & OPER_HACK_IDENTIFY_NOLOAD) {
582                        send_cmd[1] = "-noload";
583                        send_cmd[2] = password;
584                } else if (irc->status & OPER_HACK_IDENTIFY_FORCE) {
585                        send_cmd[1] = "-force";
586                        send_cmd[2] = password;
587                }
588                irc_send_num(irc, 491, ":Trying to identify");
589                root_command(irc, send_cmd);
590        } else if (irc->status & OPER_HACK_REGISTER) {
591                char *send_cmd[] = { "register", password, NULL };
592                irc_send_num(irc, 491, ":Trying to identify");
593                root_command(irc, send_cmd);
594        }
595
596        irc->status &= ~OPER_HACK_ANY;
597        g_free(password);
598}
599
600static void irc_cmd_invite(irc_t *irc, char **cmd)
601{
602        irc_channel_t *ic;
603        irc_user_t *iu;
604
605        if ((iu = irc_user_by_name(irc, cmd[1])) == NULL) {
606                irc_send_num(irc, 401, "%s :No such nick", cmd[1]);
607                return;
608        } else if ((ic = irc_channel_by_name(irc, cmd[2])) == NULL) {
609                irc_send_num(irc, 403, "%s :No such channel", cmd[2]);
610                return;
611        }
612
613        if (!ic->f->invite) {
614                irc_send_num(irc, 482, "%s :Can't invite people here", cmd[2]);
615        } else if (ic->f->invite(ic, iu)) {
616                irc_send_num(irc, 341, "%s %s", iu->nick, ic->name);
617        }
618}
619
620static void irc_cmd_kick(irc_t *irc, char **cmd)
621{
622        irc_channel_t *ic;
623        irc_user_t *iu;
624
625        if ((iu = irc_user_by_name(irc, cmd[2])) == NULL) {
626                irc_send_num(irc, 401, "%s :No such nick", cmd[2]);
627                return;
628        } else if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
629                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
630                return;
631        } else if (!ic->f->kick) {
632                irc_send_num(irc, 482, "%s :Can't kick people here", cmd[1]);
633                return;
634        }
635
636        ic->f->kick(ic, iu, cmd[3] ? cmd[3] : NULL);
637}
638
639static void irc_cmd_userhost(irc_t *irc, char **cmd)
640{
641        int i;
642
643        /* [TV] Usable USERHOST-implementation according to
644                RFC1459. Without this, mIRC shows an error
645                while connecting, and the used way of rejecting
646                breaks standards.
647        */
648
649        for (i = 1; cmd[i]; i++) {
650                irc_user_t *iu = irc_user_by_name(irc, cmd[i]);
651
652                if (iu) {
653                        irc_send_num(irc, 302, ":%s=%c%s@%s", iu->nick,
654                                     irc_user_get_away(iu) ? '-' : '+',
655                                     iu->user, iu->host);
656                }
657        }
658}
659
660static void irc_cmd_ison(irc_t *irc, char **cmd)
661{
662        char buff[IRC_MAX_LINE];
663        int lenleft, i;
664
665        buff[0] = '\0';
666
667        /* [SH] Leave room for : and \0 */
668        lenleft = IRC_MAX_LINE - 2;
669
670        for (i = 1; cmd[i]; i++) {
671                char *this, *next;
672
673                this = cmd[i];
674                while (*this) {
675                        irc_user_t *iu;
676
677                        if ((next = strchr(this, ' '))) {
678                                *next = 0;
679                        }
680
681                        if ((iu = irc_user_by_name(irc, this)) &&
682                            iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
683                                lenleft -= strlen(iu->nick) + 1;
684
685                                if (lenleft < 0) {
686                                        break;
687                                }
688
689                                strcat(buff, iu->nick);
690                                strcat(buff, " ");
691                        }
692
693                        if (next) {
694                                *next = ' ';
695                                this = next + 1;
696                        } else {
697                                break;
698                        }
699                }
700
701                /* *sigh* */
702                if (lenleft < 0) {
703                        break;
704                }
705        }
706
707        if (strlen(buff) > 0) {
708                buff[strlen(buff) - 1] = '\0';
709        }
710
711        irc_send_num(irc, 303, ":%s", buff);
712}
713
714static void irc_cmd_watch(irc_t *irc, char **cmd)
715{
716        int i;
717
718        /* Obviously we could also mark a user structure as being
719           watched, but what if the WATCH command is sent right
720           after connecting? The user won't exist yet then... */
721        for (i = 1; cmd[i]; i++) {
722                char *nick;
723                irc_user_t *iu;
724
725                if (!cmd[i][0] || !cmd[i][1]) {
726                        break;
727                }
728
729                nick = g_strdup(cmd[i] + 1);
730                nick_lc(irc, nick);
731
732                iu = irc_user_by_name(irc, nick);
733
734                if (cmd[i][0] == '+') {
735                        if (!g_hash_table_lookup(irc->watches, nick)) {
736                                g_hash_table_insert(irc->watches, nick, nick);
737                        }
738
739                        if (iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
740                                irc_send_num(irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
741                                             iu->host, (int) time(NULL), "is online");
742                        } else {
743                                irc_send_num(irc, 605, "%s %s %s %d :%s", nick, "*", "*",
744                                             (int) time(NULL), "is offline");
745                        }
746                } else if (cmd[i][0] == '-') {
747                        gpointer okey, ovalue;
748
749                        if (g_hash_table_lookup_extended(irc->watches, nick, &okey, &ovalue)) {
750                                g_hash_table_remove(irc->watches, okey);
751                                g_free(okey);
752
753                                irc_send_num(irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching");
754                        }
755                }
756        }
757}
758
759static void irc_cmd_topic(irc_t *irc, char **cmd)
760{
761        irc_channel_t *ic = irc_channel_by_name(irc, cmd[1]);
762        const char *new = cmd[2];
763
764        if (ic == NULL) {
765                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
766        } else if (new) {
767                if (ic->f->topic == NULL) {
768                        irc_send_num(irc, 482, "%s :Can't change this channel's topic", ic->name);
769                } else if (ic->f->topic(ic, new)) {
770                        irc_send_topic(ic, TRUE);
771                }
772        } else {
773                irc_send_topic(ic, FALSE);
774        }
775}
776
777static void irc_cmd_away(irc_t *irc, char **cmd)
778{
779        if (cmd[1] && *cmd[1]) {
780                char away[strlen(cmd[1]) + 1];
781                int i, j;
782
783                /* Copy away string, but skip control chars. Mainly because
784                   Jabber really doesn't like them. */
785                for (i = j = 0; cmd[1][i]; i++) {
786                        if ((unsigned char) (away[j] = cmd[1][i]) >= ' ') {
787                                j++;
788                        }
789                }
790                away[j] = '\0';
791
792                irc_send_num(irc, 306, ":You're now away: %s", away);
793                set_setstr(&irc->b->set, "away", away);
794        } else {
795                irc_send_num(irc, 305, ":Welcome back");
796                set_setstr(&irc->b->set, "away", NULL);
797        }
798}
799
800static void irc_cmd_list(irc_t *irc, char **cmd)
801{
802        GSList *l;
803
804        for (l = irc->channels; l; l = l->next) {
805                irc_channel_t *ic = l->data;
806
807                irc_send_num(irc, 322, "%s %d :%s",
808                             ic->name, g_slist_length(ic->users), ic->topic ? : "");
809        }
810        irc_send_num(irc, 323, ":%s", "End of /LIST");
811}
812
813static void irc_cmd_version(irc_t *irc, char **cmd)
814{
815        irc_send_num(irc, 351, "%s-%s. %s :",
816                     PACKAGE, BITLBEE_VERSION, irc->root->host);
817}
818
819static void irc_cmd_completions(irc_t *irc, char **cmd)
820{
821        help_t *h;
822        set_t *s;
823        int i;
824
825        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK");
826
827        for (i = 0; root_commands[i].command; i++) {
828                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command);
829        }
830
831        for (h = global.help; h; h = h->next) {
832                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title);
833        }
834
835        for (s = irc->b->set; s; s = s->next) {
836                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key);
837        }
838
839        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END");
840}
841
842static void irc_cmd_rehash(irc_t *irc, char **cmd)
843{
844        if (global.conf->runmode == RUNMODE_INETD) {
845                ipc_master_cmd_rehash(NULL, NULL);
846        } else {
847                ipc_to_master(cmd);
848        }
849
850        irc_send_num(irc, 382, "%s :Rehashing", global.conf_file);
851}
852
853static const command_t irc_commands[] = {
854        { "cap",         1, irc_cmd_cap,         0 },
855        { "pass",        1, irc_cmd_pass,        0 },
856        { "proxy",       5, irc_cmd_proxy,       IRC_CMD_PRE_LOGIN },
857        { "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
858        { "nick",        1, irc_cmd_nick,        0 },
859        { "quit",        0, irc_cmd_quit,        0 },
860        { "ping",        0, irc_cmd_ping,        0 },
861        { "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
862        { "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
863        { "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
864        { "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
865        { "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
866        { "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
867        { "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
868        { "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
869        { "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
870        { "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
871        { "notice",      1, irc_cmd_notice,      IRC_CMD_LOGGED_IN },
872        { "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
873        { "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
874        { "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
875        { "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
876        { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
877        { "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
878        { "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
879        { "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
880        { "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
881        { "kick",        2, irc_cmd_kick,        IRC_CMD_LOGGED_IN },
882        { "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
883        { "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
884        { "list",        0, irc_cmd_list,        IRC_CMD_LOGGED_IN },
885        { "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
886        { "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
887        { "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
888        { "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
889        { "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
890        { "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
891        { "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
892        { "authenticate", 1, irc_cmd_authenticate, 0 },
893        { NULL }
894};
895
896void irc_exec(irc_t *irc, char *cmd[])
897{
898        int i, n_arg;
899
900        if (!cmd[0]) {
901                return;
902        }
903
904        for (i = 0; irc_commands[i].command; i++) {
905                if (g_strcasecmp(irc_commands[i].command, cmd[0]) == 0) {
906                        /* There should be no typo in the next line: */
907                        for (n_arg = 0; cmd[n_arg]; n_arg++) {
908                                ;
909                        }
910                        n_arg--;
911
912                        if (irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN) {
913                                irc_send_num(irc, 462, ":Only allowed before logging in");
914                        } else if (irc_commands[i].flags & IRC_CMD_LOGGED_IN && !(irc->status & USTATUS_LOGGED_IN)) {
915                                irc_send_num(irc, 451, ":Register first");
916                        } else if (irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr(irc->umode, 'o')) {
917                                irc_send_num(irc, 481, ":Permission denied - You're not an IRC operator");
918                        } else if (n_arg < irc_commands[i].required_parameters) {
919                                irc_send_num(irc, 461, "%s :Need more parameters", cmd[0]);
920                        } else if (irc_commands[i].flags & IRC_CMD_TO_MASTER) {
921                                /* IPC doesn't make sense in inetd mode,
922                                    but the function will catch that. */
923                                ipc_to_master(cmd);
924                        } else {
925                                irc_commands[i].execute(irc, cmd);
926                        }
927
928                        return;
929                }
930        }
931
932        if (irc->status & USTATUS_LOGGED_IN) {
933                irc_send_num(irc, 421, "%s :Unknown command", cmd[0]);
934        }
935}
Note: See TracBrowser for help on using the repository browser.