source: root_commands.c @ 537d9b9

Last change on this file since 537d9b9 was 537d9b9, checked in by dequis <dx@…>, at 2016-11-20T08:40:36Z

Merge master up to commit '9f03c47' into parson

  • Property mode set to 100644
File size: 42.4 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 if (prpl->options & PRPL_OPT_NO_PASSWORD) {
473                                *a->pass = '\0';
474                        } else if (prpl->options & PRPL_OPT_PASSWORD_OPTIONAL) {
475                                *a->pass = '\0';
476                                irc_rootmsg(irc, "Passwords are optional for this account. "
477                                            "If you wish to enter the password with /OPER, do "
478                                            "account %s set -del password", a->tag);
479                        } else {
480                                irc_rootmsg(irc, "You can now use the /OPER command to "
481                                            "enter the password");
482                                if (oauth) {
483                                        irc_rootmsg(irc, "Alternatively, enable OAuth if "
484                                                    "the account supports it: account %s "
485                                                    "set oauth on", a->tag);
486                                }
487                        }
488                } else if (prpl->options & PRPL_OPT_NO_PASSWORD) {
489                        irc_rootmsg(irc, "Note: this account doesn't use password for login");
490                }
491
492                return;
493        } else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
494                int i = 0;
495
496                if (strchr(irc->umode, 'b')) {
497                        irc_rootmsg(irc, "Account list:");
498                }
499
500                for (a = irc->b->accounts; a; a = a->next) {
501                        char *con = NULL, *protocol = NULL;
502
503                        if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
504                                con = " (connected)";
505                        } else if (a->ic) {
506                                con = " (connecting)";
507                        } else if (a->reconnect) {
508                                con = " (awaiting reconnect)";
509                        } else {
510                                con = "";
511                        }
512                        if (a->prpl == &protocol_missing) {
513                                protocol = g_strdup_printf("%s (missing!)", set_getstr(&a->set, "_protocol_name"));
514                        } else {
515                                protocol = g_strdup(a->prpl->name);
516                        }
517
518                        irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, protocol, a->user, con);
519                        g_free(protocol);
520
521                        i++;
522                }
523                irc_rootmsg(irc, "End of account list");
524
525                return;
526        } else if (cmd[2]) {
527                /* Try the following two only if cmd[2] == NULL */
528        } else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
529                if (irc->b->accounts) {
530                        irc_rootmsg(irc, "Trying to get all accounts connected...");
531
532                        for (a = irc->b->accounts; a; a = a->next) {
533                                if (!a->ic && a->auto_connect && a->prpl != &protocol_missing) {
534                                        if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
535                                                irc_rootmsg(irc, "Enter password for account %s "
536                                                            "first (use /OPER)", a->tag);
537                                        } else {
538                                                account_on(irc->b, a);
539                                        }
540                                }
541                        }
542                } else {
543                        irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
544                }
545
546                return;
547        } else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
548                irc_rootmsg(irc, "Deactivating all active (re)connections...");
549
550                for (a = irc->b->accounts; a; a = a->next) {
551                        if (a->ic) {
552                                account_off(irc->b, a);
553                        } else if (a->reconnect) {
554                                cancel_auto_reconnect(a);
555                        }
556                }
557
558                return;
559        }
560
561        MIN_ARGS(2);
562        len = strlen(cmd[2]);
563
564        /* At least right now, don't accept on/off/set/del as account IDs even
565           if they're a proper match, since people not familiar with the new
566           syntax yet may get a confusing/nasty surprise. */
567        if (g_strcasecmp(cmd[1], "on") == 0 ||
568            g_strcasecmp(cmd[1], "off") == 0 ||
569            g_strcasecmp(cmd[1], "set") == 0 ||
570            g_strcasecmp(cmd[1], "del") == 0 ||
571            (a = account_get(irc->b, cmd[1])) == NULL) {
572                irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);
573
574                return;
575        }
576
577        if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
578                if (a->flags & ACC_FLAG_LOCKED) {
579                        irc_rootmsg(irc, "Account is locked, can't delete");
580                }
581                else if (a->ic) {
582                        irc_rootmsg(irc, "Account is still logged in, can't delete");
583                } else {
584                        account_del(irc->b, a);
585                        irc_rootmsg(irc, "Account deleted");
586                }
587        } else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
588                if (a->ic) {
589                        irc_rootmsg(irc, "Account already online");
590                } else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
591                        irc_rootmsg(irc, "Enter password for account %s "
592                                    "first (use /OPER)", a->tag);
593                } else if (a->prpl == &protocol_missing) {
594                        irc_rootmsg(irc, "Protocol `%s' not recognised (plugin may be missing or not running?)",
595                                    set_getstr(&a->set, "_protocol_name"));
596                } else {
597                        account_on(irc->b, a);
598                }
599        } else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
600                if (a->ic) {
601                        account_off(irc->b, a);
602                } else if (a->reconnect) {
603                        cancel_auto_reconnect(a);
604                        irc_rootmsg(irc, "Reconnect cancelled");
605                } else {
606                        irc_rootmsg(irc, "Account already offline");
607                }
608        } else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
609                cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
610        } else {
611                irc_rootmsg(irc,
612                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
613                            cmd[2]);
614        }
615}
616
617static void cmd_channel(irc_t *irc, char **cmd)
618{
619        irc_channel_t *ic;
620        int len;
621
622        len = strlen(cmd[1]);
623
624        if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
625                GSList *l;
626                int i = 0;
627
628                if (strchr(irc->umode, 'b')) {
629                        irc_rootmsg(irc, "Channel list:");
630                }
631
632                for (l = irc->channels; l; l = l->next) {
633                        irc_channel_t *ic = l->data;
634
635                        irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
636                                    set_getstr(&ic->set, "type"),
637                                    ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");
638
639                        i++;
640                }
641                irc_rootmsg(irc, "End of channel list");
642
643                return;
644        }
645
646        if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
647                /* If this doesn't match any channel, maybe this is the short
648                   syntax (only works when used inside a channel). */
649                if ((ic = irc->root->last_channel) &&
650                    (len = strlen(cmd[1])) &&
651                    g_strncasecmp(cmd[1], "set", len) == 0) {
652                        cmd_set_real(irc, cmd + 1, &ic->set, NULL);
653                } else {
654                        irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
655                }
656
657                return;
658        }
659
660        MIN_ARGS(2);
661        len = strlen(cmd[2]);
662
663        if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
664                cmd_set_real(irc, cmd + 2, &ic->set, NULL);
665        } else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
666                if (!(ic->flags & IRC_CHANNEL_JOINED) &&
667                    ic != ic->irc->default_channel) {
668                        irc_rootmsg(irc, "Channel %s deleted.", ic->name);
669                        irc_channel_free(ic);
670                } else {
671                        irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
672                                    "channels you're still in cannot be deleted).",
673                                    irc->default_channel->name);
674                }
675        } else {
676                irc_rootmsg(irc,
677                            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
678                            cmd[1]);
679        }
680}
681
682static void cmd_add(irc_t *irc, char **cmd)
683{
684        account_t *a;
685        int add_on_server = 1;
686        char *handle = NULL, *s;
687
688        if (g_strcasecmp(cmd[1], "-tmp") == 0) {
689                MIN_ARGS(3);
690                add_on_server = 0;
691                cmd++;
692        }
693
694        if (!(a = account_get(irc->b, cmd[1]))) {
695                irc_rootmsg(irc, "Invalid account");
696                return;
697        } else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
698                irc_rootmsg(irc, "That account is not on-line");
699                return;
700        } else if (add_on_server && !a->prpl->add_buddy) {
701                irc_rootmsg(irc, "IM protocol does not support contact list modification");
702                return;
703        }
704
705        if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
706            (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
707                /* If there's no @ or it's the last char, append the user's
708                   domain name now. Exclude handles starting with a _ so
709                   adding _xmlconsole will keep working. */
710                if (s) {
711                        *s = '\0';
712                }
713                if ((s = strchr(a->user, '@'))) {
714                        cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
715                }
716        }
717
718        if (cmd[3]) {
719                if (!nick_ok(irc, cmd[3])) {
720                        irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
721                        return;
722                } else if (irc_user_by_name(irc, cmd[3])) {
723                        irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
724                        return;
725                } else {
726                        nick_set_raw(a, cmd[2], cmd[3]);
727                }
728        }
729
730        if (add_on_server) {
731                irc_channel_t *ic;
732                char *s, *group = NULL;;
733
734                if ((ic = irc->root->last_channel) &&
735                    (s = set_getstr(&ic->set, "fill_by")) &&
736                    strcmp(s, "group") == 0 &&
737                    (group = set_getstr(&ic->set, "group"))) {
738                        irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
739                                    cmd[2], group);
740                } else {
741                        irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
742                }
743
744                a->prpl->add_buddy(a->ic, cmd[2], group);
745        } else {
746                bee_user_t *bu;
747                irc_user_t *iu;
748
749                /* Only for add -tmp. For regular adds, this callback will
750                   be called once the IM server confirms. */
751                if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
752                    (iu = bu->ui_data)) {
753                        irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
754                                    "to contact `%s'", iu->nick, cmd[2]);
755                }
756        }
757
758        g_free(handle);
759}
760
761static void cmd_remove(irc_t *irc, char **cmd)
762{
763        irc_user_t *iu;
764        bee_user_t *bu;
765        char *s;
766
767        if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
768                irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
769                return;
770        }
771        s = g_strdup(bu->handle);
772
773        if (bu->ic->acc->prpl->remove_buddy) {
774                bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
775        } else {
776                irc_rootmsg(irc, "IM protocol does not support contact list modification, "
777                                 "removal will likely not be permanent");
778        }
779
780        nick_del(bu);
781        if (g_slist_find(irc->users, iu)) {
782                bee_user_free(irc->b, bu);
783        }
784
785        irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
786        g_free(s);
787
788        return;
789}
790
791static void cmd_info(irc_t *irc, char **cmd)
792{
793        struct im_connection *ic;
794        account_t *a;
795
796        if (!cmd[2]) {
797                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
798                if (!iu || !iu->bu) {
799                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
800                        return;
801                }
802                ic = iu->bu->ic;
803                cmd[2] = iu->bu->handle;
804        } else if (!(a = account_get(irc->b, cmd[1]))) {
805                irc_rootmsg(irc, "Invalid account");
806                return;
807        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
808                irc_rootmsg(irc, "That account is not on-line");
809                return;
810        }
811
812        if (!ic->acc->prpl->get_info) {
813                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
814        } else {
815                ic->acc->prpl->get_info(ic, cmd[2]);
816        }
817}
818
819static void cmd_rename(irc_t *irc, char **cmd)
820{
821        irc_user_t *iu, *old;
822        gboolean del = g_strcasecmp(cmd[1], "-del") == 0;
823
824        iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);
825
826        if (iu == NULL) {
827                irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
828        } else if (del) {
829                if (iu->bu) {
830                        bee_irc_user_nick_reset(iu);
831                }
832                irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
833        } else if (iu == irc->user) {
834                irc_rootmsg(irc, "Use /nick to change your own nickname");
835        } else if (!nick_ok(irc, cmd[2])) {
836                irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
837        } else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
838                irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
839        } else {
840                if (!irc_user_set_nick(iu, cmd[2])) {
841                        irc_rootmsg(irc, "Error while changing nick");
842                        return;
843                }
844
845                if (iu == irc->root) {
846                        /* If we're called internally (user did "set root_nick"),
847                           let's not go O(INF). :-) */
848                        if (strcmp(cmd[0], "set_rename") != 0) {
849                                set_setstr(&irc->b->set, "root_nick", cmd[2]);
850                        }
851                } else if (iu->bu) {
852                        nick_set(iu->bu, cmd[2]);
853                }
854
855                irc_rootmsg(irc, "Nick successfully changed");
856        }
857}
858
859char *set_eval_root_nick(set_t *set, char *new_nick)
860{
861        irc_t *irc = set->data;
862
863        if (strcmp(irc->root->nick, new_nick) != 0) {
864                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
865
866                cmd_rename(irc, cmd);
867        }
868
869        return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
870}
871
872static void cmd_block(irc_t *irc, char **cmd)
873{
874        struct im_connection *ic;
875        account_t *a;
876
877        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
878                char *format;
879                GSList *l;
880
881                if (strchr(irc->umode, 'b') != NULL) {
882                        format = "%s\t%s";
883                } else {
884                        format = "%-32.32s  %-16.16s";
885                }
886
887                irc_rootmsg(irc, format, "Handle", "Nickname");
888                for (l = a->ic->deny; l; l = l->next) {
889                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
890                        irc_user_t *iu = bu ? bu->ui_data : NULL;
891                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
892                }
893                irc_rootmsg(irc, "End of list.");
894
895                return;
896        } else if (!cmd[2]) {
897                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
898                if (!iu || !iu->bu) {
899                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
900                        return;
901                }
902                ic = iu->bu->ic;
903                cmd[2] = iu->bu->handle;
904        } else if (!(a = account_get(irc->b, cmd[1]))) {
905                irc_rootmsg(irc, "Invalid account");
906                return;
907        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
908                irc_rootmsg(irc, "That account is not on-line");
909                return;
910        }
911
912        if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
913                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
914        } else {
915                imc_rem_allow(ic, cmd[2]);
916                imc_add_block(ic, cmd[2]);
917                irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
918        }
919}
920
921static void cmd_allow(irc_t *irc, char **cmd)
922{
923        struct im_connection *ic;
924        account_t *a;
925
926        if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
927                char *format;
928                GSList *l;
929
930                if (strchr(irc->umode, 'b') != NULL) {
931                        format = "%s\t%s";
932                } else {
933                        format = "%-32.32s  %-16.16s";
934                }
935
936                irc_rootmsg(irc, format, "Handle", "Nickname");
937                for (l = a->ic->permit; l; l = l->next) {
938                        bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
939                        irc_user_t *iu = bu ? bu->ui_data : NULL;
940                        irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
941                }
942                irc_rootmsg(irc, "End of list.");
943
944                return;
945        } else if (!cmd[2]) {
946                irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
947                if (!iu || !iu->bu) {
948                        irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
949                        return;
950                }
951                ic = iu->bu->ic;
952                cmd[2] = iu->bu->handle;
953        } else if (!(a = account_get(irc->b, cmd[1]))) {
954                irc_rootmsg(irc, "Invalid account");
955                return;
956        } else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
957                irc_rootmsg(irc, "That account is not on-line");
958                return;
959        }
960
961        if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
962                irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
963        } else {
964                imc_rem_block(ic, cmd[2]);
965                imc_add_allow(ic, cmd[2]);
966
967                irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
968        }
969}
970
971static void cmd_yesno(irc_t *irc, char **cmd)
972{
973        query_t *q = NULL;
974        int numq = 0;
975
976        if (irc->queries == NULL) {
977                /* Alright, alright, let's add a tiny easter egg here. */
978                static irc_t *last_irc = NULL;
979                static time_t last_time = 0;
980                static int times = 0;
981                static const char *msg[] = {
982                        "Oh yeah, that's right.",
983                        "Alright, alright. Now go back to work.",
984                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
985                        "Yes?",
986                        "No?",
987                };
988
989                if (last_irc == irc && time(NULL) - last_time < 15) {
990                        if ((++times >= 3)) {
991                                irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
992                                last_irc = NULL;
993                                times = 0;
994                                return;
995                        }
996                } else {
997                        last_time = time(NULL);
998                        last_irc = irc;
999                        times = 0;
1000                }
1001
1002                irc_rootmsg(irc, "Did I ask you something?");
1003                return;
1004        }
1005
1006        /* If there's an argument, the user seems to want to answer another question than the
1007           first/last (depending on the query_order setting) one. */
1008        if (cmd[1]) {
1009                if (sscanf(cmd[1], "%d", &numq) != 1) {
1010                        irc_rootmsg(irc, "Invalid query number");
1011                        return;
1012                }
1013
1014                for (q = irc->queries; q; q = q->next, numq--) {
1015                        if (numq == 0) {
1016                                break;
1017                        }
1018                }
1019
1020                if (!q) {
1021                        irc_rootmsg(irc, "Uhm, I never asked you something like that...");
1022                        return;
1023                }
1024        }
1025
1026        if (g_strcasecmp(cmd[0], "yes") == 0) {
1027                query_answer(irc, q, 1);
1028        } else if (g_strcasecmp(cmd[0], "no") == 0) {
1029                query_answer(irc, q, 0);
1030        }
1031}
1032
1033static void cmd_set(irc_t *irc, char **cmd)
1034{
1035        cmd_set_real(irc, cmd, &irc->b->set, NULL);
1036}
1037
1038static void cmd_blist(irc_t *irc, char **cmd)
1039{
1040        int online = 0, away = 0, offline = 0, ismatch = 0;
1041        GSList *l;
1042        GRegex *regex = NULL;
1043        GError *error = NULL;
1044        char s[256];
1045        char *format;
1046        int n_online = 0, n_away = 0, n_offline = 0;
1047
1048        if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
1049                online = offline = away = 1;
1050        } else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
1051                offline = 1;
1052        } else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
1053                away = 1;
1054        } else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
1055                online = 1;
1056        } else {
1057                online = away = 1;
1058        }
1059
1060        if (cmd[2]) {
1061                regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
1062        }
1063
1064        if (error) {
1065                irc_rootmsg(irc, error->message);
1066                g_error_free(error);
1067        }
1068
1069        if (strchr(irc->umode, 'b') != NULL) {
1070                format = "%s\t%s\t%s";
1071        } else {
1072                format = "%-16.16s  %-40.40s  %s";
1073        }
1074
1075        irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");
1076
1077        if (irc->root->last_channel &&
1078            strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
1079                irc->root->last_channel = NULL;
1080        }
1081
1082        for (l = irc->users; l; l = l->next) {
1083                irc_user_t *iu = l->data;
1084                bee_user_t *bu = iu->bu;
1085
1086                if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
1087                        ismatch = 1;
1088                } else {
1089                        ismatch = 0;
1090                }
1091
1092                if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
1093                        continue;
1094                }
1095
1096                if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
1097                        if (ismatch == 1 && online == 1) {
1098                                char st[256] = "Online";
1099
1100                                if (bu->status_msg) {
1101                                        g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
1102                                }
1103
1104                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1105                                irc_rootmsg(irc, format, iu->nick, s, st);
1106                        }
1107
1108                        n_online++;
1109                }
1110
1111                if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
1112                        if (ismatch == 1 && away == 1) {
1113                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1114                                irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
1115                        }
1116                        n_away++;
1117                }
1118
1119                if (!(bu->flags & BEE_USER_ONLINE)) {
1120                        if (ismatch == 1 && offline == 1) {
1121                                g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
1122                                irc_rootmsg(irc, format, iu->nick, s, "Offline");
1123                        }
1124                        n_offline++;
1125                }
1126        }
1127
1128        irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
1129                    n_away, n_offline);
1130
1131        if (regex) {
1132                g_regex_unref(regex);
1133        }
1134}
1135
1136static gint prplcmp(gconstpointer a, gconstpointer b)
1137{
1138        const struct prpl *pa = a;
1139        const struct prpl *pb = b;
1140
1141        return g_strcasecmp(pa->name, pb->name);
1142}
1143
1144static void prplstr(GList *prpls, GString *gstr)
1145{
1146        const char *last = NULL;
1147        GList *l;
1148        struct prpl *p;
1149
1150        prpls = g_list_copy(prpls);
1151        prpls = g_list_sort(prpls, prplcmp);
1152
1153        for (l = prpls; l; l = l->next) {
1154                p = l->data;
1155
1156                if (last && g_strcasecmp(p->name, last) == 0) {
1157                        /* Ignore duplicates (mainly for libpurple) */
1158                        continue;
1159                }
1160
1161                if (gstr->len != 0) {
1162                        g_string_append(gstr, ", ");
1163                }
1164
1165                g_string_append(gstr, p->name);
1166                last = p->name;
1167        }
1168
1169        g_list_free(prpls);
1170}
1171
1172static void cmd_plugins(irc_t *irc, char **cmd)
1173{
1174        GList *prpls;
1175        GString *gstr;
1176
1177#ifdef WITH_PLUGINS
1178        GList *l;
1179        struct plugin_info *info;
1180
1181        for (l = get_plugins(); l; l = l->next) {
1182                info = l->data;
1183                irc_rootmsg(irc, "%s:", info->name);
1184                irc_rootmsg(irc, "  Version: %s", info->version);
1185
1186                if (info->description) {
1187                        irc_rootmsg(irc, "  Description: %s", info->description);
1188                }
1189
1190                if (info->author) {
1191                        irc_rootmsg(irc, "  Author: %s", info->author);
1192                }
1193
1194                if (info->url) {
1195                        irc_rootmsg(irc, "  URL: %s", info->url);
1196                }
1197
1198                irc_rootmsg(irc, "");
1199        }
1200#endif
1201
1202        gstr = g_string_new(NULL);
1203        prpls = get_protocols();
1204
1205        if (prpls) {
1206                prplstr(prpls, gstr);
1207                irc_rootmsg(irc, "Enabled Protocols: %s", gstr->str);
1208                g_string_truncate(gstr, 0);
1209        }
1210
1211        prpls = get_protocols_disabled();
1212
1213        if (prpls) {
1214                prplstr(prpls, gstr);
1215                irc_rootmsg(irc, "Disabled Protocols: %s", gstr->str);
1216        }
1217
1218        g_string_free(gstr, TRUE);
1219}
1220
1221static void cmd_qlist(irc_t *irc, char **cmd)
1222{
1223        query_t *q = irc->queries;
1224        int num;
1225
1226        if (!q) {
1227                irc_rootmsg(irc, "There are no pending questions.");
1228                return;
1229        }
1230
1231        irc_rootmsg(irc, "Pending queries:");
1232
1233        for (num = 0; q; q = q->next, num++) {
1234                if (q->ic) { /* Not necessary yet, but it might come later */
1235                        irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
1236                } else {
1237                        irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
1238                }
1239        }
1240}
1241
1242static void cmd_chat(irc_t *irc, char **cmd)
1243{
1244        account_t *acc;
1245
1246        if (g_strcasecmp(cmd[1], "add") == 0) {
1247                bee_chat_info_t *ci;
1248                char *channel, *room, *s;
1249                struct irc_channel *ic;
1250                guint i;
1251
1252                MIN_ARGS(3);
1253
1254                if (!(acc = account_get(irc->b, cmd[2]))) {
1255                        irc_rootmsg(irc, "Invalid account");
1256                        return;
1257                } else if (!acc->prpl->chat_join) {
1258                        irc_rootmsg(irc, "Named chatrooms not supported on that account.");
1259                        return;
1260                }
1261
1262                if (cmd[3][0] == '!') {
1263                        if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
1264                                irc_rootmsg(irc, "Not logged in to account.");
1265                                return;
1266                        } else if (!acc->prpl->chat_list) {
1267                                irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
1268                                return;
1269                        }
1270
1271                        i = g_ascii_strtoull(cmd[3] + 1, NULL, 10);
1272                        ci = g_slist_nth_data(acc->ic->chatlist, i - 1);
1273
1274                        if (ci == NULL) {
1275                                irc_rootmsg(irc, "Invalid chatroom index");
1276                                return;
1277                        }
1278
1279                        room = ci->title;
1280                } else {
1281                        room = cmd[3];
1282                }
1283
1284                if (cmd[4] == NULL) {
1285                        channel = g_strdup(room);
1286                        if ((s = strchr(channel, '@'))) {
1287                                *s = 0;
1288                        }
1289                } else {
1290                        channel = g_strdup(cmd[4]);
1291                }
1292
1293                if (strchr(CTYPES, channel[0]) == NULL) {
1294                        s = g_strdup_printf("#%s", channel);
1295                        g_free(channel);
1296                        channel = s;
1297
1298                        irc_channel_name_strip(channel);
1299                }
1300
1301                if ((ic = irc_channel_new(irc, channel)) &&
1302                    set_setstr(&ic->set, "type", "chat") &&
1303                    set_setstr(&ic->set, "chat_type", "room") &&
1304                    set_setstr(&ic->set, "account", cmd[2]) &&
1305                    set_setstr(&ic->set, "room", room)) {
1306                        irc_rootmsg(irc, "Chatroom successfully added.");
1307                } else {
1308                        if (ic) {
1309                                irc_channel_free(ic);
1310                        }
1311
1312                        irc_rootmsg(irc, "Could not add chatroom.");
1313                }
1314                g_free(channel);
1315        } else if (g_strcasecmp(cmd[1], "list") == 0) {
1316                MIN_ARGS(2);
1317
1318                if (!(acc = account_get(irc->b, cmd[2]))) {
1319                        irc_rootmsg(irc, "Invalid account");
1320                        return;
1321                } else if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
1322                        irc_rootmsg(irc, "Not logged in to account.");
1323                        return;
1324                } else if (!acc->prpl->chat_list) {
1325                        irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
1326                        return;
1327                }
1328
1329                acc->prpl->chat_list(acc->ic, cmd[3]);
1330        } else if (g_strcasecmp(cmd[1], "with") == 0) {
1331                irc_user_t *iu;
1332
1333                MIN_ARGS(2);
1334
1335                if ((iu = irc_user_by_name(irc, cmd[2])) &&
1336                    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
1337                        if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
1338                                irc_rootmsg(irc, "(Possible) failure while trying to open "
1339                                            "a groupchat with %s.", iu->nick);
1340                        }
1341                } else {
1342                        irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
1343                }
1344        } else if (g_strcasecmp(cmd[1], "set") == 0 ||
1345                   g_strcasecmp(cmd[1], "del") == 0) {
1346                irc_rootmsg(irc,
1347                            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
1348                cmd_channel(irc, cmd);
1349        } else {
1350                irc_rootmsg(irc,
1351                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
1352                            cmd[1]);
1353        }
1354}
1355
1356/* some arbitrary numbers */
1357#define CHAT_TITLE_LEN_MIN 20
1358#define CHAT_TITLE_LEN_MAX 100
1359
1360void cmd_chat_list_finish(struct im_connection *ic)
1361{
1362        account_t *acc = ic->acc;
1363        bee_chat_info_t *ci;
1364        char *hformat, *iformat, *topic, *padded;
1365        GSList *l;
1366        guint i = 0;
1367        long title_len, new_len;
1368        irc_t *irc = ic->bee->ui_data;
1369
1370        if (ic->chatlist == NULL) {
1371                irc_rootmsg(irc, "No existing chatrooms");
1372                return;
1373        }
1374
1375        /* find a reasonable width for the table */
1376        title_len = CHAT_TITLE_LEN_MIN;
1377
1378        for (l = ic->chatlist; l; l = l->next) {
1379                ci = l->data;
1380                new_len = g_utf8_strlen(ci->title, -1);
1381
1382                if (new_len >= CHAT_TITLE_LEN_MAX) {
1383                        title_len = CHAT_TITLE_LEN_MAX;
1384                        break;
1385                } else if (title_len < new_len) {
1386                        title_len = new_len;
1387                }
1388        }
1389
1390        if (strchr(irc->umode, 'b') != NULL) {
1391                hformat = "%s\t%s\t%s";
1392                iformat = "%u\t%s\t%s";
1393        } else {
1394                hformat = "%s  %s  %s";
1395                iformat = "%5u  %s  %s";
1396        }
1397
1398        padded = str_pad_and_truncate("Title", title_len, NULL);
1399        irc_rootmsg(irc, hformat, "Index", padded, "Topic");
1400        g_free(padded);
1401
1402        for (l = ic->chatlist; l; l = l->next) {
1403                ci = l->data;
1404                topic = ci->topic ? ci->topic : "";
1405
1406                padded = str_pad_and_truncate(ci->title, title_len, "[...]");
1407                irc_rootmsg(irc, iformat, ++i, padded, topic);
1408                g_free(padded);
1409        }
1410
1411        irc_rootmsg(irc, "%u %s chatrooms", i, acc->tag);
1412}
1413
1414static void cmd_group(irc_t *irc, char **cmd)
1415{
1416        GSList *l;
1417        int len;
1418
1419        len = strlen(cmd[1]);
1420        if (g_strncasecmp(cmd[1], "list", len) == 0) {
1421                int n = 0;
1422
1423                if (strchr(irc->umode, 'b')) {
1424                        irc_rootmsg(irc, "Group list:");
1425                }
1426
1427                for (l = irc->b->groups; l; l = l->next) {
1428                        bee_group_t *bg = l->data;
1429                        irc_rootmsg(irc, "%d. %s", n++, bg->name);
1430                }
1431                irc_rootmsg(irc, "End of group list");
1432        } else if (g_strncasecmp(cmd[1], "info", len) == 0) {
1433                bee_group_t *bg;
1434                int n = 0;
1435
1436                MIN_ARGS(2);
1437                bg = bee_group_by_name(irc->b, cmd[2], FALSE);
1438
1439                if (bg) {
1440                        if (strchr(irc->umode, 'b')) {
1441                                irc_rootmsg(irc, "Members of %s:", cmd[2]);
1442                        }
1443                        for (l = irc->b->users; l; l = l->next) {
1444                                bee_user_t *bu = l->data;
1445                                if (bu->group == bg) {
1446                                        irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
1447                                }
1448                        }
1449                        irc_rootmsg(irc, "End of member list");
1450                } else {
1451                        irc_rootmsg(irc,
1452                                    "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
1453                                    cmd[2]);
1454                }
1455        } else {
1456                irc_rootmsg(irc,
1457                            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
1458                            cmd[1]);
1459        }
1460}
1461
1462static void cmd_transfer(irc_t *irc, char **cmd)
1463{
1464        GSList *files = irc->file_transfers;
1465        GSList *next;
1466
1467        enum { LIST, REJECT, CANCEL };
1468        int subcmd = LIST;
1469        int fid;
1470
1471        if (!files) {
1472                irc_rootmsg(irc, "No pending transfers");
1473                return;
1474        }
1475
1476        if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
1477                subcmd = REJECT;
1478        } else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
1479                   cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
1480                subcmd = CANCEL;
1481        }
1482
1483        for (; files; files = next) {
1484                next = files->next;
1485                file_transfer_t *file = files->data;
1486
1487                switch (subcmd) {
1488                case LIST:
1489                        if (file->status == FT_STATUS_LISTENING) {
1490                                irc_rootmsg(irc,
1491                                            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1492                        } else {
1493                                int kb_per_s = 0;
1494                                time_t diff = time(NULL) - file->started ? : 1;
1495                                if ((file->started > 0) && (file->bytes_transferred > 0)) {
1496                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1497                                }
1498
1499                                irc_rootmsg(irc,
1500                                            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
1501                                            file->file_name,
1502                                            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
1503                        }
1504                        break;
1505                case REJECT:
1506                        if (file->status == FT_STATUS_LISTENING) {
1507                                irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
1508                                imcb_file_canceled(file->ic, file, "Denied by user");
1509                        }
1510                        break;
1511                case CANCEL:
1512                        if (file->local_id == fid) {
1513                                irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
1514                                imcb_file_canceled(file->ic, file, "Canceled by user");
1515                        }
1516                        break;
1517                }
1518        }
1519}
1520
1521static void cmd_nick(irc_t *irc, char **cmd)
1522{
1523        irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
1524}
1525
1526/* Maybe this should be a stand-alone command as well? */
1527static void bitlbee_whatsnew(irc_t *irc)
1528{
1529        int last = set_getint(&irc->b->set, "last_version");
1530        char s[16], *msg;
1531
1532        if (last >= BITLBEE_VERSION_CODE) {
1533                return;
1534        }
1535
1536        msg = help_get_whatsnew(&(global.help), last);
1537
1538        if (msg) {
1539                irc_rootmsg(irc, "%s: This seems to be your first time using this "
1540                            "this version of BitlBee. Here's a list of new "
1541                            "features you may like to know about:\n\n%s\n",
1542                            irc->user->nick, msg);
1543        }
1544
1545        g_free(msg);
1546
1547        g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
1548        set_setstr(&irc->b->set, "last_version", s);
1549}
1550
1551/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1552command_t root_commands[] = {
1553        { "account",        1, cmd_account,        0 },
1554        { "add",            2, cmd_add,            0 },
1555        { "allow",          1, cmd_allow,          0 },
1556        { "blist",          0, cmd_blist,          0 },
1557        { "block",          1, cmd_block,          0 },
1558        { "channel",        1, cmd_channel,        0 },
1559        { "chat",           1, cmd_chat,           0 },
1560        { "drop",           1, cmd_drop,           0 },
1561        { "ft",             0, cmd_transfer,       0 },
1562        { "group",          1, cmd_group,          0 },
1563        { "help",           0, cmd_help,           0 },
1564        { "identify",       0, cmd_identify,       0 },
1565        { "info",           1, cmd_info,           0 },
1566        { "nick",           1, cmd_nick,           0 },
1567        { "no",             0, cmd_yesno,          0 },
1568        { "plugins",        0, cmd_plugins,        0 },
1569        { "qlist",          0, cmd_qlist,          0 },
1570        { "register",       0, cmd_register,       0 },
1571        { "remove",         1, cmd_remove,         0 },
1572        { "rename",         2, cmd_rename,         0 },
1573        { "save",           0, cmd_save,           0 },
1574        { "set",            0, cmd_set,            0 },
1575        { "transfer",       0, cmd_transfer,       0 },
1576        { "yes",            0, cmd_yesno,          0 },
1577        /* Not expecting too many plugins adding root commands so just make a
1578           dumb array with some empty entried at the end. */
1579        { NULL },
1580        { NULL },
1581        { NULL },
1582        { NULL },
1583        { NULL },
1584        { NULL },
1585        { NULL },
1586        { NULL },
1587        { NULL },
1588};
1589static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);
1590
1591gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
1592{
1593        int i;
1594
1595        if (root_commands[num_root_commands - 2].command) {
1596                /* Planning fail! List is full. */
1597                return FALSE;
1598        }
1599
1600        for (i = 0; root_commands[i].command; i++) {
1601                if (g_strcasecmp(root_commands[i].command, command) == 0) {
1602                        return FALSE;
1603                } else if (g_strcasecmp(root_commands[i].command, command) > 0) {
1604                        break;
1605                }
1606        }
1607        memmove(root_commands + i + 1, root_commands + i,
1608                sizeof(command_t) * (num_root_commands - i - 1));
1609
1610        root_commands[i].command = g_strdup(command);
1611        root_commands[i].required_parameters = params;
1612        root_commands[i].execute = func;
1613        root_commands[i].flags = flags;
1614
1615        return TRUE;
1616}
Note: See TracBrowser for help on using the repository browser.