source: irc_commands.c @ 5c90890

Last change on this file since 5c90890 was 5c90890, checked in by dequis <dx@…>, at 2018-07-12T08:54:12Z

Stop using the irc->users linked list, use the hash table instead

irc_user_new() mentions that the reason this list is kept is for easy
iteration. Luckily, this is the future, and GHashTableIter exists now.

The main point of this is to get rid of the g_slist_insert_sorted() in
irc_user_set_nick() which is a particularly slow part of loading large
user lists, and scales poorly

In a test with discord, the GUILD_SYNC event is now 4 times faster, on
top of the improvements of the other bee_user hash tables patch.
Combining both patches it went from 136 to 6 seconds for 50k members.

  • Property mode set to 100644
File size: 27.0 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                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
466static 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
514static 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
532static 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
539static void irc_cmd_oper_hack(irc_t *irc, char **cmd);
540
541static 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
559static 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
602static 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
622static 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
641static 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
662static 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
716static 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
761static 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
779static 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
802static 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
815static 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
821static 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
844static 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
855static 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
898void 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}
Note: See TracBrowser for help on using the repository browser.