source: root_commands.c @ 446a23e

Last change on this file since 446a23e was 446a23e, checked in by Dennis Kaarsemaker <dennis@…>, at 2016-03-23T06:44:13Z

Add a setting to disable 'account add'

In a locked down bitlbee instance it is useful to disable the 'account
add' command.

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