source: root_commands.c @ f892236

Last change on this file since f892236 was 7616eec, checked in by dequis <dx@…>, at 2016-01-26T15:53:56Z

root_commands: Fix use-after-free after doing "transfer reject"

Not a big deal because as far as I can see not much happens between the
g_slist_remove() in dcc_close() and accessing files->next. I'd expect
that pointer to remain null after being freed most of the time.

  • Property mode set to 100644
File size: 36.8 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                        if (is_protocol_disabled(cmd[2])) {
428                                irc_rootmsg(irc, "Protocol disabled in global config");
429                        } else {
430                                irc_rootmsg(irc, "Unknown protocol");
431                        }
432                        return;
433                }
434
435                for (a = irc->b->accounts; a; a = a->next) {
436                        if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
437                                irc_rootmsg(irc, "Warning: You already have an account with "
438                                            "protocol `%s' and username `%s'. Are you accidentally "
439                                            "trying to add it twice?", prpl->name, cmd[3]);
440                        }
441                }
442
443                a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
444                if (cmd[5]) {
445                        irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
446                                    "is now deprecated. Use `account set' instead.");
447                        set_setstr(&a->set, "server", cmd[5]);
448                }
449
450                irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);
451
452                if (cmd[4] == NULL) {
453                        set_t *oauth = set_find(&a->set, "oauth");
454                        if (oauth && bool2int(set_value(oauth))) {
455                                *a->pass = '\0';
456                                irc_rootmsg(irc, "No need to enter a password for this "
457                                            "account since it's using OAuth");
458                        } else {
459                                irc_rootmsg(irc, "You can now use the /OPER command to "
460                                            "enter the password");
461                                if (oauth) {
462                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
463                                                    "the account supports it: account %s "
464                                                    "set oauth on", a->tag);
465                                }
466                        }
467                }
468
469                return;
470        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
471                int i = 0;
472
473                if (strchr(irc->umode, 'b')) {
474                        irc_rootmsg(irc, "Account list:");
475                }
476
477                for (a = irc->b->accounts; a; a = a->next) {
478                        char *con;
479
480                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
481                                con = " (connected)";
482                        } else if (a->ic) {
483                                con = " (connecting)";
484                        } else if (a->reconnect) {
485                                con = " (awaiting reconnect)";
486                        } else {
487                                con = "";
488                        }
489
490                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con);
491
492                        i++;
493                }
494                irc_rootmsg(irc, "End of account list");
495
496                return;
497        } else if (cmd[2]) {
498                /* Try the following two only if cmd[2] == NULL */
499        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
500                if (irc->b->accounts) {
501                        irc_rootmsg(irc, "Trying to get all accounts connected...");
502
503                        for (a = irc->b->accounts; a; a = a->next) {
504                                if (!a->ic && a->auto_connect) {
505                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
506                                                irc_rootmsg(irc, "Enter password for account %s "
507                                                            "first (use /OPER)", a->tag);
508                                        } else {
509                                                account_on(irc->b, a);
510                                        }
511                                }
512                        }
513                } else {
514                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
515                }
516
517                return;
518        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
519                irc_rootmsg(irc, "Deactivating all active (re)connections...");
520
521                for (a = irc->b->accounts; a; a = a->next) {
522                        if (a->ic) {
523                                account_off(irc->b, a);
524                        } else if (a->reconnect) {
525                                cancel_auto_reconnect(a);
526                        }
527                }
528
529                return;
530        }
531
532        MIN_ARGS(2);
533        len = strlen(cmd[2]);
534
535        /* At least right now, don't accept on/off/set/del as account IDs even
536           if they're a proper match, since people not familiar with the new
537           syntax yet may get a confusing/nasty surprise. */
538        if (g_strcasecmp(cmd[1], "on") == 0 ||
539            g_strcasecmp(cmd[1], "off") == 0 ||
540            g_strcasecmp(cmd[1], "set") == 0 ||
541            g_strcasecmp(cmd[1], "del") == 0 ||
542            (a = account_get(irc->b, cmd[1])) == NULL) {
543                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
544
545                return;
546        }
547
548        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
549                if (a->ic) {
550                        irc_rootmsg(irc, "Account is still logged in, can't delete");
551                } else {
552                        account_del(irc->b, a);
553                        irc_rootmsg(irc, "Account deleted");
554                }
555        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
556                if (a->ic) {
557                        irc_rootmsg(irc, "Account already online");
558                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
559                        irc_rootmsg(irc, "Enter password for account %s "
560                                    "first (use /OPER)", a->tag);
561                } else {
562                        account_on(irc->b, a);
563                }
564        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
565                if (a->ic) {
566                        account_off(irc->b, a);
567                } else if (a->reconnect) {
568                        cancel_auto_reconnect(a);
569                        irc_rootmsg(irc, "Reconnect cancelled");
570                } else {
571                        irc_rootmsg(irc, "Account already offline");
572                }
573        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
574                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
575        } else {
576                irc_rootmsg(irc,
577                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
578                            cmd[2]);
579        }
580}
581
582static void cmd_channel(irc_t *irc, char **cmd)
583{
584        irc_channel_t *ic;
585        int len;
586
587        len = strlen(cmd[1]);
588
589        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
590                GSList *l;
591                int i = 0;
592
593                if (strchr(irc->umode, 'b')) {
594                        irc_rootmsg(irc, "Channel list:");
595                }
596
597                for (l = irc->channels; l; l = l->next) {
598                        irc_channel_t *ic = l->data;
599
600                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
601                                    set_getstr(&ic->set, "type"),
602                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
603
604                        i++;
605                }
606                irc_rootmsg(irc, "End of channel list");
607
608                return;
609        }
610
611        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
612                /* If this doesn't match any channel, maybe this is the short
613                   syntax (only works when used inside a channel). */
614                if ((ic = irc->root->last_channel) &&
615                    (len = strlen(cmd[1])) &&
616                    g_strncasecmp(cmd[1], "set", len) == 0) {
617                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
618                } else {
619                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
620                }
621
622                return;
623        }
624
625        MIN_ARGS(2);
626        len = strlen(cmd[2]);
627
628        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
629                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
630        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
631                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
632                    ic != ic->irc->default_channel) {
633                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
634                        irc_channel_free(ic);
635                } else {
636                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
637                                    "channels you're still in cannot be deleted).",
638                                    irc->default_channel->name);
639                }
640        } else {
641                irc_rootmsg(irc,
642                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
643                            cmd[1]);
644        }
645}
646
647static void cmd_add(irc_t *irc, char **cmd)
648{
649        account_t *a;
650        int add_on_server = 1;
651        char *handle = NULL, *s;
652
653        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
654                MIN_ARGS(3);
655                add_on_server = 0;
656                cmd++;
657        }
658
659        if (!(a = account_get(irc->b, cmd[1]))) {
660                irc_rootmsg(irc, "Invalid account");
661                return;
662        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
663                irc_rootmsg(irc, "That account is not on-line");
664                return;
665        }
666
667        if (cmd[3]) {
668                if (!nick_ok(irc, cmd[3])) {
669                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
670                        return;
671                } else if (irc_user_by_name(irc, cmd[3])) {
672                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
673                        return;
674                } else {
675                        nick_set_raw(a, cmd[2], cmd[3]);
676                }
677        }
678
679        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
680            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
681                /* If there's no @ or it's the last char, append the user's
682                   domain name now. Exclude handles starting with a _ so
683                   adding _xmlconsole will keep working. */
684                if (s) {
685                        *s = '\0';
686                }
687                if ((s = strchr(a->user, '@'))) {
688                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
689                }
690        }
691
692        if (add_on_server) {
693                irc_channel_t *ic;
694                char *s, *group = NULL;;
695
696                if ((ic = irc->root->last_channel) &&
697                    (s = set_getstr(&ic->set, "fill_by")) &&
698                    strcmp(s, "group") == 0 &&
699                    (group = set_getstr(&ic->set, "group"))) {
700                        irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
701                                    cmd[2], group);
702                } else {
703                        irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
704                }
705
706                a->prpl->add_buddy(a->ic, cmd[2], group);
707        } else {
708                bee_user_t *bu;
709                irc_user_t *iu;
710
711                /* Only for add -tmp. For regular adds, this callback will
712                   be called once the IM server confirms. */
713                if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
714                    (iu = bu->ui_data)) {
715                        irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
716                                    "to contact `%s'", iu->nick, cmd[2]);
717                }
718        }
719
720        g_free(handle);
721}
722
723static void cmd_remove(irc_t *irc, char **cmd)
724{
725        irc_user_t *iu;
726        bee_user_t *bu;
727        char *s;
728
729        if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
730                irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
731                return;
732        }
733        s = g_strdup(bu->handle);
734
735        bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
736        nick_del(bu);
737        if (g_slist_find(irc->users, iu)) {
738                bee_user_free(irc->b, bu);
739        }
740
741        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
742        g_free(s);
743
744        return;
745}
746
747static void cmd_info(irc_t *irc, char **cmd)
748{
749        struct im_connection *ic;
750        account_t *a;
751
752        if (!cmd[2]) {
753                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
754                if (!iu || !iu->bu) {
755                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
756                        return;
757                }
758                ic = iu->bu->ic;
759                cmd[2] = iu->bu->handle;
760        } else if (!(a = account_get(irc->b, cmd[1]))) {
761                irc_rootmsg(irc, "Invalid account");
762                return;
763        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
764                irc_rootmsg(irc, "That account is not on-line");
765                return;
766        }
767
768        if (!ic->acc->prpl->get_info) {
769                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
770        } else {
771                ic->acc->prpl->get_info(ic, cmd[2]);
772        }
773}
774
775static void cmd_rename(irc_t *irc, char **cmd)
776{
777        irc_user_t *iu, *old;
778        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
779
780        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
781
782        if (iu == NULL) {
783                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
784        } else if (del) {
785                if (iu->bu) {
786                        bee_irc_user_nick_reset(iu);
787                }
788                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
789        } else if (iu == irc->user) {
790                irc_rootmsg(irc, "Use /nick to change your own nickname");
791        } else if (!nick_ok(irc, cmd[2])) {
792                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
793        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
794                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
795        } else {
796                if (!irc_user_set_nick(iu, cmd[2])) {
797                        irc_rootmsg(irc, "Error while changing nick");
798                        return;
799                }
800
801                if (iu == irc->root) {
802                        /* If we're called internally (user did "set root_nick"),
803                           let's not go O(INF). :-) */
804                        if (strcmp(cmd[0], "set_rename") != 0) {
805                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
806                        }
807                } else if (iu->bu) {
808                        nick_set(iu->bu, cmd[2]);
809                }
810
811                irc_rootmsg(irc, "Nick successfully changed");
812        }
813}
814
815char *set_eval_root_nick(set_t *set, char *new_nick)
816{
817        irc_t *irc = set->data;
818
819        if (strcmp(irc->root->nick, new_nick) != 0) {
820                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
821
822                cmd_rename(irc, cmd);
823        }
824
825        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
826}
827
828static void cmd_block(irc_t *irc, char **cmd)
829{
830        struct im_connection *ic;
831        account_t *a;
832
833        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
834                char *format;
835                GSList *l;
836
837                if (strchr(irc->umode, 'b') != NULL) {
838                        format = "%s\t%s";
839                } else {
840                        format = "%-32.32s  %-16.16s";
841                }
842
843                irc_rootmsg(irc, format, "Handle", "Nickname");
844                for (l = a->ic->deny; l; l = l->next) {
845                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
846                        irc_user_t *iu = bu ? bu->ui_data : NULL;
847                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
848                }
849                irc_rootmsg(irc, "End of list.");
850
851                return;
852        } else if (!cmd[2]) {
853                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
854                if (!iu || !iu->bu) {
855                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
856                        return;
857                }
858                ic = iu->bu->ic;
859                cmd[2] = iu->bu->handle;
860        } else if (!(a = account_get(irc->b, cmd[1]))) {
861                irc_rootmsg(irc, "Invalid account");
862                return;
863        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
864                irc_rootmsg(irc, "That account is not on-line");
865                return;
866        }
867
868        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
869                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
870        } else {
871                imc_rem_allow(ic, cmd[2]);
872                imc_add_block(ic, cmd[2]);
873                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
874        }
875}
876
877static void cmd_allow(irc_t *irc, char **cmd)
878{
879        struct im_connection *ic;
880        account_t *a;
881
882        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
883                char *format;
884                GSList *l;
885
886                if (strchr(irc->umode, 'b') != NULL) {
887                        format = "%s\t%s";
888                } else {
889                        format = "%-32.32s  %-16.16s";
890                }
891
892                irc_rootmsg(irc, format, "Handle", "Nickname");
893                for (l = a->ic->permit; l; l = l->next) {
894                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
895                        irc_user_t *iu = bu ? bu->ui_data : NULL;
896                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
897                }
898                irc_rootmsg(irc, "End of list.");
899
900                return;
901        } else if (!cmd[2]) {
902                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
903                if (!iu || !iu->bu) {
904                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
905                        return;
906                }
907                ic = iu->bu->ic;
908                cmd[2] = iu->bu->handle;
909        } else if (!(a = account_get(irc->b, cmd[1]))) {
910                irc_rootmsg(irc, "Invalid account");
911                return;
912        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
913                irc_rootmsg(irc, "That account is not on-line");
914                return;
915        }
916
917        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
918                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
919        } else {
920                imc_rem_block(ic, cmd[2]);
921                imc_add_allow(ic, cmd[2]);
922
923                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
924        }
925}
926
927static void cmd_yesno(irc_t *irc, char **cmd)
928{
929        query_t *q = NULL;
930        int numq = 0;
931
932        if (irc->queries == NULL) {
933                /* Alright, alright, let's add a tiny easter egg here. */
934                static irc_t *last_irc = NULL;
935                static time_t last_time = 0;
936                static int times = 0;
937                static const char *msg[] = {
938                        "Oh yeah, that's right.",
939                        "Alright, alright. Now go back to work.",
940                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
941                        "Yes?",
942                        "No?",
943                };
944
945                if (last_irc == irc && time(NULL) - last_time < 15) {
946                        if ((++times >= 3)) {
947                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
948                                last_irc = NULL;
949                                times = 0;
950                                return;
951                        }
952                } else {
953                        last_time = time(NULL);
954                        last_irc = irc;
955                        times = 0;
956                }
957
958                irc_rootmsg(irc, "Did I ask you something?");
959                return;
960        }
961
962        /* If there's an argument, the user seems to want to answer another question than the
963           first/last (depending on the query_order setting) one. */
964        if (cmd[1]) {
965                if (sscanf(cmd[1], "%d", &numq) != 1) {
966                        irc_rootmsg(irc, "Invalid query number");
967                        return;
968                }
969
970                for (q = irc->queries; q; q = q->next, numq--) {
971                        if (numq == 0) {
972                                break;
973                        }
974                }
975
976                if (!q) {
977                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
978                        return;
979                }
980        }
981
982        if (g_strcasecmp(cmd[0], "yes") == 0) {
983                query_answer(irc, q, 1);
984        } else if (g_strcasecmp(cmd[0], "no") == 0) {
985                query_answer(irc, q, 0);
986        }
987}
988
989static void cmd_set(irc_t *irc, char **cmd)
990{
991        cmd_set_real(irc, cmd, &irc->b->set, NULL);
992}
993
994static void cmd_blist(irc_t *irc, char **cmd)
995{
996        int online = 0, away = 0, offline = 0, ismatch = 0;
997        GSList *l;
998        GRegex *regex = NULL;
999        GError *error = NULL;
1000        char s[256];
1001        char *format;
1002        int n_online = 0, n_away = 0, n_offline = 0;
1003
1004        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
1005                online = offline = away = 1;
1006        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1007                offline = 1;
1008        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1009                away = 1;
1010        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1011                online = 1;
1012        } else {
1013                online = away = 1;
1014        }
1015
1016        if (cmd[2]) {
1017                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1018        }
1019
1020        if (error) {
1021                irc_rootmsg(irc, error->message);
1022                g_error_free(error);
1023        }
1024
1025        if (strchr(irc->umode, 'b') != NULL) {
1026                format = "%s\t%s\t%s";
1027        } else {
1028                format = "%-16.16s  %-40.40s  %s";
1029        }
1030
1031        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1032
1033        if (irc->root->last_channel &&
1034            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1035                irc->root->last_channel = NULL;
1036        }
1037
1038        for (l = irc->users; l; l = l->next) {
1039                irc_user_t *iu = l->data;
1040                bee_user_t *bu = iu->bu;
1041
1042                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1043                        ismatch = 1;
1044                } else {
1045                        ismatch = 0;
1046                }
1047
1048                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1049                        continue;
1050                }
1051
1052                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1053                        if (ismatch == 1 && online == 1) {
1054                                char st[256] = "Online";
1055
1056                                if (bu->status_msg) {
1057                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1058                                }
1059
1060                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1061                                irc_rootmsg(irc, format, iu->nick, s, st);
1062                        }
1063
1064                        n_online++;
1065                }
1066
1067                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1068                        if (ismatch == 1 && away == 1) {
1069                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1070                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1071                        }
1072                        n_away++;
1073                }
1074
1075                if (!(bu->flags & BEE_USER_ONLINE)) {
1076                        if (ismatch == 1 && offline == 1) {
1077                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1078                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1079                        }
1080                        n_offline++;
1081                }
1082        }
1083
1084        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1085                    n_away, n_offline);
1086
1087        if (regex) {
1088                g_regex_unref(regex);
1089        }
1090}
1091
1092static void cmd_qlist(irc_t *irc, char **cmd)
1093{
1094        query_t *q = irc->queries;
1095        int num;
1096
1097        if (!q) {
1098                irc_rootmsg(irc, "There are no pending questions.");
1099                return;
1100        }
1101
1102        irc_rootmsg(irc, "Pending queries:");
1103
1104        for (num = 0; q; q = q->next, num++) {
1105                if (q->ic) { /* Not necessary yet, but it might come later */
1106                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1107                } else {
1108                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1109                }
1110        }
1111}
1112
1113static void cmd_chat(irc_t *irc, char **cmd)
1114{
1115        account_t *acc;
1116
1117        if (g_strcasecmp(cmd[1], "add") == 0) {
1118                char *channel, *s;
1119                struct irc_channel *ic;
1120
1121                MIN_ARGS(3);
1122
1123                if (!(acc = account_get(irc->b, cmd[2]))) {
1124                        irc_rootmsg(irc, "Invalid account");
1125                        return;
1126                } else if (!acc->prpl->chat_join) {
1127                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1128                        return;
1129                }
1130
1131                if (cmd[4] == NULL) {
1132                        channel = g_strdup(cmd[3]);
1133                        if ((s = strchr(channel, '@'))) {
1134                                *s = 0;
1135                        }
1136                } else {
1137                        channel = g_strdup(cmd[4]);
1138                }
1139
1140                if (strchr(CTYPES, channel[0]) == NULL) {
1141                        s = g_strdup_printf("#%s", channel);
1142                        g_free(channel);
1143                        channel = s;
1144
1145                        irc_channel_name_strip(channel);
1146                }
1147
1148                if ((ic = irc_channel_new(irc, channel)) &&
1149                    set_setstr(&ic->set, "type", "chat") &&
1150                    set_setstr(&ic->set, "chat_type", "room") &&
1151                    set_setstr(&ic->set, "account", cmd[2]) &&
1152                    set_setstr(&ic->set, "room", cmd[3])) {
1153                        irc_rootmsg(irc, "Chatroom successfully added.");
1154                } else {
1155                        if (ic) {
1156                                irc_channel_free(ic);
1157                        }
1158
1159                        irc_rootmsg(irc, "Could not add chatroom.");
1160                }
1161                g_free(channel);
1162        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1163                irc_user_t *iu;
1164
1165                MIN_ARGS(2);
1166
1167                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1168                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1169                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1170                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1171                                            "a groupchat with %s.", iu->nick);
1172                        }
1173                } else {
1174                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1175                }
1176        } else if (g_strcasecmp(cmd[1], "list") == 0 ||
1177                   g_strcasecmp(cmd[1], "set") == 0 ||
1178                   g_strcasecmp(cmd[1], "del") == 0) {
1179                irc_rootmsg(irc,
1180                            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
1181                cmd_channel(irc, cmd);
1182        } else {
1183                irc_rootmsg(irc,
1184                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1185                            cmd[1]);
1186        }
1187}
1188
1189static void cmd_group(irc_t *irc, char **cmd)
1190{
1191        GSList *l;
1192        int len;
1193
1194        len = strlen(cmd[1]);
1195        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1196                int n = 0;
1197
1198                if (strchr(irc->umode, 'b')) {
1199                        irc_rootmsg(irc, "Group list:");
1200                }
1201
1202                for (l = irc->b->groups; l; l = l->next) {
1203                        bee_group_t *bg = l->data;
1204                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1205                }
1206                irc_rootmsg(irc, "End of group list");
1207        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1208                bee_group_t *bg;
1209                int n = 0;
1210
1211                MIN_ARGS(2);
1212                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1213
1214                if (bg) {
1215                        if (strchr(irc->umode, 'b')) {
1216                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1217                        }
1218                        for (l = irc->b->users; l; l = l->next) {
1219                                bee_user_t *bu = l->data;
1220                                if (bu->group == bg) {
1221                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1222                                }
1223                        }
1224                        irc_rootmsg(irc, "End of member list");
1225                } else {
1226                        irc_rootmsg(irc,
1227                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1228                                    cmd[2]);
1229                }
1230        } else {
1231                irc_rootmsg(irc,
1232                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1233                            cmd[1]);
1234        }
1235}
1236
1237static void cmd_transfer(irc_t *irc, char **cmd)
1238{
1239        GSList *files = irc->file_transfers;
1240        GSList *next;
1241
1242        enum { LIST, REJECT, CANCEL };
1243        int subcmd = LIST;
1244        int fid;
1245
1246        if (!files) {
1247                irc_rootmsg(irc, "No pending transfers");
1248                return;
1249        }
1250
1251        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1252                subcmd = REJECT;
1253        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1254                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1255                subcmd = CANCEL;
1256        }
1257
1258        for (; files; files = next) {
1259                next = files->next;
1260                file_transfer_t *file = files->data;
1261
1262                switch (subcmd) {
1263                case LIST:
1264                        if (file->status == FT_STATUS_LISTENING) {
1265                                irc_rootmsg(irc,
1266                                            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1267                        } else {
1268                                int kb_per_s = 0;
1269                                time_t diff = time(NULL) - file->started ? : 1;
1270                                if ((file->started > 0) && (file->bytes_transferred > 0)) {
1271                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1272                                }
1273
1274                                irc_rootmsg(irc,
1275                                            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
1276                                            file->file_name,
1277                                            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
1278                        }
1279                        break;
1280                case REJECT:
1281                        if (file->status == FT_STATUS_LISTENING) {
1282                                irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
1283                                imcb_file_canceled(file->ic, file, "Denied by user");
1284                        }
1285                        break;
1286                case CANCEL:
1287                        if (file->local_id == fid) {
1288                                irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
1289                                imcb_file_canceled(file->ic, file, "Canceled by user");
1290                        }
1291                        break;
1292                }
1293        }
1294}
1295
1296static void cmd_nick(irc_t *irc, char **cmd)
1297{
1298        irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
1299}
1300
1301/* Maybe this should be a stand-alone command as well? */
1302static void bitlbee_whatsnew(irc_t *irc)
1303{
1304        int last = set_getint(&irc->b->set, "last_version");
1305        char s[16], *msg;
1306
1307        if (last >= BITLBEE_VERSION_CODE) {
1308                return;
1309        }
1310
1311        msg = help_get_whatsnew(&(global.help), last);
1312
1313        if (msg) {
1314                irc_rootmsg(irc, "%s: This seems to be your first time using this "
1315                            "this version of BitlBee. Here's a list of new "
1316                            "features you may like to know about:\n\n%s\n",
1317                            irc->user->nick, msg);
1318        }
1319
1320        g_free(msg);
1321
1322        g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
1323        set_setstr(&irc->b->set, "last_version", s);
1324}
1325
1326/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1327command_t root_commands[] = {
1328        { "account",        1, cmd_account,        0 },
1329        { "add",            2, cmd_add,            0 },
1330        { "allow",          1, cmd_allow,          0 },
1331        { "blist",          0, cmd_blist,          0 },
1332        { "block",          1, cmd_block,          0 },
1333        { "channel",        1, cmd_channel,        0 },
1334        { "chat",           1, cmd_chat,           0 },
1335        { "drop",           1, cmd_drop,           0 },
1336        { "ft",             0, cmd_transfer,       0 },
1337        { "group",          1, cmd_group,          0 },
1338        { "help",           0, cmd_help,           0 },
1339        { "identify",       0, cmd_identify,       0 },
1340        { "info",           1, cmd_info,           0 },
1341        { "nick",           1, cmd_nick,           0 },
1342        { "no",             0, cmd_yesno,          0 },
1343        { "qlist",          0, cmd_qlist,          0 },
1344        { "register",       0, cmd_register,       0 },
1345        { "remove",         1, cmd_remove,         0 },
1346        { "rename",         2, cmd_rename,         0 },
1347        { "save",           0, cmd_save,           0 },
1348        { "set",            0, cmd_set,            0 },
1349        { "transfer",       0, cmd_transfer,       0 },
1350        { "yes",            0, cmd_yesno,          0 },
1351        /* Not expecting too many plugins adding root commands so just make a
1352           dumb array with some empty entried at the end. */
1353        { NULL },
1354        { NULL },
1355        { NULL },
1356        { NULL },
1357        { NULL },
1358        { NULL },
1359        { NULL },
1360        { NULL },
1361        { NULL },
1362};
1363static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);
1364
1365gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
1366{
1367        int i;
1368
1369        if (root_commands[num_root_commands - 2].command) {
1370                /* Planning fail! List is full. */
1371                return FALSE;
1372        }
1373
1374        for (i = 0; root_commands[i].command; i++) {
1375                if (g_strcasecmp(root_commands[i].command, command) == 0) {
1376                        return FALSE;
1377                } else if (g_strcasecmp(root_commands[i].command, command) > 0) {
1378                        break;
1379                }
1380        }
1381        memmove(root_commands + i + 1, root_commands + i,
1382                sizeof(command_t) * (num_root_commands - i - 1));
1383
1384        root_commands[i].command = g_strdup(command);
1385        root_commands[i].required_parameters = params;
1386        root_commands[i].execute = func;
1387        root_commands[i].flags = flags;
1388
1389        return TRUE;
1390}
Note: See TracBrowser for help on using the repository browser.