source: root_commands.c @ 7b05842

Last change on this file since 7b05842 was 35987a1, checked in by Wilmer van der Gaast <wilmer@…>, at 2014-02-28T23:17:28Z

Allow use of "ac x set -del password" to use /oper to change the password
"securely". Patch from Flexo, bug #1117.

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