ced942458a86059b8adaf603150cdc78ddeb7269
[jelmer/ctrlproxy.git] / src / admin.c
1 /*
2         ctrlproxy: A modular IRC proxy
3         admin: module for remote administration. 
4
5         (c) 2003-2006 Jelmer Vernooij <jelmer@nl.linux.org>
6
7         This program is free software; you can redistribute it and/or modify
8         it under the terms of the GNU General Public License as published by
9         the Free Software Foundation; either version 2 of the License, or
10         (at your option) any later version.
11
12         This program is distributed in the hope that it will be useful,
13         but WITHOUT ANY WARRANTY; without even the implied warranty of
14         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15         GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to the Free Software
19         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 */
21
22 #include "internals.h"
23 #include <string.h>
24 #include "admin.h"
25 #include "help.h"
26 #include "irc.h"
27
28 help_t *help;
29
30 #define ADMIN_CHANNEL "#ctrlproxy"
31
32 GList *admin_commands = NULL;
33 guint longest_command = 0;
34
35 static void privmsg_admin_out(admin_handle h, const char *data)
36 {
37         struct client *c = h->client;
38         char *nick = c->nick;
39         char *hostmask;
40
41         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->info.name);
42         if (c->network->state) nick = c->network->state->me.nick;
43         client_send_args_ex(c, hostmask, "NOTICE", nick, data, NULL);
44
45         g_free(hostmask);
46 }
47
48 static void network_admin_out(admin_handle h, const char *data)
49 {
50         struct client *c = h->client;
51         char *hostmask;
52
53         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->info.name);
54         virtual_network_recv_args(c->network, hostmask, "PRIVMSG", ADMIN_CHANNEL, 
55                                                           data, NULL);
56
57         g_free(hostmask);
58 }
59
60 static void cmd_help(admin_handle h, char **args, void *userdata)
61 {
62         const char *s;
63
64         s = help_get(help, args[1] != NULL?args[1]:"index");
65
66         if (s == NULL) {
67                 if (args[1] == NULL)
68                         admin_out(h, "Sorry, help not available");
69                 else
70                         admin_out(h, "Sorry, no help for %s available", args[1]);
71                 return;
72         }
73
74         while (strncmp(s, "%\n", 2) != 0) {
75                 char *tmp;
76                 admin_out(h, "%s", tmp = g_strndup(s, strchr(s, '\n')-s));
77                 g_free(tmp);
78                         
79                 s = strchr(s, '\n')+1;
80         }
81 }
82
83 struct client *admin_get_client(admin_handle h)
84 {
85         return h->client;
86 }
87
88 struct global *admin_get_global(admin_handle h)
89 {
90         return h->global;
91 }
92
93 struct network *admin_get_network(admin_handle h)
94 {
95         return h->network;
96 }
97
98 void admin_out(admin_handle h, const char *fmt, ...)
99 {
100         va_list ap;
101         char *msg;
102         va_start(ap, fmt);
103         msg = g_strdup_vprintf(fmt, ap);
104         va_end(ap);
105
106         h->send_fn(h, msg);
107
108         g_free(msg);
109 }
110
111 static void add_network (admin_handle h, char **args, void *userdata)
112 {
113         struct network_config *nc;
114
115         if (args[1] == NULL) {
116                 admin_out(h, "No name specified");
117                 return;
118         }
119
120         if (find_network(admin_get_global(h), args[1]) != NULL) {
121                 admin_out(h, "Network with name `%s' already exists", args[1]);
122                 return;
123         }
124
125         nc = network_config_init(admin_get_global(h)->config);
126         g_free(nc->name); nc->name = g_strdup(args[1]);
127         load_network(admin_get_global(h), nc);
128
129         admin_out(h, "Network `%s' added. Use ADDSERVER to add a server to this network.", args[1]);
130 }
131
132 static void del_network (admin_handle h, char **args, void *userdata)
133 {
134         struct network *n;
135
136         if (args[1] == NULL) {
137                 admin_out(h, "Not enough parameters");
138                 return;
139         }
140
141         n = find_network(admin_get_global(h), args[1]);
142         if (n == NULL) {
143                 admin_out(h, "No such network `%s'", args[1]);
144                 return;
145         }
146
147         disconnect_network(n);
148
149         unload_network(n);
150
151         admin_out(h, "Network `%s' deleted", args[1]);
152 }
153
154 static void add_server (admin_handle h, char **args, void *userdata)
155 {
156         struct network *n;
157         struct tcp_server_config *s;
158         char *t;
159
160         if(!args[1] || !args[2]) {
161                 admin_out(h, "Not enough parameters");
162                 return;
163         }
164
165         n = find_network(admin_get_global(h), args[1]);
166
167         if (!n) {
168                 admin_out(h, "No such network '%s'", args[1]);
169                 return;
170         }
171
172         if (n->config->type != NETWORK_TCP) {
173                 admin_out(h, "Not a TCP/IP network!");
174                 return;
175         }
176
177         s = g_new0(struct tcp_server_config, 1);
178
179         s->host = g_strdup(args[2]);
180         if ((t = strchr(s->host, ':'))) {
181                 *t = '\0';
182                 s->port = g_strdup(t+1);
183         } else {
184                 s->port = g_strdup("6667");
185         }
186         s->ssl = FALSE;
187         s->password = args[3]?g_strdup(args[3]):NULL;
188
189         n->config->type_settings.tcp_servers = g_list_append(n->config->type_settings.tcp_servers, s);
190
191         admin_out(h, "Server added to `%s'", args[1]);
192 }
193
194 static void com_connect_network (admin_handle h, char **args, void *userdata)
195 {
196         struct network *s;
197         if(!args[1]) {
198                  admin_out(h, "No network specified");
199                  return;
200         }
201
202         s = find_network(admin_get_global(h), args[1]);
203
204         if (!s) {
205                 admin_out(h, "No such network `%s'", args[1]);
206                 return;
207         }
208
209         switch (s->connection.state) {
210                 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
211                         admin_out(h, "Connecting to `%s'", args[1]);
212                         connect_network(s);
213                         break;
214                 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
215                         admin_out(h, "Forcing reconnect to `%s'", args[1]);
216                         disconnect_network(s);
217                         network_select_next_server(s);
218                         connect_network(s);
219                         break;
220                 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
221                         admin_out(h, "Connect to `%s' already in progress", args[1]);
222                         break;
223                 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
224                         admin_out(h, "Already connected to `%s'", args[1]);
225                         break;
226         }
227 }
228
229 static void com_disconnect_network (admin_handle h, char **args, void *userdata)
230 {
231         struct network *n;
232
233         n = admin_get_network(h);
234
235         if (args[1] != NULL) {
236                 n = find_network(admin_get_global(h), args[1]);
237                 if(!n) {
238                         admin_out(h, "Can't find active network with that name");
239                         return;
240                 }
241         }
242
243         if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
244                 admin_out(h, "Already disconnected from `%s'", args[1]);
245         } else {
246                 admin_out(h, "Disconnecting from `%s'", args[1]);
247                 disconnect_network(n);
248         }
249 }
250
251 static void com_next_server (admin_handle h, char **args, void *userdata) 
252 {
253         struct network *n;
254         const char *name;
255
256
257         if(args[1] != NULL) {
258                 name = args[1];
259                 n = find_network(admin_get_global(h), args[1]);
260         } else {
261                 n = admin_get_network(h);
262                 name = n->info.name;
263         }
264         if(!n) {
265                 admin_out(h, "%s: Not connected", name);
266         } else {
267                 admin_out(h, "%s: Reconnecting", name);
268                 disconnect_network(n);
269                 network_select_next_server(n);
270                 connect_network(n);
271         }
272 }
273
274 static void com_save_config (admin_handle h, char **args, void *userdata)
275
276         const char *adm_dir;
277         global_update_config(admin_get_global(h));
278         adm_dir = args[1]?args[1]:admin_get_global(h)->config->config_dir; 
279         save_configuration(admin_get_global(h)->config, adm_dir);
280         admin_out(h, "Configuration saved in %s", adm_dir);
281 }
282
283
284
285 static void list_networks(admin_handle h, char **args, void *userdata)
286 {
287         GList *gl;
288         for (gl = admin_get_global(h)->networks; gl; gl = gl->next) {
289                 struct network *n = gl->data;
290
291                 switch (n->connection.state) {
292                 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
293                         if (n->connection.data.tcp.last_disconnect_reason)
294                                 admin_out(h, "%s: Not connected: %s", n->info.name, 
295                                                   n->connection.data.tcp.last_disconnect_reason);
296                         else
297                                 admin_out(h, "%s: Not connected", n->info.name);
298                         break;
299                 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
300                         admin_out(h, "%s: Reconnecting", n->info.name);
301                         break;
302                 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
303                 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
304                         admin_out(h, "%s: connected", n->info.name);
305                         break;
306                 }
307         }
308 }
309
310 static void detach_client(admin_handle h, char **args, void *userdata)
311 {
312         struct client *c = admin_get_client(h);
313
314         disconnect_client(c, "Client exiting");
315 }
316
317 static void dump_joined_channels(admin_handle h, char **args, void *userdata)
318 {
319         struct network *n;
320         GList *gl;
321
322         if (args[1] != NULL) {
323                 n = find_network(admin_get_global(h), args[1]);
324                 if(n == NULL) {
325                         admin_out(h, "Can't find network '%s'", args[1]);
326                         return;
327                 }
328         } else {
329                 n = admin_get_network(h);
330         }
331
332         if (!n->state) {
333                 admin_out(h, "Network '%s' not connected", n->info.name);
334                 return;
335         }
336
337         for (gl = n->state->channels; gl; gl = gl->next) {
338                 struct channel_state *ch = (struct channel_state *)gl->data;
339                 admin_out(h, "%s", ch->name);
340         }
341 }
342
343 #ifdef DEBUG
344 static void do_abort(admin_handle h, char **args, void *userdata)
345 {
346         abort();
347 }
348 #endif
349
350 static void handle_die(admin_handle h, char **args, void *userdata)
351 {
352         exit(0);
353 }
354
355 static GHashTable *markers = NULL;
356
357 static void repl_command(admin_handle h, char **args, void *userdata)
358 {
359         struct linestack_marker *lm;
360         struct network *n;
361         
362         n = admin_get_network(h);
363
364         lm = g_hash_table_lookup(markers, n);
365
366         if (n->linestack == NULL) {
367                 admin_out(h, "No backlog available. Perhaps the connection to the network is down?");
368                 return;
369         }
370
371         if(!args[1]) {
372                 admin_out(h, "Sending backlog for network '%s'", n->info.name);
373
374                 linestack_send(n->linestack, lm, NULL, admin_get_client(h),
375                                            TRUE, n->global->config->report_time);
376
377                 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
378
379                 return;
380         } 
381
382         /* Backlog for specific nick/channel */
383         admin_out(h, "Sending backlog for channel %s", args[1]);
384
385         linestack_send_object(n->linestack, args[1], lm, NULL, 
386                                                   admin_get_client(h), TRUE, 
387                                                   n->global->config->report_time);
388
389         g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
390 }
391
392 static void cmd_log_level(admin_handle h, char **args, void *userdata)
393 {
394         extern enum log_level current_log_level;
395         
396         if (args[1] == NULL) 
397                 admin_out(h, "Current log level: %d", current_log_level);
398         else {
399                 int x = atoi(args[1]);
400                 if (x < 0 || x > 5) 
401                         admin_out(h, "Invalid log level %d", x);
402                 else { 
403                         current_log_level = x;
404                         admin_out(h, "Log level changed to %d", x);
405                 }
406         }
407 }
408
409 static void handle_charset(admin_handle h, char **args, void *userdata)
410 {
411         struct client *c;
412
413         if (args[1] == NULL) {
414                 admin_out(h, "No charset specified");
415                 return;
416         }
417
418         c = admin_get_client(h);
419
420         if (!client_set_charset(c, args[1])) {
421                 admin_out(h, "Error setting charset: %s", args[1]);
422         }
423 }
424
425 static void cmd_echo(admin_handle h, char **args, void *userdata)
426 {
427         admin_out(h, "%s", args[1]);
428 }
429
430 static gint cmp_cmd(gconstpointer a, gconstpointer b)
431 {
432         const struct admin_command *cmda = a, *cmdb = b;
433
434         return g_strcasecmp(cmda->name, cmdb->name);
435 }
436
437 void register_admin_command(const struct admin_command *cmd)
438 {
439         admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
440         if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
441 }
442
443 void unregister_admin_command(const struct admin_command *cmd)
444 {
445         admin_commands = g_list_remove(admin_commands, cmd);
446 }
447
448 gboolean process_cmd(admin_handle h, const char *cmd)
449 {
450         char **args = NULL;
451         GList *gl;
452
453         if (!cmd) {
454                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
455                 return TRUE;
456         }
457
458         args = g_strsplit(cmd, " ", 0);
459
460         if (!args[0]) {
461                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
462                 return TRUE;
463         }
464
465         /* Ok, arguments are processed now. Execute the corresponding command */
466         for (gl = admin_commands; gl; gl = gl->next) {
467                 struct admin_command *cmd = (struct admin_command *)gl->data;
468                 if(!g_strcasecmp(cmd->name, args[0])) {
469                         cmd->handler(h, args, cmd->userdata);
470                         g_strfreev(args);
471                         return TRUE;
472                 }
473         }
474
475         admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
476
477         g_strfreev(args);
478
479         return TRUE;
480 }
481
482 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
483 {
484         int i;
485         char *tmp;
486         gboolean ret;
487         struct admin_handle ah;
488
489         if (l->args[cmdoffset] == NULL) {
490                 client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0], "Not enough parameters", NULL);
491                 return TRUE;
492         }
493
494         tmp = g_strdup(l->args[cmdoffset]);
495
496         /* Add everything after l->args[cmdoffset] to tmp */
497         for(i = cmdoffset+1; l->args[i]; i++) {
498                 char *oldtmp = tmp;
499                 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
500                 g_free(oldtmp);
501         }
502
503         ah.send_fn = privmsg_admin_out;
504         ah.client = c;
505         ah.network = c->network;
506         ah.global = c->network->global;
507         ret = process_cmd(&ah, tmp);
508
509         g_free(tmp);
510
511         return ret;
512 }
513
514 static gboolean admin_net_init(struct network *n)
515 {
516         char *hostmask;
517         char *nicks;
518
519         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->info.name);
520         
521         virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
522         virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL, 
523                 "CtrlProxy administration channel | Type `help' for more information",
524                                                           NULL);
525         nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
526
527         virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
528         g_free(nicks);
529         virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
530
531         g_free(hostmask);
532
533         return TRUE;
534 }
535
536 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
537 {
538         if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
539                 !g_strcasecmp(l->args[0], "NOTICE")) {
540                 struct admin_handle ah;
541
542                 if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
543                         virtual_network_recv_args(n, n->state->me.hostmask, l->args[0], l->args[1], NULL);
544                         return TRUE;
545                 }
546
547                 if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) && 
548                         g_strcasecmp(l->args[1], "ctrlproxy")) {
549                         virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
550                         return TRUE;
551                 }
552
553                 ah.send_fn = network_admin_out;
554                 ah.user_data = NULL;
555                 ah.client = c;
556                 ah.network = n;
557                 ah.global = n->global;
558
559                 return process_cmd(&ah, l->args[2]);
560         } else if (!g_strcasecmp(l->args[0], "ISON")) {
561                 int i;
562                 char *tmp;
563                 GList *gl = NULL;
564
565                 if (l->args[1] == NULL) {
566                         virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
567                         return TRUE;
568                 }
569
570                 for (i = 1; l->args[i]; i++) {
571                         if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
572                                 !g_strcasecmp(l->args[i], n->state->me.nick)) {
573                                 gl = g_list_append(gl, l->args[i]);
574                         }
575                 }
576                 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
577                 g_free(tmp);
578                 g_list_free(gl);
579                 return TRUE;
580         } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
581                 GList *gl = NULL;
582                 char *tmp;
583                 int i;
584
585                 if (l->args[1] == NULL) {
586                         virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
587                         return TRUE;
588                 }
589
590                 for (i = 1; l->args[i]; i++) {
591                         if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
592                                 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], get_my_hostname()));
593                         }
594                         if (!g_strcasecmp(l->args[i], n->state->me.nick)) {
595                                 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], n->state->me.hostname));
596                         }
597                 }
598
599                 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
600                 g_free(tmp);
601                 while (gl) {
602                         g_free(gl->data);
603                         gl = g_list_remove(gl, gl->data);
604                 }
605                 return TRUE;
606         } else if (!g_strcasecmp(l->args[0], "QUIT")) {
607                 return TRUE;
608         } else if (!g_strcasecmp(l->args[0], "MODE")) {
609                 /* FIXME: Do something here ? */
610                 return TRUE;
611         } else if (!g_strcasecmp(l->args[0], "WHO")) {
612                 if (!strcmp(l->args[1], ADMIN_CHANNEL) || 
613                         !strcmp(l->args[1], "ctrlproxy")) {
614                         virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
615                                                                           "ctrlproxy",
616                                                                           get_my_hostname(),
617                                                                           get_my_hostname(),
618                                                                           "ctrlproxy",
619                                                                           "H",
620                                                                           "0 CtrlProxy user",
621                                                                           NULL);
622                 }
623                 if (!strcmp(l->args[1], ADMIN_CHANNEL) ||
624                         !strcmp(l->args[1], n->state->me.nick)) {
625                         char *fullname = g_strdup_printf("0 %s", n->state->me.fullname);
626                         virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
627                                                                           n->state->me.username,
628                                                                           n->state->me.hostname,
629                                                                           get_my_hostname(),
630                                                                           n->state->me.nick,
631                                                                           "H",
632                                                                           fullname,
633                                                                           NULL);
634                         g_free(fullname);
635                 }
636
637                 virtual_network_recv_response(n, RPL_ENDOFWHO, l->args[1], "End of /WHO list.", NULL);
638
639                 return TRUE;
640         } else if (!g_strcasecmp(l->args[0], "JOIN")) {
641                 if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
642                         virtual_network_recv_response(n, ERR_NOSUCHCHANNEL, l->args[1], "No such channel", NULL);
643                 }
644                 return TRUE;
645         } else if (!g_strcasecmp(l->args[0], "PART")) {
646                 if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
647                         virtual_network_recv_response(n, ERR_NOTONCHANNEL, l->args[1], "You're not on that channel", NULL);
648                 } else {
649                         virtual_network_recv_args(n, n->state->me.hostmask, "PART", l->args[1], NULL);
650                         admin_net_init(n);
651                 }
652                 return TRUE;
653         } else if (!g_strcasecmp(l->args[0], "WHOIS")) {
654                 /* FIXME: Send something sensible */
655                 virtual_network_recv_response(n, RPL_ENDOFWHOIS, l->args[1], 
656                                                                           "End of /WHOIS list.", NULL);
657                 return TRUE;
658         } else if (!g_strcasecmp(l->args[0], "AWAY")) {
659                 if (l->args[1] != NULL && strcmp(l->args[1], "") != 0) {
660                         virtual_network_recv_response(n, RPL_NOWAWAY, "You are now marked as being away", NULL);
661                 } else {
662                         virtual_network_recv_response(n, RPL_UNAWAY, "You are no longer marked as being away", NULL);
663                 }
664                 return TRUE;
665         } else {
666                 virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
667                 log_global(LOG_TRACE, "Unhandled command `%s' to admin network", 
668                                    l->args[0]);
669                 return TRUE;
670         }
671 }
672
673 struct virtual_network_ops admin_network = {
674         "admin", admin_net_init, admin_to_server, NULL
675 };
676
677
678 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
679 {
680         extern struct global *my_global;
681         struct line *l;
682         char *tmp, *hostmask;
683         GList *gl;
684         static gboolean entered = FALSE;
685
686         if (!my_global || !my_global->config || 
687                 !my_global->config->admin_log) 
688                 return;
689
690         if (level < LOG_INFO)
691                 return;
692
693         if (entered)
694                 return; /* Prevent inifinite recursion.. */
695
696         entered = TRUE;
697
698         tmp = g_strdup_printf("%s%s%s%s%s%s", 
699                                                   data, 
700                                                   n?" (":"",
701                                                   n?n->info.name:"", 
702                                                   c?"/":"",
703                                                   c?c->description:"",
704                                                   n?")":"");
705
706         for (gl = my_global->networks; gl; gl = gl->next) {
707                 struct network *network = gl->data;
708
709                 if (network->connection.data.virtual.ops != &admin_network)
710                         continue;
711
712                 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->info.name);
713                 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL); 
714                 g_free(hostmask);
715                 
716                 virtual_network_recv_line(network, l);
717
718                 free_line(l);
719         }
720
721         g_free(tmp);
722
723         entered = FALSE;
724 }
725
726 const static struct admin_command builtin_commands[] = {
727         { "ADDNETWORK", add_network },
728         { "ADDSERVER", add_server },
729         { "BACKLOG", repl_command },
730         { "CONNECT", com_connect_network },
731         { "DELNETWORK", del_network },
732         { "ECHO", cmd_echo },
733         { "LOG_LEVEL", cmd_log_level },
734         { "NEXTSERVER", com_next_server },
735         { "CHARSET", handle_charset },
736         { "DIE", handle_die },
737         { "DISCONNECT", com_disconnect_network },
738         { "LISTNETWORKS", list_networks },
739         { "SAVECONFIG", com_save_config },
740         { "DETACH", detach_client },
741         { "HELP", cmd_help },
742         { "DUMPJOINEDCHANNELS", dump_joined_channels },
743 #ifdef DEBUG
744         { "ABORT", do_abort },
745 #endif
746         { NULL }
747 };
748
749 void init_admin(void) 
750 {
751         int i;
752         for(i = 0; builtin_commands[i].name; i++) {
753                 register_admin_command(&builtin_commands[i]);
754         }
755
756         register_virtual_network(&admin_network);
757
758         markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);
759 }