source: root_commands.c @ a7baf40

Last change on this file since a7baf40 was b4f496e, checked in by dequis <dx@…>, at 2016-11-19T07:32:48Z

Improve handling of unknown protocols / missing plugins

Instead of failing to load the config, a fake prpl is created to load
the account, keep its settings, and refuse to log in with a helpful
error message.

Also added a new explain_unknown_protocol() function which returns text
which attempts to explain why a protocol is missing, handling several
typical cases, including the future removal of several dead libpurple
plugins.

That message is shown when logging in to a loaded account with a missing
protocol and when adding a new one with 'account add', with the
difference that the latter doesn't leave a placeholder fake account.

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