source: root_commands.c @ ba52ac5

Last change on this file since ba52ac5 was 3fbce97, checked in by Wilmer van der Gaast <wilmer@…>, at 2016-09-24T20:14:34Z

Merge branch 'master' into parson

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