source: root_commands.c @ ce199b7

Last change on this file since ce199b7 was ce199b7, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-21T11:21:04Z

Make it easier to add OAuth-authenticated accounts without having to type
a bogus password.

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