aboutsummaryrefslogtreecommitdiffstats
path: root/root_commands.c
blob: 0ea380af08008a2be1b67c5ff6671d288392f2f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
  /********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2012 Wilmer van der Gaast and others                *
  \********************************************************************/

/* MSN module - Miscellaneous utilities                                 */

/*
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
  Suite 330, Boston, MA  02111-1307  USA
*/

#include "nogaim.h"
#include "msn.h"
#include "md5.h"
#include "soap.h"
#include <ctype.h>

static char *adlrml_entry( const char *handle_, msn_buddy_flags_t list )
{
	char *domain, handle[strlen(handle_)+1];
	
	strcpy( handle, handle_ );
	if( ( domain = strchr( handle, '@' ) ) )
		*(domain++) = '\0';
	else
		return NULL;
	
	return g_markup_printf_escaped( "<ml><d n=\"%s\"><c n=\"%s\" l=\"%d\" t=\"1\"/></d></ml>",
		domain, handle, list );
}

int msn_buddy_list_add( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *realname, const char *group )
{
	struct msn_data *md = ic->proto_data;
	char groupid[8];
	bee_user_t *bu;
	struct msn_buddy_data *bd;
	char *adl;
	
	*groupid = '\0';
#if 0
	if( group )
	{
		int i;
		for( i = 0; i < md->groupcount; i ++ )
			if( g_strcasecmp( md->grouplist[i], group ) == 0 )
			{
				g_snprintf( groupid, sizeof( groupid ), " %d", i );
				break;
			}
		
		if( *groupid == '\0' )
		{
			/* Have to create this group, it doesn't exist yet. */
			struct msn_groupadd *ga;
			GSList *l;
			
			for( l = md->grpq; l; l = l->next )
			{
				ga = l->data;
				if( g_strcasecmp( ga->group, group ) == 0 )
					break;
			}
			
			ga = g_new0( struct msn_groupadd, 1 );
			ga->who = g_strdup( who );
			ga->group = g_strdup( group );
			md->grpq = g_slist_prepend( md->grpq, ga );
			
			if( l == NULL )
			{
				char groupname[strlen(group)+1];
				strcpy( groupname, group );
				http_encode( groupname );
				g_snprintf( buf, sizeof( buf ), "ADG %d %s %d\r\n", ++md->trId, groupname, 0 );
				return msn_write( ic, buf, strlen( buf ) );
			}
			else
			{
				/* This can happen if the user's doing lots of adds to a
				   new group at once; we're still waiting for the server
				   to confirm group creation. */
				return 1;
			}
		}
	}
#endif
	
	if( !( ( bu = bee_user_by_handle( ic->bee, ic, who ) ) ||
	       ( bu = bee_user_new( ic->bee, ic, who, 0 ) ) ) ||
	    !( bd = bu->data ) || bd->flags & list )
		return 1;
	
	bd->flags |= list;
	
	if( list == MSN_BUDDY_FL )
		msn_soap_ab_contact_add( ic, bu );
	else
		msn_soap_memlist_edit( ic, who, TRUE, list );
	
	if( ( adl = adlrml_entry( who, list ) ) )
	{
		int st = msn_ns_write( ic, -1, "ADL %d %zd\r\n%s",
		                       ++md->trId, strlen( adl ), adl );
		g_free( adl );
		
		return st;
	}
	
	return 1;
}

int msn_buddy_list_remove( struct im_connection *ic, msn_buddy_flags_t list, const char *who, const char *group )
{
	struct msn_data *md = ic->proto_data;
	char groupid[8];
	bee_user_t *bu;
	struct msn_buddy_data *bd;
	char *adl;
	
	*groupid = '\0';
#if 0
	if( group )
	{
		int i;
		for( i = 0; i < md->groupcount; i ++ )
			if( g_strcasecmp( md->grouplist[i], group ) == 0 )
			{
				g_snprintf( groupid, sizeof( groupid ), " %d", i );
				break;
			}
	}
#endif
	
	if( !( bu = bee_user_by_handle( ic->bee, ic, who ) ) ||
	    !( bd = bu->data ) || !( bd->flags & list ) )
		return 1;
	
	bd->flags &= ~list;
	
	if( list == MSN_BUDDY_FL )
		msn_soap_ab_contact_del( ic, bu );
	else
		msn_soap_memlist_edit( ic, who, FALSE, list );
	
	if( ( adl = adlrml_entry( who, list ) ) )
	{
		int st = msn_ns_write( ic, -1, "RML %d %zd\r\n%s",
		                       ++md->trId, strlen( adl ), adl );
		g_free( adl );
		
		return st;
	}
	
	return 1;
}

struct msn_buddy_ask_data
{
	struct im_connection *ic;
	char *handle;
	char *realname;
};

static void msn_buddy_ask_yes( void *data )
{
	struct msn_buddy_ask_data *bla = data;
	
	msn_buddy_list_add( bla->ic, MSN_BUDDY_AL, bla->handle, bla->realname, NULL );
	
	imcb_ask_add( bla->ic, bla->handle, NULL );
	
	g_free( bla->handle );
	g_free( bla->realname );
	g_free( bla );
}

static void msn_buddy_ask_no( void *data )
{
	struct msn_buddy_ask_data *bla = data;
	
	msn_buddy_list_add( bla->ic, MSN_BUDDY_BL, bla->handle, bla->realname, NULL );
	
	g_free( bla->handle );
	g_free( bla->realname );
	g_free( bla );
}

