source: irc_commands.c @ df67b48

Last change on this file since df67b48 was df67b48, checked in by Wilmer van der Gaast <wilmer@…>, at 2017-04-03T20:55:50Z

Add PROXY command. Not actually an IRC protocol command, it's a HAProxy
trick supported by stunnel to indicate where the connection originally
came from. Looks a little better on public servers.

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