source: root_commands.c @ 727a68b

Last change on this file since 727a68b was f95e606, checked in by dequis <dx@…>, at 2016-10-17T04:37:45Z

chat list: Fix alignment/truncation when utf8 characters are present

By using the str_pad_and_truncate() function I just added

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