source: root_commands.c @ 3ac6d9f

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

Support for locked-down accounts

In certain situations, e.g. when working with pregenerated
configurations, it is useful to be able lock down accounts so they
cannot be deleted and authentication information (user, password,
server) cannot be changed.

We mark such sensitive settings with ACC_SET_LOCKABLE and will refuse to
change them if the account is locked by setting the ACC_FLAG_LOCKED
flag.

This flag is stored in the xml files as account attribute locked="true".

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