source: irc_commands.c @ d11822a

Last change on this file since d11822a was b097945, checked in by Wilmer van der Gaast <github@…>, at 2017-04-06T20:25:08Z

Move canohost functions (diff licence) to separate file.

  • Property mode set to 100644
File size: 26.8 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, 903, ":Password accepted");
150                return TRUE;
151
152        } else if (status == STORAGE_INVALID_PASSWORD) {
153                irc_send_num(irc, 904, ":Incorrect password");
154        } else if (status == STORAGE_NO_SUCH_USER) {
155                irc_send_num(irc, 904, ":The nick is (probably) not registered");
156        } else {
157                irc_send_num(irc, 904, ":Unknown SASL authentication error");
158        }
159
160        return FALSE;
161}
162
163static void irc_cmd_authenticate(irc_t *irc, char **cmd)
164{
165        /* require the CAP to be enabled, and don't allow authentication before server password */
166        if (!(irc->caps & CAP_SASL) ||
167            (global.conf->authmode == AUTHMODE_CLOSED && !(irc->status & USTATUS_AUTHORIZED))) {
168                return;
169        }
170
171        if (irc->status & USTATUS_SASL_PLAIN_PENDING) {
172                char *user, *pass;
173
174                irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
175
176                if (!irc_sasl_plain_parse(cmd[1], &user, &pass)) {
177                        irc_send_num(irc, 904, ":SASL authentication failed");
178                        return;
179                }
180
181                /* let's not support the nick != user case
182                 * if NICK is received after SASL, it will just fail after registration */
183                if (user && irc->user->nick && strcmp(user, irc->user->nick) != 0) {
184                        irc_send_num(irc, 902, ":Your SASL username does not match your nickname");
185
186                } else if (irc_sasl_check_pass(irc, user, pass)) {
187                        /* and here we do the same thing as the PASS command*/
188                        if (irc->status & USTATUS_LOGGED_IN) {
189                                char *send_cmd[] = { "identify", pass, NULL };
190                                root_command(irc, send_cmd);
191                        } else {
192                                /* no check_login here - wait for CAP END */
193                                irc_setpass(irc, pass);
194                        }
195                }
196
197                g_free(user);
198                g_free(pass);
199
200        } else if (irc->status & USTATUS_IDENTIFIED) {
201                irc_send_num(irc, 907, ":You have already authenticated");
202
203        } else if (strcmp(cmd[1], "*") == 0) {
204                irc_send_num(irc, 906, ":SASL authentication aborted");
205                irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
206
207        } else if (g_strcasecmp(cmd[1], "PLAIN") == 0) {
208                irc_write(irc, "AUTHENTICATE +");
209                irc->status |= USTATUS_SASL_PLAIN_PENDING;
210
211        } else {
212                irc_send_num(irc, 908, "PLAIN :is the available SASL mechanism");
213                irc_send_num(irc, 904, ":SASL authentication failed");
214                irc->status &= ~USTATUS_SASL_PLAIN_PENDING;
215        }
216}
217
218static void irc_cmd_user(irc_t *irc, char **cmd)
219{
220        irc->user->user = g_strdup(cmd[1]);
221        irc->user->fullname = g_strdup(cmd[4]);
222
223        irc_check_login(irc);
224}
225
226static void irc_cmd_nick(irc_t *irc, char **cmd)
227{
228        irc_user_t *iu;
229
230        if ((iu = irc_user_by_name(irc, cmd[1])) && iu != irc->user) {
231                irc_send_num(irc, 433, "%s :This nick is already in use", cmd[1]);
232        } else if (!nick_ok(NULL, cmd[1])) {
233                /* [SH] Invalid characters. */
234                irc_send_num(irc, 432, "%s :This nick contains invalid characters", cmd[1]);
235        } else if (irc->status & USTATUS_LOGGED_IN) {
236                /* WATCH OUT: iu from the first if reused here to check if the
237                   new nickname is the same (other than case, possibly). If it
238                   is, no need to reset identify-status. */
239                if ((irc->status & USTATUS_IDENTIFIED) && iu != irc->user) {
240                        irc_setpass(irc, NULL);
241                        irc->status &= ~USTATUS_IDENTIFIED;
242                        irc_umode_set(irc, "-R", 1);
243
244                        if (irc->caps & CAP_SASL) {
245                                irc_send_num(irc, 901, "%s!%s@%s :You are now logged out",
246                                        irc->user->nick, irc->user->user, irc->user->host);
247                        }
248
249                        irc_rootmsg(irc, "Changing nicks resets your identify status. "
250                                    "Re-identify or register a new account if you want "
251                                    "your configuration to be saved. See \x02help "
252                                    "nick_changes\x02.");
253                }
254
255                if (strcmp(cmd[1], irc->user->nick) != 0) {
256                        irc_user_set_nick(irc->user, cmd[1]);
257                }
258        } else {
259                g_free(irc->user->nick);
260                irc->user->nick = g_strdup(cmd[1]);
261
262                irc_check_login(irc);
263        }
264}
265
266static void irc_cmd_quit(irc_t *irc, char **cmd)
267{
268        if (cmd[1] && *cmd[1]) {
269                irc_abort(irc, 0, "Quit: %s", cmd[1]);
270        } else {
271                irc_abort(irc, 0, "Leaving...");
272        }
273}
274
275static void irc_cmd_ping(irc_t *irc, char **cmd)
276{
277        irc_write(irc, ":%s PONG %s :%s", irc->root->host,
278                  irc->root->host, cmd[1] ? cmd[1] : irc->root->host);
279}
280
281static void irc_cmd_pong(irc_t *irc, char **cmd)
282{
283        /* We could check the value we get back from the user, but in
284           fact we don't care, we're just happy s/he's still alive. */
285        irc->last_pong = gettime();
286        irc->pinging = 0;
287}
288
289static void irc_cmd_join(irc_t *irc, char **cmd)
290{
291        char *comma, *s = cmd[1];
292
293        while (s) {
294                irc_channel_t *ic;
295
296                if ((comma = strchr(s, ','))) {
297                        *comma = '\0';
298                }
299
300                if ((ic = irc_channel_by_name(irc, s)) == NULL &&
301                    (ic = irc_channel_new(irc, s))) {
302                        if (strcmp(set_getstr(&ic->set, "type"), "control") != 0) {
303                                /* Autoconfiguration is for control channels only ATM. */
304                        } else if (bee_group_by_name(ic->irc->b, ic->name + 1, FALSE)) {
305                                set_setstr(&ic->set, "group", ic->name + 1);
306                                set_setstr(&ic->set, "fill_by", "group");
307                        } else if (set_setstr(&ic->set, "protocol", ic->name + 1)) {
308                                set_setstr(&ic->set, "fill_by", "protocol");
309                        } else if (set_setstr(&ic->set, "account", ic->name + 1)) {
310                                set_setstr(&ic->set, "fill_by", "account");
311                        } else {
312                                /* The set commands above will run this already,
313                                   but if we didn't hit any, we have to fill the
314                                   channel with the default population. */
315                                bee_irc_channel_update(ic->irc, ic, NULL);
316                        }
317                } else if (ic == NULL) {
318                        irc_send_num(irc, 479, "%s :Invalid channel name", s);
319                        goto next;
320                }
321
322                if (ic->flags & IRC_CHANNEL_JOINED) {
323                        /* Dude, you're already there...
324                           RFC doesn't have any reply for that though? */
325                        goto next;
326                }
327
328                if (ic->f->join && !ic->f->join(ic)) {
329                        /* The story is: FALSE either means the handler
330                           showed an error message, or is doing some work
331                           before the join should be confirmed. (In the
332                           latter case, the caller should take care of that
333                           confirmation.) TRUE means all's good, let the
334                           user join the channel right away. */
335                        goto next;
336                }
337
338                irc_channel_add_user(ic, irc->user);
339
340next:
341                if (comma) {
342                        s = comma + 1;
343                        *comma = ',';
344                } else {
345                        break;
346                }
347        }
348}
349
350static void irc_cmd_names(irc_t *irc, char **cmd)
351{
352        irc_channel_t *ic;
353
354        if (cmd[1] && (ic = irc_channel_by_name(irc, cmd[1]))) {
355                irc_send_names(ic);
356        }
357        /* With no args, we should show /names of all chans. Make the code
358           below work well if necessary.
359        else
360        {
361                GSList *l;
362
363                for( l = irc->channels; l; l = l->next )
364                        irc_send_names( l->data );
365        }
366        */
367}
368
369static void irc_cmd_part(irc_t *irc, char **cmd)
370{
371        irc_channel_t *ic;
372
373        if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
374                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
375        } else if (irc_channel_del_user(ic, irc->user, IRC_CDU_PART, cmd[2])) {
376                if (ic->f->part) {
377                        ic->f->part(ic, NULL);
378                }
379        } else {
380                irc_send_num(irc, 442, "%s :You're not on that channel", cmd[1]);
381        }
382}
383
384static void irc_cmd_whois(irc_t *irc, char **cmd)
385{
386        char *nick = cmd[1];
387        irc_user_t *iu = irc_user_by_name(irc, nick);
388
389        if (iu) {
390                irc_send_whois(iu);
391        } else {
392                irc_send_num(irc, 401, "%s :Nick does not exist", nick);
393        }
394}
395
396static void irc_cmd_whowas(irc_t *irc, char **cmd)
397{
398        /* For some reason irssi tries a whowas when whois fails. We can
399           ignore this, but then the user never gets a "user not found"
400           message from irssi which is a bit annoying. So just respond
401           with not-found and irssi users will get better error messages */
402
403        irc_send_num(irc, 406, "%s :Nick does not exist", cmd[1]);
404        irc_send_num(irc, 369, "%s :End of WHOWAS", cmd[1]);
405}
406
407static void irc_cmd_motd(irc_t *irc, char **cmd)
408{
409        irc_send_motd(irc);
410}
411
412static void irc_cmd_mode(irc_t *irc, char **cmd)
413{
414        if (irc_channel_name_ok(cmd[1])) {
415                irc_channel_t *ic;
416
417                if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
418                        irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
419                } else if (cmd[2]) {
420                        if (*cmd[2] == '+' || *cmd[2] == '-') {
421                                irc_send_num(irc, 477, "%s :Can't change channel modes", cmd[1]);
422                        } else if (*cmd[2] == 'b') {
423                                irc_send_num(irc, 368, "%s :No bans possible", cmd[1]);
424                        }
425                } else {
426                        irc_send_num(irc, 324, "%s +%s", cmd[1], ic->mode);
427                }
428        } else {
429                if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
430                        if (cmd[2]) {
431                                irc_umode_set(irc, cmd[2], 0);
432                        } else {
433                                irc_send_num(irc, 221, "+%s", irc->umode);
434                        }
435                } else {
436                        irc_send_num(irc, 502, ":Don't touch their modes");
437                }
438        }
439}
440
441static void irc_cmd_who(irc_t *irc, char **cmd)
442{
443        char *channel = cmd[1];
444        irc_channel_t *ic;
445        irc_user_t *iu;
446
447        if (!channel || *channel == '0' || *channel == '*' || !*channel) {
448                irc_send_who(irc, irc->users, "**");
449        } else if ((ic = irc_channel_by_name(irc, channel))) {
450                irc_send_who(irc, ic->users, channel);
451        } else if ((iu = irc_user_by_name(irc, channel))) {
452                /* Tiny hack! */
453                GSList *l = g_slist_append(NULL, iu);
454                irc_send_who(irc, l, channel);
455                g_slist_free(l);
456        } else {
457                irc_send_num(irc, 403, "%s :No such channel", channel);
458        }
459}
460
461static void irc_cmd_privmsg(irc_t *irc, char **cmd)
462{
463        irc_channel_t *ic;
464        irc_user_t *iu;
465
466        if (!cmd[2]) {
467                irc_send_num(irc, 412, ":No text to send");
468                return;
469        }
470
471        /* Don't treat CTCP actions as real CTCPs, just convert them right now. */
472        if (g_strncasecmp(cmd[2], "\001ACTION", 7) == 0) {
473                cmd[2] += 4;
474                memcpy(cmd[2], "/me", 3);
475                if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
476                        cmd[2][strlen(cmd[2]) - 1] = '\0';
477                }
478        }
479
480        if (irc_channel_name_ok(cmd[1]) &&
481            (ic = irc_channel_by_name(irc, cmd[1]))) {
482                if (cmd[2][0] == '\001') {
483                        /* CTCPs to channels? Nah. Maybe later. */
484                } else if (ic->f->privmsg) {
485                        ic->f->privmsg(ic, cmd[2]);
486                }
487        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
488                if (cmd[2][0] == '\001') {
489                        char **ctcp;
490
491                        if (iu->f->ctcp == NULL) {
492                                return;
493                        }
494                        if (cmd[2][strlen(cmd[2]) - 1] == '\001') {
495                                cmd[2][strlen(cmd[2]) - 1] = '\0';
496                        }
497
498                        ctcp = split_command_parts(cmd[2] + 1, 0);
499                        iu->f->ctcp(iu, ctcp);
500                } else if (iu->f->privmsg) {
501                        iu->last_channel = NULL;
502                        iu->f->privmsg(iu, cmd[2]);
503                }
504        } else {
505                irc_send_num(irc, 401, "%s :No such nick/channel", cmd[1]);
506        }
507}
508
509static void irc_cmd_notice(irc_t *irc, char **cmd)
510{
511        irc_user_t *iu;
512
513        if (!cmd[2]) {
514                irc_send_num(irc, 412, ":No text to send");
515                return;
516        }
517
518        /* At least for now just echo. IIRC some IRC clients use self-notices
519           for lag checks, so try to support that. */
520        if (nick_cmp(NULL, cmd[1], irc->user->nick) == 0) {
521                irc_send_msg(irc->user, "NOTICE", irc->user->nick, cmd[2], NULL);
522        } else if ((iu = irc_user_by_name(irc, cmd[1]))) {
523                iu->f->privmsg(iu, cmd[2]);
524        }
525}
526
527static void irc_cmd_nickserv(irc_t *irc, char **cmd)
528{
529        /* [SH] This aliases the NickServ command to PRIVMSG root */
530        /* [TV] This aliases the NS command to PRIVMSG root as well */
531        root_command(irc, cmd + 1);
532}
533
534static void irc_cmd_oper_hack(irc_t *irc, char **cmd);
535
536static void irc_cmd_oper(irc_t *irc, char **cmd)
537{
538        /* Very non-standard evil but useful/secure hack, see below. */
539        if (irc->status & OPER_HACK_ANY) {
540                return irc_cmd_oper_hack(irc, cmd);
541        }
542
543        if (global.conf->oper_pass &&
544            (strncmp(global.conf->oper_pass, "md5:", 4) == 0 ?
545             md5_verify_password(cmd[2], global.conf->oper_pass + 4) == 0 :
546             strcmp(cmd[2], global.conf->oper_pass) == 0)) {
547                irc_umode_set(irc, "+o", 1);
548                irc_send_num(irc, 381, ":Password accepted");
549        } else {
550                irc_send_num(irc, 491, ":Incorrect password");
551        }
552}
553
554static void irc_cmd_oper_hack(irc_t *irc, char **cmd)
555{
556        char *password = g_strjoinv(" ", cmd + 2);
557
558        /* /OPER can now also be used to enter IM/identify passwords without
559           echoing. It's a hack but the extra password security is worth it. */
560        if (irc->status & OPER_HACK_ACCOUNT_PASSWORD) {
561                account_t *a;
562
563                for (a = irc->b->accounts; a; a = a->next) {
564                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
565                                set_setstr(&a->set, "password", password);
566                                irc_rootmsg(irc, "Password added to IM account "
567                                            "%s", a->tag);
568                                /* The IRC client may expect this. 491 suggests the OPER
569                                   password was wrong, so the client won't expect a +o.
570                                   It may however repeat the password prompt. We'll see. */
571                                irc_send_num(irc, 491, ":Password added to IM account "
572                                             "%s", a->tag);
573                        }
574                }
575        } else if (irc->status & OPER_HACK_IDENTIFY) {
576                char *send_cmd[] = { "identify", password, NULL, NULL };
577                irc->status &= ~OPER_HACK_IDENTIFY;
578                if (irc->status & OPER_HACK_IDENTIFY_NOLOAD) {
579                        send_cmd[1] = "-noload";
580                        send_cmd[2] = password;
581                } else if (irc->status & OPER_HACK_IDENTIFY_FORCE) {
582                        send_cmd[1] = "-force";
583                        send_cmd[2] = password;
584                }
585                irc_send_num(irc, 491, ":Trying to identify");
586                root_command(irc, send_cmd);
587        } else if (irc->status & OPER_HACK_REGISTER) {
588                char *send_cmd[] = { "register", password, NULL };
589                irc_send_num(irc, 491, ":Trying to identify");
590                root_command(irc, send_cmd);
591        }
592
593        irc->status &= ~OPER_HACK_ANY;
594        g_free(password);
595}
596
597static void irc_cmd_invite(irc_t *irc, char **cmd)
598{
599        irc_channel_t *ic;
600        irc_user_t *iu;
601
602        if ((iu = irc_user_by_name(irc, cmd[1])) == NULL) {
603                irc_send_num(irc, 401, "%s :No such nick", cmd[1]);
604                return;
605        } else if ((ic = irc_channel_by_name(irc, cmd[2])) == NULL) {
606                irc_send_num(irc, 403, "%s :No such channel", cmd[2]);
607                return;
608        }
609
610        if (!ic->f->invite) {
611                irc_send_num(irc, 482, "%s :Can't invite people here", cmd[2]);
612        } else if (ic->f->invite(ic, iu)) {
613                irc_send_num(irc, 341, "%s %s", iu->nick, ic->name);
614        }
615}
616
617static void irc_cmd_kick(irc_t *irc, char **cmd)
618{
619        irc_channel_t *ic;
620        irc_user_t *iu;
621
622        if ((iu = irc_user_by_name(irc, cmd[2])) == NULL) {
623                irc_send_num(irc, 401, "%s :No such nick", cmd[2]);
624                return;
625        } else if ((ic = irc_channel_by_name(irc, cmd[1])) == NULL) {
626                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
627                return;
628        } else if (!ic->f->kick) {
629                irc_send_num(irc, 482, "%s :Can't kick people here", cmd[1]);
630                return;
631        }
632
633        ic->f->kick(ic, iu, cmd[3] ? cmd[3] : NULL);
634}
635
636static void irc_cmd_userhost(irc_t *irc, char **cmd)
637{
638        int i;
639
640        /* [TV] Usable USERHOST-implementation according to
641                RFC1459. Without this, mIRC shows an error
642                while connecting, and the used way of rejecting
643                breaks standards.
644        */
645
646        for (i = 1; cmd[i]; i++) {
647                irc_user_t *iu = irc_user_by_name(irc, cmd[i]);
648
649                if (iu) {
650                        irc_send_num(irc, 302, ":%s=%c%s@%s", iu->nick,
651                                     irc_user_get_away(iu) ? '-' : '+',
652                                     iu->user, iu->host);
653                }
654        }
655}
656
657static void irc_cmd_ison(irc_t *irc, char **cmd)
658{
659        char buff[IRC_MAX_LINE];
660        int lenleft, i;
661
662        buff[0] = '\0';
663
664        /* [SH] Leave room for : and \0 */
665        lenleft = IRC_MAX_LINE - 2;
666
667        for (i = 1; cmd[i]; i++) {
668                char *this, *next;
669
670                this = cmd[i];
671                while (*this) {
672                        irc_user_t *iu;
673
674                        if ((next = strchr(this, ' '))) {
675                                *next = 0;
676                        }
677
678                        if ((iu = irc_user_by_name(irc, this)) &&
679                            iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
680                                lenleft -= strlen(iu->nick) + 1;
681
682                                if (lenleft < 0) {
683                                        break;
684                                }
685
686                                strcat(buff, iu->nick);
687                                strcat(buff, " ");
688                        }
689
690                        if (next) {
691                                *next = ' ';
692                                this = next + 1;
693                        } else {
694                                break;
695                        }
696                }
697
698                /* *sigh* */
699                if (lenleft < 0) {
700                        break;
701                }
702        }
703
704        if (strlen(buff) > 0) {
705                buff[strlen(buff) - 1] = '\0';
706        }
707
708        irc_send_num(irc, 303, ":%s", buff);
709}
710
711static void irc_cmd_watch(irc_t *irc, char **cmd)
712{
713        int i;
714
715        /* Obviously we could also mark a user structure as being
716           watched, but what if the WATCH command is sent right
717           after connecting? The user won't exist yet then... */
718        for (i = 1; cmd[i]; i++) {
719                char *nick;
720                irc_user_t *iu;
721
722                if (!cmd[i][0] || !cmd[i][1]) {
723                        break;
724                }
725
726                nick = g_strdup(cmd[i] + 1);
727                nick_lc(irc, nick);
728
729                iu = irc_user_by_name(irc, nick);
730
731                if (cmd[i][0] == '+') {
732                        if (!g_hash_table_lookup(irc->watches, nick)) {
733                                g_hash_table_insert(irc->watches, nick, nick);
734                        }
735
736                        if (iu && iu->bu && iu->bu->flags & BEE_USER_ONLINE) {
737                                irc_send_num(irc, 604, "%s %s %s %d :%s", iu->nick, iu->user,
738                                             iu->host, (int) time(NULL), "is online");
739                        } else {
740                                irc_send_num(irc, 605, "%s %s %s %d :%s", nick, "*", "*",
741                                             (int) time(NULL), "is offline");
742                        }
743                } else if (cmd[i][0] == '-') {
744                        gpointer okey, ovalue;
745
746                        if (g_hash_table_lookup_extended(irc->watches, nick, &okey, &ovalue)) {
747                                g_hash_table_remove(irc->watches, okey);
748                                g_free(okey);
749
750                                irc_send_num(irc, 602, "%s %s %s %d :%s", nick, "*", "*", 0, "Stopped watching");
751                        }
752                }
753        }
754}
755
756static void irc_cmd_topic(irc_t *irc, char **cmd)
757{
758        irc_channel_t *ic = irc_channel_by_name(irc, cmd[1]);
759        const char *new = cmd[2];
760
761        if (ic == NULL) {
762                irc_send_num(irc, 403, "%s :No such channel", cmd[1]);
763        } else if (new) {
764                if (ic->f->topic == NULL) {
765                        irc_send_num(irc, 482, "%s :Can't change this channel's topic", ic->name);
766                } else if (ic->f->topic(ic, new)) {
767                        irc_send_topic(ic, TRUE);
768                }
769        } else {
770                irc_send_topic(ic, FALSE);
771        }
772}
773
774static void irc_cmd_away(irc_t *irc, char **cmd)
775{
776        if (cmd[1] && *cmd[1]) {
777                char away[strlen(cmd[1]) + 1];
778                int i, j;
779
780                /* Copy away string, but skip control chars. Mainly because
781                   Jabber really doesn't like them. */
782                for (i = j = 0; cmd[1][i]; i++) {
783                        if ((unsigned char) (away[j] = cmd[1][i]) >= ' ') {
784                                j++;
785                        }
786                }
787                away[j] = '\0';
788
789                irc_send_num(irc, 306, ":You're now away: %s", away);
790                set_setstr(&irc->b->set, "away", away);
791        } else {
792                irc_send_num(irc, 305, ":Welcome back");
793                set_setstr(&irc->b->set, "away", NULL);
794        }
795}
796
797static void irc_cmd_list(irc_t *irc, char **cmd)
798{
799        GSList *l;
800
801        for (l = irc->channels; l; l = l->next) {
802                irc_channel_t *ic = l->data;
803
804                irc_send_num(irc, 322, "%s %d :%s",
805                             ic->name, g_slist_length(ic->users), ic->topic ? : "");
806        }
807        irc_send_num(irc, 323, ":%s", "End of /LIST");
808}
809
810static void irc_cmd_version(irc_t *irc, char **cmd)
811{
812        irc_send_num(irc, 351, "%s-%s. %s :",
813                     PACKAGE, BITLBEE_VERSION, irc->root->host);
814}
815
816static void irc_cmd_completions(irc_t *irc, char **cmd)
817{
818        help_t *h;
819        set_t *s;
820        int i;
821
822        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS OK");
823
824        for (i = 0; root_commands[i].command; i++) {
825                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS %s", root_commands[i].command);
826        }
827
828        for (h = global.help; h; h = h->next) {
829                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS help %s", h->title);
830        }
831
832        for (s = irc->b->set; s; s = s->next) {
833                irc_send_msg_f(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS set %s", s->key);
834        }
835
836        irc_send_msg_raw(irc->root, "NOTICE", irc->user->nick, "COMPLETIONS END");
837}
838
839static void irc_cmd_rehash(irc_t *irc, char **cmd)
840{
841        if (global.conf->runmode == RUNMODE_INETD) {
842                ipc_master_cmd_rehash(NULL, NULL);
843        } else {
844                ipc_to_master(cmd);
845        }
846
847        irc_send_num(irc, 382, "%s :Rehashing", global.conf_file);
848}
849
850static const command_t irc_commands[] = {
851        { "cap",         1, irc_cmd_cap,         0 },
852        { "pass",        1, irc_cmd_pass,        0 },
853        { "proxy",       5, irc_cmd_proxy,       IRC_CMD_PRE_LOGIN },
854        { "user",        4, irc_cmd_user,        IRC_CMD_PRE_LOGIN },
855        { "nick",        1, irc_cmd_nick,        0 },
856        { "quit",        0, irc_cmd_quit,        0 },
857        { "ping",        0, irc_cmd_ping,        0 },
858        { "pong",        0, irc_cmd_pong,        IRC_CMD_LOGGED_IN },
859        { "join",        1, irc_cmd_join,        IRC_CMD_LOGGED_IN },
860        { "names",       1, irc_cmd_names,       IRC_CMD_LOGGED_IN },
861        { "part",        1, irc_cmd_part,        IRC_CMD_LOGGED_IN },
862        { "whois",       1, irc_cmd_whois,       IRC_CMD_LOGGED_IN },
863        { "whowas",      1, irc_cmd_whowas,      IRC_CMD_LOGGED_IN },
864        { "motd",        0, irc_cmd_motd,        IRC_CMD_LOGGED_IN },
865        { "mode",        1, irc_cmd_mode,        IRC_CMD_LOGGED_IN },
866        { "who",         0, irc_cmd_who,         IRC_CMD_LOGGED_IN },
867        { "privmsg",     1, irc_cmd_privmsg,     IRC_CMD_LOGGED_IN },
868        { "notice",      1, irc_cmd_notice,      IRC_CMD_LOGGED_IN },
869        { "nickserv",    1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
870        { "ns",          1, irc_cmd_nickserv,    IRC_CMD_LOGGED_IN },
871        { "away",        0, irc_cmd_away,        IRC_CMD_LOGGED_IN },
872        { "version",     0, irc_cmd_version,     IRC_CMD_LOGGED_IN },
873        { "completions", 0, irc_cmd_completions, IRC_CMD_LOGGED_IN },
874        { "userhost",    1, irc_cmd_userhost,    IRC_CMD_LOGGED_IN },
875        { "ison",        1, irc_cmd_ison,        IRC_CMD_LOGGED_IN },
876        { "watch",       1, irc_cmd_watch,       IRC_CMD_LOGGED_IN },
877        { "invite",      2, irc_cmd_invite,      IRC_CMD_LOGGED_IN },
878        { "kick",        2, irc_cmd_kick,        IRC_CMD_LOGGED_IN },
879        { "topic",       1, irc_cmd_topic,       IRC_CMD_LOGGED_IN },
880        { "oper",        2, irc_cmd_oper,        IRC_CMD_LOGGED_IN },
881        { "list",        0, irc_cmd_list,        IRC_CMD_LOGGED_IN },
882        { "die",         0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
883        { "deaf",        0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
884        { "wallops",     1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
885        { "wall",        1, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
886        { "rehash",      0, irc_cmd_rehash,      IRC_CMD_OPER_ONLY },
887        { "restart",     0, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
888        { "kill",        2, NULL,                IRC_CMD_OPER_ONLY | IRC_CMD_TO_MASTER },
889        { "authenticate", 1, irc_cmd_authenticate, 0 },
890        { NULL }
891};
892
893void irc_exec(irc_t *irc, char *cmd[])
894{
895        int i, n_arg;
896
897        if (!cmd[0]) {
898                return;
899        }
900
901        for (i = 0; irc_commands[i].command; i++) {
902                if (g_strcasecmp(irc_commands[i].command, cmd[0]) == 0) {
903                        /* There should be no typo in the next line: */
904                        for (n_arg = 0; cmd[n_arg]; n_arg++) {
905                                ;
906                        }
907                        n_arg--;
908
909                        if (irc_commands[i].flags & IRC_CMD_PRE_LOGIN && irc->status & USTATUS_LOGGED_IN) {
910                                irc_send_num(irc, 462, ":Only allowed before logging in");
911                        } else if (irc_commands[i].flags & IRC_CMD_LOGGED_IN && !(irc->status & USTATUS_LOGGED_IN)) {
912                                irc_send_num(irc, 451, ":Register first");
913                        } else if (irc_commands[i].flags & IRC_CMD_OPER_ONLY && !strchr(irc->umode, 'o')) {
914                                irc_send_num(irc, 481, ":Permission denied - You're not an IRC operator");
915                        } else if (n_arg < irc_commands[i].required_parameters) {
916                                irc_send_num(irc, 461, "%s :Need more parameters", cmd[0]);
917                        } else if (irc_commands[i].flags & IRC_CMD_TO_MASTER) {
918                                /* IPC doesn't make sense in inetd mode,
919                                    but the function will catch that. */
920                                ipc_to_master(cmd);
921                        } else {
922                                irc_commands[i].execute(irc, cmd);
923                        }
924
925                        return;
926                }
927        }
928
929        if (irc->status & USTATUS_LOGGED_IN) {
930                irc_send_num(irc, 421, "%s :Unknown command", cmd[0]);
931        }
932}
Note: See TracBrowser for help on using the repository browser.