source: root_commands.c @ e92c4f4

Last change on this file since e92c4f4 was e92c4f4, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-11T00:29:19Z

Takeover stuff now works in daemon mode as well.

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