source: root_commands.c @ a429907

Last change on this file since a429907 was a429907, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-12-05T12:28:07Z

rename -del

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