source: root_commands.c @ e41ba05

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

Allow individual settings to be locked down

This allows a site admin who pregenerates configs to mark certain
settings as untouchable, ensuring that users cannot mess up their
settings too badly.

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