source: root_commands.c @ 91ae87d

Last change on this file since 91ae87d was fda194f, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-03-19T21:01:50Z

Allow identify -noload/-force without typing a password. (/OPER)
Patch from trac3r, bug #814.

  • Property mode set to 100644
File size: 36.3 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2010 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" );
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_ADD;
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'. Note that the syntax "
556                             "of the account command changed, see \x02help account\x02.", cmd[1] );
557               
558                return;
559        }
560       
561        if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
562        {
563                if( a->ic )
564                {
565                        irc_rootmsg( irc, "Account is still logged in, can't delete" );
566                }
567                else
568                {
569                        account_del( irc->b, a );
570                        irc_rootmsg( irc, "Account deleted" );
571                }
572        }
573        else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 )
574        {
575                if( a->ic )
576                        irc_rootmsg( irc, "Account already online" );
577                else if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
578                        irc_rootmsg( irc, "Enter password for account %s "
579                                     "first (use /OPER)", a->tag );
580                else
581                        account_on( irc->b, a );
582        }
583        else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 )
584        {
585                if( a->ic )
586                {
587                        account_off( irc->b, a );
588                }
589                else if( a->reconnect )
590                {
591                        cancel_auto_reconnect( a );
592                        irc_rootmsg( irc, "Reconnect cancelled" );
593                }
594                else
595                {
596                        irc_rootmsg( irc, "Account already offline" );
597                }
598        }
599        else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
600        {
601                cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags );
602        }
603        else
604        {
605                irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
606        }
607}
608
609static void cmd_channel( irc_t *irc, char **cmd )
610{
611        irc_channel_t *ic;
612        int len;
613       
614        len = strlen( cmd[1] );
615       
616        if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
617        {
618                GSList *l;
619                int i = 0;
620               
621                if( strchr( irc->umode, 'b' ) )
622                        irc_rootmsg( irc, "Channel list:" );
623               
624                for( l = irc->channels; l; l = l->next )
625                {
626                        irc_channel_t *ic = l->data;
627                       
628                        irc_rootmsg( irc, "%2d. %s, %s channel%s", i, ic->name,
629                                     set_getstr( &ic->set, "type" ),
630                                     ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" );
631                       
632                        i ++;
633                }
634                irc_rootmsg( irc, "End of channel list" );
635               
636                return;
637        }
638       
639        if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL )
640        {
641                /* If this doesn't match any channel, maybe this is the short
642                   syntax (only works when used inside a channel). */
643                if( ( ic = irc->root->last_channel ) &&
644                    ( len = strlen( cmd[1] ) ) &&
645                    g_strncasecmp( cmd[1], "set", len ) == 0 )
646                        cmd_set_real( irc, cmd + 1, &ic->set, NULL );
647                else
648                        irc_rootmsg( irc, "Could not find channel `%s'", cmd[1] );
649               
650                return;
651        }
652       
653        MIN_ARGS( 2 );
654        len = strlen( cmd[2] );
655       
656        if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
657        {
658                cmd_set_real( irc, cmd + 2, &ic->set, NULL );
659        }
660        else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
661        {
662                if( !( ic->flags & IRC_CHANNEL_JOINED ) &&
663                    ic != ic->irc->default_channel )
664                {
665                        irc_rootmsg( irc, "Channel %s deleted.", ic->name );
666                        irc_channel_free( ic );
667                }
668                else
669                        irc_rootmsg( irc, "Couldn't remove channel (main channel %s or "
670                                          "channels you're still in cannot be deleted).",
671                                          irc->default_channel->name );
672        }
673        else
674        {
675                irc_rootmsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
676        }
677}
678
679static void cmd_add( irc_t *irc, char **cmd )
680{
681        account_t *a;
682        int add_on_server = 1;
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( 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( add_on_server )
721        {
722                irc_channel_t *ic;
723                char *s, *group = NULL;;
724               
725                if( ( ic = irc->root->last_channel ) &&
726                    ( s = set_getstr( &ic->set, "fill_by" ) ) &&
727                    strcmp( s, "group" ) == 0 &&
728                    ( group = set_getstr( &ic->set, "group" ) ) )
729                        irc_rootmsg( irc, "Adding `%s' to contact list (group %s)",
730                                     cmd[2], group );
731                else
732                        irc_rootmsg( irc, "Adding `%s' to contact list", cmd[2] );
733               
734                a->prpl->add_buddy( a->ic, cmd[2], group );
735        }
736        else
737        {
738                bee_user_t *bu;
739                irc_user_t *iu;
740               
741                /* Only for add -tmp. For regular adds, this callback will
742                   be called once the IM server confirms. */
743                if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) &&
744                    ( iu = bu->ui_data ) )
745                        irc_rootmsg( irc, "Temporarily assigned nickname `%s' "
746                                     "to contact `%s'", iu->nick, cmd[2] );
747        }
748       
749}
750
751static void cmd_remove( irc_t *irc, char **cmd )
752{
753        irc_user_t *iu;
754        bee_user_t *bu;
755        char *s;
756       
757        if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) )
758        {
759                irc_rootmsg( irc, "Buddy `%s' not found", cmd[1] );
760                return;
761        }
762        s = g_strdup( bu->handle );
763       
764        bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL );
765        nick_del( bu );
766        if( g_slist_find( irc->users, iu ) )
767                bee_user_free( irc->b, bu );
768       
769        irc_rootmsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
770        g_free( s );
771       
772        return;
773}
774
775static void cmd_info( irc_t *irc, char **cmd )
776{
777        struct im_connection *ic;
778        account_t *a;
779       
780        if( !cmd[2] )
781        {
782                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
783                if( !iu || !iu->bu )
784                {
785                        irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
786                        return;
787                }
788                ic = iu->bu->ic;
789                cmd[2] = iu->bu->handle;
790        }
791        else if( !( a = account_get( irc->b, cmd[1] ) ) )
792        {
793                irc_rootmsg( irc, "Invalid account" );
794                return;
795        }
796        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
797        {
798                irc_rootmsg( irc, "That account is not on-line" );
799                return;
800        }
801       
802        if( !ic->acc->prpl->get_info )
803        {
804                irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
805        }
806        else
807        {
808                ic->acc->prpl->get_info( ic, cmd[2] );
809        }
810}
811
812static void cmd_rename( irc_t *irc, char **cmd )
813{
814        irc_user_t *iu, *old;
815        gboolean del = g_strcasecmp( cmd[1], "-del" ) == 0;
816       
817        iu = irc_user_by_name( irc, cmd[del ? 2 : 1] );
818       
819        if( iu == NULL )
820        {
821                irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
822        }
823        else if( del )
824        {
825                if( iu->bu )
826                        bee_irc_user_nick_reset( iu );
827                irc_rootmsg( irc, "Nickname reset to `%s'", iu->nick );
828        }
829        else if( iu == irc->user )
830        {
831                irc_rootmsg( irc, "Use /nick to change your own nickname" );
832        }
833        else if( !nick_ok( cmd[2] ) )
834        {
835                irc_rootmsg( irc, "Nick `%s' is invalid", cmd[2] );
836        }
837        else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
838        {
839                irc_rootmsg( irc, "Nick `%s' already exists", cmd[2] );
840        }
841        else
842        {
843                if( !irc_user_set_nick( iu, cmd[2] ) )
844                {
845                        irc_rootmsg( irc, "Error while changing nick" );
846                        return;
847                }
848               
849                if( iu == irc->root )
850                {
851                        /* If we're called internally (user did "set root_nick"),
852                           let's not go O(INF). :-) */
853                        if( strcmp( cmd[0], "set_rename" ) != 0 )
854                                set_setstr( &irc->b->set, "root_nick", cmd[2] );
855                }
856                else if( iu->bu )
857                {
858                        nick_set( iu->bu, cmd[2] );
859                }
860               
861                irc_rootmsg( irc, "Nick successfully changed" );
862        }
863}
864
865char *set_eval_root_nick( set_t *set, char *new_nick )
866{
867        irc_t *irc = set->data;
868       
869        if( strcmp( irc->root->nick, new_nick ) != 0 )
870        {
871                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
872               
873                cmd_rename( irc, cmd );
874        }
875       
876        return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID;
877}
878
879static void cmd_block( irc_t *irc, char **cmd )
880{
881        struct im_connection *ic;
882        account_t *a;
883       
884        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
885        {
886                char *format;
887                GSList *l;
888               
889                if( strchr( irc->umode, 'b' ) != NULL )
890                        format = "%s\t%s";
891                else
892                        format = "%-32.32s  %-16.16s";
893               
894                irc_rootmsg( irc, format, "Handle", "Nickname" );
895                for( l = a->ic->deny; l; l = l->next )
896                {
897                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
898                        irc_user_t *iu = bu ? bu->ui_data : NULL;
899                        irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" );
900                }
901                irc_rootmsg( irc, "End of list." );
902               
903                return;
904        }
905        else if( !cmd[2] )
906        {
907                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
908                if( !iu || !iu->bu )
909                {
910                        irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
911                        return;
912                }
913                ic = iu->bu->ic;
914                cmd[2] = iu->bu->handle;
915        }
916        else if( !( a = account_get( irc->b, cmd[1] ) ) )
917        {
918                irc_rootmsg( irc, "Invalid account" );
919                return;
920        }
921        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
922        {
923                irc_rootmsg( irc, "That account is not on-line" );
924                return;
925        }
926       
927        if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit )
928        {
929                irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
930        }
931        else
932        {
933                imc_rem_allow( ic, cmd[2] );
934                imc_add_block( ic, cmd[2] );
935                irc_rootmsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] );
936        }
937}
938
939static void cmd_allow( irc_t *irc, char **cmd )
940{
941        struct im_connection *ic;
942        account_t *a;
943       
944        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
945        {
946                char *format;
947                GSList *l;
948               
949                if( strchr( irc->umode, 'b' ) != NULL )
950                        format = "%s\t%s";
951                else
952                        format = "%-32.32s  %-16.16s";
953               
954                irc_rootmsg( irc, format, "Handle", "Nickname" );
955                for( l = a->ic->permit; l; l = l->next )
956                {
957                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
958                        irc_user_t *iu = bu ? bu->ui_data : NULL;
959                        irc_rootmsg( irc, format, l->data, iu ? iu->nick : "(none)" );
960                }
961                irc_rootmsg( irc, "End of list." );
962               
963                return;
964        }
965        else if( !cmd[2] )
966        {
967                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
968                if( !iu || !iu->bu )
969                {
970                        irc_rootmsg( irc, "Nick `%s' does not exist", cmd[1] );
971                        return;
972                }
973                ic = iu->bu->ic;
974                cmd[2] = iu->bu->handle;
975        }
976        else if( !( a = account_get( irc->b, cmd[1] ) ) )
977        {
978                irc_rootmsg( irc, "Invalid account" );
979                return;
980        }
981        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
982        {
983                irc_rootmsg( irc, "That account is not on-line" );
984                return;
985        }
986       
987        if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit )
988        {
989                irc_rootmsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
990        }
991        else
992        {
993                imc_rem_block( ic, cmd[2] );
994                imc_add_allow( ic, cmd[2] );
995               
996                irc_rootmsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] );
997        }
998}
999
1000static void cmd_yesno( irc_t *irc, char **cmd )
1001{
1002        query_t *q = NULL;
1003        int numq = 0;
1004       
1005        if( irc->queries == NULL )
1006        {
1007                /* Alright, alright, let's add a tiny easter egg here. */
1008                static irc_t *last_irc = NULL;
1009                static time_t last_time = 0;
1010                static int times = 0;
1011                static const char *msg[] = {
1012                        "Oh yeah, that's right.",
1013                        "Alright, alright. Now go back to work.",
1014                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
1015                        "Yes?",
1016                        "No?",
1017                };
1018               
1019                if( last_irc == irc && time( NULL ) - last_time < 15 )
1020                {
1021                        if( ( ++times >= 3 ) )
1022                        {
1023                                irc_rootmsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] );
1024                                last_irc = NULL;
1025                                times = 0;
1026                                return;
1027                        }
1028                }
1029                else
1030                {
1031                        last_time = time( NULL );
1032                        last_irc = irc;
1033                        times = 0;
1034                }
1035               
1036                irc_rootmsg( irc, "Did I ask you something?" );
1037                return;
1038        }
1039       
1040        /* If there's an argument, the user seems to want to answer another question than the
1041           first/last (depending on the query_order setting) one. */
1042        if( cmd[1] )
1043        {
1044                if( sscanf( cmd[1], "%d", &numq ) != 1 )
1045                {
1046                        irc_rootmsg( irc, "Invalid query number" );
1047                        return;
1048                }
1049               
1050                for( q = irc->queries; q; q = q->next, numq -- )
1051                        if( numq == 0 )
1052                                break;
1053               
1054                if( !q )
1055                {
1056                        irc_rootmsg( irc, "Uhm, I never asked you something like that..." );
1057                        return;
1058                }
1059        }
1060       
1061        if( g_strcasecmp( cmd[0], "yes" ) == 0 )
1062                query_answer( irc, q, 1 );
1063        else if( g_strcasecmp( cmd[0], "no" ) == 0 )
1064                query_answer( irc, q, 0 );
1065}
1066
1067static void cmd_set( irc_t *irc, char **cmd )
1068{
1069        cmd_set_real( irc, cmd, &irc->b->set, NULL );
1070}
1071
1072static void cmd_blist( irc_t *irc, char **cmd )
1073{
1074        int online = 0, away = 0, offline = 0;
1075        GSList *l;
1076        char s[256];
1077        char *format;
1078        int n_online = 0, n_away = 0, n_offline = 0;
1079       
1080        if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 )
1081                online = offline = away = 1;
1082        else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 )
1083                offline = 1;
1084        else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 )
1085                away = 1;
1086        else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 )
1087                online = 1;
1088        else
1089                online = away = 1;
1090       
1091        if( strchr( irc->umode, 'b' ) != NULL )
1092                format = "%s\t%s\t%s";
1093        else
1094                format = "%-16.16s  %-40.40s  %s";
1095       
1096        irc_rootmsg( irc, format, "Nick", "Handle/Account", "Status" );
1097       
1098        if( irc->root->last_channel &&
1099            strcmp( set_getstr( &irc->root->last_channel->set, "type" ), "control" ) != 0 )
1100                irc->root->last_channel = NULL;
1101       
1102        for( l = irc->users; l; l = l->next )
1103        {
1104                irc_user_t *iu = l->data;
1105                bee_user_t *bu = iu->bu;
1106               
1107                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1108                    ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE )
1109                        continue;
1110               
1111                if( online == 1 )
1112                {
1113                        char st[256] = "Online";
1114                       
1115                        if( bu->status_msg )
1116                                g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
1117                       
1118                        g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
1119                        irc_rootmsg( irc, format, iu->nick, s, st );
1120                }
1121               
1122                n_online ++;
1123        }
1124
1125        for( l = irc->users; l; l = l->next )
1126        {
1127                irc_user_t *iu = l->data;
1128                bee_user_t *bu = iu->bu;
1129               
1130                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1131                    !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) )
1132                        continue;
1133               
1134                if( away == 1 )
1135                {
1136                        g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
1137                        irc_rootmsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
1138                }
1139                n_away ++;
1140        }
1141       
1142        for( l = irc->users; l; l = l->next )
1143        {
1144                irc_user_t *iu = l->data;
1145                bee_user_t *bu = iu->bu;
1146               
1147                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1148                    bu->flags & BEE_USER_ONLINE )
1149                        continue;
1150               
1151                if( offline == 1 )
1152                {
1153                        g_snprintf( s, sizeof( s ) - 1, "%s %s", bu->handle, bu->ic->acc->tag );
1154                        irc_rootmsg( irc, format, iu->nick, s, "Offline" );
1155                }
1156                n_offline ++;
1157        }
1158       
1159        irc_rootmsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
1160}
1161
1162static void cmd_qlist( irc_t *irc, char **cmd )
1163{
1164        query_t *q = irc->queries;
1165        int num;
1166       
1167        if( !q )
1168        {
1169                irc_rootmsg( irc, "There are no pending questions." );
1170                return;
1171        }
1172       
1173        irc_rootmsg( irc, "Pending queries:" );
1174       
1175        for( num = 0; q; q = q->next, num ++ )
1176                if( q->ic ) /* Not necessary yet, but it might come later */
1177                        irc_rootmsg( irc, "%d, %s: %s", num, q->ic->acc->tag, q->question );
1178                else
1179                        irc_rootmsg( irc, "%d, BitlBee: %s", num, q->question );
1180}
1181
1182static void cmd_chat( irc_t *irc, char **cmd )
1183{
1184        account_t *acc;
1185       
1186        if( g_strcasecmp( cmd[1], "add" ) == 0 )
1187        {
1188                char *channel, *s;
1189                struct irc_channel *ic;
1190               
1191                MIN_ARGS( 3 );
1192               
1193                if( !( acc = account_get( irc->b, cmd[2] ) ) )
1194                {
1195                        irc_rootmsg( irc, "Invalid account" );
1196                        return;
1197                }
1198                else if( !acc->prpl->chat_join )
1199                {
1200                        irc_rootmsg( irc, "Named chatrooms not supported on that account." );
1201                        return;
1202                }
1203               
1204                if( cmd[4] == NULL )
1205                {
1206                        channel = g_strdup( cmd[3] );
1207                        if( ( s = strchr( channel, '@' ) ) )
1208                                *s = 0;
1209                }
1210                else
1211                {
1212                        channel = g_strdup( cmd[4] );
1213                }
1214               
1215                if( strchr( CTYPES, channel[0] ) == NULL )
1216                {
1217                        s = g_strdup_printf( "#%s", channel );
1218                        g_free( channel );
1219                        channel = s;
1220                       
1221                        irc_channel_name_strip( channel );
1222                }
1223               
1224                if( ( ic = irc_channel_new( irc, channel ) ) &&
1225                    set_setstr( &ic->set, "type", "chat" ) &&
1226                    set_setstr( &ic->set, "chat_type", "room" ) &&
1227                    set_setstr( &ic->set, "account", cmd[2] ) &&
1228                    set_setstr( &ic->set, "room", cmd[3] ) )
1229                {
1230                        irc_rootmsg( irc, "Chatroom successfully added." );
1231                }
1232                else
1233                {
1234                        if( ic )
1235                                irc_channel_free( ic );
1236                       
1237                        irc_rootmsg( irc, "Could not add chatroom." );
1238                }
1239                g_free( channel );
1240        }
1241        else if( g_strcasecmp( cmd[1], "with" ) == 0 )
1242        {
1243                irc_user_t *iu;
1244               
1245                MIN_ARGS( 2 );
1246               
1247                if( ( iu = irc_user_by_name( irc, cmd[2] ) ) &&
1248                    iu->bu && iu->bu->ic->acc->prpl->chat_with )
1249                {
1250                        if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
1251                        {
1252                                irc_rootmsg( irc, "(Possible) failure while trying to open "
1253                                                  "a groupchat with %s.", iu->nick );
1254                        }
1255                }
1256                else
1257                {
1258                        irc_rootmsg( irc, "Can't open a groupchat with %s.", cmd[2] );
1259                }
1260        }
1261        else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
1262                 g_strcasecmp( cmd[1], "set" ) == 0 ||
1263                 g_strcasecmp( cmd[1], "del" ) == 0 )
1264        {
1265                irc_rootmsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
1266                cmd_channel( irc, cmd );
1267        }
1268        else
1269        {
1270                irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
1271        }
1272}
1273
1274static void cmd_group( irc_t *irc, char **cmd )
1275{
1276        GSList *l;
1277        int len;
1278       
1279        len = strlen( cmd[1] );
1280        if( g_strncasecmp( cmd[1], "list", len ) == 0 )
1281        {
1282                int n = 0;
1283               
1284                if( strchr( irc->umode, 'b' ) )
1285                        irc_rootmsg( irc, "Group list:" );
1286               
1287                for( l = irc->b->groups; l; l = l->next )
1288                {
1289                        bee_group_t *bg = l->data;
1290                        irc_rootmsg( irc, "%d. %s", n ++, bg->name );
1291                }
1292                irc_rootmsg( irc, "End of group list" );
1293        }
1294        else
1295        {
1296                irc_rootmsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] );
1297        }
1298}
1299
1300static void cmd_transfer( irc_t *irc, char **cmd )
1301{
1302        GSList *files = irc->file_transfers;
1303        enum { LIST, REJECT, CANCEL };
1304        int subcmd = LIST;
1305        int fid;
1306
1307        if( !files )
1308        {
1309                irc_rootmsg( irc, "No pending transfers" );
1310                return;
1311        }
1312
1313        if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) )
1314        {
1315                subcmd = REJECT;
1316        }
1317        else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && 
1318                 cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) )
1319        {
1320                subcmd = CANCEL;
1321        }
1322
1323        for( ; files; files = g_slist_next( files ) )
1324        {
1325                file_transfer_t *file = files->data;
1326               
1327                switch( subcmd ) {
1328                case LIST:
1329                        if ( file->status == FT_STATUS_LISTENING )
1330                                irc_rootmsg( irc, 
1331                                        "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1332                        else 
1333                        {
1334                                int kb_per_s = 0;
1335                                time_t diff = time( NULL ) - file->started ? : 1;
1336                                if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
1337                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1338                                       
1339                                irc_rootmsg( irc, 
1340                                        "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 
1341                                        file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
1342                        }
1343                        break;
1344                case REJECT:
1345                        if( file->status == FT_STATUS_LISTENING )
1346                        {
1347                                irc_rootmsg( irc, "Rejecting file transfer for %s", file->file_name );
1348                                imcb_file_canceled( file->ic, file, "Denied by user" );
1349                        }
1350                        break;
1351                case CANCEL:
1352                        if( file->local_id == fid )
1353                        {
1354                                irc_rootmsg( irc, "Canceling file transfer for %s", file->file_name );
1355                                imcb_file_canceled( file->ic, file, "Canceled by user" );
1356                        }
1357                        break;
1358                }
1359        }
1360}
1361
1362static void cmd_nick( irc_t *irc, char **cmd )
1363{
1364        irc_rootmsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] );
1365}
1366
1367/* Maybe this should be a stand-alone command as well? */
1368static void bitlbee_whatsnew( irc_t *irc )
1369{
1370        int last = set_getint( &irc->b->set, "last_version" );
1371        char s[16], *msg;
1372       
1373        if( last >= BITLBEE_VERSION_CODE )
1374                return;
1375       
1376        msg = help_get_whatsnew( &(global.help), last );
1377       
1378        if( msg )
1379                irc_rootmsg( irc, "%s: This seems to be your first time using this "
1380                                  "this version of BitlBee. Here's a list of new "
1381                                  "features you may like to know about:\n\n%s\n",
1382                                  irc->user->nick, msg );
1383       
1384        g_free( msg );
1385       
1386        g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE );
1387        set_setstr( &irc->b->set, "last_version", s );
1388}
1389
1390/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1391command_t root_commands[] = {
1392        { "account",        1, cmd_account,        0 },
1393        { "add",            2, cmd_add,            0 },
1394        { "allow",          1, cmd_allow,          0 },
1395        { "blist",          0, cmd_blist,          0 },
1396        { "block",          1, cmd_block,          0 },
1397        { "channel",        1, cmd_channel,        0 },
1398        { "chat",           1, cmd_chat,           0 },
1399        { "drop",           1, cmd_drop,           0 },
1400        { "ft",             0, cmd_transfer,       0 },
1401        { "group",          1, cmd_group,          0 },
1402        { "help",           0, cmd_help,           0 }, 
1403        { "identify",       0, cmd_identify,       0 },
1404        { "info",           1, cmd_info,           0 },
1405        { "nick",           1, cmd_nick,           0 },
1406        { "no",             0, cmd_yesno,          0 },
1407        { "qlist",          0, cmd_qlist,          0 },
1408        { "register",       0, cmd_register,       0 },
1409        { "remove",         1, cmd_remove,         0 },
1410        { "rename",         2, cmd_rename,         0 },
1411        { "save",           0, cmd_save,           0 },
1412        { "set",            0, cmd_set,            0 },
1413        { "transfer",       0, cmd_transfer,       0 },
1414        { "yes",            0, cmd_yesno,          0 },
1415        /* Not expecting too many plugins adding root commands so just make a
1416           dumb array with some empty entried at the end. */
1417        { NULL },
1418        { NULL },
1419        { NULL },
1420        { NULL },
1421        { NULL },
1422        { NULL },
1423        { NULL },
1424        { NULL },
1425        { NULL },
1426};
1427static const int num_root_commands = sizeof( root_commands ) / sizeof( command_t );
1428
1429gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags )
1430{
1431        int i;
1432       
1433        if( root_commands[num_root_commands-2].command )
1434                /* Planning fail! List is full. */
1435                return FALSE;
1436       
1437        for( i = 0; root_commands[i].command; i++ )
1438        {
1439                if( g_strcasecmp( root_commands[i].command, command ) == 0 )
1440                        return FALSE;
1441                else if( g_strcasecmp( root_commands[i].command, command ) > 0 )
1442                        break;
1443        }
1444        memmove( root_commands + i + 1, root_commands + i,
1445                 sizeof( command_t ) * ( num_root_commands - i - 1 ) );
1446       
1447        root_commands[i].command = g_strdup( command );
1448        root_commands[i].required_parameters = params;
1449        root_commands[i].execute = func;
1450        root_commands[i].flags = flags;
1451       
1452        return TRUE;
1453}
Note: See TracBrowser for help on using the repository browser.