source: irc_commands.c @ 8e6ecfe

Last change on this file since 8e6ecfe was 8e6ecfe, checked in by Dennis Kaarsemaker <dennis@…>, at 2016-03-25T18:07:53Z

Authentication: scaffolding for multiple authentication backends

Instead of always putting users passwords in XML files, allow site
admins to configure a different authentication method to integrate
authentication with other systems.

This doesn't add any authentication backends yet, merely the
scaffolding. Notably:

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