source: root_commands.c @ 9f03c47

Last change on this file since 9f03c47 was 9f03c47, checked in by dequis <dx@…>, at 2016-11-14T00:37:14Z

Improve support for protocols which don't require a password

This adds a prpl_options_t enum with flags, which mostly just brings
OPT_PROTO_{NO_PASSWORD,PASSWORD_OPTIONAL} from libpurple as
PRPL_OPT_{NO_PASSWORD,PASSWORD_OPTIONAL}

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