source: root_commands.c @ 9564e55

Last change on this file since 9564e55 was 9564e55, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-11-22T13:17:45Z

Allow omitting the password argument to "account add", to then separately
enter the password using the /OPER command (which will not echo to the
screen and/or logs).

It's a fairly ugly hack but the improved password security is worth it
IMHO.

  • Property mode set to 100644
File size: 35.3 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2010 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* User manager (root) commands                                         */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#define BITLBEE_CORE
27#include "commands.h"
28#include "bitlbee.h"
29#include "help.h"
30#include "ipc.h"
31
32void root_command_string( irc_t *irc, char *command )
33{
34        root_command( irc, split_command_parts( command ) );
35}
36
37#define MIN_ARGS( x, y... )                                                    \
38        do                                                                     \
39        {                                                                      \
40                int blaat;                                                     \
41                for( blaat = 0; blaat <= x; blaat ++ )                         \
42                        if( cmd[blaat] == NULL )                               \
43                        {                                                      \
44                                irc_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" );
430               
431                if( cmd[4] == NULL )
432                        irc_usermsg( irc, "Now, use /OPER to enter your password for this account" );
433               
434                return;
435        }
436        else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
437        {
438                int i = 0;
439               
440                if( strchr( irc->umode, 'b' ) )
441                        irc_usermsg( irc, "Account list:" );
442               
443                for( a = irc->b->accounts; a; a = a->next )
444                {
445                        char *con;
446                       
447                        if( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) )
448                                con = " (connected)";
449                        else if( a->ic )
450                                con = " (connecting)";
451                        else if( a->reconnect )
452                                con = " (awaiting reconnect)";
453                        else
454                                con = "";
455                       
456                        irc_usermsg( irc, "%2d (%s): %s, %s%s", i, a->tag, a->prpl->name, a->user, con );
457                       
458                        i ++;
459                }
460                irc_usermsg( irc, "End of account list" );
461               
462                return;
463        }
464        else if( cmd[2] )
465        {
466                /* Try the following two only if cmd[2] == NULL */
467        }
468        else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 )
469        {
470                if ( irc->b->accounts )
471                {
472                        irc_usermsg( irc, "Trying to get all accounts connected..." );
473               
474                        for( a = irc->b->accounts; a; a = a->next )
475                                if( !a->ic && a->auto_connect )
476                                {
477                                        if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
478                                                irc_usermsg( irc, "Enter password for account %s(%s) "
479                                                             "first (use /OPER)", a->prpl->name, a->user );
480                                        else
481                                                account_on( irc->b, a );
482                                }
483                } 
484                else
485                {
486                        irc_usermsg( irc, "No accounts known. Use `account add' to add one." );
487                }
488               
489                return;
490        }
491        else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 )
492        {
493                irc_usermsg( irc, "Deactivating all active (re)connections..." );
494               
495                for( a = irc->b->accounts; a; a = a->next )
496                {
497                        if( a->ic )
498                                account_off( irc->b, a );
499                        else if( a->reconnect )
500                                cancel_auto_reconnect( a );
501                }
502               
503                return;
504        }
505       
506        MIN_ARGS( 2 );
507        len = strlen( cmd[2] );
508       
509        /* At least right now, don't accept on/off/set/del as account IDs even
510           if they're a proper match, since people not familiar with the new
511           syntax yet may get a confusing/nasty surprise. */
512        if( g_strcasecmp( cmd[1], "on" ) == 0 ||
513            g_strcasecmp( cmd[1], "off" ) == 0 ||
514            g_strcasecmp( cmd[1], "set" ) == 0 ||
515            g_strcasecmp( cmd[1], "del" ) == 0 ||
516            ( a = account_get( irc->b, cmd[1] ) ) == NULL )
517        {
518                irc_usermsg( irc, "Could not find account `%s'. Note that the syntax "
519                             "of the account command changed, see \x02help account\x02.", cmd[1] );
520               
521                return;
522        }
523       
524        if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
525        {
526                if( a->ic )
527                {
528                        irc_usermsg( irc, "Account is still logged in, can't delete" );
529                }
530                else
531                {
532                        account_del( irc->b, a );
533                        irc_usermsg( irc, "Account deleted" );
534                }
535        }
536        else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 )
537        {
538                if( a->ic )
539                        irc_usermsg( irc, "Account already online" );
540                else if( strcmp( a->pass, PASSWORD_PENDING ) == 0 )
541                        irc_usermsg( irc, "Enter password for account %s(%s) "
542                                     "first (use /OPER)", a->prpl->name, a->user );
543                else
544                        account_on( irc->b, a );
545        }
546        else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 )
547        {
548                if( a->ic )
549                {
550                        account_off( irc->b, a );
551                }
552                else if( a->reconnect )
553                {
554                        cancel_auto_reconnect( a );
555                        irc_usermsg( irc, "Reconnect cancelled" );
556                }
557                else
558                {
559                        irc_usermsg( irc, "Account already offline" );
560                }
561        }
562        else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
563        {
564                cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags );
565        }
566        else
567        {
568                irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
569        }
570}
571
572static void cmd_channel( irc_t *irc, char **cmd )
573{
574        irc_channel_t *ic;
575        int len;
576       
577        len = strlen( cmd[1] );
578       
579        if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
580        {
581                GSList *l;
582                int i = 0;
583               
584                if( strchr( irc->umode, 'b' ) )
585                        irc_usermsg( irc, "Channel list:" );
586               
587                for( l = irc->channels; l; l = l->next )
588                {
589                        irc_channel_t *ic = l->data;
590                       
591                        irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name,
592                                     set_getstr( &ic->set, "type" ),
593                                     ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" );
594                       
595                        i ++;
596                }
597                irc_usermsg( irc, "End of channel list" );
598               
599                return;
600        }
601       
602        if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL )
603        {
604                /* If this doesn't match any channel, maybe this is the short
605                   syntax (only works when used inside a channel). */
606                if( ( ic = irc->root->last_channel ) &&
607                    ( len = strlen( cmd[1] ) ) &&
608                    g_strncasecmp( cmd[1], "set", len ) == 0 )
609                        cmd_set_real( irc, cmd + 1, &ic->set, NULL );
610                else
611                        irc_usermsg( irc, "Could not find channel `%s'", cmd[1] );
612               
613                return;
614        }
615       
616        MIN_ARGS( 2 );
617        len = strlen( cmd[2] );
618       
619        if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
620        {
621                cmd_set_real( irc, cmd + 2, &ic->set, NULL );
622        }
623        else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
624        {
625                if( !( ic->flags & IRC_CHANNEL_JOINED ) &&
626                    ic != ic->irc->default_channel )
627                {
628                        irc_usermsg( irc, "Channel %s deleted.", ic->name );
629                        irc_channel_free( ic );
630                }
631                else
632                        irc_usermsg( irc, "Couldn't remove channel (main channel %s or "
633                                          "channels you're still in cannot be deleted).",
634                                          irc->default_channel->name );
635        }
636        else
637        {
638                irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
639        }
640}
641
642static void cmd_add( irc_t *irc, char **cmd )
643{
644        account_t *a;
645        int add_on_server = 1;
646       
647        if( g_strcasecmp( cmd[1], "-tmp" ) == 0 )
648        {
649                MIN_ARGS( 3 );
650                add_on_server = 0;
651                cmd ++;
652        }
653       
654        if( !( a = account_get( irc->b, cmd[1] ) ) )
655        {
656                irc_usermsg( irc, "Invalid account" );
657                return;
658        }
659        else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) )
660        {
661                irc_usermsg( irc, "That account is not on-line" );
662                return;
663        }
664       
665        if( cmd[3] )
666        {
667                if( !nick_ok( cmd[3] ) )
668                {
669                        irc_usermsg( irc, "The requested nick `%s' is invalid", cmd[3] );
670                        return;
671                }
672                else if( irc_user_by_name( irc, cmd[3] ) )
673                {
674                        irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] );
675                        return;
676                }
677                else
678                {
679                        nick_set_raw( a, cmd[2], cmd[3] );
680                }
681        }
682       
683        if( add_on_server )
684        {
685                irc_channel_t *ic;
686                char *s, *group = NULL;;
687               
688                if( ( ic = irc->root->last_channel ) &&
689                    ( s = set_getstr( &ic->set, "fill_by" ) ) &&
690                    strcmp( s, "group" ) == 0 &&
691                    ( group = set_getstr( &ic->set, "group" ) ) )
692                        irc_usermsg( irc, "Adding `%s' to contact list (group %s)",
693                                     cmd[2], group );
694                else
695                        irc_usermsg( irc, "Adding `%s' to contact list", cmd[2] );
696               
697                a->prpl->add_buddy( a->ic, cmd[2], group );
698        }
699        else
700        {
701                bee_user_t *bu;
702                irc_user_t *iu;
703               
704                /* Only for add -tmp. For regular adds, this callback will
705                   be called once the IM server confirms. */
706                if( ( bu = bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL ) ) &&
707                    ( iu = bu->ui_data ) )
708                        irc_usermsg( irc, "Temporarily assigned nickname `%s' "
709                                     "to contact `%s'", iu->nick, cmd[2] );
710        }
711       
712}
713
714static void cmd_remove( irc_t *irc, char **cmd )
715{
716        irc_user_t *iu;
717        bee_user_t *bu;
718        char *s;
719       
720        if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) )
721        {
722                irc_usermsg( irc, "Buddy `%s' not found", cmd[1] );
723                return;
724        }
725        s = g_strdup( bu->handle );
726       
727        bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL );
728        nick_del( bu );
729        if( g_slist_find( irc->users, iu ) )
730                bee_user_free( irc->b, bu );
731       
732        irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
733        g_free( s );
734       
735        return;
736}
737
738static void cmd_info( irc_t *irc, char **cmd )
739{
740        struct im_connection *ic;
741        account_t *a;
742       
743        if( !cmd[2] )
744        {
745                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
746                if( !iu || !iu->bu )
747                {
748                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
749                        return;
750                }
751                ic = iu->bu->ic;
752                cmd[2] = iu->bu->handle;
753        }
754        else if( !( a = account_get( irc->b, cmd[1] ) ) )
755        {
756                irc_usermsg( irc, "Invalid account" );
757                return;
758        }
759        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
760        {
761                irc_usermsg( irc, "That account is not on-line" );
762                return;
763        }
764       
765        if( !ic->acc->prpl->get_info )
766        {
767                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
768        }
769        else
770        {
771                ic->acc->prpl->get_info( ic, cmd[2] );
772        }
773}
774
775static void cmd_rename( irc_t *irc, char **cmd )
776{
777        irc_user_t *iu, *old;
778       
779        iu = irc_user_by_name( irc, cmd[1] );
780       
781        if( iu == NULL )
782        {
783                irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
784        }
785        else if( iu == irc->user )
786        {
787                irc_usermsg( irc, "Use /nick to change your own nickname" );
788        }
789        else if( !nick_ok( cmd[2] ) )
790        {
791                irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
792        }
793        else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
794        {
795                irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
796        }
797        else
798        {
799                if( !irc_user_set_nick( iu, cmd[2] ) )
800                {
801                        irc_usermsg( irc, "Error while changing nick" );
802                        return;
803                }
804               
805                if( iu == irc->root )
806                {
807                        /* If we're called internally (user did "set root_nick"),
808                           let's not go O(INF). :-) */
809                        if( strcmp( cmd[0], "set_rename" ) != 0 )
810                                set_setstr( &irc->b->set, "root_nick", cmd[2] );
811                }
812                else if( iu->bu )
813                {
814                        nick_set( iu->bu, cmd[2] );
815                }
816               
817                irc_usermsg( irc, "Nick successfully changed" );
818        }
819}
820
821char *set_eval_root_nick( set_t *set, char *new_nick )
822{
823        irc_t *irc = set->data;
824       
825        if( strcmp( irc->root->nick, new_nick ) != 0 )
826        {
827                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
828               
829                cmd_rename( irc, cmd );
830        }
831       
832        return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID;
833}
834
835static void cmd_block( irc_t *irc, char **cmd )
836{
837        struct im_connection *ic;
838        account_t *a;
839       
840        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
841        {
842                char *format;
843                GSList *l;
844               
845                if( strchr( irc->umode, 'b' ) != NULL )
846                        format = "%s\t%s";
847                else
848                        format = "%-32.32s  %-16.16s";
849               
850                irc_usermsg( irc, format, "Handle", "Nickname" );
851                for( l = a->ic->deny; l; l = l->next )
852                {
853                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
854                        irc_user_t *iu = bu ? bu->ui_data : NULL;
855                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
856                }
857                irc_usermsg( irc, "End of list." );
858               
859                return;
860        }
861        else if( !cmd[2] )
862        {
863                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
864                if( !iu || !iu->bu )
865                {
866                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
867                        return;
868                }
869                ic = iu->bu->ic;
870                cmd[2] = iu->bu->handle;
871        }
872        else if( !( a = account_get( irc->b, cmd[1] ) ) )
873        {
874                irc_usermsg( irc, "Invalid account" );
875                return;
876        }
877        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
878        {
879                irc_usermsg( irc, "That account is not on-line" );
880                return;
881        }
882       
883        if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit )
884        {
885                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
886        }
887        else
888        {
889                imc_rem_allow( ic, cmd[2] );
890                imc_add_block( ic, cmd[2] );
891                irc_usermsg( irc, "Buddy `%s' moved from allow- to block-list", cmd[2] );
892        }
893}
894
895static void cmd_allow( irc_t *irc, char **cmd )
896{
897        struct im_connection *ic;
898        account_t *a;
899       
900        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
901        {
902                char *format;
903                GSList *l;
904               
905                if( strchr( irc->umode, 'b' ) != NULL )
906                        format = "%s\t%s";
907                else
908                        format = "%-32.32s  %-16.16s";
909               
910                irc_usermsg( irc, format, "Handle", "Nickname" );
911                for( l = a->ic->permit; l; l = l->next )
912                {
913                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
914                        irc_user_t *iu = bu ? bu->ui_data : NULL;
915                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
916                }
917                irc_usermsg( irc, "End of list." );
918               
919                return;
920        }
921        else if( !cmd[2] )
922        {
923                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
924                if( !iu || !iu->bu )
925                {
926                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
927                        return;
928                }
929                ic = iu->bu->ic;
930                cmd[2] = iu->bu->handle;
931        }
932        else if( !( a = account_get( irc->b, cmd[1] ) ) )
933        {
934                irc_usermsg( irc, "Invalid account" );
935                return;
936        }
937        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
938        {
939                irc_usermsg( irc, "That account is not on-line" );
940                return;
941        }
942       
943        if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit )
944        {
945                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
946        }
947        else
948        {
949                imc_rem_block( ic, cmd[2] );
950                imc_add_allow( ic, cmd[2] );
951               
952                irc_usermsg( irc, "Buddy `%s' moved from block- to allow-list", cmd[2] );
953        }
954}
955
956static void cmd_yesno( irc_t *irc, char **cmd )
957{
958        query_t *q = NULL;
959        int numq = 0;
960       
961        if( irc->queries == NULL )
962        {
963                /* Alright, alright, let's add a tiny easter egg here. */
964                static irc_t *last_irc = NULL;
965                static time_t last_time = 0;
966                static int times = 0;
967                static const char *msg[] = {
968                        "Oh yeah, that's right.",
969                        "Alright, alright. Now go back to work.",
970                        "Buuuuuuuuuuuuuuuurp... Excuse me!",
971                        "Yes?",
972                        "No?",
973                };
974               
975                if( last_irc == irc && time( NULL ) - last_time < 15 )
976                {
977                        if( ( ++times >= 3 ) )
978                        {
979                                irc_usermsg( irc, "%s", msg[rand()%(sizeof(msg)/sizeof(char*))] );
980                                last_irc = NULL;
981                                times = 0;
982                                return;
983                        }
984                }
985                else
986                {
987                        last_time = time( NULL );
988                        last_irc = irc;
989                        times = 0;
990                }
991               
992                irc_usermsg( irc, "Did I ask you something?" );
993                return;
994        }
995       
996        /* If there's an argument, the user seems to want to answer another question than the
997           first/last (depending on the query_order setting) one. */
998        if( cmd[1] )
999        {
1000                if( sscanf( cmd[1], "%d", &numq ) != 1 )
1001                {
1002                        irc_usermsg( irc, "Invalid query number" );
1003                        return;
1004                }
1005               
1006                for( q = irc->queries; q; q = q->next, numq -- )
1007                        if( numq == 0 )
1008                                break;
1009               
1010                if( !q )
1011                {
1012                        irc_usermsg( irc, "Uhm, I never asked you something like that..." );
1013                        return;
1014                }
1015        }
1016       
1017        if( g_strcasecmp( cmd[0], "yes" ) == 0 )
1018                query_answer( irc, q, 1 );
1019        else if( g_strcasecmp( cmd[0], "no" ) == 0 )
1020                query_answer( irc, q, 0 );
1021}
1022
1023static void cmd_set( irc_t *irc, char **cmd )
1024{
1025        cmd_set_real( irc, cmd, &irc->b->set, NULL );
1026}
1027
1028static void cmd_blist( irc_t *irc, char **cmd )
1029{
1030        int online = 0, away = 0, offline = 0;
1031        GSList *l;
1032        char s[256];
1033        char *format;
1034        int n_online = 0, n_away = 0, n_offline = 0;
1035       
1036        if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 )
1037                online = offline = away = 1;
1038        else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 )
1039                offline = 1;
1040        else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 )
1041                away = 1;
1042        else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 )
1043                online = 1;
1044        else
1045                online = away = 1;
1046       
1047        if( strchr( irc->umode, 'b' ) != NULL )
1048                format = "%s\t%s\t%s";
1049        else
1050                format = "%-16.16s  %-40.40s  %s";
1051       
1052        irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" );
1053       
1054        if( irc->root->last_channel &&
1055            strcmp( set_getstr( &irc->root->last_channel->set, "type" ), "control" ) != 0 )
1056                irc->root->last_channel = NULL;
1057       
1058        for( l = irc->users; l; l = l->next )
1059        {
1060                irc_user_t *iu = l->data;
1061                bee_user_t *bu = iu->bu;
1062               
1063                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1064                    ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE )
1065                        continue;
1066               
1067                if( online == 1 )
1068                {
1069                        char st[256] = "Online";
1070                       
1071                        if( bu->status_msg )
1072                                g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
1073                       
1074                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
1075                        irc_usermsg( irc, format, iu->nick, s, st );
1076                }
1077               
1078                n_online ++;
1079        }
1080
1081        for( l = irc->users; l; l = l->next )
1082        {
1083                irc_user_t *iu = l->data;
1084                bee_user_t *bu = iu->bu;
1085               
1086                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1087                    !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) )
1088                        continue;
1089               
1090                if( away == 1 )
1091                {
1092                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
1093                        irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
1094                }
1095                n_away ++;
1096        }
1097       
1098        for( l = irc->users; l; l = l->next )
1099        {
1100                irc_user_t *iu = l->data;
1101                bee_user_t *bu = iu->bu;
1102               
1103                if( !bu || ( irc->root->last_channel && !irc_channel_wants_user( irc->root->last_channel, iu ) ) ||
1104                    bu->flags & BEE_USER_ONLINE )
1105                        continue;
1106               
1107                if( offline == 1 )
1108                {
1109                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
1110                        irc_usermsg( irc, format, iu->nick, s, "Offline" );
1111                }
1112                n_offline ++;
1113        }
1114       
1115        irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
1116}
1117
1118static void cmd_qlist( irc_t *irc, char **cmd )
1119{
1120        query_t *q = irc->queries;
1121        int num;
1122       
1123        if( !q )
1124        {
1125                irc_usermsg( irc, "There are no pending questions." );
1126                return;
1127        }
1128       
1129        irc_usermsg( irc, "Pending queries:" );
1130       
1131        for( num = 0; q; q = q->next, num ++ )
1132                if( q->ic ) /* Not necessary yet, but it might come later */
1133                        irc_usermsg( irc, "%d, %s(%s): %s", num, q->ic->acc->prpl->name, q->ic->acc->user, q->question );
1134                else
1135                        irc_usermsg( irc, "%d, BitlBee: %s", num, q->question );
1136}
1137
1138static void cmd_chat( irc_t *irc, char **cmd )
1139{
1140        account_t *acc;
1141       
1142        if( g_strcasecmp( cmd[1], "add" ) == 0 )
1143        {
1144                char *channel, *s;
1145                struct irc_channel *ic;
1146               
1147                MIN_ARGS( 3 );
1148               
1149                if( !( acc = account_get( irc->b, cmd[2] ) ) )
1150                {
1151                        irc_usermsg( irc, "Invalid account" );
1152                        return;
1153                }
1154                else if( !acc->prpl->chat_join )
1155                {
1156                        irc_usermsg( irc, "Named chatrooms not supported on that account." );
1157                        return;
1158                }
1159               
1160                if( cmd[4] == NULL )
1161                {
1162                        channel = g_strdup( cmd[3] );
1163                        if( ( s = strchr( channel, '@' ) ) )
1164                                *s = 0;
1165                }
1166                else
1167                {
1168                        channel = g_strdup( cmd[4] );
1169                }
1170               
1171                if( strchr( CTYPES, channel[0] ) == NULL )
1172                {
1173                        s = g_strdup_printf( "#%s", channel );
1174                        g_free( channel );
1175                        channel = s;
1176                       
1177                        irc_channel_name_strip( channel );
1178                }
1179               
1180                if( ( ic = irc_channel_new( irc, channel ) ) &&
1181                    set_setstr( &ic->set, "type", "chat" ) &&
1182                    set_setstr( &ic->set, "chat_type", "room" ) &&
1183                    set_setstr( &ic->set, "account", cmd[2] ) &&
1184                    set_setstr( &ic->set, "room", cmd[3] ) )
1185                {
1186                        irc_usermsg( irc, "Chatroom successfully added." );
1187                }
1188                else
1189                {
1190                        if( ic )
1191                                irc_channel_free( ic );
1192                       
1193                        irc_usermsg( irc, "Could not add chatroom." );
1194                }
1195                g_free( channel );
1196        }
1197        else if( g_strcasecmp( cmd[1], "with" ) == 0 )
1198        {
1199                irc_user_t *iu;
1200               
1201                MIN_ARGS( 2 );
1202               
1203                if( ( iu = irc_user_by_name( irc, cmd[2] ) ) &&
1204                    iu->bu && iu->bu->ic->acc->prpl->chat_with )
1205                {
1206                        if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
1207                        {
1208                                irc_usermsg( irc, "(Possible) failure while trying to open "
1209                                                  "a groupchat with %s.", iu->nick );
1210                        }
1211                }
1212                else
1213                {
1214                        irc_usermsg( irc, "Can't open a groupchat with %s.", cmd[2] );
1215                }
1216        }
1217        else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
1218                 g_strcasecmp( cmd[1], "set" ) == 0 ||
1219                 g_strcasecmp( cmd[1], "del" ) == 0 )
1220        {
1221                irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
1222                cmd_channel( irc, cmd );
1223        }
1224        else
1225        {
1226                irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
1227        }
1228}
1229
1230static void cmd_group( irc_t *irc, char **cmd )
1231{
1232        GSList *l;
1233        int len;
1234       
1235        len = strlen( cmd[1] );
1236        if( g_strncasecmp( cmd[1], "list", len ) == 0 )
1237        {
1238                int n = 0;
1239               
1240                if( strchr( irc->umode, 'b' ) )
1241                        irc_usermsg( irc, "Group list:" );
1242               
1243                for( l = irc->b->groups; l; l = l->next )
1244                {
1245                        bee_group_t *bg = l->data;
1246                        irc_usermsg( irc, "%d. %s", n ++, bg->name );
1247                }
1248                irc_usermsg( irc, "End of group list" );
1249        }
1250        else
1251        {
1252                irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group", cmd[1] );
1253        }
1254}
1255
1256static void cmd_transfer( irc_t *irc, char **cmd )
1257{
1258        GSList *files = irc->file_transfers;
1259        enum { LIST, REJECT, CANCEL };
1260        int subcmd = LIST;
1261        int fid;
1262
1263        if( !files )
1264        {
1265                irc_usermsg( irc, "No pending transfers" );
1266                return;
1267        }
1268
1269        if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) )
1270        {
1271                subcmd = REJECT;
1272        }
1273        else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && 
1274                 cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) )
1275        {
1276                subcmd = CANCEL;
1277        }
1278
1279        for( ; files; files = g_slist_next( files ) )
1280        {
1281                file_transfer_t *file = files->data;
1282               
1283                switch( subcmd ) {
1284                case LIST:
1285                        if ( file->status == FT_STATUS_LISTENING )
1286                                irc_usermsg( irc, 
1287                                        "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1288                        else 
1289                        {
1290                                int kb_per_s = 0;
1291                                time_t diff = time( NULL ) - file->started ? : 1;
1292                                if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
1293                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1294                                       
1295                                irc_usermsg( irc, 
1296                                        "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 
1297                                        file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
1298                        }
1299                        break;
1300                case REJECT:
1301                        if( file->status == FT_STATUS_LISTENING )
1302                        {
1303                                irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name );
1304                                imcb_file_canceled( file->ic, file, "Denied by user" );
1305                        }
1306                        break;
1307                case CANCEL:
1308                        if( file->local_id == fid )
1309                        {
1310                                irc_usermsg( irc, "Canceling file transfer for %s", file->file_name );
1311                                imcb_file_canceled( file->ic, file, "Canceled by user" );
1312                        }
1313                        break;
1314                }
1315        }
1316}
1317
1318static void cmd_nick( irc_t *irc, char **cmd )
1319{
1320        irc_usermsg( irc, "This command is deprecated. Try: account %s set display_name", cmd[1] );
1321}
1322
1323/* Maybe this should be a stand-alone command as well? */
1324static void bitlbee_whatsnew( irc_t *irc )
1325{
1326        int last = set_getint( &irc->b->set, "last_version" );
1327        char s[16], *msg;
1328       
1329        if( last >= BITLBEE_VERSION_CODE )
1330                return;
1331       
1332        msg = help_get_whatsnew( &(global.help), last );
1333       
1334        if( msg )
1335                irc_usermsg( irc, "%s: This seems to be your first time using this "
1336                                  "this version of BitlBee. Here's a list of new "
1337                                  "features you may like to know about:\n\n%s\n",
1338                                  irc->user->nick, msg );
1339       
1340        g_free( msg );
1341       
1342        g_snprintf( s, sizeof( s ), "%d", BITLBEE_VERSION_CODE );
1343        set_setstr( &irc->b->set, "last_version", s );
1344}
1345
1346/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
1347command_t root_commands[] = {
1348        { "account",        1, cmd_account,        0 },
1349        { "add",            2, cmd_add,            0 },
1350        { "allow",          1, cmd_allow,          0 },
1351        { "blist",          0, cmd_blist,          0 },
1352        { "block",          1, cmd_block,          0 },
1353        { "channel",        1, cmd_channel,        0 },
1354        { "chat",           1, cmd_chat,           0 },
1355        { "drop",           1, cmd_drop,           0 },
1356        { "ft",             0, cmd_transfer,       0 },
1357        { "group",          1, cmd_group,          0 },
1358        { "help",           0, cmd_help,           0 }, 
1359        { "identify",       1, cmd_identify,       0 },
1360        { "info",           1, cmd_info,           0 },
1361        { "nick",           1, cmd_nick,           0 },
1362        { "no",             0, cmd_yesno,          0 },
1363        { "qlist",          0, cmd_qlist,          0 },
1364        { "register",       1, cmd_register,       0 },
1365        { "remove",         1, cmd_remove,         0 },
1366        { "rename",         2, cmd_rename,         0 },
1367        { "save",           0, cmd_save,           0 },
1368        { "set",            0, cmd_set,            0 },
1369        { "transfer",       0, cmd_transfer,       0 },
1370        { "yes",            0, cmd_yesno,          0 },
1371        /* Not expecting too many plugins adding root commands so just make a
1372           dumb array with some empty entried at the end. */
1373        { NULL },
1374        { NULL },
1375        { NULL },
1376        { NULL },
1377        { NULL },
1378        { NULL },
1379        { NULL },
1380        { NULL },
1381        { NULL },
1382};
1383static const int num_root_commands = sizeof( root_commands ) / sizeof( command_t );
1384
1385gboolean root_command_add( const char *command, int params, void (*func)(irc_t *, char **args), int flags )
1386{
1387        int i;
1388       
1389        if( root_commands[num_root_commands-2].command )
1390                /* Planning fail! List is full. */
1391                return FALSE;
1392       
1393        for( i = 0; root_commands[i].command; i++ )
1394        {
1395                if( g_strcasecmp( root_commands[i].command, command ) == 0 )
1396                        return FALSE;
1397                else if( g_strcasecmp( root_commands[i].command, command ) > 0 )
1398                        break;
1399        }
1400        memmove( root_commands + i + 1, root_commands + i,
1401                 sizeof( command_t ) * ( num_root_commands - i - 1 ) );
1402       
1403        root_commands[i].command = g_strdup( command );
1404        root_commands[i].required_parameters = params;
1405        root_commands[i].execute = func;
1406        root_commands[i].flags = flags;
1407       
1408        return TRUE;
1409}
Note: See TracBrowser for help on using the repository browser.