void msn_buddy_ask( bee_user_t *bu )
{
	struct msn_buddy_ask_data *bla;
	struct msn_buddy_data *bd = bu->data;
	char buf[1024];
	
	if( ( bd->flags & 30 ) != 8 && ( bd->flags & 30 ) != 16 )
		return;
	
	bla = g_new0( struct msn_buddy_ask_data, 1 );
	bla->ic = bu->ic;
	bla->handle = g_strdup( bu->handle );
	bla->realname = g_strdup( bu->fullname );
	
	g_snprintf( buf, sizeof( buf ),
	            "The user %s (%s) wants to add you to his/her buddy list.",
	            bu->handle, bu->fullname );
	imcb_ask( bu->ic, buf, bla, msn_buddy_ask_yes, pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/********************************************************************\
  * BitlBee -- An IRC to other IM-networks gateway                     *
  *                                                                    *
  * Copyright 2002-2013 Wilmer van der Gaast and others                *
  \********************************************************************/

/* User manager (root) commands                                         */

/*
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License with
  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
  if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
  Fifth Floor, Boston, MA  02110-1301  USA
*/

#define BITLBEE_CORE
#include "commands.h"
#include "bitlbee.h"
#include "help.h"
#include "ipc.h"

void root_command_string(irc_t *irc, char *command)
{
	root_command(irc, split_command_parts(command, 0));
}

#define MIN_ARGS(x, y ...)                                                    \
	do                                                                     \
	{                                                                      \
		int blaat;                                                     \
		for (blaat = 0; blaat <= x; blaat++) {                         \
			if (cmd[blaat] == NULL)                               \
			{                                                      \
				irc_rootmsg(irc, "Not enough parameters given (need %d).", x); \
				return y;                                      \
			} }                                                      \
	} while (0)

void root_command(irc_t *irc, char *cmd[])
{
	int i, len;

	if (!cmd[0]) {
		return;
	}

	len = strlen(cmd[0]);
	for (i = 0; root_commands[i].command; i++) {
		if (g_strncasecmp(root_commands[i].command, cmd[0], len) == 0) {
			if (root_commands[i + 1].command &&
			    g_strncasecmp(root_commands[i + 1].command, cmd[0], len) == 0) {
				/* Only match on the first letters if the match is unique. */
				break;
			}

			MIN_ARGS(root_commands[i].required_parameters);

			root_commands[i].execute(irc, cmd);
			return;
		}
	}

	irc_rootmsg(irc, "Unknown command: %s. Please use \x02help commands\x02 to get a list of available commands.",
	            cmd[0]);
}

static void cmd_help(irc_t *irc, char **cmd)
{
	char param[80];
	int i;
	char *s;

	memset(param, 0, sizeof(param));
	for (i = 1; (cmd[i] != NULL && (strlen(param) < (sizeof(param) - 1))); i++) {
		if (i != 1) {   // prepend space except for the first parameter
			strcat(param, " ");
		}
		strncat(param, cmd[i], sizeof(param) - strlen(param) - 1);
	}

	s = help_get(&(global.help), param);
	if (!s) {
		s = help_get(&(global.help), "");
	}

	if (s) {
		irc_rootmsg(irc, "%s", s);
		g_free(s);
	} else {
		irc_rootmsg(irc, "Error opening helpfile.");
	}
}

static void cmd_account(irc_t *irc, char **cmd);
static void bitlbee_whatsnew(irc_t *irc);

static void cmd_identify(irc_t *irc, char **cmd)
{
	storage_status_t status;
	gboolean load = TRUE;
	char *password = cmd[1];

	if (irc->status & USTATUS_IDENTIFIED) {
		irc_rootmsg(irc, "You're already logged in.");
		return;
	}

	if (cmd[1] == NULL) {
	} else if (strncmp(cmd[1], "-no", 3) == 0) {
		load = FALSE;
		password = cmd[2];
		if (password == NULL) {
			irc->status |= OPER_HACK_IDENTIFY_NOLOAD;
		}
	} else if (strncmp(cmd[1], "-force", 6) == 0) {
		password = cmd[2];
		if (password == NULL) {
			irc->status |= OPER_HACK_IDENTIFY_FORCE;
		}
	} else if (irc->b->accounts != NULL) {
		irc_rootmsg(irc,
		            "You're trying to identify yourself, but already have "
		            "at least one IM account set up. "
		            "Use \x02identify -noload\x02 or \x02identify -force\x02 "
		            "instead (see \x02help identify\x02).");
		return;
	}

	if (password == NULL) {
		irc_rootmsg(irc, "About to identify, use /OPER to enter the password");
		irc->status |= OPER_HACK_IDENTIFY;
		return;
	}

	status = auth_check_pass(irc, irc->user->nick, password);
	if (load && (status == STORAGE_OK)) {
		status = storage_load(irc, password);
	}

	switch (status) {
	case STORAGE_INVALID_PASSWORD:
		irc_rootmsg(irc, "Incorrect password");
		break;
	case STORAGE_NO_SUCH_USER:
		irc_rootmsg(irc, "The nick is (probably) not registered");
		break;
	case STORAGE_OK:
		irc_rootmsg(irc, "Password accepted%s",
		            load ? ", settings and accounts loaded" : "");
		irc->status |= USTATUS_IDENTIFIED;
		irc_umode_set(irc, "+R", 1);

		if (irc->caps & CAP_SASL) {
			irc_user_t *iu = irc->user;
			irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
				iu->nick, iu->user, iu->host, iu->nick, iu->nick);
		}

		bitlbee_whatsnew(irc);

		/* The following code is a bit hairy now. With takeover
		   support, we shouldn't immediately auto_connect in case
		   we're going to offer taking over an existing session.
		   Do it in 200ms since that should give the parent process
		   enough time to come back to us. */
		if (load) {
			irc_channel_auto_joins(irc, NULL);
			if (!set_getbool(&irc->default_channel->set, "auto_join")) {
				irc_channel_del_user(irc->default_channel, irc->user,
				                     IRC_CDU_PART, "auto_join disabled "
				                     "for this channel.");
			}
			if (set_getbool(&irc->b->set, "auto_connect")) {
				irc->login_source_id = b_timeout_add(200,
				                                     cmd_identify_finish, irc);
			}
		}

		/* If ipc_child_identify() returns FALSE, it means we're
		   already sure that there's no takeover target (only
		   possible in 1-process daemon mode). Start auto_connect
		   immediately. */
		if (!ipc_child_identify(irc) && load) {
			cmd_identify_finish(irc, 0, 0);
		}

		break;
	case STORAGE_OTHER_ERROR:
	default:
		irc_rootmsg(irc, "Unknown error while loading configuration");
		break;
	}
}

gboolean cmd_identify_finish(gpointer data, gint fd, b_input_condition cond)
{
	char *account_on[] = { "account", "on", NULL };
	irc_t *irc = data;

	if (set_getbool(&irc->b->set, "auto_connect")) {
		cmd_account(irc, account_on);
	}

	b_event_remove(irc->login_source_id);
	irc->login_source_id = -1;
	return FALSE;
}

static void cmd_register(irc_t *irc, char **cmd)
{
	char s[16];

	if (global.conf->authmode == AUTHMODE_REGISTERED) {
		irc_rootmsg(irc, "This server does not allow registering new accounts");
		return;
	}

	if (cmd[1] == NULL) {
		irc_rootmsg(irc, "About to register, use /OPER to enter the password");
		irc->status |= OPER_HACK_REGISTER;
		return;
	}

	switch (storage_save(irc, cmd[1], FALSE)) {
	case STORAGE_ALREADY_EXISTS:
		irc_rootmsg(irc, "Nick is already registered");
		break;

	case STORAGE_OK:
		irc_rootmsg(irc, "Account successfully created");
		irc_setpass(irc, cmd[1]);
		irc->status |= USTATUS_IDENTIFIED;
		irc_umode_set(irc, "+R", 1);

		if (irc->caps & CAP_SASL) {
			irc_user_t *iu = irc->user;
			irc_send_num(irc, 900, "%s!%s@%s %s :You are now logged in as %s",
				iu->nick, iu->user, iu->host, iu->nick, iu->nick);
		}

		/* Set this var now, or anyone who logs in to his/her
		   newly created account for the first time gets the
		   whatsnew story. */
		g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
		set_setstr(&irc->b->set, "last_version", s);
		break;

	default:
		irc_rootmsg(irc, "Error registering");
		break;
	}
}

static void cmd_drop(irc_t *irc, char **cmd)
{
	storage_status_t status;

	status = auth_check_pass(irc, irc->user->nick, cmd[1]);
	if (status == STORAGE_OK) {
		status = storage_remove(irc->user->nick);
	}

	switch (status) {
	case STORAGE_NO_SUCH_USER:
		irc_rootmsg(irc, "That account does not exist");
		break;
	case STORAGE_INVALID_PASSWORD:
		irc_rootmsg(irc, "Password invalid");
		break;
	case STORAGE_OK:
		irc_setpass(irc, NULL);
		irc->status &= ~USTATUS_IDENTIFIED;
		irc_umode_set(irc, "-R", 1);
		irc_rootmsg(irc, "Account `%s' removed", irc->user->nick);
		break;
	default:
		irc_rootmsg(irc, "Error: `%d'", status);
		break;
	}
}

static void cmd_save(irc_t *irc, char **cmd)
{
	if ((irc->status & USTATUS_IDENTIFIED) == 0) {
		irc_rootmsg(irc, "Please create an account first (see \x02help register\x02)");
	} else if (storage_save(irc, NULL, TRUE) == STORAGE_OK) {
		irc_rootmsg(irc, "Configuration saved");
	} else {
		irc_rootmsg(irc, "Configuration could not be saved!");
	}
}

static void cmd_showset(irc_t *irc, set_t **head, char *key)
{
	set_t *set;
	char *val;

	if ((val = set_getstr(head, key))) {
		irc_rootmsg(irc, "%s = `%s'", key, val);
	} else if (!(set = set_find(head, key))) {
		irc_rootmsg(irc, "Setting `%s' does not exist.", key);
		if (*head == irc->b->set) {
			irc_rootmsg(irc, "It might be an account or channel setting. "
			            "See \x02help account set\x02 and \x02help channel set\x02.");
		}
	} else if (set->flags & SET_PASSWORD) {
		irc_rootmsg(irc, "%s = `********' (hidden)", key);
	} else {
		irc_rootmsg(irc, "%s is empty", key);
	}
}

typedef set_t** (*cmd_set_findhead)(irc_t*, char*);
typedef int (*cmd_set_checkflags)(irc_t*, set_t *set);

static int cmd_set_real(irc_t *irc, char **cmd, set_t **head, cmd_set_checkflags checkflags)
{
	char *set_name = NULL, *value = NULL;
	gboolean del = FALSE;

	if (cmd[1] && g_strncasecmp(cmd[1], "-del", 4) == 0) {
		MIN_ARGS(2, 0);
		set_name = cmd[2];
		del = TRUE;
	} else {
		set_name = cmd[1];
		value = cmd[2];
	}

	if (set_name && (value || del)) {
		set_t *s = set_find(head, set_name);
		int st;

		if (s && s->flags & SET_LOCKED) {
			irc_rootmsg(irc, "This setting can not be changed");
			return 0;
		}
		if (s && checkflags && checkflags(irc, s) == 0) {
			return 0;
		}

		if (del) {
			st = set_reset(head, set_name);
		} else {
			st = set_setstr(head, set_name, value);
		}

		if (set_getstr(head, set_name) == NULL &&
		    set_find(head, set_name)) {
			/* This happens when changing the passwd, for example.
			   Showing these msgs instead gives slightly clearer
			   feedback. */
			if (st) {
				irc_rootmsg(irc, "Setting changed successfully");
			} else {
				irc_rootmsg(irc, "Failed to change setting");
			}
		} else {
			cmd_showset(irc, head, set_name);
		}
	} else if (set_name) {
		cmd_showset(irc, head, set_name);
	} else {
		set_t *s = *head;
		while (s) {
			if (set_isvisible(s)) {
				cmd_showset(irc, &s, s->key);
			}
			s = s->next;
		}
	}

	return 1;
}

static int cmd_account_set_checkflags(irc_t *irc, set_t *s)
{
	account_t *a = s->data;

	if (a->ic && s && s->flags & ACC_SET_OFFLINE_ONLY) {
		irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "off");
		return 0;
	} else if (!a->ic && s && s->flags & ACC_SET_ONLINE_ONLY) {
		irc_rootmsg(irc, "This setting can only be changed when the account is %s-line", "on");
		return 0;
	} else if (a->flags & ACC_FLAG_LOCKED && s && s->flags & ACC_SET_LOCKABLE) {
		irc_rootmsg(irc, "This setting can not be changed for locked accounts");
		return 0;
	}

	return 1;
}

