source: root_commands.c @ 256164c

Last change on this file since 256164c was 17f057d, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-13T00:34:07Z

As I intended for a while already, use account tags everywhere instead of
"protocol(handle)". It's guaranteed to be unique and also shorter.

It may suck for people who have multiple accounts and didn't change their
tags, but that'll hopefully remind them to change them.

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