source: root_commands.c @ 129e282

Last change on this file since 129e282 was 58b63de6, checked in by dequis <dx@…>, at 2015-10-08T08:34:18Z

IRCv3 SASL capability + PLAIN method

Only plain, no other methods. We don't have built-in SSL to implement
EXTERNAL (certfp) and nothing else is worth implementing.

The actual authentication is pretty much like sending a server password
(when the server's authmode isn't closed), which means it happens in
cmd_identify, but this code also calls storage_check_pass() to send the
required success/failure replies.

SASL doesn't give us much benefit other than standards compliance, but
some clients might appreciate it.

And having a fifth way to do the same thing doesn't hurt! Now we have:

  • identify in &bitlbee
  • identify to nickserv (alias for root)
  • 'nickserv' and 'ns' irc commands
  • server password
  • sasl plain
  • Property mode set to 100644
File size: 36.7 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* User manager (root) 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 "commands.h"
28#include "bitlbee.h"
29#include "help.h"
30#include "ipc.h"
31
32void root_command_string(irc_t *irc, char *command)
33{
34        root_command(irc, split_command_parts(command, 0));
35}
36
37#define MIN_ARGS(x, y ...)                                                    \
38        do                                                                     \
39        {                                                                      \
40                int blaat;                                                     \
41                for (blaat = 0; blaat <= x; blaat++) {                         \
42                        if (cmd[blaat] == NULL)                               \
43                        {                                                      \
44                                irc_rootmsg(irc, "Not enough parameters given (need %d).", x); \
45                                return y;                                      \
46                        } }                                                      \
47        } while (0)
48
49void root_command(irc_t *irc, char *cmd[])
50{
51        int i, len;
52
53        if (!cmd[0]) {
54                return;
55        }
56
57        len = strlen(cmd[0]);
58        for (i = 0; root_commands[i].command; i++) {
59                if (g_strncasecmp(root_commands[i].command, cmd[0], len) == 0) {
60                        if (root_commands[i + 1].command &&
61                            g_strncasecmp(root_commands[i + 1].command, cmd[0], len) == 0) {
62                                /* Only match on the first letters if the match is unique. */
63                                break;
64                        }
65
66                        MIN_ARGS(root_commands[i].required_parameters);
67
68                        root_commands[i].execute(irc, cmd);
69                        return;
70                }
71        }
72
73        irc_rootmsg(irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.",
74                    cmd[0]);
75}
76
77static void cmd_help(irc_t *irc, char **cmd)
78{
79        char param[80];
80        int i;
81        char *s;
82
83        memset(param, 0, sizeof(param));
84        for (i = 1; (cmd[i] != NULL && (strlen(param) < (sizeof(param) - 1))); i++) {
85                if (i != 1) {   // prepend space except for the first parameter
86                        strcat(param, " ");
87                }
88                strncat(param, cmd[i], sizeof(param) - strlen(param) - 1);
89        }
90
91        s = help_get(&(global.help), param);
92        if (!s) {
93                s = help_get(&(global.help), "");
94        }
95
96        if (s) {
97                irc_rootmsg(irc, "%s", s);
98                g_free(s);
99        } else {
100                irc_rootmsg(irc, "Error opening helpfile.");
101        }
102}
103
104static void cmd_account(irc_t *irc, char **cmd);
105static void bitlbee_whatsnew(irc_t *irc);
106
107static void cmd_identify(irc_t *irc, char **cmd)
108{
109        storage_status_t status;
110        gboolean load = TRUE;
111        char *password = cmd[1];
112
113        if (irc->status & USTATUS_IDENTIFIED) {
114                irc_rootmsg(irc, "You're already logged in.");
115                return;
116        }
117
118        if (cmd[1] == NULL) {
119        } else if (strncmp(cmd[1], "-no", 3) == 0) {
120                load = FALSE;
121                password = cmd[2];
122                if (password == NULL) {
123                        irc->status |= OPER_HACK_IDENTIFY_NOLOAD;
124                }
125        } else if (strncmp(cmd[1], "-force", 6) == 0) {
126                password = cmd[2];
127                if (password == NULL) {
128                        irc->status |= OPER_HACK_IDENTIFY_FORCE;
129                }
130        } else if (irc->b->accounts != NULL) {
131                irc_rootmsg(irc,
132                            "You're trying to identify yourself, but already have "
133                            "at least one IM account set up. "
134                            "Use \x02identify -noload\x02 or \x02identify -force\x02 "
135                            "instead (see \x02help identify\x02).");
136                return;
137        }
138
139        if (password == NULL) {
140                irc_rootmsg(irc, "About to identify, use /OPER to enter the password");
141                irc->status |= OPER_HACK_IDENTIFY;
142                return;
143        }
144
145        if (load) {
146                status = storage_load(irc, password);
147        } else {
148                status = storage_check_pass(irc->user->nick, password);
149        }
150
151        switch (status) {
152        case STORAGE_INVALID_PASSWORD:
153                irc_rootmsg(irc, "Incorrect password");
154                break;
155        case STORAGE_NO_SUCH_USER:
156                irc_rootmsg(irc, "The nick is (probably) not registered");
157                break;
158        case STORAGE_OK:
159                irc_rootmsg(irc, "Password accepted%s",
160                            load ? ", settings and accounts loaded" : "");
161                irc_setpass(irc, password);
162                irc->status |= USTATUS_IDENTIFIED;
163                irc_umode_set(irc, "+R", 1);
164
165                if (irc->caps & CAP_SASL) {
166                        irc_user_t *iu = irc->user;
167                        irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
168                                iu->nick, iu->user, iu->host, iu->nick, iu->nick);
169                }
170
171                bitlbee_whatsnew(irc);
172
173                /* The following code is a bit hairy now. With takeover
174                   support, we shouldn't immediately auto_connect in case
175                   we're going to offer taking over an existing session.
176                   Do it in 200ms since that should give the parent process
177                   enough time to come back to us. */
178                if (load) {
179                        irc_channel_auto_joins(irc, NULL);
180                        if (!set_getbool(&irc->default_channel->set, "auto_join")) {
181                                irc_channel_del_user(irc->default_channel, irc->user,
182                                                     IRC_CDU_PART, "auto_join disabled "
183                                                     "for this channel.");
184                        }
185                        if (set_getbool(&irc->b->set, "auto_connect")) {
186                                irc->login_source_id = b_timeout_add(200,
187                                                                     cmd_identify_finish, irc);
188                        }
189                }
190
191                /* If ipc_child_identify() returns FALSE, it means we're
192                   already sure that there's no takeover target (only
193                   possible in 1-process daemon mode). Start auto_connect
194                   immediately. */
195                if (!ipc_child_identify(irc) && load) {
196                        cmd_identify_finish(irc, 0, 0);
197                }
198
199                break;
200        case STORAGE_OTHER_ERROR:
201        default:
202                irc_rootmsg(irc, "Unknown error while loading configuration");
203                break;
204        }
205}
206
207gboolean cmd_identify_finish(gpointer data, gint fd, b_input_condition cond)
208{
209        char *account_on[] = { "account", "on", NULL };
210        irc_t *irc = data;
211
212        if (set_getbool(&irc->b->set, "auto_connect")) {
213                cmd_account(irc, account_on);
214        }
215
216        b_event_remove(irc->login_source_id);
217        irc->login_source_id = -1;
218        return FALSE;
219}
220
221static void cmd_register(irc_t *irc, char **cmd)
222{
223        char s[16];
224
225        if (global.conf->authmode == AUTHMODE_REGISTERED) {
226                irc_rootmsg(irc, "This server does not allow registering new accounts");
227                return;
228        }
229
230        if (cmd[1] == NULL) {
231                irc_rootmsg(irc, "About to register, use /OPER to enter the password");
232                irc->status |= OPER_HACK_REGISTER;
233                return;
234        }
235
236        switch (storage_save(irc, cmd[1], FALSE)) {
237        case STORAGE_ALREADY_EXISTS:
238                irc_rootmsg(irc, "Nick is already registered");
239                break;
240
241        case STORAGE_OK:
242                irc_rootmsg(irc, "Account successfully created");
243                irc_setpass(irc, cmd[1]);
244                irc->status |= USTATUS_IDENTIFIED;
245                irc_umode_set(irc, "+R", 1);
246
247                if (irc->caps & CAP_SASL) {
248                        irc_user_t *iu = irc->user;
249                        irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
250                                iu->nick, iu->user, iu->host, iu->nick, iu->nick);
251                }
252
253                /* Set this var now, or anyone who logs in to his/her
254                   newly created account for the first time gets the
255                   whatsnew story. */
256                g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
257                set_setstr(&irc->b->set, "last_version", s);
258                break;
259
260        default:
261                irc_rootmsg(irc, "Error registering");
262                break;
263        }
264}
265
266static void cmd_drop(irc_t *irc, char **cmd)
267{
268        storage_status_t status;
269
270        status = storage_remove(irc->user->nick, cmd[1]);
271        switch (status) {
272        case STORAGE_NO_SUCH_USER:
273                irc_rootmsg(irc, "That account does not exist");
274                break;
275        case STORAGE_INVALID_PASSWORD:
276                irc_rootmsg(irc, "Password invalid");
277                break;
278        case STORAGE_OK:
279                irc_setpass(irc, NULL);
280                irc->status &= ~USTATUS_IDENTIFIED;
281                irc_umode_set(irc, "-R", 1);
282                irc_rootmsg(irc, "Account `%s' removed", irc->user->nick);
283                break;
284        default:
285                irc_rootmsg(irc, "Error: `%d'", status);
286                break;
287        }
288}
289
290static void cmd_save(irc_t *irc, char **cmd)
291{
292        if ((irc->status & USTATUS_IDENTIFIED) == 0) {
293                irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)");
294        } else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) {
295                irc_rootmsg(irc, "Configuration saved");
296        } else {
297                irc_rootmsg(irc, "Configuration could not be saved!");
298        }
299}
300
301static void cmd_showset(irc_t *irc, set_t **head, char *key)
302{
303        set_t *set;
304        char *val;
305
306        if ((val = set_getstr(head, key))) {
307                irc_rootmsg(irc, "%s = `%s'", key, val);
308        } else if (!(set = set_find(head, key))) {
309                irc_rootmsg(irc, "Setting `%s' does not exist.", key);
310                if (*head == irc->b->set) {
311                        irc_rootmsg(irc, "It might be an account or channel setting. "
312                                    "See \x02help account set\x02 and \x02help channel set\x02.");
313                }
314        } else if (set->flags & SET_PASSWORD) {
315                irc_rootmsg(irc, "%s = `********' (hidden)", key);
316        } else {
317                irc_rootmsg(irc, "%s is empty", key);
318        }
319}
320
321typedef set_t** (*cmd_set_findhead)(irc_t*, char*);
322typedef int (*cmd_set_checkflags)(irc_t*, set_t *set);
323
324static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags)
325{
326        char *set_name = NULL, *value = NULL;
327        gboolean del = FALSE;
328
329        if (cmd[1] && g_strncasecmp(cmd[1], "-del", 4) == 0) {
330                MIN_ARGS(2, 0);
331                set_name = cmd[2];
332                del = TRUE;
333        } else {
334                set_name = cmd[1];
335                value = cmd[2];
336        }
337
338        if (set_name && (value || del)) {
339                set_t *s = set_find(head, set_name);
340                int st;
341
342                if (s && checkflags && checkflags(irc, s) == 0) {
343                        return 0;
344                }
345
346                if (del) {
347                        st = set_reset(head, set_name);
348                } else {
349                        st = set_setstr(head, set_name, value);
350                }
351
352                if (set_getstr(head, set_name) == NULL &&
353                    set_find(head, set_name)) {
354                        /* This happens when changing the passwd, for example.
355                           Showing these msgs instead gives slightly clearer
356                           feedback. */
357                        if (st) {
358                                irc_rootmsg(irc, "Setting changed successfully");
359                        } else {
360                                irc_rootmsg(irc, "Failed to change setting");
361                        }
362                } else {
363                        cmd_showset(irc, head, set_name);
364                }
365        } else if (set_name) {
366                cmd_showset(irc, head, set_name);
367        } else {
368                set_t *s = *head;
369                while (s) {
370                        if (set_isvisible(s)) {
371                                cmd_showset(irc, &s, s->key);
372                        }
373                        s = s->next;
374                }
375        }
376
377        return 1;
378}
379
380static int cmd_account_set_checkflags(irc_t *irc, set_t *s)
381{
382        account_t *a = s->data;
383
384        if (a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY) {
385                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "off");
386                return 0;
387        } else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) {
388                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on");
389                return 0;
390        }
391
392        return 1;
393}
394
395static void cmd_account(irc_t *irc, char **cmd)
396{
397        account_t *a;
398        int len;
399
400        if (global.conf->authmode == AUTHMODE_REGISTERED && !(irc->status & USTATUS_IDENTIFIED)) {
401                irc_rootmsg(irc, "This server only accepts registered users");
402                return;
403        }
404
405        len = strlen(cmd[1]);
406
407        if (len >= 1 && g_strncasecmp(cmd[1], "add", len) == 0) {
408                struct prpl *prpl;
409
410                MIN_ARGS(3);
411
412                if (cmd[4] == NULL) {
413                        for (a = irc->b->accounts; a; a = a->next) {
414                                if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
415                                        irc_rootmsg(irc, "Enter password for account %s "
416                                                    "first (use /OPER)", a->tag);
417                                        return;
418                                }
419                        }
420
421                        irc->status |= OPER_HACK_ACCOUNT_PASSWORD;
422                }
423
424                prpl = find_protocol(cmd[2]);
425
426                if (prpl == NULL) {
427                        irc_rootmsg(irc, "Unknown protocol");
428                        return;
429                }
430
431                for (a = irc->b->accounts; a; a = a->next) {
432                        if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
433                                irc_rootmsg(irc, "Warning: You already have an account with "
434                                            "protocol `%s' and username `%s'. Are you accidentally "
435                                            "trying to add it twice?", prpl->name, cmd[3]);
436                        }
437                }
438
439                a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
440                if (cmd[5]) {
441                        irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
442                                    "is now deprecated. Use `account set' instead.");
443                        set_setstr(&a->set, "server", cmd[5]);
444                }
445
446                irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);
447
448                if (cmd[4] == NULL) {
449                        set_t *oauth = set_find(&a->set, "oauth");
450                        if (oauth && bool2int(set_value(oauth))) {
451                                *a->pass = '\0';
452                                irc_rootmsg(irc, "No need to enter a password for this "
453                                            "account since it's using OAuth");
454                        } else {
455                                irc_rootmsg(irc, "You can now use the /OPER command to "
456                                            "enter the password");
457                                if (oauth) {
458                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
459                                                    "the account supports it: account %s "
460                                                    "set oauth on", a->tag);
461                                }
462                        }
463                }
464
465                return;
466        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
467                int i = 0;
468
469                if (strchr(irc->umode, 'b')) {
470                        irc_rootmsg(irc, "Account list:");
471                }
472
473                for (a = irc->b->accounts; a; a = a->next) {
474                        char *con;
475
476                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
477                                con = " (connected)";
478                        } else if (a->ic) {
479                                con = " (connecting)";
480                        } else if (a->reconnect) {
481                                con = " (awaiting reconnect)";
482                        } else {
483                                con = "";
484                        }
485
486                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con);
487
488                        i++;
489                }
490                irc_rootmsg(irc, "End of account list");
491
492                return;
493        } else if (cmd[2]) {
494                /* Try the following two only if cmd[2] == NULL */
495        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
496                if (irc->b->accounts) {
497                        irc_rootmsg(irc, "Trying to get all accounts connected...");
498
499                        for (a = irc->b->accounts; a; a = a->next) {
500                                if (!a->ic && a->auto_connect) {
501                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
502                                                irc_rootmsg(irc, "Enter password for account %s "
503                                                            "first (use /OPER)", a->tag);
504                                        } else {
505                                                account_on(irc->b, a);
506                                        }
507                                }
508                        }
509                } else {
510                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
511                }
512
513                return;
514        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
515                irc_rootmsg(irc, "Deactivating all active (re)connections...");
516
517                for (a = irc->b->accounts; a; a = a->next) {
518                        if (a->ic) {
519                                account_off(irc->b, a);
520                        } else if (a->reconnect) {
521                                cancel_auto_reconnect(a);
522                        }
523                }
524
525                return;
526        }
527
528        MIN_ARGS(2);
529        len = strlen(cmd[2]);
530
531        /* At least right now, don't accept on/off/set/del as account IDs even
532           if they're a proper match, since people not familiar with the new
533           syntax yet may get a confusing/nasty surprise. */
534        if (g_strcasecmp(cmd[1], "on") == 0 ||
535            g_strcasecmp(cmd[1], "off") == 0 ||
536            g_strcasecmp(cmd[1], "set") == 0 ||
537            g_strcasecmp(cmd[1], "del") == 0 ||
538            (a = account_get(irc->b, cmd[1])) == NULL) {
539                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
540
541                return;
542        }
543
544        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
545                if (a->ic) {
546                        irc_rootmsg(irc, "Account is still logged in, can't delete");
547                } else {
548                        account_del(irc->b, a);
549                        irc_rootmsg(irc, "Account deleted");
550                }
551        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
552                if (a->ic) {
553                        irc_rootmsg(irc, "Account already online");
554                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
555                        irc_rootmsg(irc, "Enter password for account %s "
556                                    "first (use /OPER)", a->tag);
557                } else {
558                        account_on(irc->b, a);
559                }
560        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
561                if (a->ic) {
562                        account_off(irc->b, a);
563                } else if (a->reconnect) {
564                        cancel_auto_reconnect(a);
565                        irc_rootmsg(irc, "Reconnect cancelled");
566                } else {
567                        irc_rootmsg(irc, "Account already offline");
568                }
569        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
570                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
571        } else {
572                irc_rootmsg(irc,
573                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
574                            cmd[2]);
575        }
576}
577
578static void cmd_channel(irc_t *irc, char **cmd)
579{
580        irc_channel_t *ic;
581        int len;
582
583        len = strlen(cmd[1]);
584
585        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
586                GSList *l;
587                int i = 0;
588
589                if (strchr(irc->umode, 'b')) {
590                        irc_rootmsg(irc, "Channel list:");
591                }
592
593                for (l = irc->channels; l; l = l->next) {
594                        irc_channel_t *ic = l->data;
595
596                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
597                                    set_getstr(&ic->set, "type"),
598                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
599
600                        i++;
601                }
602                irc_rootmsg(irc, "End of channel list");
603
604                return;
605        }
606
607        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
608                /* If this doesn't match any channel, maybe this is the short
609                   syntax (only works when used inside a channel). */
610                if ((ic = irc->root->last_channel) &&
611                    (len = strlen(cmd[1])) &&
612                    g_strncasecmp(cmd[1], "set", len) == 0) {
613                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
614                } else {
615                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
616                }
617
618                return;
619        }
620
621        MIN_ARGS(2);
622        len = strlen(cmd[2]);
623
624        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
625                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
626        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
627                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
628                    ic != ic->irc->default_channel) {
629                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
630                        irc_channel_free(ic);
631                } else {
632                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
633                                    "channels you're still in cannot be deleted).",
634                                    irc->default_channel->name);
635                }
636        } else {
637                irc_rootmsg(irc,
638                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
639                            cmd[1]);
640        }
641}
642
643static void cmd_add(irc_t *irc, char **cmd)
644{
645        account_t *a;
646        int add_on_server = 1;
647        char *handle = NULL, *s;
648
649        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
650                MIN_ARGS(3);
651                add_on_server = 0;
652                cmd++;
653        }
654
655        if (!(a = account_get(irc->b, cmd[1]))) {
656                irc_rootmsg(irc, "Invalid account");
657                return;
658        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
659                irc_rootmsg(irc, "That account is not on-line");
660                return;
661        }
662
663        if (cmd[3]) {
664                if (!nick_ok(irc, cmd[3])) {
665                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
666                        return;
667                } else if (irc_user_by_name(irc, cmd[3])) {
668                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
669                        return;
670                } else {
671                        nick_set_raw(a, cmd[2], cmd[3]);
672                }
673        }
674
675        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
676            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
677                /* If there's no @ or it's the last char, append the user's
678                   domain name now. Exclude handles starting with a _ so
679                   adding _xmlconsole will keep working. */
680                if (s) {
681                        *s = '\0';
682                }
683                if ((s = strchr(a->user, '@'))) {
684                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
685                }
686        }
687
688        if (add_on_server) {
689                irc_channel_t *ic;
690                char *s, *group = NULL;;
691
692                if ((ic = irc->root->last_channel) &&
693                    (s = set_getstr(&ic->set, "fill_by")) &&
694                    strcmp(s, "group") == 0 &&
695                    (group = set_getstr(&ic->set, "group"))) {
696                        irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
697                                    cmd[2], group);
698                } else {
699                        irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
700                }
701
702                a->prpl->add_buddy(a->ic, cmd[2], group);
703        } else {
704                bee_user_t *bu;
705                irc_user_t *iu;
706
707                /* Only for add -tmp. For regular adds, this callback will
708                   be called once the IM server confirms. */
709                if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
710                    (iu = bu->ui_data)) {
711                        irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
712                                    "to contact `%s'", iu->nick, cmd[2]);
713                }
714        }
715
716        g_free(handle);
717}
718
719static void cmd_remove(irc_t *irc, char **cmd)
720{
721        irc_user_t *iu;
722        bee_user_t *bu;
723        char *s;
724
725        if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
726                irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
727                return;
728        }
729        s = g_strdup(bu->handle);
730
731        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
732        nick_del(bu);
733        if (g_slist_find(irc->users, iu)) {
734                bee_user_free(irc->b, bu);
735        }
736
737        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
738        g_free(s);
739
740        return;
741}
742
743static void cmd_info(irc_t *irc, char **cmd)
744{
745        struct im_connection *ic;
746        account_t *a;
747
748        if (!cmd[2]) {
749                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
750                if (!iu || !iu->bu) {
751                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
752                        return;
753                }
754                ic = iu->bu->ic;
755                cmd[2] = iu->bu->handle;
756        } else if (!(a = account_get(irc->b, cmd[1]))) {
757                irc_rootmsg(irc, "Invalid account");
758                return;
759        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
760                irc_rootmsg(irc, "That account is not on-line");
761                return;
762        }
763
764        if (!ic->acc->prpl->get_info) {
765                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
766        } else {
767                ic->acc->prpl->get_info(ic, cmd[2]);
768        }
769}
770
771static void cmd_rename(irc_t *irc, char **cmd)
772{
773        irc_user_t *iu, *old;
774        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
775
776        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
777
778        if (iu == NULL) {
779                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
780        } else if (del) {
781                if (iu->bu) {
782                        bee_irc_user_nick_reset(iu);
783                }
784                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
785        } else if (iu == irc->user) {
786                irc_rootmsg(irc, "Use /nick to change your own nickname");
787        } else if (!nick_ok(irc, cmd[2])) {
788                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
789        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
790                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
791        } else {
792                if (!irc_user_set_nick(iu, cmd[2])) {
793                        irc_rootmsg(irc, "Error while changing nick");
794                        return;
795                }
796
797                if (iu == irc->root) {
798                        /* If we're called internally (user did "set root_nick"),
799                           let's not go O(INF). :-) */
800                        if (strcmp(cmd[0], "set_rename") != 0) {
801                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
802                        }
803                } else if (iu->bu) {
804                        nick_set(iu->bu, cmd[2]);
805                }
806
807                irc_rootmsg(irc, "Nick successfully changed");
808        }
809}
810
811char *set_eval_root_nick(set_t *set, char *new_nick)
812{
813        irc_t *irc = set->data;
814
815        if (strcmp(irc->root->nick, new_nick) != 0) {
816                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
817
818                cmd_rename(irc, cmd);
819        }
820
821        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
822}
823
824static void cmd_block(irc_t *irc, char **cmd)
825{
826        struct im_connection *ic;
827        account_t *a;
828
829        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
830                char *format;
831                GSList *l;
832
833                if (strchr(irc->umode, 'b') != NULL) {
834                        format = "%s\t%s";
835                } else {
836                        format = "%-32.32s  %-16.16s";
837                }
838
839                irc_rootmsg(irc, format, "Handle", "Nickname");
840                for (l = a->ic->deny; l; l = l->next) {
841                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
842                        irc_user_t *iu = bu ? bu->ui_data : NULL;
843                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
844                }
845                irc_rootmsg(irc, "End of list.");
846
847                return;
848        } else if (!cmd[2]) {
849                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
850                if (!iu || !iu->bu) {
851                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
852                        return;
853                }
854                ic = iu->bu->ic;
855                cmd[2] = iu->bu->handle;
856        } else if (!(a = account_get(irc->b, cmd[1]))) {
857                irc_rootmsg(irc, "Invalid account");
858                return;
859        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
860                irc_rootmsg(irc, "That account is not on-line");
861                return;
862        }
863
864        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
865                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
866        } else {
867                imc_rem_allow(ic, cmd[2]);
868                imc_add_block(ic, cmd[2]);
869                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
870        }
871}
872
873static void cmd_allow(irc_t *irc, char **cmd)
874{
875        struct im_connection *ic;
876        account_t *a;
877
878        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
879                char *format;
880                GSList *l;
881
882                if (strchr(irc->umode, 'b') != NULL) {
883                        format = "%s\t%s";
884                } else {
885                        format = "%-32.32s  %-16.16s";
886                }
887
888                irc_rootmsg(irc, format, "Handle", "Nickname");
889                for (l = a->ic->permit; l; l = l->next) {
890                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
891                        irc_user_t *iu = bu ? bu->ui_data : NULL;
892                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
893                }
894                irc_rootmsg(irc, "End of list.");
895
896                return;
897        } else if (!cmd[2]) {
898                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
899                if (!iu || !iu->bu) {
900                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
901                        return;
902                }
903                ic = iu->bu->ic;
904                cmd[2] = iu->bu->handle;
905        } else if (!(a = account_get(irc->b, cmd[1]))) {
906                irc_rootmsg(irc, "Invalid account");
907                return;
908        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
909                irc_rootmsg(irc, "That account is not on-line");
910                return;
911        }
912
913        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
914                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
915        } else {
916                imc_rem_block(ic, cmd[2]);
917                imc_add_allow(ic, cmd[2]);
918
919                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
920        }
921}
922
923static void cmd_yesno(irc_t *irc, char **cmd)
924{
925        query_t *q = NULL;
926        int numq = 0;
927
928        if (irc->queries == NULL) {
929                /* Alright, alright, let's add a tiny easter egg here. */
930                static irc_t *last_irc = NULL;
931                static time_t last_time = 0;
932                static int times = 0;
933                static const char *msg[] = {
934                        "Oh yeah, that's right.",
935                        "Alright, alright. Now go back to work.",
936                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
937                        "Yes?",
938                        "No?",
939                };
940
941                if (last_irc == irc && time(NULL) - last_time < 15) {
942                        if ((++times >= 3)) {
943                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
944                                last_irc = NULL;
945                                times = 0;
946                                return;
947                        }
948                } else {
949                        last_time = time(NULL);
950                        last_irc = irc;
951                        times = 0;
952                }
953
954                irc_rootmsg(irc, "Did I ask you something?");
955                return;
956        }
957
958        /* If there's an argument, the user seems to want to answer another question than the
959           first/last (depending on the query_order setting) one. */
960        if (cmd[1]) {
961                if (sscanf(cmd[1], "%d", &numq) != 1) {
962                        irc_rootmsg(irc, "Invalid query number");
963                        return;
964                }
965
966                for (q = irc->queries; q; q = q->next, numq--) {
967                        if (numq == 0) {
968                                break;
969                        }
970                }
971
972                if (!q) {
973                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
974                        return;
975                }
976        }
977
978        if (g_strcasecmp(cmd[0], "yes") == 0) {
979                query_answer(irc, q, 1);
980        } else if (g_strcasecmp(cmd[0], "no") == 0) {
981                query_answer(irc, q, 0);
982        }
983}
984
985static void cmd_set(irc_t *irc, char **cmd)
986{
987        cmd_set_real(irc, cmd, &irc->b->set, NULL);
988}
989
990static void cmd_blist(irc_t *irc, char **cmd)
991{
992        int online = 0, away = 0, offline = 0, ismatch = 0;
993        GSList *l;
994        GRegex *regex = NULL;
995        GError *error = NULL;
996        char s[256];
997        char *format;
998        int n_online = 0, n_away = 0, n_offline = 0;
999
1000        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
1001                online = offline = away = 1;
1002        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1003                offline = 1;
1004        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1005                away = 1;
1006        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1007                online = 1;
1008        } else {
1009                online = away = 1;
1010        }
1011
1012        if (cmd[2]) {
1013                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1014        }
1015
1016        if (error) {
1017                irc_rootmsg(irc, error->message);
1018                g_error_free(error);
1019        }
1020
1021        if (strchr(irc->umode, 'b') != NULL) {
1022                format = "%s\t%s\t%s";
1023        } else {
1024                format = "%-16.16s  %-40.40s  %s";
1025        }
1026
1027        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1028
1029        if (irc->root->last_channel &&
1030            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1031                irc->root->last_channel = NULL;
1032        }
1033
1034        for (l = irc->users; l; l = l->next) {
1035                irc_user_t *iu = l->data;
1036                bee_user_t *bu = iu->bu;
1037
1038                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1039                        ismatch = 1;
1040                } else {
1041                        ismatch = 0;
1042                }
1043
1044                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1045                        continue;
1046                }
1047
1048                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1049                        if (ismatch == 1 && online == 1) {
1050                                char st[256] = "Online";
1051
1052                                if (bu->status_msg) {
1053                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1054                                }
1055
1056                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1057                                irc_rootmsg(irc, format, iu->nick, s, st);
1058                        }
1059
1060                        n_online++;
1061                }
1062
1063                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1064                        if (ismatch == 1 && away == 1) {
1065                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1066                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1067                        }
1068                        n_away++;
1069                }
1070
1071                if (!(bu->flags & BEE_USER_ONLINE)) {
1072                        if (ismatch == 1 && offline == 1) {
1073                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1074                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1075                        }
1076                        n_offline++;
1077                }
1078        }
1079
1080        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1081                    n_away, n_offline);
1082
1083        if (regex) {
1084                g_regex_unref(regex);
1085        }
1086}
1087
1088static void cmd_qlist(irc_t *irc, char **cmd)
1089{
1090        query_t *q = irc->queries;
1091        int num;
1092
1093        if (!q) {
1094                irc_rootmsg(irc, "There are no pending questions.");
1095                return;
1096        }
1097
1098        irc_rootmsg(irc, "Pending queries:");
1099
1100        for (num = 0; q; q = q->next, num++) {
1101                if (q->ic) { /* Not necessary yet, but it might come later */
1102                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1103                } else {
1104                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1105                }
1106        }
1107}
1108
1109static void cmd_chat(irc_t *irc, char **cmd)
1110{
1111        account_t *acc;
1112
1113        if (g_strcasecmp(cmd[1], "add") == 0) {
1114                char *channel, *s;
1115                struct irc_channel *ic;
1116
1117                MIN_ARGS(3);
1118
1119                if (!(acc = account_get(irc->b, cmd[2]))) {
1120                        irc_rootmsg(irc, "Invalid account");
1121                        return;
1122                } else if (!acc->prpl->chat_join) {
1123                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1124                        return;
1125                }
1126
1127                if (cmd[4] == NULL) {
1128                        channel = g_strdup(cmd[3]);
1129                        if ((s = strchr(channel, '@'))) {
1130                                *s = 0;
1131                        }
1132                } else {
1133                        channel = g_strdup(cmd[4]);
1134                }
1135
1136                if (strchr(CTYPES, channel[0]) == NULL) {
1137                        s = g_strdup_printf("#%s", channel);
1138                        g_free(channel);
1139                        channel = s;
1140
1141                        irc_channel_name_strip(channel);
1142                }
1143
1144                if ((ic = irc_channel_new(irc, channel)) &&
1145                    set_setstr(&ic->set, "type", "chat") &&
1146                    set_setstr(&ic->set, "chat_type", "room") &&
1147                    set_setstr(&ic->set, "account", cmd[2]) &&
1148                    set_setstr(&ic->set, "room", cmd[3])) {
1149                        irc_rootmsg(irc, "Chatroom successfully added.");
1150                } else {
1151                        if (ic) {
1152                                irc_channel_free(ic);
1153                        }
1154
1155                        irc_rootmsg(irc, "Could not add chatroom.");
1156                }
1157                g_free(channel);
1158        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1159                irc_user_t *iu;
1160
1161                MIN_ARGS(2);
1162
1163                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1164                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1165                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1166                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1167                                            "a groupchat with %s.", iu->nick);
1168                        }
1169                } else {
1170                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1171                }
1172        } else if (g_strcasecmp(cmd[1], "list") == 0 ||
1173                   g_strcasecmp(cmd[1], "set") == 0 ||
1174                   g_strcasecmp(cmd[1], "del") == 0) {
1175                irc_rootmsg(irc,
1176                            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
1177                cmd_channel(irc, cmd);
1178        } else {
1179                irc_rootmsg(irc,
1180                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1181                            cmd[1]);
1182        }
1183}
1184
1185static void cmd_group(irc_t *irc, char **cmd)
1186{
1187        GSList *l;
1188        int len;
1189
1190        len = strlen(cmd[1]);
1191        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1192                int n = 0;
1193
1194                if (strchr(irc->umode, 'b')) {
1195                        irc_rootmsg(irc, "Group list:");
1196                }
1197
1198                for (l = irc->b->groups; l; l = l->next) {
1199                        bee_group_t *bg = l->data;
1200                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1201                }
1202                irc_rootmsg(irc, "End of group list");
1203        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1204                bee_group_t *bg;
1205                int n = 0;
1206
1207                MIN_ARGS(2);
1208                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1209
1210                if (bg) {
1211                        if (strchr(irc->umode, 'b')) {
1212                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1213                        }
1214                        for (l = irc->b->users; l; l = l->next) {
1215                                bee_user_t *bu = l->data;
1216                                if (bu->group == bg) {
1217                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1218                                }
1219                        }
1220                        irc_rootmsg(irc, "End of member list");
1221                } else {
1222                        irc_rootmsg(irc,
1223                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1224                                    cmd[2]);
1225                }
1226        } else {
1227                irc_rootmsg(irc,
1228                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1229                            cmd[1]);
1230        }
1231}
1232
1233static void cmd_transfer(irc_t *irc, char **cmd)
1234{
1235        GSList *files = irc->file_transfers;
1236
1237        enum { LIST, REJECT, CANCEL };
1238        int subcmd = LIST;
1239        int fid;
1240
1241        if (!files) {
1242                irc_rootmsg(irc, "No pending transfers");
1243                return;
1244        }
1245
1246        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1247                subcmd = REJECT;
1248        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1249                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1250                subcmd = CANCEL;
1251        }
1252
1253        for (; files; files = g_slist_next(files)) {
1254                file_transfer_t *file = files->data;
1255
1256                switch (subcmd) {
1257                case LIST:
1258                        if (file->status == FT_STATUS_LISTENING) {
1259                                irc_rootmsg(irc,
1260                                            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1261                        } else {
1262                                int kb_per_s = 0;
1263                                time_t diff = time(NULL) - file->started ? : 1;
1264                                if ((file->started > 0) && (file->bytes_transferred > 0)) {
1265                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1266                                }
1267
1268                                irc_rootmsg(irc,
1269                                            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
1270                                            file->file_name,
1271                                            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
1272                        }
1273                        break;
1274                case REJECT:
1275                        if (file->status == FT_STATUS_LISTENING) {
1276                                irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
1277                                imcb_file_canceled(file->ic, file, "Denied by user");
1278                        }
1279                        break;
1280                case CANCEL:
1281                        if (file->local_id == fid) {
1282                                irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
1283                                imcb_file_canceled(file->ic, file, "Canceled by user");
1284                        }
1285                        break;
1286                }
1287        }
1288}
1289
1290static void cmd_nick(irc_t *irc, char **cmd)
1291{
1292        irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
1293}
1294
1295/* Maybe this should be a stand-alone command as well? */
1296static void bitlbee_whatsnew(irc_t *irc)
1297{
1298        int last = set_getint(&irc->b->set, "last_version");
1299        char s[16], *msg;
1300
1301        if (last >= BITLBEE_VERSION_CODE) {
1302                return;
1303        }
1304
1305        msg = help_get_whatsnew(&(global.help), last);
1306
1307        if (msg) {
1308                irc_rootmsg(irc, "%s: This seems to be your first time using this "
1309                            "this version of BitlBee. Here's a list of new "
1310                            "features you may like to know about:\n\n%s\n",
1311                            irc->user->nick, msg);
1312        }
1313
1314        g_free(msg);
1315
1316        g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
1317        set_setstr(&irc->b->set, "last_version", s);
1318}
1319
1320/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1321command_t root_commands[] = {
1322        { "account",        1, cmd_account,        0 },
1323        { "add",            2, cmd_add,            0 },
1324        { "allow",          1, cmd_allow,          0 },
1325        { "blist",          0, cmd_blist,          0 },
1326        { "block",          1, cmd_block,          0 },
1327        { "channel",        1, cmd_channel,        0 },
1328        { "chat",           1, cmd_chat,           0 },
1329        { "drop",           1, cmd_drop,           0 },
1330        { "ft",             0, cmd_transfer,       0 },
1331        { "group",          1, cmd_group,          0 },
1332        { "help",           0, cmd_help,           0 },
1333        { "identify",       0, cmd_identify,       0 },
1334        { "info",           1, cmd_info,           0 },
1335        { "nick",           1, cmd_nick,           0 },
1336        { "no",             0, cmd_yesno,          0 },
1337        { "qlist",          0, cmd_qlist,          0 },
1338        { "register",       0, cmd_register,       0 },
1339        { "remove",         1, cmd_remove,         0 },
1340        { "rename",         2, cmd_rename,         0 },
1341        { "save",           0, cmd_save,           0 },
1342        { "set",            0, cmd_set,            0 },
1343        { "transfer",       0, cmd_transfer,       0 },
1344        { "yes",            0, cmd_yesno,          0 },
1345        /* Not expecting too many plugins adding root commands so just make a
1346           dumb array with some empty entried at the end. */
1347        { NULL },
1348        { NULL },
1349        { NULL },
1350        { NULL },
1351        { NULL },
1352        { NULL },
1353        { NULL },
1354        { NULL },
1355        { NULL },
1356};
1357static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);
1358
1359gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
1360{
1361        int i;
1362
1363        if (root_commands[num_root_commands - 2].command) {
1364                /* Planning fail! List is full. */
1365                return FALSE;
1366        }
1367
1368        for (i = 0; root_commands[i].command; i++) {
1369                if (g_strcasecmp(root_commands[i].command, command) == 0) {
1370                        return FALSE;
1371                } else if (g_strcasecmp(root_commands[i].command, command) > 0) {
1372                        break;
1373                }
1374        }
1375        memmove(root_commands + i + 1, root_commands + i,
1376                sizeof(command_t) * (num_root_commands - i - 1));
1377
1378        root_commands[i].command = g_strdup(command);
1379        root_commands[i].required_parameters = params;
1380        root_commands[i].execute = func;
1381        root_commands[i].flags = flags;
1382
1383        return TRUE;
1384}
Note: See TracBrowser for help on using the repository browser.