static void cmd_account(irc_t *irc, char **cmd)
{
	account_t *a;
	int len;

	if (global.conf->authmode == AUTHMODE_REGISTERED && !(irc->status & USTATUS_IDENTIFIED)) {
		irc_rootmsg(irc, "This server only accepts registered users");
		return;
	}

	len = strlen(cmd[1]);

	if (len >= 1 && g_strncasecmp(cmd[1], "add", len) == 0) {
		struct prpl *prpl;

		MIN_ARGS(3);

		if (!global.conf->allow_account_add) {
			irc_rootmsg(irc, "This server does not allow adding new accounts");
			return;
		}

		if (cmd[4] == NULL) {
			for (a = irc->b->accounts; a; a = a->next) {
				if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
					irc_rootmsg(irc, "Enter password for account %s "
					            "first (use /OPER)", a->tag);
					return;
				}
			}

			irc->status |= OPER_HACK_ACCOUNT_PASSWORD;
		}

		prpl = find_protocol(cmd[2]);

		if (prpl == NULL) {
			char *msg = explain_unknown_protocol(cmd[2]);
			irc_rootmsg(irc, "Unknown protocol");
			irc_rootmsg(irc, msg);
			g_free(msg);
			return;
		}

		for (a = irc->b->accounts; a; a = a->next) {
			if (a->prpl == prpl && prpl->handle_cmp(a->user, cmd[3]) == 0) {
				irc_rootmsg(irc, "Warning: You already have an account with "
				            "protocol `%s' and username `%s'. Are you accidentally "
				            "trying to add it twice?", prpl->name, cmd[3]);
			}
		}

		a = account_add(irc->b, prpl, cmd[3], cmd[4] ? cmd[4] : PASSWORD_PENDING);
		if (cmd[5]) {
			irc_rootmsg(irc, "Warning: Passing a servername/other flags to `account add' "
			            "is now deprecated. Use `account set' instead.");
			set_setstr(&a->set, "server", cmd[5]);
		}

		irc_rootmsg(irc, "Account successfully added with tag %s", a->tag);

		if (cmd[4] == NULL) {
			set_t *oauth = set_find(&a->set, "oauth");
			if (oauth && bool2int(set_value(oauth))) {
				*a->pass = '\0';
				irc_rootmsg(irc, "No need to enter a password for this "
				            "account since it's using OAuth");
			} else if (prpl->options & PRPL_OPT_NO_PASSWORD) {
				*a->pass = '\0';
			} else if (prpl->options & PRPL_OPT_PASSWORD_OPTIONAL) {
				*a->pass = '\0';
				irc_rootmsg(irc, "Passwords are optional for this account. "
				            "If you wish to enter the password with /OPER, do "
				            "account %s set -del password", a->tag);
			} else {
				irc_rootmsg(irc, "You can now use the /OPER command to "
				            "enter the password");
				if (oauth) {
					irc_rootmsg(irc, "Alternatively, enable OAuth if "
					            "the account supports it: account %s "
					            "set oauth on", a->tag);
				}
			}
		} else if (prpl->options & PRPL_OPT_NO_PASSWORD) {
			irc_rootmsg(irc, "Note: this account doesn't use password for login");
		}

		return;
	} else if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
		int i = 0;

		if (strchr(irc->umode, 'b')) {
			irc_rootmsg(irc, "Account list:");
		}

		for (a = irc->b->accounts; a; a = a->next) {
			char *con = NULL, *protocol = NULL;

			if (a->ic && (a->ic->flags & OPT_LOGGED_IN)) {
				con = " (connected)";
			} else if (a->ic) {
				con = " (connecting)";
			} else if (a->reconnect) {
				con = " (awaiting reconnect)";
			} else {
				con = "";
			}

			if (a->prpl == &protocol_missing) {
				protocol = g_strdup_printf("%s (missing!)", set_getstr(&a->set, "_protocol_name"));
			} else {
				protocol = g_strdup(a->prpl->name);
			}

			irc_rootmsg(irc, "%2d (%s): %s, %s%s", i, a->tag, protocol, a->user, con);
			g_free(protocol);

			i++;
		}
		irc_rootmsg(irc, "End of account list");

		return;
	} else if (cmd[2]) {
		/* Try the following two only if cmd[2] == NULL */
	} else if (len >= 2 && g_strncasecmp(cmd[1], "on", len) == 0) {
		if (irc->b->accounts) {
			irc_rootmsg(irc, "Trying to get all accounts connected...");

			for (a = irc->b->accounts; a; a = a->next) {
				if (!a->ic && a->auto_connect && a->prpl != &protocol_missing) {
					if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
						irc_rootmsg(irc, "Enter password for account %s "
						            "first (use /OPER)", a->tag);
					} else {
						account_on(irc->b, a);
					}
				}
			}
		} else {
			irc_rootmsg(irc, "No accounts known. Use `account add' to add one.");
		}

		return;
	} else if (len >= 2 && g_strncasecmp(cmd[1], "off", len) == 0) {
		irc_rootmsg(irc, "Deactivating all active (re)connections...");

		for (a = irc->b->accounts; a; a = a->next) {
			if (a->ic) {
				account_off(irc->b, a);
			} else if (a->reconnect) {
				cancel_auto_reconnect(a);
			}
		}

		return;
	}

	MIN_ARGS(2);
	len = strlen(cmd[2]);

	/* At least right now, don't accept on/off/set/del as account IDs even
	   if they're a proper match, since people not familiar with the new
	   syntax yet may get a confusing/nasty surprise. */
	if (g_strcasecmp(cmd[1], "on") == 0 ||
	    g_strcasecmp(cmd[1], "off") == 0 ||
	    g_strcasecmp(cmd[1], "set") == 0 ||
	    g_strcasecmp(cmd[1], "del") == 0 ||
	    (a = account_get(irc->b, cmd[1])) == NULL) {
		irc_rootmsg(irc, "Could not find account `%s'.", cmd[1]);

		return;
	}

	if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
		if (a->flags & ACC_FLAG_LOCKED) {
			irc_rootmsg(irc, "Account is locked, can't delete");
		}
		else if (a->ic) {
			irc_rootmsg(irc, "Account is still logged in, can't delete");
		} else {
			account_del(irc->b, a);
			irc_rootmsg(irc, "Account deleted");
		}
	} else if (len >= 2 && g_strncasecmp(cmd[2], "on", len) == 0) {
		if (a->ic) {
			irc_rootmsg(irc, "Account already online");
		} else if (strcmp(a->pass, PASSWORD_PENDING) == 0) {
			irc_rootmsg(irc, "Enter password for account %s "
			            "first (use /OPER)", a->tag);
		} else if (a->prpl == &protocol_missing) {
			char *proto = set_getstr(&a->set, "_protocol_name");
			char *msg = explain_unknown_protocol(proto);
			irc_rootmsg(irc, "Unknown protocol `%s'", proto);
			irc_rootmsg(irc, msg);
			g_free(msg);
		} else {
			account_on(irc->b, a);
		}
	} else if (len >= 2 && g_strncasecmp(cmd[2], "off", len) == 0) {
		if (a->ic) {
			account_off(irc->b, a);
		} else if (a->reconnect) {
			cancel_auto_reconnect(a);
			irc_rootmsg(irc, "Reconnect cancelled");
		} else {
			irc_rootmsg(irc, "Account already offline");
		}
	} else if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
		cmd_set_real(irc, cmd + 2, &a->set, cmd_account_set_checkflags);
	} else {
		irc_rootmsg(irc,
		            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "account",
		            cmd[2]);
	}
}

