source: root_commands.c @ 4cb21b7

Last change on this file since 4cb21b7 was 2c18fcd, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-12-02T12:40:03Z

Removing comment about new syntax for the "account" command, which by now
is two years old already.

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