source: root_commands.c @ 5447c59

Last change on this file since 5447c59 was 7a9d968, checked in by Wilmer van der Gaast <wilmer@…>, at 2018-03-10T11:30:39Z

Merge branch 'master' into HEAD

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