static void cmd_channel(irc_t *irc, char **cmd)
{
	irc_channel_t *ic;
	int len;

	len = strlen(cmd[1]);

	if (len >= 1 && g_strncasecmp(cmd[1], "list", len) == 0) {
		GSList *l;
		int i = 0;

		if (strchr(irc->umode, 'b')) {
			irc_rootmsg(irc, "Channel list:");
		}

		for (l = irc->channels; l; l = l->next) {
			irc_channel_t *ic = l->data;

			irc_rootmsg(irc, "%2d. %s, %s channel%s", i, ic->name,
			            set_getstr(&ic->set, "type"),
			            ic->flags & IRC_CHANNEL_JOINED ? " (joined)" : "");

			i++;
		}
		irc_rootmsg(irc, "End of channel list");

		return;
	}

	if ((ic = irc_channel_get(irc, cmd[1])) == NULL) {
		/* If this doesn't match any channel, maybe this is the short
		   syntax (only works when used inside a channel). */
		if ((ic = irc->root->last_channel) &&
		    (len = strlen(cmd[1])) &&
		    g_strncasecmp(cmd[1], "set", len) == 0) {
			cmd_set_real(irc, cmd + 1, &ic->set, NULL);
		} else {
			irc_rootmsg(irc, "Could not find channel `%s'", cmd[1]);
		}

		return;
	}

	MIN_ARGS(2);
	len = strlen(cmd[2]);

	if (len >= 1 && g_strncasecmp(cmd[2], "set", len) == 0) {
		cmd_set_real(irc, cmd + 2, &ic->set, NULL);
	} else if (len >= 1 && g_strncasecmp(cmd[2], "del", len) == 0) {
		if (!(ic->flags & IRC_CHANNEL_JOINED) &&
		    ic != ic->irc->default_channel) {
			irc_rootmsg(irc, "Channel %s deleted.", ic->name);
			irc_channel_free(ic);
		} else {
			irc_rootmsg(irc, "Couldn't remove channel (main channel %s or "
			            "channels you're still in cannot be deleted).",
			            irc->default_channel->name);
		}
	} else {
		irc_rootmsg(irc,
		            "Unknown command: %s [...] %s. Please use \x02help commands\x02 to get a list of available commands.", "channel",
		            cmd[1]);
	}
}

