source: root_commands.c @ 17aa9a2

Last change on this file since 17aa9a2 was f8feb8a, checked in by Wilmer van der Gaast <wilmer@…>, at 2015-05-07T10:33:06Z

add_buddy and remove_buddy should no longer be mandatory.

  • Property mode set to 100644
File size: 36.7 KB
Line 
1/********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2013 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* User manager (root) commands                                         */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23  Fifth Floor, Boston, MA  02110-1301  USA
24*/
25
26#define BITLBEE_CORE
27#include "commands.h"
28#include "bitlbee.h"
29#include "help.h"
30#include "ipc.h"
31
32void root_command_string(irc_t *irc, char *command)
33{
34        root_command(irc, split_command_parts(command, 0));
35}
36
37#define MIN_ARGS(x, y ...)                                                    \
38        do                                                                     \
39        {                                                                      \
40                int blaat;                                                     \
41                for (blaat = 0; blaat <= x; blaat++) {                         \
42                        if (cmd[blaat] == NULL)                               \
43                        {                                                      \
44                                irc_rootmsg(irc, "Not enough parameters given (need %d).", x); \
45                                return y;                                      \
46                        } }                                                      \
47        } while (0)
48
49void root_command(irc_t *irc, char *cmd[])
50{
51        int i, len;
52
53        if (!cmd[0]) {
54                return;
55        }
56
57        len = strlen(cmd[0]);
58        for (i = 0; root_commands[i].command; i++) {
59                if (g_strncasecmp(root_commands[i].command, cmd[0], len) == 0) {
60                        if (root_commands[i + 1].command &&
61                            g_strncasecmp(root_commands[i + 1].command, cmd[0], len) == 0) {
62                                /* Only match on the first letters if the match is unique. */
63                                break;
64                        }
65
66                        MIN_ARGS(root_commands[i].required_parameters);
67
68                        root_commands[i].execute(irc, cmd);
69                        return;
70                }
71        }
72
73        irc_rootmsg(irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.",
74                    cmd[0]);
75}
76
77static void cmd_help(irc_t *irc, char **cmd)
78{
79        char param[80];
80        int i;
81        char *s;
82
83        memset(param, 0, sizeof(param));
84        for (i = 1; (cmd[i] != NULL && (strlen(param) < (sizeof(param) - 1))); i++) {
85                if (i != 1) {   // prepend space except for the first parameter
86                        strcat(param, " ");
87                }
88                strncat(param, cmd[i], sizeof(param) - strlen(param) - 1);
89        }
90
91        s = help_get(&(global.help), param);
92        if (!s) {
93                s = help_get(&(global.help), "");
94        }
95
96        if (s) {
97                irc_rootmsg(irc, "%s", s);
98                g_free(s);
99        } else {
100                irc_rootmsg(irc, "Error opening helpfile.");
101        }
102}
103
104static void cmd_account(irc_t *irc, char **cmd);
105static void bitlbee_whatsnew(irc_t *irc);
106
107static void cmd_identify(irc_t *irc, char **cmd)
108{
109        storage_status_t status;
110        gboolean load = TRUE;
111        char *password = cmd[1];
112
113        if (irc->status & USTATUS_IDENTIFIED) {
114                irc_rootmsg(irc, "You're already logged in.");
115                return;
116        }
117
118        if (cmd[1] == NULL) {
119        } else if (strncmp(cmd[1], "-no", 3) == 0) {
120                load = FALSE;
121                password = cmd[2];
122                if (password == NULL) {
123                        irc->status |= OPER_HACK_IDENTIFY_NOLOAD;
124                }
125        } else if (strncmp(cmd[1], "-force", 6) == 0) {
126                password = cmd[2];
127                if (password == NULL) {
128                        irc->status |= OPER_HACK_IDENTIFY_FORCE;
129                }
130        } else if (irc->b->accounts != NULL) {
131                irc_rootmsg(irc,
132                            "You're trying to identify yourself, but already have "
133                            "at least one IM account set up. "
134                            "Use \x02identify -noload\x02 or \x02identify -force\x02 "
135                            "instead (see \x02help identify\x02).");
136                return;
137        }
138
139        if (password == NULL) {
140                irc_rootmsg(irc, "About to identify, use /OPER to enter the password");
141                irc->status |= OPER_HACK_IDENTIFY;
142                return;
143        }
144
145        if (load) {
146                status = storage_load(irc, password);
147        } else {
148                status = storage_check_pass(irc->user->nick, password);
149        }
150
151        switch (status) {
152        case STORAGE_INVALID_PASSWORD:
153                irc_rootmsg(irc, "Incorrect password");
154                break;
155        case STORAGE_NO_SUCH_USER:
156                irc_rootmsg(irc, "The nick is (probably) not registered");
157                break;
158        case STORAGE_OK:
159                irc_rootmsg(irc, "Password accepted%s",
160                            load ? ", settings and accounts loaded" : "");
161                irc_setpass(irc, password);
162                irc->status |= USTATUS_IDENTIFIED;
163                irc_umode_set(irc, "+R", 1);
164
165                bitlbee_whatsnew(irc);
166
167                /* The following code is a bit hairy now. With takeover
168                   support, we shouldn't immediately auto_connect in case
169                   we're going to offer taking over an existing session.
170                   Do it in 200ms since that should give the parent process
171                   enough time to come back to us. */
172                if (load) {
173                        irc_channel_auto_joins(irc, NULL);
174                        if (!set_getbool(&irc->default_channel->set, "auto_join")) {
175                                irc_channel_del_user(irc->default_channel, irc->user,
176                                                     IRC_CDU_PART, "auto_join disabled "
177                                                     "for this channel.");
178                        }
179                        if (set_getbool(&irc->b->set, "auto_connect")) {
180                                irc->login_source_id = b_timeout_add(200,
181                                                                     cmd_identify_finish, irc);
182                        }
183                }
184
185                /* If ipc_child_identify() returns FALSE, it means we're
186                   already sure that there's no takeover target (only
187                   possible in 1-process daemon mode). Start auto_connect
188                   immediately. */
189                if (!ipc_child_identify(irc) && load) {
190                        cmd_identify_finish(irc, 0, 0);
191                }
192
193                break;
194        case STORAGE_OTHER_ERROR:
195        default:
196                irc_rootmsg(irc, "Unknown error while loading configuration");
197                break;
198        }
199}
200
201gboolean cmd_identify_finish(gpointer data, gint fd, b_input_condition cond)
202{
203        char *account_on[] = { "account", "on", NULL };
204        irc_t *irc = data;
205
206        if (set_getbool(&irc->b->set, "auto_connect")) {
207                cmd_account(irc, account_on);
208        }
209
210        b_event_remove(irc->login_source_id);
211        irc->login_source_id = -1;
212        return FALSE;
213}
214
215static void cmd_register(irc_t *irc, char **cmd)
216{
217        char s[16];
218
219        if (global.conf->authmode == AUTHMODE_REGISTERED) {
220                irc_rootmsg(irc, "This server does not allow registering new accounts");
221                return;
222        }
223
224        if (cmd[1] == NULL) {
225                irc_rootmsg(irc, "About to register, use /OPER to enter the password");
226                irc->status |= OPER_HACK_REGISTER;
227                return;
228        }
229
230        switch (storage_save(irc, cmd[1], FALSE)) {
231        case STORAGE_ALREADY_EXISTS:
232                irc_rootmsg(irc, "Nick is already registered");
233                break;
234
235        case STORAGE_OK:
236                irc_rootmsg(irc, "Account successfully created");
237                irc_setpass(irc, cmd[1]);
238                irc->status |= USTATUS_IDENTIFIED;
239                irc_umode_set(irc, "+R", 1);
240
241                /* Set this var now, or anyone who logs in to his/her
242                   newly created account for the first time gets the
243                   whatsnew story. */
244                g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
245                set_setstr(&irc->b->set, "last_version", s);
246                break;
247
248        default:
249                irc_rootmsg(irc, "Error registering");
250                break;
251        }
252}
253
254static void cmd_drop(irc_t *irc, char **cmd)
255{
256        storage_status_t status;
257
258        status = storage_remove(irc->user->nick, cmd[1]);
259        switch (status) {
260        case STORAGE_NO_SUCH_USER:
261                irc_rootmsg(irc, "That account does not exist");
262                break;
263        case STORAGE_INVALID_PASSWORD:
264                irc_rootmsg(irc, "Password invalid");
265                break;
266        case STORAGE_OK:
267                irc_setpass(irc, NULL);
268                irc->status &= ~USTATUS_IDENTIFIED;
269                irc_umode_set(irc, "-R", 1);
270                irc_rootmsg(irc, "Account `%s' removed", irc->user->nick);
271                break;
272        default:
273                irc_rootmsg(irc, "Error: `%d'", status);
274                break;
275        }
276}
277
278static void cmd_save(irc_t *irc, char **cmd)
279{
280        if ((irc->status & USTATUS_IDENTIFIED) == 0) {
281                irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)");
282        } else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) {
283                irc_rootmsg(irc, "Configuration saved");
284        } else {
285                irc_rootmsg(irc, "Configuration could not be saved!");
286        }
287}
288
289static void cmd_showset(irc_t *irc, set_t **head, char *key)
290{
291        set_t *set;
292        char *val;
293
294        if ((val = set_getstr(head, key))) {
295                irc_rootmsg(irc, "%s = `%s'", key, val);
296        } else if (!(set = set_find(head, key))) {
297                irc_rootmsg(irc, "Setting `%s' does not exist.", key);
298                if (*head == irc->b->set) {
299                        irc_rootmsg(irc, "It might be an account or channel setting. "
300                                    "See \x02help account set\x02 and \x02help channel set\x02.");
301                }
302        } else if (set->flags & SET_PASSWORD) {
303                irc_rootmsg(irc, "%s = `********' (hidden)", key);
304        } else {
305                irc_rootmsg(irc, "%s is empty", key);
306        }
307}
308
309typedef set_t** (*cmd_set_findhead)(irc_t*, char*);
310typedef int (*cmd_set_checkflags)(irc_t*, set_t *set);
311
312static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags)
313{
314        char *set_name = NULL, *value = NULL;
315        gboolean del = FALSE;
316
317        if (cmd[1] && g_strncasecmp(cmd[1], "-del", 4) == 0) {
318                MIN_ARGS(2, 0);
319                set_name = cmd[2];
320                del = TRUE;
321        } else {
322                set_name = cmd[1];
323                value = cmd[2];
324        }
325
326        if (set_name && (value || del)) {
327                set_t *s = set_find(head, set_name);
328                int st;
329
330                if (s && checkflags && checkflags(irc, s) == 0) {
331                        return 0;
332                }
333
334                if (del) {
335                        st = set_reset(head, set_name);
336                } else {
337                        st = set_setstr(head, set_name, value);
338                }
339
340                if (set_getstr(head, set_name) == NULL &&
341                    set_find(head, set_name)) {
342                        /* This happens when changing the passwd, for example.
343                           Showing these msgs instead gives slightly clearer
344                           feedback. */
345                        if (st) {
346                                irc_rootmsg(irc, "Setting changed successfully");
347                        } else {
348                                irc_rootmsg(irc, "Failed to change setting");
349                        }
350                } else {
351                        cmd_showset(irc, head, set_name);
352                }
353        } else if (set_name) {
354                cmd_showset(irc, head, set_name);
355        } else {
356                set_t *s = *head;
357                while (s) {
358                        if (set_isvisible(s)) {
359                                cmd_showset(irc, &s, s->key);
360                        }
361                        s = s->next;
362                }
363        }
364
365        return 1;
366}
367
368static int cmd_account_set_checkflags(irc_t *irc, set_t *s)
369{
370        account_t *a = s->data;
371
372        if (a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY) {
373                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "off");
374                return 0;
375        } else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) {
376                irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on");
377                return 0;
378        }
379
380        return 1;
381}
382
383static void cmd_account(irc_t *irc, char **cmd)
384{
385        account_t *a;
386        int len;
387
388        if (global.conf->authmode == AUTHMODE_REGISTERED && !(irc->status & USTATUS_IDENTIFIED)) {
389                irc_rootmsg(irc, "This server only accepts registered users");
390                return;
391        }
392
393        len = strlen(cmd[1]);
394
395        if (len >= 1 && g_strncasecmp(cmd[1], "add", len) == 0) {
396                struct prpl *prpl;
397
398                MIN_ARGS(3);
399
400                if (cmd[4] == NULL) {
401                        for (a = irc->b->accounts; a; a = a->next) {
402                                if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
403                                        irc_rootmsg(irc, "Enter password for account %s "
404                                                    "first (use /OPER)", a->tag);
405                                        return;
406                                }
407                        }
408
409                        irc->status |= OPER_HACK_ACCOUNT_PASSWORD;
410                }
411
412                prpl = find_protocol(cmd[2]);
413
414                if (prpl == NULL) {
415                        irc_rootmsg(irc, "Unknown protocol");
416                        return;
417                }
418
419                for (a = irc->b->accounts; a; a = a->next) {
420                        if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
421                                irc_rootmsg(irc, "Warning: You already have an account with "
422                                            "protocol `%s' and username `%s'. Are you accidentally "
423                                            "trying to add it twice?", prpl->name, cmd[3]);
424                        }
425                }
426
427                a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
428                if (cmd[5]) {
429                        irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
430                                    "is now deprecated. Use `account set' instead.");
431                        set_setstr(&a->set, "server", cmd[5]);
432                }
433
434                irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);
435
436                if (cmd[4] == NULL) {
437                        set_t *oauth = set_find(&a->set, "oauth");
438                        if (oauth && bool2int(set_value(oauth))) {
439                                *a->pass = '\0';
440                                irc_rootmsg(irc, "No need to enter a password for this "
441                                            "account since it's using OAuth");
442                        } else {
443                                irc_rootmsg(irc, "You can now use the /OPER command to "
444                                            "enter the password");
445                                if (oauth) {
446                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
447                                                    "the account supports it: account %s "
448                                                    "set oauth on", a->tag);
449                                }
450                        }
451                }
452
453                return;
454        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
455                int i = 0;
456
457                if (strchr(irc->umode, 'b')) {
458                        irc_rootmsg(irc, "Account list:");
459                }
460
461                for (a = irc->b->accounts; a; a = a->next) {
462                        char *con;
463
464                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
465                                con = " (connected)";
466                        } else if (a->ic) {
467                                con = " (connecting)";
468                        } else if (a->reconnect) {
469                                con = " (awaiting reconnect)";
470                        } else {
471                                con = "";
472                        }
473
474                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con);
475
476                        i++;
477                }
478                irc_rootmsg(irc, "End of account list");
479
480                return;
481        } else if (cmd[2]) {
482                /* Try the following two only if cmd[2] == NULL */
483        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
484                if (irc->b->accounts) {
485                        irc_rootmsg(irc, "Trying to get all accounts connected...");
486
487                        for (a = irc->b->accounts; a; a = a->next) {
488                                if (!a->ic && a->auto_connect) {
489                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
490                                                irc_rootmsg(irc, "Enter password for account %s "
491                                                            "first (use /OPER)", a->tag);
492                                        } else {
493                                                account_on(irc->b, a);
494                                        }
495                                }
496                        }
497                } else {
498                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
499                }
500
501                return;
502        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
503                irc_rootmsg(irc, "Deactivating all active (re)connections...");
504
505                for (a = irc->b->accounts; a; a = a->next) {
506                        if (a->ic) {
507                                account_off(irc->b, a);
508                        } else if (a->reconnect) {
509                                cancel_auto_reconnect(a);
510                        }
511                }
512
513                return;
514        }
515
516        MIN_ARGS(2);
517        len = strlen(cmd[2]);
518
519        /* At least right now, don't accept on/off/set/del as account IDs even
520           if they're a proper match, since people not familiar with the new
521           syntax yet may get a confusing/nasty surprise. */
522        if (g_strcasecmp(cmd[1], "on") == 0 ||
523            g_strcasecmp(cmd[1], "off") == 0 ||
524            g_strcasecmp(cmd[1], "set") == 0 ||
525            g_strcasecmp(cmd[1], "del") == 0 ||
526            (a = account_get(irc->b, cmd[1])) == NULL) {
527                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
528
529                return;
530        }
531
532        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
533                if (a->ic) {
534                        irc_rootmsg(irc, "Account is still logged in, can't delete");
535                } else {
536                        account_del(irc->b, a);
537                        irc_rootmsg(irc, "Account deleted");
538                }
539        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
540                if (a->ic) {
541                        irc_rootmsg(irc, "Account already online");
542                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
543                        irc_rootmsg(irc, "Enter password for account %s "
544                                    "first (use /OPER)", a->tag);
545                } else {
546                        account_on(irc->b, a);
547                }
548        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
549                if (a->ic) {
550                        account_off(irc->b, a);
551                } else if (a->reconnect) {
552                        cancel_auto_reconnect(a);
553                        irc_rootmsg(irc, "Reconnect cancelled");
554                } else {
555                        irc_rootmsg(irc, "Account already offline");
556                }
557        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
558                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
559        } else {
560                irc_rootmsg(irc,
561                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
562                            cmd[2]);
563        }
564}
565
566static void cmd_channel(irc_t *irc, char **cmd)
567{
568        irc_channel_t *ic;
569        int len;
570
571        len = strlen(cmd[1]);
572
573        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
574                GSList *l;
575                int i = 0;
576
577                if (strchr(irc->umode, 'b')) {
578                        irc_rootmsg(irc, "Channel list:");
579                }
580
581                for (l = irc->channels; l; l = l->next) {
582                        irc_channel_t *ic = l->data;
583
584                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
585                                    set_getstr(&ic->set, "type"),
586                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
587
588                        i++;
589                }
590                irc_rootmsg(irc, "End of channel list");
591
592                return;
593        }
594
595        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
596                /* If this doesn't match any channel, maybe this is the short
597                   syntax (only works when used inside a channel). */
598                if ((ic = irc->root->last_channel) &&
599                    (len = strlen(cmd[1])) &&
600                    g_strncasecmp(cmd[1], "set", len) == 0) {
601                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
602                } else {
603                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
604                }
605
606                return;
607        }
608
609        MIN_ARGS(2);
610        len = strlen(cmd[2]);
611
612        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
613                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
614        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
615                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
616                    ic != ic->irc->default_channel) {
617                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
618                        irc_channel_free(ic);
619                } else {
620                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
621                                    "channels you're still in cannot be deleted).",
622                                    irc->default_channel->name);
623                }
624        } else {
625                irc_rootmsg(irc,
626                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
627                            cmd[1]);
628        }
629}
630
631static void cmd_add(irc_t *irc, char **cmd)
632{
633        account_t *a;
634        int add_on_server = 1;
635        char *handle = NULL, *s;
636
637        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
638                MIN_ARGS(3);
639                add_on_server = 0;
640                cmd++;
641        }
642
643        if (!(a = account_get(irc->b, cmd[1]))) {
644                irc_rootmsg(irc, "Invalid account");
645                return;
646        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
647                irc_rootmsg(irc, "That account is not on-line");
648                return;
649        } else if (add_on_server && !a->prpl->add_buddy) {
650                irc_rootmsg(irc, "IM protocol does not support contact list modification");
651                return;
652        }
653
654        if (cmd[3]) {
655                if (!nick_ok(irc, cmd[3])) {
656                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
657                        return;
658                } else if (irc_user_by_name(irc, cmd[3])) {
659                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
660                        return;
661                } else {
662                        nick_set_raw(a, cmd[2], cmd[3]);
663                }
664        }
665
666        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
667            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
668                /* If there's no @ or it's the last char, append the user's
669                   domain name now. Exclude handles starting with a _ so
670                   adding _xmlconsole will keep working. */
671                if (s) {
672                        *s = '\0';
673                }
674                if ((s = strchr(a->user, '@'))) {
675                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
676                }
677        }
678
679        if (add_on_server) {
680                irc_channel_t *ic;
681                char *s, *group = NULL;;
682
683                if ((ic = irc->root->last_channel) &&
684                    (s = set_getstr(&ic->set, "fill_by")) &&
685                    strcmp(s, "group") == 0 &&
686                    (group = set_getstr(&ic->set, "group"))) {
687                        irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
688                                    cmd[2], group);
689                } else {
690                        irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
691                }
692
693                a->prpl->add_buddy(a->ic, cmd[2], group);
694        } else {
695                bee_user_t *bu;
696                irc_user_t *iu;
697
698                /* Only for add -tmp. For regular adds, this callback will
699                   be called once the IM server confirms. */
700                if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
701                    (iu = bu->ui_data)) {
702                        irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
703                                    "to contact `%s'", iu->nick, cmd[2]);
704                }
705        }
706
707        g_free(handle);
708}
709
710static void cmd_remove(irc_t *irc, char **cmd)
711{
712        irc_user_t *iu;
713        bee_user_t *bu;
714        char *s;
715
716        if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
717                irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
718                return;
719        }
720        s = g_strdup(bu->handle);
721
722        if (bu->ic->acc->prpl->remove_buddy) {
723                bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
724        } else {
725                irc_rootmsg(irc, "IM protocol does not support contact list modification, "
726                                 "removal will likely not be permanent");
727        }
728
729        nick_del(bu);
730        if (g_slist_find(irc->users, iu)) {
731                bee_user_free(irc->b, bu);
732        }
733
734        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
735        g_free(s);
736
737        return;
738}
739
740static void cmd_info(irc_t *irc, char **cmd)
741{
742        struct im_connection *ic;
743        account_t *a;
744
745        if (!cmd[2]) {
746                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
747                if (!iu || !iu->bu) {
748                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
749                        return;
750                }
751                ic = iu->bu->ic;
752                cmd[2] = iu->bu->handle;
753        } else if (!(a = account_get(irc->b, cmd[1]))) {
754                irc_rootmsg(irc, "Invalid account");
755                return;
756        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
757                irc_rootmsg(irc, "That account is not on-line");
758                return;
759        }
760
761        if (!ic->acc->prpl->get_info) {
762                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
763        } else {
764                ic->acc->prpl->get_info(ic, cmd[2]);
765        }
766}
767
768static void cmd_rename(irc_t *irc, char **cmd)
769{
770        irc_user_t *iu, *old;
771        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
772
773        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
774
775        if (iu == NULL) {
776                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
777        } else if (del) {
778                if (iu->bu) {
779                        bee_irc_user_nick_reset(iu);
780                }
781                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
782        } else if (iu == irc->user) {
783                irc_rootmsg(irc, "Use /nick to change your own nickname");
784        } else if (!nick_ok(irc, cmd[2])) {
785                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
786        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
787                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
788        } else {
789                if (!irc_user_set_nick(iu, cmd[2])) {
790                        irc_rootmsg(irc, "Error while changing nick");
791                        return;
792                }
793
794                if (iu == irc->root) {
795                        /* If we're called internally (user did "set root_nick"),
796                           let's not go O(INF). :-) */
797                        if (strcmp(cmd[0], "set_rename") != 0) {
798                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
799                        }
800                } else if (iu->bu) {
801                        nick_set(iu->bu, cmd[2]);
802                }
803
804                irc_rootmsg(irc, "Nick successfully changed");
805        }
806}
807
808char *set_eval_root_nick(set_t *set, char *new_nick)
809{
810        irc_t *irc = set->data;
811
812        if (strcmp(irc->root->nick, new_nick) != 0) {
813                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
814
815                cmd_rename(irc, cmd);
816        }
817
818        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
819}
820
821static void cmd_block(irc_t *irc, char **cmd)
822{
823        struct im_connection *ic;
824        account_t *a;
825
826        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
827                char *format;
828                GSList *l;
829
830                if (strchr(irc->umode, 'b') != NULL) {
831                        format = "%s\t%s";
832                } else {
833                        format = "%-32.32s  %-16.16s";
834                }
835
836                irc_rootmsg(irc, format, "Handle", "Nickname");
837                for (l = a->ic->deny; l; l = l->next) {
838                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
839                        irc_user_t *iu = bu ? bu->ui_data : NULL;
840                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
841                }
842                irc_rootmsg(irc, "End of list.");
843
844                return;
845        } else if (!cmd[2]) {
846                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
847                if (!iu || !iu->bu) {
848                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
849                        return;
850                }
851                ic = iu->bu->ic;
852                cmd[2] = iu->bu->handle;
853        } else if (!(a = account_get(irc->b, cmd[1]))) {
854                irc_rootmsg(irc, "Invalid account");
855                return;
856        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
857                irc_rootmsg(irc, "That account is not on-line");
858                return;
859        }
860
861        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
862                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
863        } else {
864                imc_rem_allow(ic, cmd[2]);
865                imc_add_block(ic, cmd[2]);
866                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
867        }
868}
869
870static void cmd_allow(irc_t *irc, char **cmd)
871{
872        struct im_connection *ic;
873        account_t *a;
874
875        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
876                char *format;
877                GSList *l;
878
879                if (strchr(irc->umode, 'b') != NULL) {
880                        format = "%s\t%s";
881                } else {
882                        format = "%-32.32s  %-16.16s";
883                }
884
885                irc_rootmsg(irc, format, "Handle", "Nickname");
886                for (l = a->ic->permit; l; l = l->next) {
887                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
888                        irc_user_t *iu = bu ? bu->ui_data : NULL;
889                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
890                }
891                irc_rootmsg(irc, "End of list.");
892
893                return;
894        } else if (!cmd[2]) {
895                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
896                if (!iu || !iu->bu) {
897                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
898                        return;
899                }
900                ic = iu->bu->ic;
901                cmd[2] = iu->bu->handle;
902        } else if (!(a = account_get(irc->b, cmd[1]))) {
903                irc_rootmsg(irc, "Invalid account");
904                return;
905        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
906                irc_rootmsg(irc, "That account is not on-line");
907                return;
908        }
909
910        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
911                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
912        } else {
913                imc_rem_block(ic, cmd[2]);
914                imc_add_allow(ic, cmd[2]);
915
916                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
917        }
918}
919
920static void cmd_yesno(irc_t *irc, char **cmd)
921{
922        query_t *q = NULL;
923        int numq = 0;
924
925        if (irc->queries == NULL) {
926                /* Alright, alright, let's add a tiny easter egg here. */
927                static irc_t *last_irc = NULL;
928                static time_t last_time = 0;
929                static int times = 0;
930                static const char *msg[] = {
931                        "Oh yeah, that's right.",
932                        "Alright, alright. Now go back to work.",
933                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
934                        "Yes?",
935                        "No?",
936                };
937
938                if (last_irc == irc && time(NULL) - last_time < 15) {
939                        if ((++times >= 3)) {
940                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
941                                last_irc = NULL;
942                                times = 0;
943                                return;
944                        }
945                } else {
946                        last_time = time(NULL);
947                        last_irc = irc;
948                        times = 0;
949                }
950
951                irc_rootmsg(irc, "Did I ask you something?");
952                return;
953        }
954
955        /* If there's an argument, the user seems to want to answer another question than the
956           first/last (depending on the query_order setting) one. */
957        if (cmd[1]) {
958                if (sscanf(cmd[1], "%d", &numq) != 1) {
959                        irc_rootmsg(irc, "Invalid query number");
960                        return;
961                }
962
963                for (q = irc->queries; q; q = q->next, numq--) {
964                        if (numq == 0) {
965                                break;
966                        }
967                }
968
969                if (!q) {
970                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
971                        return;
972                }
973        }
974
975        if (g_strcasecmp(cmd[0], "yes") == 0) {
976                query_answer(irc, q, 1);
977        } else if (g_strcasecmp(cmd[0], "no") == 0) {
978                query_answer(irc, q, 0);
979        }
980}
981
982static void cmd_set(irc_t *irc, char **cmd)
983{
984        cmd_set_real(irc, cmd, &irc->b->set, NULL);
985}
986
987static void cmd_blist(irc_t *irc, char **cmd)
988{
989        int online = 0, away = 0, offline = 0, ismatch = 0;
990        GSList *l;
991        GRegex *regex = NULL;
992        GError *error = NULL;
993        char s[256];
994        char *format;
995        int n_online = 0, n_away = 0, n_offline = 0;
996
997        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
998                online = offline = away = 1;
999        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1000                offline = 1;
1001        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1002                away = 1;
1003        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1004                online = 1;
1005        } else {
1006                online = away = 1;
1007        }
1008
1009        if (cmd[2]) {
1010                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1011        }
1012
1013        if (error) {
1014                irc_rootmsg(irc, error->message);
1015                g_error_free(error);
1016        }
1017
1018        if (strchr(irc->umode, 'b') != NULL) {
1019                format = "%s\t%s\t%s";
1020        } else {
1021                format = "%-16.16s  %-40.40s  %s";
1022        }
1023
1024        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1025
1026        if (irc->root->last_channel &&
1027            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1028                irc->root->last_channel = NULL;
1029        }
1030
1031        for (l = irc->users; l; l = l->next) {
1032                irc_user_t *iu = l->data;
1033                bee_user_t *bu = iu->bu;
1034
1035                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1036                        ismatch = 1;
1037                } else {
1038                        ismatch = 0;
1039                }
1040
1041                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1042                        continue;
1043                }
1044
1045                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1046                        if (ismatch == 1 && online == 1) {
1047                                char st[256] = "Online";
1048
1049                                if (bu->status_msg) {
1050                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1051                                }
1052
1053                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1054                                irc_rootmsg(irc, format, iu->nick, s, st);
1055                        }
1056
1057                        n_online++;
1058                }
1059
1060                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1061                        if (ismatch == 1 && away == 1) {
1062                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1063                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1064                        }
1065                        n_away++;
1066                }
1067
1068                if (!(bu->flags & BEE_USER_ONLINE)) {
1069                        if (ismatch == 1 && offline == 1) {
1070                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1071                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1072                        }
1073                        n_offline++;
1074                }
1075        }
1076
1077        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1078                    n_away, n_offline);
1079
1080        if (regex) {
1081                g_regex_unref(regex);
1082        }
1083}
1084
1085static void cmd_qlist(irc_t *irc, char **cmd)
1086{
1087        query_t *q = irc->queries;
1088        int num;
1089
1090        if (!q) {
1091                irc_rootmsg(irc, "There are no pending questions.");
1092                return;
1093        }
1094
1095        irc_rootmsg(irc, "Pending queries:");
1096
1097        for (num = 0; q; q = q->next, num++) {
1098                if (q->ic) { /* Not necessary yet, but it might come later */
1099                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1100                } else {
1101                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1102                }
1103        }
1104}
1105
1106static void cmd_chat(irc_t *irc, char **cmd)
1107{
1108        account_t *acc;
1109
1110        if (g_strcasecmp(cmd[1], "add") == 0) {
1111                char *channel, *s;
1112                struct irc_channel *ic;
1113
1114                MIN_ARGS(3);
1115
1116                if (!(acc = account_get(irc->b, cmd[2]))) {
1117                        irc_rootmsg(irc, "Invalid account");
1118                        return;
1119                } else if (!acc->prpl->chat_join) {
1120                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1121                        return;
1122                }
1123
1124                if (cmd[4] == NULL) {
1125                        channel = g_strdup(cmd[3]);
1126                        if ((s = strchr(channel, '@'))) {
1127                                *s = 0;
1128                        }
1129                } else {
1130                        channel = g_strdup(cmd[4]);
1131                }
1132
1133                if (strchr(CTYPES, channel[0]) == NULL) {
1134                        s = g_strdup_printf("#%s", channel);
1135                        g_free(channel);
1136                        channel = s;
1137
1138                        irc_channel_name_strip(channel);
1139                }
1140
1141                if ((ic = irc_channel_new(irc, channel)) &&
1142                    set_setstr(&ic->set, "type", "chat") &&
1143                    set_setstr(&ic->set, "chat_type", "room") &&
1144                    set_setstr(&ic->set, "account", cmd[2]) &&
1145                    set_setstr(&ic->set, "room", cmd[3])) {
1146                        irc_rootmsg(irc, "Chatroom successfully added.");
1147                } else {
1148                        if (ic) {
1149                                irc_channel_free(ic);
1150                        }
1151
1152                        irc_rootmsg(irc, "Could not add chatroom.");
1153                }
1154                g_free(channel);
1155        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1156                irc_user_t *iu;
1157
1158                MIN_ARGS(2);
1159
1160                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1161                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1162                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1163                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1164                                            "a groupchat with %s.", iu->nick);
1165                        }
1166                } else {
1167                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1168                }
1169        } else if (g_strcasecmp(cmd[1], "list") == 0 ||
1170                   g_strcasecmp(cmd[1], "set") == 0 ||
1171                   g_strcasecmp(cmd[1], "del") == 0) {
1172                irc_rootmsg(irc,
1173                            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
1174                cmd_channel(irc, cmd);
1175        } else {
1176                irc_rootmsg(irc,
1177                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1178                            cmd[1]);
1179        }
1180}
1181
1182static void cmd_group(irc_t *irc, char **cmd)
1183{
1184        GSList *l;
1185        int len;
1186
1187        len = strlen(cmd[1]);
1188        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1189                int n = 0;
1190
1191                if (strchr(irc->umode, 'b')) {
1192                        irc_rootmsg(irc, "Group list:");
1193                }
1194
1195                for (l = irc->b->groups; l; l = l->next) {
1196                        bee_group_t *bg = l->data;
1197                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1198                }
1199                irc_rootmsg(irc, "End of group list");
1200        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1201                bee_group_t *bg;
1202                int n = 0;
1203
1204                MIN_ARGS(2);
1205                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1206
1207                if (bg) {
1208                        if (strchr(irc->umode, 'b')) {
1209                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1210                        }
1211                        for (l = irc->b->users; l; l = l->next) {
1212                                bee_user_t *bu = l->data;
1213                                if (bu->group == bg) {
1214                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1215                                }
1216                        }
1217                        irc_rootmsg(irc, "End of member list");
1218                } else {
1219                        irc_rootmsg(irc,
1220                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1221                                    cmd[2]);
1222                }
1223        } else {
1224                irc_rootmsg(irc,
1225                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1226                            cmd[1]);
1227        }
1228}
1229
1230static void cmd_transfer(irc_t *irc, char **cmd)
1231{
1232        GSList *files = irc->file_transfers;
1233
1234        enum { LIST, REJECT, CANCEL };
1235        int subcmd = LIST;
1236        int fid;
1237
1238        if (!files) {
1239                irc_rootmsg(irc, "No pending transfers");
1240                return;
1241        }
1242
1243        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1244                subcmd = REJECT;
1245        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1246                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1247                subcmd = CANCEL;
1248        }
1249
1250        for (; files; files = g_slist_next(files)) {
1251                file_transfer_t *file = files->data;
1252
1253                switch (subcmd) {
1254                case LIST:
1255                        if (file->status == FT_STATUS_LISTENING) {
1256                                irc_rootmsg(irc,
1257                                            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1258                        } else {
1259                                int kb_per_s = 0;
1260                                time_t diff = time(NULL) - file->started ? : 1;
1261                                if ((file->started > 0) && (file->bytes_transferred > 0)) {
1262                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1263                                }
1264
1265                                irc_rootmsg(irc,
1266                                            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
1267                                            file->file_name,
1268                                            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
1269                        }
1270                        break;
1271                case REJECT:
1272                        if (file->status == FT_STATUS_LISTENING) {
1273                                irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
1274                                imcb_file_canceled(file->ic, file, "Denied by user");
1275                        }
1276                        break;
1277                case CANCEL:
1278                        if (file->local_id == fid) {
1279                                irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
1280                                imcb_file_canceled(file->ic, file, "Canceled by user");
1281                        }
1282                        break;
1283                }
1284        }
1285}
1286
1287static void cmd_nick(irc_t *irc, char **cmd)
1288{
1289        irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
1290}
1291
1292/* Maybe this should be a stand-alone command as well? */
1293static void bitlbee_whatsnew(irc_t *irc)
1294{
1295        int last = set_getint(&irc->b->set, "last_version");
1296        char s[16], *msg;
1297
1298        if (last >= BITLBEE_VERSION_CODE) {
1299                return;
1300        }
1301
1302        msg = help_get_whatsnew(&(global.help), last);
1303
1304        if (msg) {
1305                irc_rootmsg(irc, "%s: This seems to be your first time using this "
1306                            "this version of BitlBee. Here's a list of new "
1307                            "features you may like to know about:\n\n%s\n",
1308                            irc->user->nick, msg);
1309        }
1310
1311        g_free(msg);
1312
1313        g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
1314        set_setstr(&irc->b->set, "last_version", s);
1315}
1316
1317/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1318command_t root_commands[] = {
1319        { "account",        1, cmd_account,        0 },
1320        { "add",            2, cmd_add,            0 },
1321        { "allow",          1, cmd_allow,          0 },
1322        { "blist",          0, cmd_blist,          0 },
1323        { "block",          1, cmd_block,          0 },
1324        { "channel",        1, cmd_channel,        0 },
1325        { "chat",           1, cmd_chat,           0 },
1326        { "drop",           1, cmd_drop,           0 },
1327        { "ft",             0, cmd_transfer,       0 },
1328        { "group",          1, cmd_group,          0 },
1329        { "help",           0, cmd_help,           0 },
1330        { "identify",       0, cmd_identify,       0 },
1331        { "info",           1, cmd_info,           0 },
1332        { "nick",           1, cmd_nick,           0 },
1333        { "no",             0, cmd_yesno,          0 },
1334        { "qlist",          0, cmd_qlist,          0 },
1335        { "register",       0, cmd_register,       0 },
1336        { "remove",         1, cmd_remove,         0 },
1337        { "rename",         2, cmd_rename,         0 },
1338        { "save",           0, cmd_save,           0 },
1339        { "set",            0, cmd_set,            0 },
1340        { "transfer",       0, cmd_transfer,       0 },
1341        { "yes",            0, cmd_yesno,          0 },
1342        /* Not expecting too many plugins adding root commands so just make a
1343           dumb array with some empty entried at the end. */
1344        { NULL },
1345        { NULL },
1346        { NULL },
1347        { NULL },
1348        { NULL },
1349        { NULL },
1350        { NULL },
1351        { NULL },
1352        { NULL },
1353};
1354static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);
1355
1356gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
1357{
1358        int i;
1359
1360        if (root_commands[num_root_commands - 2].command) {
1361                /* Planning fail! List is full. */
1362                return FALSE;
1363        }
1364
1365        for (i = 0; root_commands[i].command; i++) {
1366                if (g_strcasecmp(root_commands[i].command, command) == 0) {
1367                        return FALSE;
1368                } else if (g_strcasecmp(root_commands[i].command, command) > 0) {
1369                        break;
1370                }
1371        }
1372        memmove(root_commands + i + 1, root_commands + i,
1373                sizeof(command_t) * (num_root_commands - i - 1));
1374
1375        root_commands[i].command = g_strdup(command);
1376        root_commands[i].required_parameters = params;
1377        root_commands[i].execute = func;
1378        root_commands[i].flags = flags;
1379
1380        return TRUE;
1381}
Note: See TracBrowser for help on using the repository browser.