source: root_commands.c @ 3ddbd23c

Last change on this file since 3ddbd23c was b441614, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-06-17T23:24:26Z

Fix HANDLE_DOMAINS + add command nick argument compatibility.

The order was wrong, it'd set the nick for $NAKED_HANDLE and then add
$NAKED_HANDLE@$DOMAIN which is pretty pointless.

  • Property mode set to 100644
File size: 37.1 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                bitlbee_whatsnew(irc);
166
167                /* The following code is a bit hairy now. With takeover
168                   support, we shouldn't immediately auto_connect in case
169                   we're going to offer taking over an existing session.
170                   Do it in 200ms since that should give the parent process
171                   enough time to come back to us. */
172                if (load) {
173                        irc_channel_auto_joins(irc, NULL);
174                        if (!set_getbool(&irc->default_channel->set, "auto_join")) {
175                                irc_channel_del_user(irc->default_channel, irc->user,
176                                                     IRC_CDU_PART, "auto_join disabled "
177                                                     "for this channel.");
178                        }
179                        if (set_getbool(&irc->b->set, "auto_connect")) {
180                                irc->login_source_id = b_timeout_add(200,
181                                                                     cmd_identify_finish, irc);
182                        }
183                }
184
185                /* If ipc_child_identify() returns FALSE, it means we're
186                   already sure that there's no takeover target (only
187                   possible in 1-process daemon mode). Start auto_connect
188                   immediately. */
189                if (!ipc_child_identify(irc) && load) {
190                        cmd_identify_finish(irc, 0, 0);
191                }
192
193                break;
194        case STORAGE_OTHER_ERROR:
195        default:
196                irc_rootmsg(irc, "Unknown error while loading configuration");
197                break;
198        }
199}
200
201gboolean cmd_identify_finish(gpointer data, gint fd, b_input_condition cond)
202{
203        char *account_on[] = { "account", "on", NULL };
204        irc_t *irc = data;
205
206        if (set_getbool(&irc->b->set, "auto_connect")) {
207                cmd_account(irc, account_on);
208        }
209
210        b_event_remove(irc->login_source_id);
211        irc->login_source_id = -1;
212        return FALSE;
213}
214
215static void cmd_register(irc_t *irc, char **cmd)
216{
217        char s[16];
218
219        if (global.conf->authmode == AUTHMODE_REGISTERED) {
220                irc_rootmsg(irc, "This server does not allow registering new accounts");
221                return;
222        }
223
224        if (cmd[1] == NULL) {
225                irc_rootmsg(irc, "About to register, use /OPER to enter the password");
226                irc->status |= OPER_HACK_REGISTER;
227                return;
228        }
229
230        switch (storage_save(irc, cmd[1], FALSE)) {
231        case STORAGE_ALREADY_EXISTS:
232                irc_rootmsg(irc, "Nick is already registered");
233                break;
234
235        case STORAGE_OK:
236                irc_rootmsg(irc, "Account successfully created");
237                irc_setpass(irc, cmd[1]);
238                irc->status |= USTATUS_IDENTIFIED;
239                irc_umode_set(irc, "+R", 1);
240
241                /* Set this var now, or anyone who logs in to his/her
242                   newly created account for the first time gets the
243                   whatsnew story. */
244                g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
245                set_setstr(&irc->b->set, "last_version", s);
246                break;
247
248        default:
249                irc_rootmsg(irc, "Error registering");
250                break;
251        }
252}
253
254static void cmd_drop(irc_t *irc, char **cmd)
255{
256        storage_status_t status;
257
258        status = storage_remove(irc->user->nick, cmd[1]);
259        switch (status) {
260        case STORAGE_NO_SUCH_USER:
261                irc_rootmsg(irc, "That account does not exist");
262                break;
263        case STORAGE_INVALID_PASSWORD:
264                irc_rootmsg(irc, "Password invalid");
265                break;
266        case STORAGE_OK:
267                irc_setpass(irc, NULL);
268                irc->status &= ~USTATUS_IDENTIFIED;
269                irc_umode_set(irc, "-R", 1);
270                irc_rootmsg(irc, "Account `%s' removed", irc->user->nick);
271                break;
272        default:
273                irc_rootmsg(irc, "Error: `%d'", status);
274                break;
275        }
276}
277
278static void cmd_save(irc_t *irc, char **cmd)
279{
280        if ((irc->status & USTATUS_IDENTIFIED) == 0) {
281                irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)");
282        } else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) {
283                irc_rootmsg(irc, "Configuration saved");
284        } else {
285                irc_rootmsg(irc, "Configuration could not be saved!");
286        }
287}
288
289static void cmd_showset(irc_t *irc, set_t **head, char *key)
290{
291        set_t *set;
292        char *val;
293
294        if ((val = set_getstr(head, key))) {
295                irc_rootmsg(irc, "%s = `%s'", key, val);
296        } else if (!(set = set_find(head, key))) {
297                irc_rootmsg(irc, "Setting `%s' does not exist.", key);
298                if (*head == irc->b->set) {
299                        irc_rootmsg(irc, "It might be an account or channel setting. "
300                                    "See \x02help account set\x02 and \x02help channel set\x02.");
301                }
302        } else if (set->flags & SET_PASSWORD) {
303                irc_rootmsg(irc, "%s = `********' (hidden)", key);
304        } else {
305                irc_rootmsg(irc, "%s is empty", key);
306        }
307}
308
309typedef set_t** (*cmd_set_findhead)(irc_t*, char*);
310typedef int (*cmd_set_checkflags)(irc_t*, set_t *set);
311
312static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags)
313{
314        char *set_name = NULL, *value = NULL;
315        gboolean del = FALSE;
316
317        if (cmd[1] && g_strncasecmp(cmd[1], "-del", 4) == 0) {
318                MIN_ARGS(2, 0);
319                set_name = cmd[2];
320                del = TRUE;
321        } else {
322                set_name = cmd[1];
323                value = cmd[2];
324        }
325
326        if (set_name && (value || del)) {
327                set_t *s = set_find(head, set_name);
328                int st;
329
330                if (s && checkflags && checkflags(irc, s) == 0) {
331                        return 0;
332                }
333
334                if (del) {
335                        st = set_reset(head, set_name);
336                } else {
337                        st = set_setstr(head, set_name, value);
338                }
339
340                if (set_getstr(head, set_name) == NULL &&
341                    set_find(head, set_name)) {
342                        /* This happens when changing the passwd, for example.
343                           Showing these msgs instead gives slightly clearer
344                           feedback. */
345                        if (st) {
346                                irc_rootmsg(irc, "Setting changed successfully");
347                        } else {
348                                irc_rootmsg(irc, "Failed to change setting");
349                        }
350                } else {
351                        cmd_showset(irc, head, set_name);
352                }
353        } else if (set_name) {
354                cmd_showset(irc, head, set_name);
355        } else {
356                set_t *s = *head;
357                while (s) {
358                        if (set_isvisible(s)) {
359                                cmd_showset(irc, &s, s->key);
360                        }
361                        s = s->next;
362                }
363        }
364
365        return 1;
366}
367
368static int cmd_account_set_checkflags(irc_t *irc, set_t *s)
369{
370        account_t *a = s->data;
371
372        if (a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY) {
373                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "off");
374                return 0;
375        } else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) {
376                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on");
377                return 0;
378        }
379
380        return 1;
381}
382
383static void cmd_account(irc_t *irc, char **cmd)
384{
385        account_t *a;
386        int len;
387
388        if (global.conf->authmode == AUTHMODE_REGISTERED && !(irc->status & USTATUS_IDENTIFIED)) {
389                irc_rootmsg(irc, "This server only accepts registered users");
390                return;
391        }
392
393        len = strlen(cmd[1]);
394
395        if (len >= 1 && g_strncasecmp(cmd[1], "add", len) == 0) {
396                struct prpl *prpl;
397
398                MIN_ARGS(3);
399
400                if (cmd[4] == NULL) {
401                        for (a = irc->b->accounts; a; a = a->next) {
402                                if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
403                                        irc_rootmsg(irc, "Enter password for account %s "
404                                                    "first (use /OPER)", a->tag);
405                                        return;
406                                }
407                        }
408
409                        irc->status |= OPER_HACK_ACCOUNT_PASSWORD;
410                }
411
412                prpl = find_protocol(cmd[2]);
413
414                if (prpl == NULL) {
415                        irc_rootmsg(irc, "Unknown protocol");
416                        return;
417                }
418
419                for (a = irc->b->accounts; a; a = a->next) {
420                        if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
421                                irc_rootmsg(irc, "Warning: You already have an account with "
422                                            "protocol `%s' and username `%s'. Are you accidentally "
423                                            "trying to add it twice?", prpl->name, cmd[3]);
424                        }
425                }
426
427                a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
428                if (cmd[5]) {
429                        irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
430                                    "is now deprecated. Use `account set' instead.");
431                        set_setstr(&a->set, "server", cmd[5]);
432                }
433
434                irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);
435
436                if (cmd[4] == NULL) {
437                        set_t *oauth = set_find(&a->set, "oauth");
438                        if (oauth && bool2int(set_value(oauth))) {
439                                *a->pass = '\0';
440                                irc_rootmsg(irc, "No need to enter a password for this "
441                                            "account since it's using OAuth");
442                        } else {
443                                irc_rootmsg(irc, "You can now use the /OPER command to "
444                                            "enter the password");
445                                if (oauth) {
446                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
447                                                    "the account supports it: account %s "
448                                                    "set oauth on", a->tag);
449                                }
450                        }
451                }
452
453                return;
454        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
455                int i = 0;
456
457                if (strchr(irc->umode, 'b')) {
458                        irc_rootmsg(irc, "Account list:");
459                }
460
461                for (a = irc->b->accounts; a; a = a->next) {
462                        char *con = NULL, *protocol = NULL;
463
464                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
465                                con = " (connected)";
466                        } else if (a->ic) {
467                                con = " (connecting)";
468                        } else if (a->reconnect) {
469                                con = " (awaiting reconnect)";
470                        } else {
471                                con = "";
472                        }
473                        if (a->prpl == &protocol_missing) {
474                                protocol = g_strdup_printf("%s (missing!)", set_getstr(&a->set, "_protocol_name"));
475                        } else {
476                                protocol = g_strdup(a->prpl->name);
477                        }
478
479                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, protocol, a->user, con);
480                        g_free(protocol);
481
482                        i++;
483                }
484                irc_rootmsg(irc, "End of account list");
485
486                return;
487        } else if (cmd[2]) {
488                /* Try the following two only if cmd[2] == NULL */
489        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
490                if (irc->b->accounts) {
491                        irc_rootmsg(irc, "Trying to get all accounts connected...");
492
493                        for (a = irc->b->accounts; a; a = a->next) {
494                                if (!a->ic && a->auto_connect && a->prpl != &protocol_missing) {
495                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
496                                                irc_rootmsg(irc, "Enter password for account %s "
497                                                            "first (use /OPER)", a->tag);
498                                        } else {
499                                                account_on(irc->b, a);
500                                        }
501                                }
502                        }
503                } else {
504                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
505                }
506
507                return;
508        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
509                irc_rootmsg(irc, "Deactivating all active (re)connections...");
510
511                for (a = irc->b->accounts; a; a = a->next) {
512                        if (a->ic) {
513                                account_off(irc->b, a);
514                        } else if (a->reconnect) {
515                                cancel_auto_reconnect(a);
516                        }
517                }
518
519                return;
520        }
521
522        MIN_ARGS(2);
523        len = strlen(cmd[2]);
524
525        /* At least right now, don't accept on/off/set/del as account IDs even
526           if they're a proper match, since people not familiar with the new
527           syntax yet may get a confusing/nasty surprise. */
528        if (g_strcasecmp(cmd[1], "on") == 0 ||
529            g_strcasecmp(cmd[1], "off") == 0 ||
530            g_strcasecmp(cmd[1], "set") == 0 ||
531            g_strcasecmp(cmd[1], "del") == 0 ||
532            (a = account_get(irc->b, cmd[1])) == NULL) {
533                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
534
535                return;
536        }
537
538        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
539                if (a->ic) {
540                        irc_rootmsg(irc, "Account is still logged in, can't delete");
541                } else {
542                        account_del(irc->b, a);
543                        irc_rootmsg(irc, "Account deleted");
544                }
545        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
546                if (a->ic) {
547                        irc_rootmsg(irc, "Account already online");
548                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
549                        irc_rootmsg(irc, "Enter password for account %s "
550                                    "first (use /OPER)", a->tag);
551                } else if (a->prpl == &protocol_missing) {
552                        irc_rootmsg(irc, "Protocol `%s' not recognised (plugin may be missing or not running?)",
553                                    set_getstr(&a->set, "_protocol_name"));
554                } else {
555                        account_on(irc->b, a);
556                }
557        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
558                if (a->ic) {
559                        account_off(irc->b, a);
560                } else if (a->reconnect) {
561                        cancel_auto_reconnect(a);
562                        irc_rootmsg(irc, "Reconnect cancelled");
563                } else {
564                        irc_rootmsg(irc, "Account already offline");
565                }
566        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
567                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
568        } else {
569                irc_rootmsg(irc,
570                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
571                            cmd[2]);
572        }
573}
574
575static void cmd_channel(irc_t *irc, char **cmd)
576{
577        irc_channel_t *ic;
578        int len;
579
580        len = strlen(cmd[1]);
581
582        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
583                GSList *l;
584                int i = 0;
585
586                if (strchr(irc->umode, 'b')) {
587                        irc_rootmsg(irc, "Channel list:");
588                }
589
590                for (l = irc->channels; l; l = l->next) {
591                        irc_channel_t *ic = l->data;
592
593                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
594                                    set_getstr(&ic->set, "type"),
595                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
596
597                        i++;
598                }
599                irc_rootmsg(irc, "End of channel list");
600
601                return;
602        }
603
604        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
605                /* If this doesn't match any channel, maybe this is the short
606                   syntax (only works when used inside a channel). */
607                if ((ic = irc->root->last_channel) &&
608                    (len = strlen(cmd[1])) &&
609                    g_strncasecmp(cmd[1], "set", len) == 0) {
610                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
611                } else {
612                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
613                }
614
615                return;
616        }
617
618        MIN_ARGS(2);
619        len = strlen(cmd[2]);
620
621        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
622                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
623        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
624                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
625                    ic != ic->irc->default_channel) {
626                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
627                        irc_channel_free(ic);
628                } else {
629                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
630                                    "channels you're still in cannot be deleted).",
631                                    irc->default_channel->name);
632                }
633        } else {
634                irc_rootmsg(irc,
635                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
636                            cmd[1]);
637        }
638}
639
640static void cmd_add(irc_t *irc, char **cmd)
641{
642        account_t *a;
643        int add_on_server = 1;
644        char *handle = NULL, *s;
645
646        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
647                MIN_ARGS(3);
648                add_on_server = 0;
649                cmd++;
650        }
651
652        if (!(a = account_get(irc->b, cmd[1]))) {
653                irc_rootmsg(irc, "Invalid account");
654                return;
655        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
656                irc_rootmsg(irc, "That account is not on-line");
657                return;
658        } else if (add_on_server && !a->prpl->add_buddy) {
659                irc_rootmsg(irc, "IM protocol does not support contact list modification");
660                return;
661        }
662
663        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
664            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
665                /* If there's no @ or it's the last char, append the user's
666                   domain name now. Exclude handles starting with a _ so
667                   adding _xmlconsole will keep working. */
668                if (s) {
669                        *s = '\0';
670                }
671                if ((s = strchr(a->user, '@'))) {
672                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
673                }
674        }
675
676        if (cmd[3]) {
677                if (!nick_ok(irc, cmd[3])) {
678                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
679                        return;
680                } else if (irc_user_by_name(irc, cmd[3])) {
681                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
682                        return;
683                } else {
684                        nick_set_raw(a, cmd[2], cmd[3]);
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        if (bu->ic->acc->prpl->remove_buddy) {
732                bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
733        } else {
734                irc_rootmsg(irc, "IM protocol does not support contact list modification, "
735                                 "removal will likely not be permanent");
736        }
737
738        nick_del(bu);
739        if (g_slist_find(irc->users, iu)) {
740                bee_user_free(irc->b, bu);
741        }
742
743        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
744        g_free(s);
745
746        return;
747}
748
749static void cmd_info(irc_t *irc, char **cmd)
750{
751        struct im_connection *ic;
752        account_t *a;
753
754        if (!cmd[2]) {
755                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
756                if (!iu || !iu->bu) {
757                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
758                        return;
759                }
760                ic = iu->bu->ic;
761                cmd[2] = iu->bu->handle;
762        } else if (!(a = account_get(irc->b, cmd[1]))) {
763                irc_rootmsg(irc, "Invalid account");
764                return;
765        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
766                irc_rootmsg(irc, "That account is not on-line");
767                return;
768        }
769
770        if (!ic->acc->prpl->get_info) {
771                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
772        } else {
773                ic->acc->prpl->get_info(ic, cmd[2]);
774        }
775}
776
777static void cmd_rename(irc_t *irc, char **cmd)
778{
779        irc_user_t *iu, *old;
780        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
781
782        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
783
784        if (iu == NULL) {
785                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
786        } else if (del) {
787                if (iu->bu) {
788                        bee_irc_user_nick_reset(iu);
789                }
790                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
791        } else if (iu == irc->user) {
792                irc_rootmsg(irc, "Use /nick to change your own nickname");
793        } else if (!nick_ok(irc, cmd[2])) {
794                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
795        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
796                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
797        } else {
798                if (!irc_user_set_nick(iu, cmd[2])) {
799                        irc_rootmsg(irc, "Error while changing nick");
800                        return;
801                }
802
803                if (iu == irc->root) {
804                        /* If we're called internally (user did "set root_nick"),
805                           let's not go O(INF). :-) */
806                        if (strcmp(cmd[0], "set_rename") != 0) {
807                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
808                        }
809                } else if (iu->bu) {
810                        nick_set(iu->bu, cmd[2]);
811                }
812
813                irc_rootmsg(irc, "Nick successfully changed");
814        }
815}
816
817char *set_eval_root_nick(set_t *set, char *new_nick)
818{
819        irc_t *irc = set->data;
820
821        if (strcmp(irc->root->nick, new_nick) != 0) {
822                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
823
824                cmd_rename(irc, cmd);
825        }
826
827        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
828}
829
830static void cmd_block(irc_t *irc, char **cmd)
831{
832        struct im_connection *ic;
833        account_t *a;
834
835        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
836                char *format;
837                GSList *l;
838
839                if (strchr(irc->umode, 'b') != NULL) {
840                        format = "%s\t%s";
841                } else {
842                        format = "%-32.32s  %-16.16s";
843                }
844
845                irc_rootmsg(irc, format, "Handle", "Nickname");
846                for (l = a->ic->deny; l; l = l->next) {
847                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
848                        irc_user_t *iu = bu ? bu->ui_data : NULL;
849                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
850                }
851                irc_rootmsg(irc, "End of list.");
852
853                return;
854        } else if (!cmd[2]) {
855                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
856                if (!iu || !iu->bu) {
857                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
858                        return;
859                }
860                ic = iu->bu->ic;
861                cmd[2] = iu->bu->handle;
862        } else if (!(a = account_get(irc->b, cmd[1]))) {
863                irc_rootmsg(irc, "Invalid account");
864                return;
865        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
866                irc_rootmsg(irc, "That account is not on-line");
867                return;
868        }
869
870        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
871                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
872        } else {
873                imc_rem_allow(ic, cmd[2]);
874                imc_add_block(ic, cmd[2]);
875                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
876        }
877}
878
879static void cmd_allow(irc_t *irc, char **cmd)
880{
881        struct im_connection *ic;
882        account_t *a;
883
884        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
885                char *format;
886                GSList *l;
887
888                if (strchr(irc->umode, 'b') != NULL) {
889                        format = "%s\t%s";
890                } else {
891                        format = "%-32.32s  %-16.16s";
892                }
893
894                irc_rootmsg(irc, format, "Handle", "Nickname");
895                for (l = a->ic->permit; l; l = l->next) {
896                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
897                        irc_user_t *iu = bu ? bu->ui_data : NULL;
898                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
899                }
900                irc_rootmsg(irc, "End of list.");
901
902                return;
903        } else if (!cmd[2]) {
904                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
905                if (!iu || !iu->bu) {
906                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
907                        return;
908                }
909                ic = iu->bu->ic;
910                cmd[2] = iu->bu->handle;
911        } else if (!(a = account_get(irc->b, cmd[1]))) {
912                irc_rootmsg(irc, "Invalid account");
913                return;
914        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
915                irc_rootmsg(irc, "That account is not on-line");
916                return;
917        }
918
919        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
920                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
921        } else {
922                imc_rem_block(ic, cmd[2]);
923                imc_add_allow(ic, cmd[2]);
924
925                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
926        }
927}
928
929static void cmd_yesno(irc_t *irc, char **cmd)
930{
931        query_t *q = NULL;
932        int numq = 0;
933
934        if (irc->queries == NULL) {
935                /* Alright, alright, let's add a tiny easter egg here. */
936                static irc_t *last_irc = NULL;
937                static time_t last_time = 0;
938                static int times = 0;
939                static const char *msg[] = {
940                        "Oh yeah, that's right.",
941                        "Alright, alright. Now go back to work.",
942                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
943                        "Yes?",
944                        "No?",
945                };
946
947                if (last_irc == irc && time(NULL) - last_time < 15) {
948                        if ((++times >= 3)) {
949                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
950                                last_irc = NULL;
951                                times = 0;
952                                return;
953                        }
954                } else {
955                        last_time = time(NULL);
956                        last_irc = irc;
957                        times = 0;
958                }
959
960                irc_rootmsg(irc, "Did I ask you something?");
961                return;
962        }
963
964        /* If there's an argument, the user seems to want to answer another question than the
965           first/last (depending on the query_order setting) one. */
966        if (cmd[1]) {
967                if (sscanf(cmd[1], "%d", &numq) != 1) {
968                        irc_rootmsg(irc, "Invalid query number");
969                        return;
970                }
971
972                for (q = irc->queries; q; q = q->next, numq--) {
973                        if (numq == 0) {
974                                break;
975                        }
976                }
977
978                if (!q) {
979                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
980                        return;
981                }
982        }
983
984        if (g_strcasecmp(cmd[0], "yes") == 0) {
985                query_answer(irc, q, 1);
986        } else if (g_strcasecmp(cmd[0], "no") == 0) {
987                query_answer(irc, q, 0);
988        }
989}
990
991static void cmd_set(irc_t *irc, char **cmd)
992{
993        cmd_set_real(irc, cmd, &irc->b->set, NULL);
994}
995
996static void cmd_blist(irc_t *irc, char **cmd)
997{
998        int online = 0, away = 0, offline = 0, ismatch = 0;
999        GSList *l;
1000        GRegex *regex = NULL;
1001        GError *error = NULL;
1002        char s[256];
1003        char *format;
1004        int n_online = 0, n_away = 0, n_offline = 0;
1005
1006        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
1007                online = offline = away = 1;
1008        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1009                offline = 1;
1010        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1011                away = 1;
1012        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1013                online = 1;
1014        } else {
1015                online = away = 1;
1016        }
1017
1018        if (cmd[2]) {
1019                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1020        }
1021
1022        if (error) {
1023                irc_rootmsg(irc, error->message);
1024                g_error_free(error);
1025        }
1026
1027        if (strchr(irc->umode, 'b') != NULL) {
1028                format = "%s\t%s\t%s";
1029        } else {
1030                format = "%-16.16s  %-40.40s  %s";
1031        }
1032
1033        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1034
1035        if (irc->root->last_channel &&
1036            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1037                irc->root->last_channel = NULL;
1038        }
1039
1040        for (l = irc->users; l; l = l->next) {
1041                irc_user_t *iu = l->data;
1042                bee_user_t *bu = iu->bu;
1043
1044                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1045                        ismatch = 1;
1046                } else {
1047                        ismatch = 0;
1048                }
1049
1050                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1051                        continue;
1052                }
1053
1054                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1055                        if (ismatch == 1 && online == 1) {
1056                                char st[256] = "Online";
1057
1058                                if (bu->status_msg) {
1059                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1060                                }
1061
1062                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1063                                irc_rootmsg(irc, format, iu->nick, s, st);
1064                        }
1065
1066                        n_online++;
1067                }
1068
1069                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1070                        if (ismatch == 1 && away == 1) {
1071                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1072                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1073                        }
1074                        n_away++;
1075                }
1076
1077                if (!(bu->flags & BEE_USER_ONLINE)) {
1078                        if (ismatch == 1 && offline == 1) {
1079                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1080                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1081                        }
1082                        n_offline++;
1083                }
1084        }
1085
1086        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1087                    n_away, n_offline);
1088
1089        if (regex) {
1090                g_regex_unref(regex);
1091        }
1092}
1093
1094static void cmd_qlist(irc_t *irc, char **cmd)
1095{
1096        query_t *q = irc->queries;
1097        int num;
1098
1099        if (!q) {
1100                irc_rootmsg(irc, "There are no pending questions.");
1101                return;
1102        }
1103
1104        irc_rootmsg(irc, "Pending queries:");
1105
1106        for (num = 0; q; q = q->next, num++) {
1107                if (q->ic) { /* Not necessary yet, but it might come later */
1108                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1109                } else {
1110                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1111                }
1112        }
1113}
1114
1115static void cmd_chat(irc_t *irc, char **cmd)
1116{
1117        account_t *acc;
1118
1119        if (g_strcasecmp(cmd[1], "add") == 0) {
1120                char *channel, *s;
1121                struct irc_channel *ic;
1122
1123                MIN_ARGS(3);
1124
1125                if (!(acc = account_get(irc->b, cmd[2]))) {
1126                        irc_rootmsg(irc, "Invalid account");
1127                        return;
1128                } else if (!acc->prpl->chat_join) {
1129                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1130                        return;
1131                }
1132
1133                if (cmd[4] == NULL) {
1134                        channel = g_strdup(cmd[3]);
1135                        if ((s = strchr(channel, '@'))) {
1136                                *s = 0;
1137                        }
1138                } else {
1139                        channel = g_strdup(cmd[4]);
1140                }
1141
1142                if (strchr(CTYPES, channel[0]) == NULL) {
1143                        s = g_strdup_printf("#%s", channel);
1144                        g_free(channel);
1145                        channel = s;
1146
1147                        irc_channel_name_strip(channel);
1148                }
1149
1150                if ((ic = irc_channel_new(irc, channel)) &&
1151                    set_setstr(&ic->set, "type", "chat") &&
1152                    set_setstr(&ic->set, "chat_type", "room") &&
1153                    set_setstr(&ic->set, "account", cmd[2]) &&
1154                    set_setstr(&ic->set, "room", cmd[3])) {
1155                        irc_rootmsg(irc, "Chatroom successfully added.");
1156                } else {
1157                        if (ic) {
1158                                irc_channel_free(ic);
1159                        }
1160
1161                        irc_rootmsg(irc, "Could not add chatroom.");
1162                }
1163                g_free(channel);
1164        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1165                irc_user_t *iu;
1166
1167                MIN_ARGS(2);
1168
1169                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1170                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1171                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1172                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1173                                            "a groupchat with %s.", iu->nick);
1174                        }
1175                } else {
1176                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1177                }
1178        } else if (g_strcasecmp(cmd[1], "list") == 0 ||
1179                   g_strcasecmp(cmd[1], "set") == 0 ||
1180                   g_strcasecmp(cmd[1], "del") == 0) {
1181                irc_rootmsg(irc,
1182                            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
1183                cmd_channel(irc, cmd);
1184        } else {
1185                irc_rootmsg(irc,
1186                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1187                            cmd[1]);
1188        }
1189}
1190
1191static void cmd_group(irc_t *irc, char **cmd)
1192{
1193        GSList *l;
1194        int len;
1195
1196        len = strlen(cmd[1]);
1197        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1198                int n = 0;
1199
1200                if (strchr(irc->umode, 'b')) {
1201                        irc_rootmsg(irc, "Group list:");
1202                }
1203
1204                for (l = irc->b->groups; l; l = l->next) {
1205                        bee_group_t *bg = l->data;
1206                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1207                }
1208                irc_rootmsg(irc, "End of group list");
1209        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1210                bee_group_t *bg;
1211                int n = 0;
1212
1213                MIN_ARGS(2);
1214                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1215
1216                if (bg) {
1217                        if (strchr(irc->umode, 'b')) {
1218                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1219                        }
1220                        for (l = irc->b->users; l; l = l->next) {
1221                                bee_user_t *bu = l->data;
1222                                if (bu->group == bg) {
1223                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1224                                }
1225                        }
1226                        irc_rootmsg(irc, "End of member list");
1227                } else {
1228                        irc_rootmsg(irc,
1229                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1230                                    cmd[2]);
1231                }
1232        } else {
1233                irc_rootmsg(irc,
1234                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1235                            cmd[1]);
1236        }
1237}
1238
1239static void cmd_transfer(irc_t *irc, char **cmd)
1240{
1241        GSList *files = irc->file_transfers;
1242
1243        enum { LIST, REJECT, CANCEL };
1244        int subcmd = LIST;
1245        int fid;
1246
1247        if (!files) {
1248                irc_rootmsg(irc, "No pending transfers");
1249                return;
1250        }
1251
1252        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1253                subcmd = REJECT;
1254        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1255                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1256                subcmd = CANCEL;
1257        }
1258
1259        for (; files; files = g_slist_next(files)) {
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.