static void cmd_add(irc_t *irc, char **cmd)
{
	account_t *a;
	int add_on_server = 1;
	char *handle = NULL, *s;

	if (g_strcasecmp(cmd[1], "-tmp") == 0) {
		MIN_ARGS(3);
		add_on_server = 0;
		cmd++;
	}

	if (!(a = account_get(irc->b, cmd[1]))) {
		irc_rootmsg(irc, "Invalid account");
		return;
	} else if (!(a->ic && (a->ic->flags & OPT_LOGGED_IN))) {
		irc_rootmsg(irc, "That account is not on-line");
		return;
	}

	if (cmd[3]) {
		if (!nick_ok(irc, cmd[3])) {
			irc_rootmsg(irc, "The requested nick `%s' is invalid", cmd[3]);
			return;
		} else if (irc_user_by_name(irc, cmd[3])) {
			irc_rootmsg(irc, "The requested nick `%s' already exists", cmd[3]);
			return;
		} else {
			nick_set_raw(a, cmd[2], cmd[3]);
		}
	}

	if ((a->flags & ACC_FLAG_HANDLE_DOMAINS) && cmd[2][0] != '_' &&
	    (!(s = strchr(cmd[2], '@')) || s[1] == '\0')) {
		/* If there's no @ or it's the last char, append the user's
		   domain name now. Exclude handles starting with a _ so
		   adding _xmlconsole will keep working. */
		if (s) {
			*s = '\0';
		}
		if ((s = strchr(a->user, '@'))) {
			cmd[2] = handle = g_strconcat(cmd[2], s, NULL);
		}
	}

	if (add_on_server) {
		irc_channel_t *ic;
		char *s, *group = NULL;;

		if ((ic = irc->root->last_channel) &&
		    (s = set_getstr(&ic->set, "fill_by")) &&
		    strcmp(s, "group") == 0 &&
		    (group = set_getstr(&ic->set, "group"))) {
			irc_rootmsg(irc, "Adding `%s' to contact list (group %s)",
			            cmd[2], group);
		} else {
			irc_rootmsg(irc, "Adding `%s' to contact list", cmd[2]);
		}

		a->prpl->add_buddy(a->ic, cmd[2], group);
	} else {
		bee_user_t *bu;
		irc_user_t *iu;

		/* Only for add -tmp. For regular adds, this callback will
		   be called once the IM server confirms. */
		if ((bu = bee_user_new(irc->b, a->ic, cmd[2], BEE_USER_LOCAL)) &&
		    (iu = bu->ui_data)) {
			irc_rootmsg(irc, "Temporarily assigned nickname `%s' "
			            "to contact `%s'", iu->nick, cmd[2]);
		}
	}

	g_free(handle);
}

static void cmd_remove(irc_t *irc, char **cmd)
{
	irc_user_t *iu;
	bee_user_t *bu;
	char *s;

	if (!(iu = irc_user_by_name(irc, cmd[1])) || !(bu = iu->bu)) {
		irc_rootmsg(irc, "Buddy `%s' not found", cmd[1]);
		return;
	}
	s = g_strdup(bu->handle);

	bu->ic->acc->prpl->remove_buddy(bu->ic, bu->handle, NULL);
	nick_del(bu);
	if (g_slist_find(irc->users, iu)) {
		bee_user_free(irc->b, bu);
	}

	irc_rootmsg(irc, "Buddy `%s' (nick %s) removed from contact list", s, cmd[1]);
	g_free(s);

	return;
}

static void cmd_info(irc_t *irc, char **cmd)
{
	struct im_connection *ic;
	account_t *a;

	if (!cmd[2]) {
		irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
		if (!iu || !iu->bu) {
			irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
			return;
		}
		ic = iu->bu->ic;
		cmd[2] = iu->bu->handle;
	} else if (!(a = account_get(irc->b, cmd[1]))) {
		irc_rootmsg(irc, "Invalid account");
		return;
	} else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
		irc_rootmsg(irc, "That account is not on-line");
		return;
	}

	if (!ic->acc->prpl->get_info) {
		irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
	} else {
		ic->acc->prpl->get_info(ic, cmd[2]);
	}
}

static void cmd_rename(irc_t *irc, char **cmd)
{
	irc_user_t *iu, *old;
	gboolean del = g_strcasecmp(cmd[1], "-del") == 0;

	iu = irc_user_by_name(irc, cmd[del ? 2 : 1]);

	if (iu == NULL) {
		irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
	} else if (del) {
		if (iu->bu) {
			bee_irc_user_nick_reset(iu);
		}
		irc_rootmsg(irc, "Nickname reset to `%s'", iu->nick);
	} else if (iu == irc->user) {
		irc_rootmsg(irc, "Use /nick to change your own nickname");
	} else if (!nick_ok(irc, cmd[2])) {
		irc_rootmsg(irc, "Nick `%s' is invalid", cmd[2]);
	} else if ((old = irc_user_by_name(irc, cmd[2])) && old != iu) {
		irc_rootmsg(irc, "Nick `%s' already exists", cmd[2]);
	} else {
		if (!irc_user_set_nick(iu, cmd[2])) {
			irc_rootmsg(irc, "Error while changing nick");
			return;
		}

		if (iu == irc->root) {
			/* If we're called internally (user did "set root_nick"),
			   let's not go O(INF). :-) */
			if (strcmp(cmd[0], "set_rename") != 0) {
				set_setstr(&irc->b->set, "root_nick", cmd[2]);
			}
		} else if (iu->bu) {
			nick_set(iu->bu, cmd[2]);
		}

		irc_rootmsg(irc, "Nick successfully changed");
	}
}

char *set_eval_root_nick(set_t *set, char *new_nick)
{
	irc_t *irc = set->data;

	if (strcmp(irc->root->nick, new_nick) != 0) {
		char *cmd[] = { "set_rename", irc->root->nick, new_nick, NULL };

		cmd_rename(irc, cmd);
	}

	return strcmp(irc->root->nick, new_nick) == 0 ? new_nick : SET_INVALID;
}

static void cmd_block(irc_t *irc, char **cmd)
{
	struct im_connection *ic;
	account_t *a;

	if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
		char *format;
		GSList *l;

		if (strchr(irc->umode, 'b') != NULL) {
			format = "%s\t%s";
		} else {
			format = "%-32.32s  %-16.16s";
		}

		irc_rootmsg(irc, format, "Handle", "Nickname");
		for (l = a->ic->deny; l; l = l->next) {
			bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
			irc_user_t *iu = bu ? bu->ui_data : NULL;
			irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
		}
		irc_rootmsg(irc, "End of list.");

		return;
	} else if (!cmd[2]) {
		irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
		if (!iu || !iu->bu) {
			irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
			return;
		}
		ic = iu->bu->ic;
		cmd[2] = iu->bu->handle;
	} else if (!(a = account_get(irc->b, cmd[1]))) {
		irc_rootmsg(irc, "Invalid account");
		return;
	} else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
		irc_rootmsg(irc, "That account is not on-line");
		return;
	}

	if (!ic->acc->prpl->add_deny || !ic->acc->prpl->rem_permit) {
		irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
	} else {
		imc_rem_allow(ic, cmd[2]);
		imc_add_block(ic, cmd[2]);
		irc_rootmsg(irc, "Buddy `%s' moved from allow- to block-list", cmd[2]);
	}
}

static void cmd_allow(irc_t *irc, char **cmd)
{
	struct im_connection *ic;
	account_t *a;

	if (!cmd[2] && (a = account_get(irc->b, cmd[1])) && a->ic) {
		char *format;
		GSList *l;

		if (strchr(irc->umode, 'b') != NULL) {
			format = "%s\t%s";
		} else {
			format = "%-32.32s  %-16.16s";
		}

		irc_rootmsg(irc, format, "Handle", "Nickname");
		for (l = a->ic->permit; l; l = l->next) {
			bee_user_t *bu = bee_user_by_handle(irc->b, a->ic, l->data);
			irc_user_t *iu = bu ? bu->ui_data : NULL;
			irc_rootmsg(irc, format, l->data, iu ? iu->nick : "(none)");
		}
		irc_rootmsg(irc, "End of list.");

		return;
	} else if (!cmd[2]) {
		irc_user_t *iu = irc_user_by_name(irc, cmd[1]);
		if (!iu || !iu->bu) {
			irc_rootmsg(irc, "Nick `%s' does not exist", cmd[1]);
			return;
		}
		ic = iu->bu->ic;
		cmd[2] = iu->bu->handle;
	} else if (!(a = account_get(irc->b, cmd[1]))) {
		irc_rootmsg(irc, "Invalid account");
		return;
	} else if (!((ic = a->ic) && (a->ic->flags & OPT_LOGGED_IN))) {
		irc_rootmsg(irc, "That account is not on-line");
		return;
	}

	if (!ic->acc->prpl->rem_deny || !ic->acc->prpl->add_permit) {
		irc_rootmsg(irc, "Command `%s' not supported by this protocol", cmd[0]);
	} else {
		imc_rem_block(ic, cmd[2]);
		imc_add_allow(ic, cmd[2]);

		irc_rootmsg(irc, "Buddy `%s' moved from block- to allow-list", cmd[2]);
	}
}

