source: root_commands.c @ 5e98ff0

Last change on this file since 5e98ff0 was 5e98ff0, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-17T14:23:20Z

Free a user structure when using the remove command. This disappeared while
most IM modules don't call back when a removal was successful.

  • Property mode set to 100644
File size: 28.9 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                {
[b1f818b]617                        nick_set_raw( 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 );
[b1f818b]645        nick_del( bu );
[5e98ff0]646        if( g_slist_find( irc->users, iu ) )
647                bee_user_free( irc->b, bu );
[dbb0ce3]648       
649        irc_usermsg( irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1] );
650        g_free( s );
651       
652        return;
653}
654
[f73b969]655static void cmd_info( irc_t *irc, char **cmd )
[b7d3cc34]656{
[0da65d5]657        struct im_connection *ic;
[b7d3cc34]658        account_t *a;
659       
660        if( !cmd[2] )
661        {
[aa44bdd]662                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
663                if( !iu || !iu->bu )
[b7d3cc34]664                {
665                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]666                        return;
[b7d3cc34]667                }
[aa44bdd]668                ic = iu->bu->ic;
669                cmd[2] = iu->bu->handle;
[b7d3cc34]670        }
[aa44bdd]671        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]672        {
673                irc_usermsg( irc, "Invalid account" );
[f73b969]674                return;
[b7d3cc34]675        }
[0da65d5]676        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]677        {
678                irc_usermsg( irc, "That account is not on-line" );
[f73b969]679                return;
[b7d3cc34]680        }
681       
[0da65d5]682        if( !ic->acc->prpl->get_info )
[b7d3cc34]683        {
684                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
685        }
[f73b969]686        else
687        {
[0da65d5]688                ic->acc->prpl->get_info( ic, cmd[2] );
[f73b969]689        }
[b7d3cc34]690}
691
[f73b969]692static void cmd_rename( irc_t *irc, char **cmd )
[b7d3cc34]693{
[9a9b520]694        irc_user_t *iu, *old;
[b7d3cc34]695       
[57c96f7]696        iu = irc_user_by_name( irc, cmd[1] );
697       
698        if( iu == NULL )
[0baed0d]699        {
[57c96f7]700                irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[0baed0d]701        }
[57c96f7]702        else if( iu == irc->user )
[b7d3cc34]703        {
[6c2404e]704                irc_usermsg( irc, "Use /nick to change your own nickname" );
[b7d3cc34]705        }
[f73b969]706        else if( !nick_ok( cmd[2] ) )
[b7d3cc34]707        {
708                irc_usermsg( irc, "Nick `%s' is invalid", cmd[2] );
709        }
[9a9b520]710        else if( ( old = irc_user_by_name( irc, cmd[2] ) ) && old != iu )
[b7d3cc34]711        {
[57c96f7]712                irc_usermsg( irc, "Nick `%s' already exists", cmd[2] );
[b7d3cc34]713        }
[f73b969]714        else
[b7d3cc34]715        {
[57c96f7]716                if( !irc_user_set_nick( iu, cmd[2] ) )
717                {
718                        irc_usermsg( irc, "Error while changing nick" );
719                        return;
720                }
721               
722                if( iu == irc->root )
[f73b969]723                {
[7125cb3]724                        /* If we're called internally (user did "set root_nick"),
725                           let's not go O(INF). :-) */
[1195cec]726                        if( strcmp( cmd[0], "set_rename" ) != 0 )
[57c96f7]727                                set_setstr( &irc->b->set, "root_nick", cmd[2] );
[f73b969]728                }
[57c96f7]729                else if( iu->bu )
[f73b969]730                {
[b1f818b]731                        nick_set( iu->bu, cmd[2] );
[f73b969]732                }
733               
734                irc_usermsg( irc, "Nick successfully changed" );
[b7d3cc34]735        }
736}
737
[1195cec]738char *set_eval_root_nick( set_t *set, char *new_nick )
739{
740        irc_t *irc = set->data;
741       
[0a6e5d1]742        if( strcmp( irc->root->nick, new_nick ) != 0 )
[1195cec]743        {
[0a6e5d1]744                char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };
[1195cec]745               
746                cmd_rename( irc, cmd );
747        }
748       
[0a6e5d1]749        return strcmp( irc->root->nick, new_nick ) == 0 ? new_nick : SET_INVALID;
[1195cec]750}
[0baed0d]751
[f73b969]752static void cmd_block( irc_t *irc, char **cmd )
[b7d3cc34]753{
[0da65d5]754        struct im_connection *ic;
[b7d3cc34]755        account_t *a;
756       
[2272cb3]757        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
[87b6a3e]758        {
759                char *format;
760                GSList *l;
761               
762                if( strchr( irc->umode, 'b' ) != NULL )
763                        format = "%s\t%s";
764                else
[57ef864]765                        format = "%-32.32s  %-16.16s";
[87b6a3e]766               
767                irc_usermsg( irc, format, "Handle", "Nickname" );
[0da65d5]768                for( l = a->ic->deny; l; l = l->next )
[87b6a3e]769                {
[2272cb3]770                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
771                        irc_user_t *iu = bu ? bu->ui_data : NULL;
772                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
[87b6a3e]773                }
774                irc_usermsg( irc, "End of list." );
775               
776                return;
777        }
778        else if( !cmd[2] )
[b7d3cc34]779        {
[2272cb3]780                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
781                if( !iu || !iu->bu )
[b7d3cc34]782                {
783                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]784                        return;
[b7d3cc34]785                }
[2272cb3]786                ic = iu->bu->ic;
787                cmd[2] = iu->bu->handle;
[b7d3cc34]788        }
[2272cb3]789        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]790        {
791                irc_usermsg( irc, "Invalid account" );
[f73b969]792                return;
[b7d3cc34]793        }
[0da65d5]794        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]795        {
796                irc_usermsg( irc, "That account is not on-line" );
[f73b969]797                return;
[b7d3cc34]798        }
799       
[0da65d5]800        if( !ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit )
[b7d3cc34]801        {
802                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
803        }
804        else
805        {
[84b045d]806                imc_rem_allow( ic, cmd[2] );
807                imc_add_block( ic, cmd[2] );
[da3b536]808                irc_usermsg( irc, "Buddy `%s' moved from your allow- to your block-list", cmd[2] );
[b7d3cc34]809        }
810}
811
[f73b969]812static void cmd_allow( irc_t *irc, char **cmd )
[b7d3cc34]813{
[0da65d5]814        struct im_connection *ic;
[b7d3cc34]815        account_t *a;
816       
[2272cb3]817        if( !cmd[2] && ( a = account_get( irc->b, cmd[1] ) ) && a->ic )
[87b6a3e]818        {
819                char *format;
820                GSList *l;
821               
822                if( strchr( irc->umode, 'b' ) != NULL )
823                        format = "%s\t%s";
824                else
[57ef864]825                        format = "%-32.32s  %-16.16s";
[87b6a3e]826               
827                irc_usermsg( irc, format, "Handle", "Nickname" );
[0da65d5]828                for( l = a->ic->permit; l; l = l->next )
[87b6a3e]829                {
[2272cb3]830                        bee_user_t *bu = bee_user_by_handle( irc->b, a->ic, l->data );
831                        irc_user_t *iu = bu ? bu->ui_data : NULL;
832                        irc_usermsg( irc, format, l->data, iu ? iu->nick : "(none)" );
[87b6a3e]833                }
834                irc_usermsg( irc, "End of list." );
835               
836                return;
837        }
838        else if( !cmd[2] )
[b7d3cc34]839        {
[2272cb3]840                irc_user_t *iu = irc_user_by_name( irc, cmd[1] );
841                if( !iu || !iu->bu )
[b7d3cc34]842                {
843                        irc_usermsg( irc, "Nick `%s' does not exist", cmd[1] );
[f73b969]844                        return;
[b7d3cc34]845                }
[2272cb3]846                ic = iu->bu->ic;
847                cmd[2] = iu->bu->handle;
[b7d3cc34]848        }
[2272cb3]849        else if( !( a = account_get( irc->b, cmd[1] ) ) )
[b7d3cc34]850        {
851                irc_usermsg( irc, "Invalid account" );
[f73b969]852                return;
[b7d3cc34]853        }
[0da65d5]854        else if( !( ( ic = a->ic ) && ( a->ic->flags & OPT_LOGGED_IN ) ) )
[b7d3cc34]855        {
856                irc_usermsg( irc, "That account is not on-line" );
[f73b969]857                return;
[b7d3cc34]858        }
859       
[0da65d5]860        if( !ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit )
[b7d3cc34]861        {
862                irc_usermsg( irc, "Command `%s' not supported by this protocol", cmd[0] );
863        }
864        else
865        {
[84b045d]866                imc_rem_block( ic, cmd[2] );
867                imc_add_allow( ic, cmd[2] );
[b7d3cc34]868               
[da3b536]869                irc_usermsg( irc, "Buddy `%s' moved from your block- to your allow-list", cmd[2] );
[b7d3cc34]870        }
871}
872
[f73b969]873static void cmd_yesno( irc_t *irc, char **cmd )
[b7d3cc34]874{
875        query_t *q = NULL;
876        int numq = 0;
877       
878        if( irc->queries == NULL )
879        {
880                irc_usermsg( irc, "Did I ask you something?" );
[f73b969]881                return;
[b7d3cc34]882        }
883       
884        /* If there's an argument, the user seems to want to answer another question than the
885           first/last (depending on the query_order setting) one. */
886        if( cmd[1] )
887        {
888                if( sscanf( cmd[1], "%d", &numq ) != 1 )
889                {
890                        irc_usermsg( irc, "Invalid query number" );
[f73b969]891                        return;
[b7d3cc34]892                }
893               
894                for( q = irc->queries; q; q = q->next, numq -- )
895                        if( numq == 0 )
896                                break;
897               
898                if( !q )
899                {
900                        irc_usermsg( irc, "Uhm, I never asked you something like that..." );
[f73b969]901                        return;
[b7d3cc34]902                }
903        }
904       
905        if( g_strcasecmp( cmd[0], "yes" ) == 0 )
906                query_answer( irc, q, 1 );
907        else if( g_strcasecmp( cmd[0], "no" ) == 0 )
908                query_answer( irc, q, 0 );
909}
910
[f73b969]911static void cmd_set( irc_t *irc, char **cmd )
[b7d3cc34]912{
[e907683]913        cmd_set_real( irc, cmd, &irc->b->set, NULL );
[b7d3cc34]914}
915
[f73b969]916static void cmd_blist( irc_t *irc, char **cmd )
[b7d3cc34]917{
918        int online = 0, away = 0, offline = 0;
[4c3519a]919        GSList *l;
[aefa533e]920        char s[256];
921        char *format;
[b7d3cc34]922        int n_online = 0, n_away = 0, n_offline = 0;
923       
924        if( cmd[1] && g_strcasecmp( cmd[1], "all" ) == 0 )
925                online = offline = away = 1;
926        else if( cmd[1] && g_strcasecmp( cmd[1], "offline" ) == 0 )
927                offline = 1;
928        else if( cmd[1] && g_strcasecmp( cmd[1], "away" ) == 0 )
929                away = 1;
930        else if( cmd[1] && g_strcasecmp( cmd[1], "online" ) == 0 )
931                online = 1;
932        else
[449a51d]933                online = away = 1;
[b7d3cc34]934       
[aefa533e]935        if( strchr( irc->umode, 'b' ) != NULL )
936                format = "%s\t%s\t%s";
937        else
938                format = "%-16.16s  %-40.40s  %s";
939       
[4c3519a]940        irc_usermsg( irc, format, "Nick", "Handle/Account", "Status" );
[b7d3cc34]941       
[4c3519a]942        for( l = irc->users; l; l = l->next )
[b7d3cc34]943        {
[4c3519a]944                irc_user_t *iu = l->data;
945                bee_user_t *bu = iu->bu;
946               
947                if( !bu || ( bu->flags & ( BEE_USER_ONLINE | BEE_USER_AWAY ) ) != BEE_USER_ONLINE )
948                        continue;
949               
[aefa533e]950                if( online == 1 )
951                {
[449a51d]952                        char st[256] = "Online";
953                       
[4c3519a]954                        if( bu->status_msg )
955                                g_snprintf( st, sizeof( st ) - 1, "Online (%s)", bu->status_msg );
[449a51d]956                       
[4c3519a]957                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
958                        irc_usermsg( irc, format, iu->nick, s, st );
[aefa533e]959                }
960               
[b7d3cc34]961                n_online ++;
962        }
963
[4c3519a]964        for( l = irc->users; l; l = l->next )
[b7d3cc34]965        {
[4c3519a]966                irc_user_t *iu = l->data;
967                bee_user_t *bu = iu->bu;
968               
969                if( !bu || !( bu->flags & BEE_USER_ONLINE ) || !( bu->flags & BEE_USER_AWAY ) )
970                        continue;
971               
[aefa533e]972                if( away == 1 )
973                {
[4c3519a]974                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
975                        irc_usermsg( irc, format, iu->nick, s, irc_user_get_away( iu ) );
[aefa533e]976                }
[b7d3cc34]977                n_away ++;
978        }
979       
[4c3519a]980        for( l = irc->users; l; l = l->next )
[b7d3cc34]981        {
[4c3519a]982                irc_user_t *iu = l->data;
983                bee_user_t *bu = iu->bu;
984               
985                if( !bu || bu->flags & BEE_USER_ONLINE )
986                        continue;
987               
[aefa533e]988                if( offline == 1 )
989                {
[4c3519a]990                        g_snprintf( s, sizeof( s ) - 1, "%s %s(%s)", bu->handle, bu->ic->acc->prpl->name, bu->ic->acc->user );
991                        irc_usermsg( irc, format, iu->nick, s, "Offline" );
[aefa533e]992                }
[b7d3cc34]993                n_offline ++;
994        }
995       
[aa5ac01]996        irc_usermsg( irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online, n_away, n_offline );
[b7d3cc34]997}
998
[f73b969]999static void cmd_qlist( irc_t *irc, char **cmd )
[b7d3cc34]1000{
1001        query_t *q = irc->queries;
1002        int num;
1003       
1004        if( !q )
1005        {
1006                irc_usermsg( irc, "There are no pending questions." );
[f73b969]1007                return;
[b7d3cc34]1008        }
1009       
1010        irc_usermsg( irc, "Pending queries:" );
1011       
1012        for( num = 0; q; q = q->next, num ++ )
[0da65d5]1013                if( q->ic ) /* Not necessary yet, but it might come later */
[c2fb3809]1014                        irc_usermsg( irc, "%d, %s(%s): %s", num, q->ic->acc->prpl->name, q->ic->acc->user, q->question );
[5c09a59]1015                else
1016                        irc_usermsg( irc, "%d, BitlBee: %s", num, q->question );
[b7d3cc34]1017}
1018
[a9a7287]1019static void cmd_chat( irc_t *irc, char **cmd )
1020{
1021        account_t *acc;
1022       
1023        if( g_strcasecmp( cmd[1], "add" ) == 0 )
1024        {
[07054a5]1025                char *channel, *s;
[7b71feb]1026                struct irc_channel *ic;
[07054a5]1027               
1028                MIN_ARGS( 3 );
[a9a7287]1029               
[7b71feb]1030                if( !( acc = account_get( irc->b, cmd[2] ) ) )
[a9a7287]1031                {
1032                        irc_usermsg( irc, "Invalid account" );
1033                        return;
1034                }
[5a75d15]1035                else if( !acc->prpl->chat_join )
1036                {
1037                        irc_usermsg( irc, "Named chatrooms not supported on that account." );
1038                        return;
1039                }
[a9a7287]1040               
[07054a5]1041                if( cmd[4] == NULL )
1042                {
1043                        channel = g_strdup( cmd[3] );
1044                        if( ( s = strchr( channel, '@' ) ) )
1045                                *s = 0;
1046                }
1047                else
1048                {
1049                        channel = g_strdup( cmd[4] );
1050                }
1051               
1052                if( strchr( CTYPES, channel[0] ) == NULL )
1053                {
[7b71feb]1054                        s = g_strdup_printf( "#%s", channel );
[07054a5]1055                        g_free( channel );
1056                        channel = s;
[134a02c]1057                       
1058                        irc_channel_name_strip( channel );
[07054a5]1059                }
1060               
[5a75d15]1061                if( ( ic = irc_channel_new( irc, channel ) ) &&
[547ea68]1062                    set_setstr( &ic->set, "type", "chat" ) &&
[5a75d15]1063                    set_setstr( &ic->set, "chat_type", "room" ) &&
1064                    set_setstr( &ic->set, "account", cmd[2] ) &&
1065                    set_setstr( &ic->set, "room", cmd[3] ) )
1066                {
1067                        irc_usermsg( irc, "Chatroom successfully added." );
1068                }
1069                else
[a9a7287]1070                {
[5a75d15]1071                        if( ic )
1072                                irc_channel_free( ic );
[a9a7287]1073                       
[5a75d15]1074                        irc_usermsg( irc, "Could not add chatroom." );
[d995c9b]1075                }
[d7db346]1076                g_free( channel );
[d995c9b]1077        }
[39f93f0]1078        else if( g_strcasecmp( cmd[1], "with" ) == 0 )
1079        {
[c1a8a16]1080                irc_user_t *iu;
[3b99524]1081               
1082                MIN_ARGS( 2 );
[39f93f0]1083               
[c1a8a16]1084                if( ( iu = irc_user_by_name( irc, cmd[2] ) ) &&
1085                    iu->bu && iu->bu->ic->acc->prpl->chat_with )
[39f93f0]1086                {
[c1a8a16]1087                        if( !iu->bu->ic->acc->prpl->chat_with( iu->bu->ic, iu->bu->handle ) )
[39f93f0]1088                        {
1089                                irc_usermsg( irc, "(Possible) failure while trying to open "
[c1a8a16]1090                                                  "a groupchat with %s.", iu->nick );
[39f93f0]1091                        }
1092                }
1093                else
1094                {
1095                        irc_usermsg( irc, "Can't open a groupchat with %s.", cmd[2] );
1096                }
1097        }
[7cd2e8a]1098        else if( g_strcasecmp( cmd[1], "list" ) == 0 ||
1099                 g_strcasecmp( cmd[1], "set" ) == 0 ||
1100                 g_strcasecmp( cmd[1], "del" ) == 0 )
1101        {
1102                irc_usermsg( irc, "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command." );
1103                cmd_channel( irc, cmd );
1104        }
[a9a7287]1105        else
1106        {
1107                irc_usermsg( irc, "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat", cmd[1] );
1108        }
[fa29d093]1109}
1110
[b8a491d]1111static void cmd_transfer( irc_t *irc, char **cmd )
[2c2df7d]1112{
1113        GSList *files = irc->file_transfers;
1114        enum { LIST, REJECT, CANCEL };
1115        int subcmd = LIST;
1116        int fid;
1117
1118        if( !files )
1119        {
1120                irc_usermsg( irc, "No pending transfers" );
1121                return;
1122        }
1123
[b8a491d]1124        if( cmd[1] && ( strcmp( cmd[1], "reject" ) == 0 ) )
[2c2df7d]1125        {
1126                subcmd = REJECT;
1127        }
[b8a491d]1128        else if( cmd[1] && ( strcmp( cmd[1], "cancel" ) == 0 ) && 
1129                 cmd[2] && ( sscanf( cmd[2], "%d", &fid ) == 1 ) )
[2c2df7d]1130        {
1131                subcmd = CANCEL;
1132        }
1133
1134        for( ; files; files = g_slist_next( files ) )
1135        {
1136                file_transfer_t *file = files->data;
1137               
1138                switch( subcmd ) {
1139                case LIST:
1140                        if ( file->status == FT_STATUS_LISTENING )
1141                                irc_usermsg( irc, 
1142                                        "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
1143                        else 
1144                        {
1145                                int kb_per_s = 0;
[44961cb]1146                                time_t diff = time( NULL ) - file->started ? : 1;
[2c2df7d]1147                                if ( ( file->started > 0 ) && ( file->bytes_transferred > 0 ) )
1148                                        kb_per_s = file->bytes_transferred / 1024 / diff;
1149                                       
1150                                irc_usermsg( irc, 
1151                                        "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id, file->file_name, 
1152                                        file->bytes_transferred/1024, file->file_size/1024, kb_per_s);
1153                        }
1154                        break;
1155                case REJECT:
1156                        if( file->status == FT_STATUS_LISTENING )
1157                        {
1158                                irc_usermsg( irc, "Rejecting file transfer for %s", file->file_name );
[9d4352c]1159                                imcb_file_canceled( file->ic, file, "Denied by user" );
[2c2df7d]1160                        }
1161                        break;
1162                case CANCEL:
1163                        if( file->local_id == fid )
1164                        {
1165                                irc_usermsg( irc, "Canceling file transfer for %s", file->file_name );
[9d4352c]1166                                imcb_file_canceled( file->ic, file, "Canceled by user" );
[2c2df7d]1167                        }
1168                        break;
1169                }
1170        }
1171}
1172
[6c56f42]1173/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
[0298d11]1174const command_t commands[] = {
[d860a8d]1175        { "account",        1, cmd_account,        0 },
[6c56f42]1176        { "add",            2, cmd_add,            0 },
[2272cb3]1177        { "allow",          1, cmd_allow,          0 },
[4c3519a]1178        { "blist",          0, cmd_blist,          0 },
[2272cb3]1179        { "block",          1, cmd_block,          0 },
[c133d4b8]1180        { "channel",        1, cmd_channel,        0 },
1181        { "chat",           1, cmd_chat,           0 },
[6c56f42]1182        { "drop",           1, cmd_drop,           0 },
[9d4352c]1183        { "ft",             0, cmd_transfer,       0 },
[6c56f42]1184        { "help",           0, cmd_help,           0 }, 
[0298d11]1185        { "identify",       1, cmd_identify,       0 },
[aa44bdd]1186        { "info",           1, cmd_info,           0 },
[6c56f42]1187        { "no",             0, cmd_yesno,          0 },
[9d4352c]1188        { "qlist",          0, cmd_qlist,          0 },
[0298d11]1189        { "register",       1, cmd_register,       0 },
[dbb0ce3]1190        { "remove",         1, cmd_remove,         0 },
[0298d11]1191        { "rename",         2, cmd_rename,         0 },
[6c56f42]1192        { "save",           0, cmd_save,           0 },
[0298d11]1193        { "set",            0, cmd_set,            0 },
[9d4352c]1194        { "transfer",       0, cmd_transfer,       0 },
[0298d11]1195        { "yes",            0, cmd_yesno,          0 },
1196        { NULL }
1197};
Note: See TracBrowser for help on using the repository browser.