source: root_commands.c @ af9f2ca

Last change on this file since af9f2ca was af9f2ca, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-11T10:59:56Z

Added allow_takeover setting for people who don't like this new functionality.

  • Property mode set to 100644
File size: 29.0 KB
RevLine 
[b7d3cc34]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
[0baed0d]4  * Copyright 2002-2010 Wilmer van der Gaast and others                *
[b7d3cc34]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"
[f1c2b21]30#include "ipc.h"
[b7d3cc34]31
[280c56a]32void root_command_string( irc_t *irc, char *command )
[7e563ed]33{
[24b8bbb]34        root_command( irc, split_command_parts( command ) );
[7e563ed]35}
36
[3b99524]37#define MIN_ARGS( x, y... )                                                    \
38        do                                                                     \
39        {                                                                      \
[07054a5]40                int blaat;                                                     \
41                for( blaat = 0; blaat <= x; blaat ++ )                         \
42                        if( cmd[blaat] == NULL )                               \
[3b99524]43                        {                                                      \
44                                irc_usermsg( irc, "Not enough parameters given (need %d).", x ); \
45                                return y;                                      \
46                        }                                                      \
47        } while( 0 )
48
[f73b969]49void root_command( irc_t *irc, char *cmd[] )
[7e563ed]50{       
[6c56f42]51        int i, len;
[7e563ed]52       
53        if( !cmd[0] )
[f73b969]54                return;
[7e563ed]55       
[6c56f42]56        len = strlen( cmd[0] );
[7e563ed]57        for( i = 0; commands[i].command; i++ )
[6c56f42]58                if( g_strncasecmp( commands[i].command, cmd[0], len ) == 0 )
[7e563ed]59                {
[6c56f42]60                        if( commands[i+1].command &&
61                            g_strncasecmp( commands[i+1].command, cmd[0], len ) == 0 )
62                                /* Only match on the first letters if the match is unique. */
63                                break;
64                       
[3b99524]65                        MIN_ARGS( commands[i].required_parameters );
66                       
[7e563ed]67                        commands[i].execute( irc, cmd );
[f73b969]68                        return;
[7e563ed]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
[f73b969]74static void cmd_help( irc_t *irc, char **cmd )
[b7d3cc34]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
[90bbb0e]101static void cmd_account( irc_t *irc, char **cmd );
102
[f73b969]103static void cmd_identify( irc_t *irc, char **cmd )
[b7d3cc34]104{
[92cb8c4]105        storage_status_t status;
106        gboolean load = TRUE;
107        char *password = cmd[1];
[b7d3cc34]108       
[92cb8c4]109        if( irc->status & USTATUS_IDENTIFIED )
[e9cf291]110        {
111                irc_usermsg( irc, "You're already logged in." );
112                return;
113        }
114       
[92cb8c4]115        if( strncmp( cmd[1], "-no", 3 ) == 0 )
116        {
117                load = FALSE;
118                password = cmd[2];
119        }
120        else if( strncmp( cmd[1], "-force", 6 ) == 0 )
121        {
122                password = cmd[2];
123        }
124        else if( irc->b->accounts != NULL )
125        {
126                irc_usermsg( irc,
127                             "You're trying to identify yourself, but already have "
128                             "at least one IM account set up. "
129                             "Use \x02identify -noload\x02 or \x02identify -force\x02 "
130                             "instead (see \x02help identify\x02)." );
131                return;
132        }
133       
134        if( password == NULL )
135        {
136                MIN_ARGS( 2 );
137        }
138       
139        if( load )
140                status = storage_load( irc, password );
141        else
142                status = storage_check_pass( irc->user->nick, password );
143       
[09adf08]144        switch (status) {
145        case STORAGE_INVALID_PASSWORD:
[b7d3cc34]146                irc_usermsg( irc, "Incorrect password" );
[09adf08]147                break;
148        case STORAGE_NO_SUCH_USER:
[b7d3cc34]149                irc_usermsg( irc, "The nick is (probably) not registered" );
[09adf08]150                break;
151        case STORAGE_OK:
[92cb8c4]152                irc_usermsg( irc, "Password accepted%s",
153                             load ? ", settings and accounts loaded" : "" );
154                irc_setpass( irc, password );
[3183c21]155                irc->status |= USTATUS_IDENTIFIED;
[238f828]156                irc_umode_set( irc, "+R", 1 );
[6c2404e]157               
[e92c4f4]158                /* The following code is a bit hairy now. With takeover
159                   support, we shouldn't immediately auto_connect in case
160                   we're going to offer taking over an existing session.
161                   Do it in 200ms since that should give the parent process
162                   enough time to come back to us. */
163                if( load )
[6c2404e]164                {
[e92c4f4]165                        irc_channel_auto_joins( irc, NULL );
166                        if( set_getbool( &irc->b->set, "auto_connect" ) )
[6c2404e]167                                irc->login_source_id = b_timeout_add( 200,
168                                        cmd_identify_finish, irc );
169                }
[e92c4f4]170               
171                /* If ipc_child_identify() returns FALSE, it means we're
172                   already sure that there's no takeover target (only
173                   possible in 1-process daemon mode). Start auto_connect
174                   immediately. */
175                if( !ipc_child_identify( irc ) && load &&
176                    set_getbool( &irc->b->set, "auto_connect" ) )
[6c2404e]177                        cmd_identify_finish( irc, 0, 0 );
178               
[09adf08]179                break;
[c121f89]180        case STORAGE_OTHER_ERROR:
[09adf08]181        default:
[c121f89]182                irc_usermsg( irc, "Unknown error while loading configuration" );
[09adf08]183                break;
[b7d3cc34]184        }
185}
186
[6c2404e]187gboolean cmd_identify_finish( gpointer data, gint fd, b_input_condition cond )
188{
189        char *account_on[] = { "account", "on", NULL };
190        irc_t *irc = data;
191       
192        cmd_account( irc, account_on );
193       
[af9f2ca]194        b_event_remove( irc->login_source_id );
[f545372]195        irc->login_source_id = -1;
[6c2404e]196        return FALSE;
197}
198
[f73b969]199static void cmd_register( irc_t *irc, char **cmd )
[b7d3cc34]200{
201        if( global.conf->authmode == AUTHMODE_REGISTERED )
202        {
203                irc_usermsg( irc, "This server does not allow registering new accounts" );
[f73b969]204                return;
[b7d3cc34]205        }
[1ee6c18]206
[3183c21]207        switch( storage_save( irc, cmd[1], FALSE ) ) {
[a1f17d4]208                case STORAGE_ALREADY_EXISTS:
209                        irc_usermsg( irc, "Nick is already registered" );
210                        break;
211                       
212                case STORAGE_OK:
[0383943]213                        irc_usermsg( irc, "Account successfully created" );
[3183c21]214                        irc_setpass( irc, cmd[1] );
[79e826a]215                        irc->status |= USTATUS_IDENTIFIED;
[238f828]216                        irc_umode_set( irc, "+R", 1 );
[a1f17d4]217                        break;
218
219                default:
220                        irc_usermsg( irc, "Error registering" );
221                        break;
[b7d3cc34]222        }
223}
224
[f73b969]225static void cmd_drop( irc_t *irc, char **cmd )
[b7d3cc34]226{
[a1f17d4]227        storage_status_t status;
228       
[1f92a58]229        status = storage_remove (irc->user->nick, cmd[1]);
[a1f17d4]230        switch (status) {
231        case STORAGE_NO_SUCH_USER:
[b7d3cc34]232                irc_usermsg( irc, "That account does not exist" );
[f73b969]233                break;
[a1f17d4]234        case STORAGE_INVALID_PASSWORD:
[1ee6c18]235                irc_usermsg( irc, "Password invalid" );
[f73b969]236                break;
[a1f17d4]237        case STORAGE_OK:
[7cad7b4]238                irc_setpass( irc, NULL );
[79e826a]239                irc->status &= ~USTATUS_IDENTIFIED;
[238f828]240                irc_umode_set( irc, "-R", 1 );
[1f92a58]241                irc_usermsg( irc, "Account `%s' removed", irc->user->nick );
[f73b969]242                break;
[a1f17d4]243        default:
[30ce1ce]244                irc_usermsg( irc, "Error: `%d'", status );
[f73b969]245                break;
[b7d3cc34]246        }
247}
[1f92a58]248
249static void cmd_save( irc_t *irc, char **cmd )
250{
251        if( ( irc->status & USTATUS_IDENTIFIED ) == 0 )
252                irc_usermsg( irc, "Please create an account first" );
253        else if( storage_save( irc, NULL, TRUE ) == STORAGE_OK )
254                irc_usermsg( irc, "Configuration saved" );
255        else
256                irc_usermsg( irc, "Configuration could not be saved!" );
257}
[b7d3cc34]258
[f536a99]259static void cmd_showset( irc_t *irc, set_t **head, char *key )
[f3579fd]260{
[f536a99]261        char *val;
[f3579fd]262       
[f536a99]263        if( ( val = set_getstr( head, key ) ) )
264                irc_usermsg( irc, "%s = `%s'", key, val );
[f3579fd]265        else
[f536a99]266                irc_usermsg( irc, "%s is empty", key );
[f3579fd]267}
268
[e7bc722]269typedef set_t** (*cmd_set_findhead)( irc_t*, char* );
[c05eb5b]270typedef int (*cmd_set_checkflags)( irc_t*, set_t *set );
[e7bc722]271
[e907683]272static int cmd_set_real( irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags )
[e7bc722]273{
[e907683]274        char *set_name = NULL, *value = NULL;
275        gboolean del = FALSE;
[e7bc722]276       
277        if( cmd[1] && g_strncasecmp( cmd[1], "-del", 4 ) == 0 )
[d4810df]278        {
279                MIN_ARGS( 2, 0 );
[e907683]280                set_name = cmd[2];
281                del = TRUE;
[d4810df]282        }
[e7bc722]283        else
284        {
[e907683]285                set_name = cmd[1];
286                value = cmd[2];
[e7bc722]287        }
288       
[e907683]289        if( set_name && ( value || del ) )
[e7bc722]290        {
291                set_t *s = set_find( head, set_name );
292                int st;
293               
[8a08d92]294                if( s && checkflags && checkflags( irc, s ) == 0 )
[e7bc722]295                        return 0;
296               
[e907683]297                if( del )
[e7bc722]298                        st = set_reset( head, set_name );
299                else
[e907683]300                        st = set_setstr( head, set_name, value );
[e7bc722]301               
302                if( set_getstr( head, set_name ) == NULL )
303                {
[e907683]304                        /* This happens when changing the passwd, for example.
305                           Showing these msgs instead gives slightly clearer
306                           feedback. */
[e7bc722]307                        if( st )
308                                irc_usermsg( irc, "Setting changed successfully" );
309                        else
310                                irc_usermsg( irc, "Failed to change setting" );
311                }
312                else
313                {
314                        cmd_showset( irc, head, set_name );
315                }
316        }
317        else if( set_name )
318        {
319                cmd_showset( irc, head, set_name );
320        }
321        else
322        {
323                set_t *s = *head;
324                while( s )
325                {
326                        cmd_showset( irc, &s, s->key );
327                        s = s->next;
328                }
329        }
330       
331        return 1;
332}
333
[c05eb5b]334static int cmd_account_set_checkflags( irc_t *irc, set_t *s )
335{
336        account_t *a = s->data;
337       
338        if( a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY )
339        {
340                irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "off" );
341                return 0;
342        }
343        else if( !a->ic && s && s->flags & ACC_SET_ONLINE_ONLY )
344        {
345                irc_usermsg( irc, "This setting can only be changed when the account is %s-line", "on" );
346                return 0;
347        }
348       
349        return 1;
350}
351
[f73b969]352static void cmd_account( irc_t *irc, char **cmd )
[b7d3cc34]353{
354        account_t *a;
[c7eb771]355        int len;
[b7d3cc34]356       
[3af70b0]357        if( global.conf->authmode == AUTHMODE_REGISTERED && !( irc->status & USTATUS_IDENTIFIED ) )
[b7d3cc34]358        {
359                irc_usermsg( irc, "This server only accepts registered users" );
[f73b969]360                return;
[b7d3cc34]361        }
362       
[c7eb771]363        len = strlen( cmd[1] );
364       
365        if( len >= 1 && g_strncasecmp( cmd[1], "add", len ) == 0 )
[b7d3cc34]366        {
[7b23afd]367                struct prpl *prpl;
[b7d3cc34]368               
[3b99524]369                MIN_ARGS( 4 );
[b7d3cc34]370               
[a9a7287]371                prpl = find_protocol( cmd[2] );
[b7d3cc34]372               
[7b23afd]373                if( prpl == NULL )
[b7d3cc34]374                {
375                        irc_usermsg( irc, "Unknown protocol" );
[f73b969]376                        return;
[b7d3cc34]377                }
378
[d860a8d]379                a = account_add( irc->b, prpl, cmd[3], cmd[4] );
[b7d3cc34]380                if( cmd[5] )
[30ce1ce]381                {
382                        irc_usermsg( irc, "Warning: Passing a servername/other flags to `account add' "
383                                          "is now deprecated. Use `account set' instead." );
[5100caa]384                        set_setstr( &a->set, "server", cmd[5] );
[30ce1ce]385                }
[b7d3cc34]386               
387                irc_usermsg( irc, "Account successfully added" );
[e907683]388               
389                return;
[b7d3cc34]390        }
[c7eb771]391        else if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
[b7d3cc34]392        {
393                int i = 0;
394               
[e6e1f18]395                if( strchr( irc->umode, 'b' ) )
396                        irc_usermsg( irc, "Account list:" );
397               
[d860a8d]398                for( a = irc->b->accounts; a; a = a->next )
[b7d3cc34]399                {
400                        char *con;
401                       
[0da65d5]402                        if( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) )
[b7d3cc34]403                                con = " (connected)";
[0da65d5]404                        else if( a->ic )
[b7d3cc34]405                                con = " (connecting)";
406                        else if( a->reconnect )
407                                con = " (awaiting reconnect)";
408                        else
409                                con = "";
410                       
[7b23afd]411                        irc_usermsg( irc, "%2d. %s, %s%s", i, a->prpl->name, a->user, con );
[b7d3cc34]412                       
413                        i ++;
414                }
415                irc_usermsg( irc, "End of account list" );
[e907683]416               
417                return;
418        }
419        else if( cmd[2] )
420        {
421                /* Try the following two only if cmd[2] == NULL */
[b7d3cc34]422        }
[c7eb771]423        else if( len >= 2 && g_strncasecmp( cmd[1], "on", len ) == 0 )
[b7d3cc34]424        {
[e907683]425                if ( irc->b->accounts )
[b7d3cc34]426                {
[e907683]427                        irc_usermsg( irc, "Trying to get all accounts connected..." );
428               
429                        for( a = irc->b->accounts; a; a = a->next )
430                                if( !a->ic && a->auto_connect )
[d860a8d]431                                        account_on( irc->b, a );
[e907683]432                } 
[b7d3cc34]433                else
434                {
[e907683]435                        irc_usermsg( irc, "No accounts known. Use `account add' to add one." );
[b7d3cc34]436                }
[e907683]437               
438                return;
[b7d3cc34]439        }
[c7eb771]440        else if( len >= 2 && g_strncasecmp( cmd[1], "off", len ) == 0 )
[b7d3cc34]441        {
[e907683]442                irc_usermsg( irc, "Deactivating all active (re)connections..." );
443               
444                for( a = irc->b->accounts; a; a = a->next )
[b7d3cc34]445                {
[0da65d5]446                        if( a->ic )
[d860a8d]447                                account_off( irc->b, a );
[b7d3cc34]448                        else if( a->reconnect )
449                                cancel_auto_reconnect( a );
[e907683]450                }
451               
452                return;
453        }
454       
455        MIN_ARGS( 2 );
[c7eb771]456        len = strlen( cmd[2] );
[e907683]457       
458        /* At least right now, don't accept on/off/set/del as account IDs even
459           if they're a proper match, since people not familiar with the new
460           syntax yet may get a confusing/nasty surprise. */
461        if( g_strcasecmp( cmd[1], "on" ) == 0 ||
462            g_strcasecmp( cmd[1], "off" ) == 0 ||
463            g_strcasecmp( cmd[1], "set" ) == 0 ||
464            g_strcasecmp( cmd[1], "del" ) == 0 ||
465            ( a = account_get( irc->b, cmd[1] ) ) == NULL )
466        {
467                irc_usermsg( irc, "Could not find account `%s'. Note that the syntax "
468                             "of the account command changed, see \x02help account\x02.", cmd[1] );
469               
470                return;
471        }
472       
[c7eb771]473        if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
[e907683]474        {
475                if( a->ic )
476                {
477                        irc_usermsg( irc, "Account is still logged in, can't delete" );
[b7d3cc34]478                }
479                else
480                {
[e907683]481                        account_del( irc->b, a );
482                        irc_usermsg( irc, "Account deleted" );
[b7d3cc34]483                }
484        }
[c7eb771]485        else if( len >= 2 && g_strncasecmp( cmd[2], "on", len ) == 0 )
[5100caa]486        {
[e907683]487                if( a->ic )
488                        irc_usermsg( irc, "Account already online" );
489                else
490                        account_on( irc->b, a );
491        }
[c7eb771]492        else if( len >= 2 && g_strncasecmp( cmd[2], "off", len ) == 0 )
[e907683]493        {
494                if( a->ic )
495                {
496                        account_off( irc->b, a );
497                }
498                else if( a->reconnect )
499                {
500                        cancel_auto_reconnect( a );
501                        irc_usermsg( irc, "Reconnect cancelled" );
502                }
503                else
504                {
505                        irc_usermsg( irc, "Account already offline" );
506                }
507        }
[c7eb771]508        else if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
[e907683]509        {
510                cmd_set_real( irc, cmd + 2, &a->set, cmd_account_set_checkflags );
[5100caa]511        }
[b7d3cc34]512        else
513        {
[e907683]514                irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account", cmd[2] );
[b7d3cc34]515        }
516}
517
[e907683]518static void cmd_channel( irc_t *irc, char **cmd )
[c133d4b8]519{
520        irc_channel_t *ic;
[c7eb771]521        int len;
522       
523        len = strlen( cmd[1] );
[c133d4b8]524       
[c7eb771]525        if( len >= 1 && g_strncasecmp( cmd[1], "list", len ) == 0 )
[36562b0]526        {
527                GSList *l;
528                int i = 0;
529               
530                if( strchr( irc->umode, 'b' ) )
531                        irc_usermsg( irc, "Channel list:" );
532               
533                for( l = irc->channels; l; l = l->next )
534                {
535                        irc_channel_t *ic = l->data;
536                       
537                        irc_usermsg( irc, "%2d. %s, %s channel%s", i, ic->name,
538                                     set_getstr( &ic->set, "type" ),
539                                     ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "" );
540                       
541                        i ++;
542                }
543                irc_usermsg( irc, "End of channel list" );
[e907683]544               
545                return;
[36562b0]546        }
[e907683]547       
548        MIN_ARGS( 2 );
[c7eb771]549        len = strlen( cmd[2] );
[e907683]550       
551        if( ( ic = irc_channel_get( irc, cmd[1] ) ) == NULL )
[a4d920b]552        {
[e907683]553                irc_usermsg( irc, "Could not find channel `%s'", cmd[1] );
554                return;
555        }
556       
[c7eb771]557        if( len >= 1 && g_strncasecmp( cmd[2], "set", len ) == 0 )
[e907683]558        {
559                cmd_set_real( irc, cmd + 2, &ic->set, NULL );
560        }
[c7eb771]561        else if( len >= 1 && g_strncasecmp( cmd[2], "del", len ) == 0 )
[e907683]562        {
563                if( !( ic->flags & IRC_CHANNEL_JOINED ) &&
[a4d920b]564                    ic != ic->irc->default_channel )
565                {
566                        irc_usermsg( irc, "Channel %s deleted.", ic->name );
567                        irc_channel_free( ic );
568                }
569                else
570                        irc_usermsg( irc, "Couldn't remove channel (main channel %s or "
571                                          "channels you're still in cannot be deleted).",
[5266354]572                                          irc->default_channel->name );
[a4d920b]573        }
[c133d4b8]574        else
575        {
[e907683]576                irc_usermsg( irc, "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel", cmd[1] );
[c133d4b8]577        }
578}
579
[f73b969]580static void cmd_add( irc_t *irc, char **cmd )
[b7d3cc34]581{
582        account_t *a;
[f0cb961]583        int add_on_server = 1;
[f8de26f]584       
585        if( g_strcasecmp( cmd[1], "-tmp" ) == 0 )
586        {
[77fc000c]587                MIN_ARGS( 3 );
[f0cb961]588                add_on_server = 0;
[7adc657]589                cmd ++;
[f8de26f]590        }
[b7d3cc34]591       
[dbb0ce3]592        if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]593        {
594                irc_usermsg( irc, "Invalid account" );
[f73b969]595                return;
[b7d3cc34]596        }
[0da65d5]597        else if( !( a->ic && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]598        {
599                irc_usermsg( irc, "That account is not on-line" );
[f73b969]600                return;
[b7d3cc34]601        }
602       
603        if( cmd[3] )
604        {
605                if( !nick_ok( cmd[3] ) )
606                {
607                        irc_usermsg( irc, "The requested nick `%s' is invalid", cmd[3] );
[f73b969]608                        return;
[b7d3cc34]609                }
[dbb0ce3]610                else if( irc_user_by_name( irc, cmd[3] ) )
[b7d3cc34]611                {
612                        irc_usermsg( irc, "The requested nick `%s' already exists", cmd[3] );
[f73b969]613                        return;
[b7d3cc34]614                }
615                else
616                {
[5b52a48]617                        nick_set( a, cmd[2], cmd[3] );
[b7d3cc34]618                }
619        }
[f8de26f]620       
[f0cb961]621        if( add_on_server )
[46d215d]622                a->prpl->add_buddy( a->ic, cmd[2], NULL );
[7adc657]623        else
[dbb0ce3]624                /* Only for add -tmp. For regular adds, this callback will
625                   be called once the IM server confirms. */
[ad404ab]626                bee_user_new( irc->b, a->ic, cmd[2], BEE_USER_LOCAL );
[f0cb961]627       
628        irc_usermsg( irc, "Adding `%s' to your contact list", cmd[2]  );
[b7d3cc34]629}
630
[dbb0ce3]631static void cmd_remove( irc_t *irc, char **cmd )
632{
633        irc_user_t *iu;
634        bee_user_t *bu;
635        char *s;
636       
637        if( !( iu = irc_user_by_name( irc, cmd[1] ) ) || !( bu = iu->bu ) )
638        {
639                irc_usermsg( irc, "Buddy `%s' not found", cmd[1] );
640                return;
641        }
642        s = g_strdup( bu->handle );
643       
644        bu->ic->acc->prpl->remove_buddy( bu->ic, bu->handle, NULL );
645        nick_del( bu->ic->acc, bu->handle );
[eabc9d2]646        //TODO(wilmer): bee_user_free() and/or let the IM mod do it? irc_user_free( irc, cmd[1] );
[dbb0ce3]647       
648        irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
649        g_free( s );
650       
651        return;
652}
653
[f73b969]654static void cmd_info( irc_t *irc, char **cmd )
[b7d3cc34]655{
[0da65d5]656        struct im_connection *ic;
[b7d3cc34]657        account_t *a;
658       
659        if( !cmd[2] )
660        {
[aa44bdd]661                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
662                if( !iu || !iu->bu )
[b7d3cc34]663                {
664                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]665                        return;
[b7d3cc34]666                }
[aa44bdd]667                ic = iu->bu->ic;
668                cmd[2] = iu->bu->handle;
[b7d3cc34]669        }
[aa44bdd]670        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]671        {
672                irc_usermsg( irc, "Invalid account" );
[f73b969]673                return;
[b7d3cc34]674        }
[0da65d5]675        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]676        {
677                irc_usermsg( irc, "That account is not on-line" );
[f73b969]678                return;
[b7d3cc34]679        }
680       
[0da65d5]681        if( !ic->acc->prpl->get_info )
[b7d3cc34]682        {
683                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
684        }
[f73b969]685        else
686        {
[0da65d5]687                ic->acc->prpl->get_info( ic, cmd[2] );
[f73b969]688        }
[b7d3cc34]689}
690
[f73b969]691static void cmd_rename( irc_t *irc, char **cmd )
[b7d3cc34]692{
[9a9b520]693        irc_user_t *iu, *old;
[b7d3cc34]694       
[57c96f7]695        iu = irc_user_by_name( irc, cmd[1] );
696       
697        if( iu == NULL )
[0baed0d]698        {
[57c96f7]699                irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[0baed0d]700        }
[57c96f7]701        else if( iu == irc->user )
[b7d3cc34]702        {
[6c2404e]703                irc_usermsg( irc, "Use /nick to change your own nickname" );
[b7d3cc34]704        }
[f73b969]705        else if( !nick_ok( cmd[2] ) )
[b7d3cc34]706        {
707                irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
708        }
[9a9b520]709        else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
[b7d3cc34]710        {
[57c96f7]711                irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
[b7d3cc34]712        }
[f73b969]713        else
[b7d3cc34]714        {
[57c96f7]715                if( !irc_user_set_nick( iu, cmd[2] ) )
716                {
717                        irc_usermsg( irc, "Error while changing nick" );
718                        return;
719                }
720               
721                if( iu == irc->root )
[f73b969]722                {
[7125cb3]723                        /* If we're called internally (user did "set root_nick"),
724                           let's not go O(INF). :-) */
[1195cec]725                        if( strcmp( cmd[0], "set_rename" ) != 0 )
[57c96f7]726                                set_setstr( &irc->b->set, "root_nick", cmd[2] );
[f73b969]727                }
[57c96f7]728                else if( iu->bu )
[f73b969]729                {
[57c96f7]730                        nick_set( iu->bu->ic->acc, iu->bu->handle, cmd[2] );
[f73b969]731                }
732               
733                irc_usermsg( irc, "Nick successfully changed" );
[b7d3cc34]734        }
735}
736
[1195cec]737char *set_eval_root_nick( set_t *set, char *new_nick )
738{
739        irc_t *irc = set->data;
740       
[0a6e5d1]741        if( strcmp( irc->root->nick, new_nick ) != 0 )
[1195cec]742        {
[0a6e5d1]743                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
[1195cec]744               
745                cmd_rename( irc, cmd );
746        }
747       
[0a6e5d1]748        return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID;
[1195cec]749}
[0baed0d]750
[f73b969]751static void cmd_block( irc_t *irc, char **cmd )
[b7d3cc34]752{
[0da65d5]753        struct im_connection *ic;
[b7d3cc34]754        account_t *a;
755       
[2272cb3]756        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
[87b6a3e]757        {
758                char *format;
759                GSList *l;
760               
761                if( strchr( irc->umode, 'b' ) != NULL )
762                        format = "%s\t%s";
763                else
[57ef864]764                        format = "%-32.32s  %-16.16s";
[87b6a3e]765               
766                irc_usermsg( irc, format, "Handle", "Nickname" );
[0da65d5]767                for( l = a->ic->deny; l; l = l->next )
[87b6a3e]768                {
[2272cb3]769                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
770                        irc_user_t *iu = bu ? bu->ui_data : NULL;
771                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
[87b6a3e]772                }
773                irc_usermsg( irc, "End of list." );
774               
775                return;
776        }
777        else if( !cmd[2] )
[b7d3cc34]778        {
[2272cb3]779                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
780                if( !iu || !iu->bu )
[b7d3cc34]781                {
782                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]783                        return;
[b7d3cc34]784                }
[2272cb3]785                ic = iu->bu->ic;
786                cmd[2] = iu->bu->handle;
[b7d3cc34]787        }
[2272cb3]788        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]789        {
790                irc_usermsg( irc, "Invalid account" );
[f73b969]791                return;
[b7d3cc34]792        }
[0da65d5]793        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]794        {
795                irc_usermsg( irc, "That account is not on-line" );
[f73b969]796                return;
[b7d3cc34]797        }
798       
[0da65d5]799        if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit )
[b7d3cc34]800        {
801                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
802        }
803        else
804        {
[84b045d]805                imc_rem_allow( ic, cmd[2] );
806                imc_add_block( ic, cmd[2] );
[da3b536]807                irc_usermsg( irc, "Buddy `%s' moved from your allow- to your block-list", cmd[2] );
[b7d3cc34]808        }
809}
810
[f73b969]811static void cmd_allow( irc_t *irc, char **cmd )
[b7d3cc34]812{
[0da65d5]813        struct im_connection *ic;
[b7d3cc34]814        account_t *a;
815       
[2272cb3]816        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
[87b6a3e]817        {
818                char *format;
819                GSList *l;
820               
821                if( strchr( irc->umode, 'b' ) != NULL )
822                        format = "%s\t%s";
823                else
[57ef864]824                        format = "%-32.32s  %-16.16s";
[87b6a3e]825               
826                irc_usermsg( irc, format, "Handle", "Nickname" );
[0da65d5]827                for( l = a->ic->permit; l; l = l->next )
[87b6a3e]828                {
[2272cb3]829                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
830                        irc_user_t *iu = bu ? bu->ui_data : NULL;
831                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
[87b6a3e]832                }
833                irc_usermsg( irc, "End of list." );
834               
835                return;
836        }
837        else if( !cmd[2] )
[b7d3cc34]838        {
[2272cb3]839                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
840                if( !iu || !iu->bu )
[b7d3cc34]841                {
842                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]843                        return;
[b7d3cc34]844                }
[2272cb3]845                ic = iu->bu->ic;
846                cmd[2] = iu->bu->handle;
[b7d3cc34]847        }
[2272cb3]848        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]849        {
850                irc_usermsg( irc, "Invalid account" );
[f73b969]851                return;
[b7d3cc34]852        }
[0da65d5]853        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]854        {
855                irc_usermsg( irc, "That account is not on-line" );
[f73b969]856                return;
[b7d3cc34]857        }
858       
[0da65d5]859        if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit )
[b7d3cc34]860        {
861                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
862        }
863        else
864        {
[84b045d]865                imc_rem_block( ic, cmd[2] );
866                imc_add_allow( ic, cmd[2] );
[b7d3cc34]867               
[da3b536]868                irc_usermsg( irc, "Buddy `%s' moved from your block- to your allow-list", cmd[2] );
[b7d3cc34]869        }
870}
871
[f73b969]872static void cmd_yesno( irc_t *irc, char **cmd )
[b7d3cc34]873{
874        query_t *q = NULL;
875        int numq = 0;
876       
877        if( irc->queries == NULL )
878        {
879                irc_usermsg( irc, "Did I ask you something?" );
[f73b969]880                return;
[b7d3cc34]881        }
882       
883        /* If there's an argument, the user seems to want to answer another question than the
884           first/last (depending on the query_order setting) one. */
885        if( cmd[1] )
886        {
887                if( sscanf( cmd[1], "%d", &numq ) != 1 )
888                {
889                        irc_usermsg( irc, "Invalid query number" );
[f73b969]890                        return;
[b7d3cc34]891                }
892               
893                for( q = irc->queries; q; q = q->next, numq -- )
894                        if( numq == 0 )
895                                break;
896               
897                if( !q )
898                {
899                        irc_usermsg( irc, "Uhm, I never asked you something like that..." );
[f73b969]900                        return;
[b7d3cc34]901                }
902        }
903       
904        if( g_strcasecmp( cmd[0], "yes" ) == 0 )
905                query_answer( irc, q, 1 );
906        else if( g_strcasecmp( cmd[0], "no" ) == 0 )
907                query_answer( irc, q, 0 );
908}
909
[f73b969]910static void cmd_set( irc_t *irc, char **cmd )
[b7d3cc34]911{
[e907683]912        cmd_set_real( irc, cmd, &irc->b->set, NULL );
[b7d3cc34]913}
914
[f73b969]915static void cmd_blist( irc_t *irc, char **cmd )
[b7d3cc34]916{
917        int online = 0, away = 0, offline = 0;
[4c3519a]918        GSList *l;
[aefa533e]919        char s[256];
920        char *format;
[b7d3cc34]921        int n_online = 0, n_away = 0, n_offline = 0;
922       
923        if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 )
924                online = offline = away = 1;
925        else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 )
926                offline = 1;
927        else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 )
928                away = 1;
929        else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 )
930                online = 1;
931        else
[449a51d]932                online = away = 1;
[b7d3cc34]933       
[aefa533e]934        if( strchr( irc->umode, 'b' ) != NULL )
935                format = "%s\t%s\t%s";
936        else
937                format = "%-16.16s  %-40.40s  %s";
938       
[4c3519a]939        irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" );
[b7d3cc34]940       
[4c3519a]941        for( l = irc->users; l; l = l->next )
[b7d3cc34]942        {
[4c3519a]943                irc_user_t *iu = l->data;
944                bee_user_t *bu = iu->bu;
945               
946                if( !bu || ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE )
947                        continue;
948               
[aefa533e]949                if( online == 1 )
950                {
[449a51d]951                        char st[256] = "Online";
952                       
[4c3519a]953                        if( bu->status_msg )
954                                g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
[449a51d]955                       
[4c3519a]956                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
957                        irc_usermsg( irc, format, iu->nick, s, st );
[aefa533e]958                }
959               
[b7d3cc34]960                n_online ++;
961        }
962
[4c3519a]963        for( l = irc->users; l; l = l->next )
[b7d3cc34]964        {
[4c3519a]965                irc_user_t *iu = l->data;
966                bee_user_t *bu = iu->bu;
967               
968                if( !bu || !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) )
969                        continue;
970               
[aefa533e]971                if( away == 1 )
972                {
[4c3519a]973                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
974                        irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
[aefa533e]975                }
[b7d3cc34]976                n_away ++;
977        }
978       
[4c3519a]979        for( l = irc->users; l; l = l->next )
[b7d3cc34]980        {
[4c3519a]981                irc_user_t *iu = l->data;
982                bee_user_t *bu = iu->bu;
983               
984                if( !bu || bu->flags & BEE_USER_ONLINE )
985                        continue;
986               
[aefa533e]987                if( offline == 1 )
988                {
[4c3519a]989                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
990                        irc_usermsg( irc, format, iu->nick, s, "Offline" );
[aefa533e]991                }
[b7d3cc34]992                n_offline ++;
993        }
994       
[aa5ac01]995        irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
[b7d3cc34]996}
997
[f73b969]998static void cmd_qlist( irc_t *irc, char **cmd )
[b7d3cc34]999{
1000        query_t *q = irc->queries;
1001        int num;
1002       
1003        if( !q )
1004        {
1005                irc_usermsg( irc, "There are no pending questions." );
[f73b969]1006                return;
[b7d3cc34]1007        }
1008       
1009        irc_usermsg( irc, "Pending queries:" );
1010       
1011        for( num = 0; q; q = q->next, num ++ )
[0da65d5]1012                if( q->ic ) /* Not necessary yet, but it might come later */
[c2fb3809]1013                        irc_usermsg( irc, "%d, %s(%s): %s", num, q->ic->acc->prpl->name, q->ic->acc->user, q->question );
[5c09a59]1014                else
1015                        irc_usermsg( irc, "%d, BitlBee: %s", num, q->question );
[b7d3cc34]1016}
1017
[a9a7287]1018static void cmd_chat( irc_t *irc, char **cmd )
1019{
1020        account_t *acc;
1021       
1022        if( g_strcasecmp( cmd[1], "add" ) == 0 )
1023        {
[07054a5]1024                char *channel, *s;
[7b71feb]1025                struct irc_channel *ic;
[07054a5]1026               
1027                MIN_ARGS( 3 );
[a9a7287]1028               
[7b71feb]1029                if( !( acc = account_get( irc->b, cmd[2] ) ) )
[a9a7287]1030                {
1031                        irc_usermsg( irc, "Invalid account" );
1032                        return;
1033                }
[5a75d15]1034                else if( !acc->prpl->chat_join )
1035                {
1036                        irc_usermsg( irc, "Named chatrooms not supported on that account." );
1037                        return;
1038                }
[a9a7287]1039               
[07054a5]1040                if( cmd[4] == NULL )
1041                {
1042                        channel = g_strdup( cmd[3] );
1043                        if( ( s = strchr( channel, '@' ) ) )
1044                                *s = 0;
1045                }
1046                else
1047                {
1048                        channel = g_strdup( cmd[4] );
1049                }
1050               
1051                if( strchr( CTYPES, channel[0] ) == NULL )
1052                {
[7b71feb]1053                        s = g_strdup_printf( "#%s", channel );
[07054a5]1054                        g_free( channel );
1055                        channel = s;
[134a02c]1056                       
1057                        irc_channel_name_strip( channel );
[07054a5]1058                }
1059               
[5a75d15]1060                if( ( ic = irc_channel_new( irc, channel ) ) &&
[547ea68]1061                    set_setstr( &ic->set, "type", "chat" ) &&
[5a75d15]1062                    set_setstr( &ic->set, "chat_type", "room" ) &&
1063                    set_setstr( &ic->set, "account", cmd[2] ) &&
1064                    set_setstr( &ic->set, "room", cmd[3] ) )
1065                {
1066                        irc_usermsg( irc, "Chatroom successfully added." );
1067                }
1068                else
[a9a7287]1069                {
[5a75d15]1070                        if( ic )
1071                                irc_channel_free( ic );
[a9a7287]1072                       
[5a75d15]1073                        irc_usermsg( irc, "Could not add chatroom." );
[d995c9b]1074                }
[d7db346]1075                g_free( channel );
[d995c9b]1076        }
[39f93f0]1077        else if( g_strcasecmp( cmd[1], "with" ) == 0 )
1078        {
[c1a8a16]1079                irc_user_t *iu;
[3b99524]1080               
1081                MIN_ARGS( 2 );
[39f93f0]1082               
[c1a8a16]1083                if( ( iu = irc_user_by_name( irc, cmd[2] ) ) &&
1084                    iu->bu && iu->bu->ic->acc->prpl->chat_with )
[39f93f0]1085                {
[c1a8a16]1086                        if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
[39f93f0]1087                        {
1088                                irc_usermsg( irc, "(Possible) failure while trying to open "
[c1a8a16]1089                                                  "a groupchat with %s.", iu->nick );
[39f93f0]1090                        }
1091                }
1092                else
1093                {
1094                        irc_usermsg( irc, "Can't open a groupchat with %s.", cmd[2] );
1095                }
1096        }
[7cd2e8a]1097        else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
1098                 g_strcasecmp( cmd[1], "set" ) == 0 ||
1099                 g_strcasecmp( cmd[1], "del" ) == 0 )
1100        {
1101                irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
1102                cmd_channel( irc, cmd );
1103        }
[a9a7287]1104        else
1105        {
1106                irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
1107        }
[fa29d093]1108}
1109
[b8a491d]1110static void cmd_transfer( irc_t *irc, char **cmd )
[2c2df7d]1111{
1112        GSList *files = irc->file_transfers;
1113        enum { LIST, REJECT, CANCEL };
1114        int subcmd = LIST;
1115        int fid;
1116
1117        if( !files )
1118        {
1119                irc_usermsg( irc, "No pending transfers" );
1120                return;
1121        }
1122
[b8a491d]1123        if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) )
[2c2df7d]1124        {
1125                subcmd = REJECT;
1126        }
[b8a491d]1127        else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && 
1128                 cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) )
[2c2df7d]1129        {
1130                subcmd = CANCEL;
1131        }
1132
1133        for( ; files; files = g_slist_next( files ) )
1134        {
1135                file_transfer_t *file = files->data;
1136               
1137                switch( subcmd ) {
1138                case LIST:
1139                        if ( file->status == FT_STATUS_LISTENING )
1140                                irc_usermsg( irc, 
1141                                        "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1142                        else 
1143                        {
1144                                int kb_per_s = 0;
[44961cb]1145                                time_t diff = time( NULL ) - file->started ? : 1;
[2c2df7d]1146                                if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
1147                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1148                                       
1149                                irc_usermsg( irc, 
1150                                        "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 
1151                                        file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
1152                        }
1153                        break;
1154                case REJECT:
1155                        if( file->status == FT_STATUS_LISTENING )
1156                        {
1157                                irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name );
[9d4352c]1158                                imcb_file_canceled( file->ic, file, "Denied by user" );
[2c2df7d]1159                        }
1160                        break;
1161                case CANCEL:
1162                        if( file->local_id == fid )
1163                        {
1164                                irc_usermsg( irc, "Canceling file transfer for %s", file->file_name );
[9d4352c]1165                                imcb_file_canceled( file->ic, file, "Canceled by user" );
[2c2df7d]1166                        }
1167                        break;
1168                }
1169        }
1170}
1171
[6c56f42]1172/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
[0298d11]1173const command_t commands[] = {
[d860a8d]1174        { "account",        1, cmd_account,        0 },
[6c56f42]1175        { "add",            2, cmd_add,            0 },
[2272cb3]1176        { "allow",          1, cmd_allow,          0 },
[4c3519a]1177        { "blist",          0, cmd_blist,          0 },
[2272cb3]1178        { "block",          1, cmd_block,          0 },
[c133d4b8]1179        { "channel",        1, cmd_channel,        0 },
1180        { "chat",           1, cmd_chat,           0 },
[6c56f42]1181        { "drop",           1, cmd_drop,           0 },
[9d4352c]1182        { "ft",             0, cmd_transfer,       0 },
[6c56f42]1183        { "help",           0, cmd_help,           0 }, 
[0298d11]1184        { "identify",       1, cmd_identify,       0 },
[aa44bdd]1185        { "info",           1, cmd_info,           0 },
[6c56f42]1186        { "no",             0, cmd_yesno,          0 },
[9d4352c]1187        { "qlist",          0, cmd_qlist,          0 },
[0298d11]1188        { "register",       1, cmd_register,       0 },
[dbb0ce3]1189        { "remove",         1, cmd_remove,         0 },
[0298d11]1190        { "rename",         2, cmd_rename,         0 },
[6c56f42]1191        { "save",           0, cmd_save,           0 },
[0298d11]1192        { "set",            0, cmd_set,            0 },
[9d4352c]1193        { "transfer",       0, cmd_transfer,       0 },
[0298d11]1194        { "yes",            0, cmd_yesno,          0 },
1195        { NULL }
1196};
Note: See TracBrowser for help on using the repository browser.