static void cmd_yesno(irc_t *irc, char **cmd)
{
	query_t *q = NULL;
	int numq = 0;

	if (irc->queries == NULL) {
		/* Alright, alright, let's add a tiny easter egg here. */
		static irc_t *last_irc = NULL;
		static time_t last_time = 0;
		static int times = 0;
		static const char *msg[] = {
			"Oh yeah, that's right.",
			"Alright, alright. Now go back to work.",
			"Buuuuuuuuuuuuuuuurp... Excuse me!",
			"Yes?",
			"No?",
		};

		if (last_irc == irc && time(NULL) - last_time < 15) {
			if ((++times >= 3)) {
				irc_rootmsg(irc, "%s", msg[rand() % (sizeof(msg) / sizeof(char*))]);
				last_irc = NULL;
				times = 0;
				return;
			}
		} else {
			last_time = time(NULL);
			last_irc = irc;
			times = 0;
		}

		irc_rootmsg(irc, "Did I ask you something?");
		return;
	}

	/* If there's an argument, the user seems to want to answer another question than the
	   first/last (depending on the query_order setting) one. */
	if (cmd[1]) {
		if (sscanf(cmd[1], "%d", &numq) != 1) {
			irc_rootmsg(irc, "Invalid query number");
			return;
		}

		for (q = irc->queries; q; q = q->next, numq--) {
			if (numq == 0) {
				break;
			}
		}

		if (!q) {
			irc_rootmsg(irc, "Uhm, I never asked you something like that...");
			return;
		}
	}

	if (g_strcasecmp(cmd[0], "yes") == 0) {
		query_answer(irc, q, 1);
	} else if (g_strcasecmp(cmd[0], "no") == 0) {
		query_answer(irc, q, 0);
	}
}

static void cmd_set(irc_t *irc, char **cmd)
{
	cmd_set_real(irc, cmd, &irc->b->set, NULL);
}

static void cmd_blist(irc_t *irc, char **cmd)
{
	int online = 0, away = 0, offline = 0, ismatch = 0;
	GSList *l;
	GRegex *regex = NULL;
	GError *error = NULL;
	char s[256];
	char *format;
	int n_online = 0, n_away = 0, n_offline = 0;

	if (cmd[1] && g_strcasecmp(cmd[1], "all") == 0) {
		online = offline = away = 1;
	} else if (cmd[1] && g_strcasecmp(cmd[1], "offline") == 0) {
		offline = 1;
	} else if (cmd[1] && g_strcasecmp(cmd[1], "away") == 0) {
		away = 1;
	} else if (cmd[1] && g_strcasecmp(cmd[1], "online") == 0) {
		online = 1;
	} else {
		online = away = 1;
	}

	if (cmd[2]) {
		regex = g_regex_new(cmd[2], G_REGEX_CASELESS, 0, &error);
	}

	if (error) {
		irc_rootmsg(irc, error->message);
		g_error_free(error);
	}

	if (strchr(irc->umode, 'b') != NULL) {
		format = "%s\t%s\t%s";
	} else {
		format = "%-16.16s  %-40.40s  %s";
	}

	irc_rootmsg(irc, format, "Nick", "Handle/Account", "Status");

	if (irc->root->last_channel &&
	    strcmp(set_getstr(&irc->root->last_channel->set, "type"), "control") != 0) {
		irc->root->last_channel = NULL;
	}

	for (l = irc->users; l; l = l->next) {
		irc_user_t *iu = l->data;
		bee_user_t *bu = iu->bu;

		if (!regex || g_regex_match(regex, iu->nick, 0, NULL)) {
			ismatch = 1;
		} else {
			ismatch = 0;
		}

		if (!bu || (irc->root->last_channel && !irc_channel_wants_user(irc->root->last_channel, iu))) {
			continue;
		}

		if ((bu->flags & (BEE_USER_ONLINE | BEE_USER_AWAY)) == BEE_USER_ONLINE) {
			if (ismatch == 1 && online == 1) {
				char st[256] = "Online";

				if (bu->status_msg) {
					g_snprintf(st, sizeof(st) - 1, "Online (%s)", bu->status_msg);
				}

				g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
				irc_rootmsg(irc, format, iu->nick, s, st);
			}

			n_online++;
		}

		if ((bu->flags & BEE_USER_ONLINE) && (bu->flags & BEE_USER_AWAY)) {
			if (ismatch == 1 && away == 1) {
				g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
				irc_rootmsg(irc, format, iu->nick, s, irc_user_get_away(iu));
			}
			n_away++;
		}

		if (!(bu->flags & BEE_USER_ONLINE)) {
			if (ismatch == 1 && offline == 1) {
				g_snprintf(s, sizeof(s) - 1, "%s %s", bu->handle, bu->ic->acc->tag);
				irc_rootmsg(irc, format, iu->nick, s, "Offline");
			}
			n_offline++;
		}
	}

	irc_rootmsg(irc, "%d buddies (%d available, %d away, %d offline)", n_online + n_away + n_offline, n_online,
	            n_away, n_offline);

	if (regex) {
		g_regex_unref(regex);
	}
}

static gint prplcmp(gconstpointer a, gconstpointer b)
{
	const struct prpl *pa = a;
	const struct prpl *pb = b;

	return g_strcasecmp(pa->name, pb->name);
}

static void prplstr(GList *prpls, GString *gstr)
{
	const char *last = NULL;
	GList *l;
	struct prpl *p;

	prpls = g_list_copy(prpls);
	prpls = g_list_sort(prpls, prplcmp);

	for (l = prpls; l; l = l->next) {
		p = l->data;

		if (last && g_strcasecmp(p->name, last) == 0) {
			/* Ignore duplicates (mainly for libpurple) */
			continue;
		}

		if (gstr->len != 0) {
			g_string_append(gstr, ", ");
		}

		g_string_append(gstr, p->name);
		last = p->name;
	}

	g_list_free(prpls);
}

static void cmd_plugins_info(irc_t *irc, char **cmd)
{
	GList *l;
	struct plugin_info *info;

	MIN_ARGS(2);

	for (l = get_plugins(); l; l = l->next) {
		info = l->data;
		if (g_strcasecmp(cmd[2], info->name) == 0) {
			break;
		}
	}

	if (!l) {
		return;
	}

	irc_rootmsg(irc, "%s:", info->name);
	irc_rootmsg(irc, "  Version: %s", info->version);

	if (info->description) {
		irc_rootmsg(irc, "  Description: %s", info->description);
	}

	if (info->author) {
		irc_rootmsg(irc, "  Author: %s", info->author);
	}

	if (info->url) {
		irc_rootmsg(irc, "  URL: %s", info->url);
	}
}

static void cmd_plugins(irc_t *irc, char **cmd)
{
	GList *prpls;
	GString *gstr;

	if (cmd[1] && g_strcasecmp(cmd[1], "info") == 0) {
		cmd_plugins_info(irc, cmd);
		return;
	}

#ifdef WITH_PLUGINS
	GList *l;
	struct plugin_info *info;
	char *format;

	if (strchr(irc->umode, 'b') != NULL) {
		format = "%s\t%s";
	} else {
		format = "%-30s  %s";
	}

	irc_rootmsg(irc, format, "Plugin", "Version");

	for (l = get_plugins(); l; l = l->next) {
		char *c;
		info = l->data;

		/* some purple plugins like to include several versions separated by newlines... */
		if ((c = strchr(info->version, '\n'))) {
			char *version = g_strndup(info->version, c - info->version);
			irc_rootmsg(irc, format, info->name, version);
			g_free(version);
		} else {
			irc_rootmsg(irc, format, info->name, info->version);
		}
	}
#endif

	irc_rootmsg(irc, "");

	gstr = g_string_new(NULL);
	prpls = get_protocols();

	if (prpls) {
		prplstr(prpls, gstr);
		irc_rootmsg(irc, "Enabled Protocols: %s", gstr->str);
		g_string_truncate(gstr, 0);
	}

	prpls = get_protocols_disabled();

	if (prpls) {
		prplstr(prpls, gstr);
		irc_rootmsg(irc, "Disabled Protocols: %s", gstr->str);
	}

	g_string_free(gstr, TRUE);
}

static void cmd_qlist(irc_t *irc, char **cmd)
{
	query_t *q = irc->queries;
	int num;

	if (!q) {
		irc_rootmsg(irc, "There are no pending questions.");
		return;
	}

	irc_rootmsg(irc, "Pending queries:");

	for (num = 0; q; q = q->next, num++) {
		if (q->ic) { /* Not necessary yet, but it might come later */
			irc_rootmsg(irc, "%d, %s: %s", num, q->ic->acc->tag, q->question);
		} else {
			irc_rootmsg(irc, "%d, BitlBee: %s", num, q->question);
		}
	}
}

static void cmd_chat(irc_t *irc, char **cmd)
{
	account_t *acc;

	if (g_strcasecmp(cmd[1], "add") == 0) {
		bee_chat_info_t *ci;
		char *channel, *room, *s;
		struct irc_channel *ic;
		guint i;

		MIN_ARGS(3);

		if (!(acc = account_get(irc->b, cmd[2]))) {
			irc_rootmsg(irc, "Invalid account");
			return;
		} else if (!acc->prpl->chat_join) {
			irc_rootmsg(irc, "Named chatrooms not supported on that account.");
			return;
		}

		if (cmd[3][0] == '!') {
			if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
				irc_rootmsg(irc, "Not logged in to account.");
				return;
			} else if (!acc->prpl->chat_list) {
				irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
				return;
			}

			i = g_ascii_strtoull(cmd[3] + 1, NULL, 10);
			ci = g_slist_nth_data(acc->ic->chatlist, i - 1);

			if (ci == NULL) {
				irc_rootmsg(irc, "Invalid chatroom index");
				return;
			}

			room = ci->title;
		} else {
			room = cmd[3];
		}

		if (cmd[4] == NULL) {
			channel = g_strdup(room);
			if ((s = strchr(channel, '@'))) {
				*s = 0;
			}
		} else {
			channel = g_strdup(cmd[4]);
		}

		if (strchr(CTYPES, channel[0]) == NULL) {
			s = g_strdup_printf("#%s", channel);
			g_free(channel);
			channel = s;

			irc_channel_name_strip(channel);
		}

		if ((ic = irc_channel_new(irc, channel)) &&
		    set_setstr(&ic->set, "type", "chat") &&
		    set_setstr(&ic->set, "chat_type", "room") &&
		    set_setstr(&ic->set, "account", cmd[2]) &&
		    set_setstr(&ic->set, "room", room)) {
			irc_rootmsg(irc, "Chatroom successfully added.");
		} else {
			if (ic) {
				irc_channel_free(ic);
			}

			irc_rootmsg(irc, "Could not add chatroom.");
		}
		g_free(channel);
	} else if (g_strcasecmp(cmd[1], "list") == 0) {
		MIN_ARGS(2);

		if (!(acc = account_get(irc->b, cmd[2]))) {
			irc_rootmsg(irc, "Invalid account");
			return;
		} else if (!acc->ic || !(acc->ic->flags & OPT_LOGGED_IN)) {
			irc_rootmsg(irc, "Not logged in to account.");
			return;
		} else if (!acc->prpl->chat_list) {
			irc_rootmsg(irc, "Listing chatrooms not supported on that account.");
			return;
		}

		acc->prpl->chat_list(acc->ic, cmd[3]);
	} else if (g_strcasecmp(cmd[1], "with") == 0) {
		irc_user_t *iu;

		MIN_ARGS(2);

		if ((iu = irc_user_by_name(irc, cmd[2])) &&
		    iu->bu && iu->bu->ic->acc->prpl->chat_with) {
			if (!iu->bu->ic->acc->prpl->chat_with(iu->bu->ic, iu->bu->handle)) {
				irc_rootmsg(irc, "(Possible) failure while trying to open "
				            "a groupchat with %s.", iu->nick);
			}
		} else {
			irc_rootmsg(irc, "Can't open a groupchat with %s.", cmd[2]);
		}
	} else if (g_strcasecmp(cmd[1], "set") == 0 ||
	           g_strcasecmp(cmd[1], "del") == 0) {
		irc_rootmsg(irc,
		            "Warning: The \002chat\002 command was mostly replaced with the \002channel\002 command.");
		cmd_channel(irc, cmd);
	} else {
		irc_rootmsg(irc,
		            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "chat",
		            cmd[1]);
	}
}

/* some arbitrary numbers */
#define CHAT_TITLE_LEN_MIN 20
#define CHAT_TITLE_LEN_MAX 100

void cmd_chat_list_finish(struct im_connection *ic)
{
	account_t *acc = ic->acc;
	bee_chat_info_t *ci;
	char *hformat, *iformat, *topic, *padded;
	GSList *l;
	guint i = 0;
	long title_len, new_len;
	irc_t *irc = ic->bee->ui_data;

	if (ic->chatlist == NULL) {
		irc_rootmsg(irc, "No existing chatrooms");
		return;
	}

	/* find a reasonable width for the table */
	title_len = CHAT_TITLE_LEN_MIN;

	for (l = ic->chatlist; l; l = l->next) {
		ci = l->data;
		new_len = g_utf8_strlen(ci->title, -1);

		if (new_len >= CHAT_TITLE_LEN_MAX) {
			title_len = CHAT_TITLE_LEN_MAX;
			break;
		} else if (title_len < new_len) {
			title_len = new_len;
		}
	}

	if (strchr(irc->umode, 'b') != NULL) {
		hformat = "%s\t%s\t%s";
		iformat = "%u\t%s\t%s";
	} else {
		hformat = "%s  %s  %s";
		iformat = "%5u  %s  %s";
	}

	padded = str_pad_and_truncate("Title", title_len, NULL);
	irc_rootmsg(irc, hformat, "Index", padded, "Topic");
	g_free(padded);

	for (l = ic->chatlist; l; l = l->next) {
		ci = l->data;
		topic = ci->topic ? ci->topic : "";

		padded = str_pad_and_truncate(ci->title, title_len, "[...]");
		irc_rootmsg(irc, iformat, ++i, padded, topic);
		g_free(padded);
	}

	irc_rootmsg(irc, "%u %s chatrooms", i, acc->tag);
}

static void cmd_group(irc_t *irc, char **cmd)
{
	GSList *l;
	int len;

	len = strlen(cmd[1]);
	if (g_strncasecmp(cmd[1], "list", len) == 0) {
		int n = 0;

		if (strchr(irc->umode, 'b')) {
			irc_rootmsg(irc, "Group list:");
		}

		for (l = irc->b->groups; l; l = l->next) {
			bee_group_t *bg = l->data;
			irc_rootmsg(irc, "%d. %s", n++, bg->name);
		}
		irc_rootmsg(irc, "End of group list");
	} else if (g_strncasecmp(cmd[1], "info", len) == 0) {
		bee_group_t *bg;
		int n = 0;

		MIN_ARGS(2);
		bg = bee_group_by_name(irc->b, cmd[2], FALSE);

		if (bg) {
			if (strchr(irc->umode, 'b')) {
				irc_rootmsg(irc, "Members of %s:", cmd[2]);
			}
			for (l = irc->b->users; l; l = l->next) {
				bee_user_t *bu = l->data;
				if (bu->group == bg) {
					irc_rootmsg(irc, "%d. %s", n++, bu->nick ? : bu->handle);
				}
			}
			irc_rootmsg(irc, "End of member list");
		} else {
			irc_rootmsg(irc,
			            "Unknown group: %s. Please use \x02group list\x02 to get a list of available groups.",
			            cmd[2]);
		}
	} else {
		irc_rootmsg(irc,
		            "Unknown command: %s %s. Please use \x02help commands\x02 to get a list of available commands.", "group",
		            cmd[1]);
	}
}

static void cmd_transfer(irc_t *irc, char **cmd)
{
	GSList *files = irc->file_transfers;
	GSList *next;

	enum { LIST, REJECT, CANCEL };
	int subcmd = LIST;
	int fid;

	if (!files) {
		irc_rootmsg(irc, "No pending transfers");
		return;
	}

	if (cmd[1] && (strcmp(cmd[1], "reject") == 0)) {
		subcmd = REJECT;
	} else if (cmd[1] && (strcmp(cmd[1], "cancel") == 0) &&
	           cmd[2] && (sscanf(cmd[2], "%d", &fid) == 1)) {
		subcmd = CANCEL;
	}

	for (; files; files = next) {
		next = files->next;
		file_transfer_t *file = files->data;

		switch (subcmd) {
		case LIST:
			if (file->status == FT_STATUS_LISTENING) {
				irc_rootmsg(irc,
				            "Pending file(id %d): %s (Listening...)", file->local_id, file->file_name);
			} else {
				int kb_per_s = 0;
				time_t diff = time(NULL) - file->started ? : 1;
				if ((file->started > 0) && (file->bytes_transferred > 0)) {
					kb_per_s = file->bytes_transferred / 1024 / diff;
				}

				irc_rootmsg(irc,
				            "Pending file(id %d): %s (%10zd/%zd kb, %d kb/s)", file->local_id,
				            file->file_name,
				            file->bytes_transferred / 1024, file->file_size / 1024, kb_per_s);
			}
			break;
		case REJECT:
			if (file->status == FT_STATUS_LISTENING) {
				irc_rootmsg(irc, "Rejecting file transfer for %s", file->file_name);
				imcb_file_canceled(file->ic, file, "Denied by user");
			}
			break;
		case CANCEL:
			if (file->local_id == fid) {
				irc_rootmsg(irc, "Canceling file transfer for %s", file->file_name);
				imcb_file_canceled(file->ic, file, "Canceled by user");
			}
			break;
		}
	}
}

static void cmd_nick(irc_t *irc, char **cmd)
{
	irc_rootmsg(irc, "This command is deprecated. Try: account %s set display_name", cmd[1]);
}

/* Maybe this should be a stand-alone command as well? */
static void bitlbee_whatsnew(irc_t *irc)
{
	int last = set_getint(&irc->b->set, "last_version");
	char s[16], *msg;

	if (last >= BITLBEE_VERSION_CODE) {
		return;
	}

	msg = help_get_whatsnew(&(global.help), last);

	if (msg) {
		irc_rootmsg(irc, "%s: This seems to be your first time using this "
		            "this version of BitlBee. Here's a list of new "
		            "features you may like to know about:\n\n%s\n",
		            irc->user->nick, msg);
	}

	g_free(msg);

	g_snprintf(s, sizeof(s), "%d", BITLBEE_VERSION_CODE);
	set_setstr(&irc->b->set, "last_version", s);
}

/* IMPORTANT: Keep this list sorted! The short command logic needs that. */
command_t root_commands[] = {
	{ "account",        1, cmd_account,        0 },
	{ "add",            2, cmd_add,            0 },
	{ "allow",          1, cmd_allow,          0 },
	{ "blist",          0, cmd_blist,          0 },
	{ "block",          1, cmd_block,          0 },
	{ "channel",        1, cmd_channel,        0 },
	{ "chat",           1, cmd_chat,           0 },
	{ "drop",           1, cmd_drop,           0 },
	{ "ft",             0, cmd_transfer,       0 },
	{ "group",          1, cmd_group,          0 },
	{ "help",           0, cmd_help,           0 },
	{ "identify",       0, cmd_identify,       0 },
	{ "info",           1, cmd_info,           0 },
	{ "nick",           1, cmd_nick,           0 },
	{ "no",             0, cmd_yesno,          0 },
	{ "plugins",        0, cmd_plugins,        0 },
	{ "qlist",          0, cmd_qlist,          0 },
	{ "register",       0, cmd_register,       0 },
	{ "remove",         1, cmd_remove,         0 },
	{ "rename",         2, cmd_rename,         0 },
	{ "save",           0, cmd_save,           0 },
	{ "set",            0, cmd_set,            0 },
	{ "transfer",       0, cmd_transfer,       0 },
	{ "yes",            0, cmd_yesno,          0 },
	/* Not expecting too many plugins adding root commands so just make a
	   dumb array with some empty entried at the end. */
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
	{ NULL },
};
static const int num_root_commands = sizeof(root_commands) / sizeof(command_t);

gboolean root_command_add(const char *command, int params, void (*func)(irc_t *, char **args), int flags)
{
	int i;

	if (root_commands[num_root_commands - 2].command) {
		/* Planning fail! List is full. */
		return FALSE;
	}

	for (i = 0; root_commands[i].command; i++) {
		if (g_strcasecmp(root_commands[i].command, command) == 0) {
			return FALSE;
		} else if (g_strcasecmp(root_commands[i].command, command) > 0) {
			break;
		}
	}
	memmove(root_commands + i + 1, root_commands + i,
	        sizeof(command_t) * (num_root_commands - i - 1));

	root_commands[i].command = g_strdup(command);
	root_commands[i].required_parameters = params;
	root_commands[i].execute = func;
	root_commands[i].flags = flags;

	return TRUE;
}