68235a7fef4c75e0ad334ab11eacc4bf2717e909
[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                 if (n->global->config->report_time)
375                         linestack_send_timed(n->linestack, lm, NULL, admin_get_client(h));
376                 else
377                         linestack_send(n->linestack, lm, NULL, admin_get_client(h));
378
379                 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
380
381                 return;
382         } 
383
384         /* Backlog for specific nick/channel */
385         admin_out(h, "Sending backlog for channel %s", args[1]);
386
387         if (n->global->config->report_time)
388                 linestack_send_object_timed(n->linestack, args[1], lm, NULL, 
389                                                                         admin_get_client(h));
390         else
391                 linestack_send_object(n->linestack, args[1], lm, NULL, 
392                                                           admin_get_client(h));
393
394         g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
395 }
396
397 static void cmd_log_level(admin_handle h, char **args, void *userdata)
398 {
399         extern enum log_level current_log_level;
400         
401         if (args[1] == NULL) 
402                 admin_out(h, "Current log level: %d", current_log_level);
403         else {
404                 int x = atoi(args[1]);
405                 if (x < 0 || x > 5) 
406                         admin_out(h, "Invalid log level %d", x);
407                 else { 
408                         current_log_level = x;
409                         admin_out(h, "Log level changed to %d", x);
410                 }
411         }
412 }
413
414 static void handle_charset(admin_handle h, char **args, void *userdata)
415 {
416         struct client *c;
417
418         if (args[1] == NULL) {
419                 admin_out(h, "No charset specified");
420                 return;
421         }
422
423         c = admin_get_client(h);
424
425         if (!client_set_charset(c, args[1])) {
426                 admin_out(h, "Error setting charset: %s", args[1]);
427         }
428 }
429
430 static void cmd_echo(admin_handle h, char **args, void *userdata)
431 {
432         admin_out(h, "%s", args[1]);
433 }
434
435 static gint cmp_cmd(gconstpointer a, gconstpointer b)
436 {
437         const struct admin_command *cmda = a, *cmdb = b;
438
439         return g_strcasecmp(cmda->name, cmdb->name);
440 }
441
442 void register_admin_command(const struct admin_command *cmd)
443 {
444         admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
445         if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
446 }
447
448 void unregister_admin_command(const struct admin_command *cmd)
449 {
450         admin_commands = g_list_remove(admin_commands, cmd);
451 }
452
453 gboolean process_cmd(admin_handle h, const char *cmd)
454 {
455         char **args = NULL;
456         GList *gl;
457
458         if (!cmd) {
459                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
460                 return TRUE;
461         }
462
463         args = g_strsplit(cmd, " ", 0);
464
465         if (!args[0]) {
466                 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
467                 return TRUE;
468         }
469
470         /* Ok, arguments are processed now. Execute the corresponding command */
471         for (gl = admin_commands; gl; gl = gl->next) {
472                 struct admin_command *cmd = (struct admin_command *)gl->data;
473                 if(!g_strcasecmp(cmd->name, args[0])) {
474                         cmd->handler(h, args, cmd->userdata);
475                         g_strfreev(args);
476                         return TRUE;
477                 }
478         }
479
480         admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
481
482         g_strfreev(args);
483
484         return TRUE;
485 }
486
487 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
488 {
489         int i;
490         char *tmp;
491         gboolean ret;
492         struct admin_handle ah;
493
494         if (l->args[cmdoffset] == NULL) {
495                 client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0], "Not enough parameters", NULL);
496                 return TRUE;
497         }
498
499         tmp = g_strdup(l->args[cmdoffset]);
500
501         /* Add everything after l->args[cmdoffset] to tmp */
502         for(i = cmdoffset+1; l->args[i]; i++) {
503                 char *oldtmp = tmp;
504                 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
505                 g_free(oldtmp);
506         }
507
508         ah.send_fn = privmsg_admin_out;
509         ah.client = c;
510         ah.network = c->network;
511         ah.global = c->network->global;
512         ret = process_cmd(&ah, tmp);
513
514         g_free(tmp);
515
516         return ret;
517 }
518
519 static gboolean admin_net_init(struct network *n)
520 {
521         char *hostmask;
522         char *nicks;
523
524         hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->info.name);
525         
526         virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
527         virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL, 
528                 "CtrlProxy administration channel | Type `help' for more information",
529                                                           NULL);
530         nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
531
532         virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
533         g_free(nicks);
534         virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
535
536         g_free(hostmask);
537
538         return TRUE;
539 }
540
541 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
542 {
543         if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
544                 !g_strcasecmp(l->args[0], "NOTICE")) {
545                 struct admin_handle ah;
546
547                 if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
548                         virtual_network_recv_args(n, n->state->me.hostmask, l->args[0], l->args[1], NULL);
549                         return TRUE;
550                 }
551
552                 if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) && 
553                         g_strcasecmp(l->args[1], "ctrlproxy")) {
554                         virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
555                         return TRUE;
556                 }
557
558                 ah.send_fn = network_admin_out;
559                 ah.user_data = NULL;
560                 ah.client = c;
561                 ah.network = n;
562                 ah.global = n->global;
563
564                 return process_cmd(&ah, l->args[2]);
565         } else if (!g_strcasecmp(l->args[0], "ISON")) {
566                 int i;
567                 char *tmp;
568                 GList *gl = NULL;
569
570                 if (l->args[1] == NULL) {
571                         virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
572                         return TRUE;
573                 }
574
575                 for (i = 1; l->args[i]; i++) {
576                         if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
577                                 !g_strcasecmp(l->args[i], n->state->me.nick)) {
578                                 gl = g_list_append(gl, l->args[i]);
579                         }
580                 }
581                 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
582                 g_free(tmp);
583                 g_list_free(gl);
584                 return TRUE;
585         } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
586                 GList *gl = NULL;
587                 char *tmp;
588                 int i;
589
590                 if (l->args[1] == NULL) {
591                         virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
592                         return TRUE;
593                 }
594
595                 for (i = 1; l->args[i]; i++) {
596                         if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
597                                 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], get_my_hostname()));
598                         }
599                         if (!g_strcasecmp(l->args[i], n->state->me.nick)) {
600                                 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], n->state->me.hostname));
601                         }
602                 }
603
604                 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
605                 g_free(tmp);
606                 while (gl) {
607                         g_free(gl->data);
608                         gl = g_list_remove(gl, gl->data);
609                 }
610                 return TRUE;
611         } else if (!g_strcasecmp(l->args[0], "QUIT")) {
612                 return TRUE;
613         } else if (!g_strcasecmp(l->args[0], "MODE")) {
614                 /* FIXME: Do something here ? */
615                 return TRUE;
616         } else if (!g_strcasecmp(l->args[0], "WHO")) {
617                 if (!strcmp(l->args[1], ADMIN_CHANNEL) || 
618                         !strcmp(l->args[1], "ctrlproxy")) {
619                         virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
620                                                                           "ctrlproxy",
621                                                                           get_my_hostname(),
622                                                                           get_my_hostname(),
623                                                                           "ctrlproxy",
624                                                                           "H",
625                                                                           "0 CtrlProxy user",
626                                                                           NULL);
627                 }
628                 if (!strcmp(l->args[1], ADMIN_CHANNEL) ||
629                         !strcmp(l->args[1], n->state->me.nick)) {
630                         char *fullname = g_strdup_printf("0 %s", n->state->me.fullname);
631                         virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
632                                                                           n->state->me.username,
633                                                                           n->state->me.hostname,
634                                                                           get_my_hostname(),
635                                                                           n->state->me.nick,
636                                                                           "H",
637                                                                           fullname,
638                                                                           NULL);
639                         g_free(fullname);
640                 }
641
642                 virtual_network_recv_response(n, RPL_ENDOFWHO, l->args[1], "End of /WHO list.", NULL);
643
644                 return TRUE;
645         } else if (!g_strcasecmp(l->args[0], "JOIN")) {
646                 if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
647                         virtual_network_recv_response(n, ERR_NOSUCHCHANNEL, l->args[1], "No such channel", NULL);
648                 }
649                 return TRUE;
650         } else if (!g_strcasecmp(l->args[0], "PART")) {
651                 if (strcmp(l->args[1], ADMIN_CHANNEL) != 0) {
652                         virtual_network_recv_response(n, ERR_NOTONCHANNEL, l->args[1], "You're not on that channel", NULL);
653                 } else {
654                         virtual_network_recv_args(n, n->state->me.hostmask, "PART", l->args[1], NULL);
655                         admin_net_init(n);
656                 }
657                 return TRUE;
658         } else if (!g_strcasecmp(l->args[0], "WHOIS")) {
659                 /* FIXME: Send something sensible */
660                 virtual_network_recv_response(n, RPL_ENDOFWHOIS, l->args[1], 
661                                                                           "End of /WHOIS list.", NULL);
662                 return TRUE;
663         } else if (!g_strcasecmp(l->args[0], "AWAY")) {
664                 if (l->args[1] != NULL && strcmp(l->args[1], "") != 0) {
665                         virtual_network_recv_response(n, RPL_NOWAWAY, "You are now marked as being away", NULL);
666                 } else {
667                         virtual_network_recv_response(n, RPL_UNAWAY, "You are no longer marked as being away", NULL);
668                 }
669                 return TRUE;
670         } else {
671                 virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
672                 log_global(LOG_TRACE, "Unhandled command `%s' to admin network", 
673                                    l->args[0]);
674                 return TRUE;
675         }
676 }
677
678 struct virtual_network_ops admin_network = {
679         "admin", admin_net_init, admin_to_server, NULL
680 };
681
682
683 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
684 {
685         extern struct global *my_global;
686         struct line *l;
687         char *tmp, *hostmask;
688         GList *gl;
689         static gboolean entered = FALSE;
690
691         if (!my_global || !my_global->config || 
692                 !my_global->config->admin_log) 
693                 return;
694
695         if (level < LOG_INFO)
696                 return;
697
698         if (entered)
699                 return; /* Prevent inifinite recursion.. */
700
701         entered = TRUE;
702
703         tmp = g_strdup_printf("%s%s%s%s%s%s", 
704                                                   data, 
705                                                   n?" (":"",
706                                                   n?n->info.name:"", 
707                                                   c?"/":"",
708                                                   c?c->description:"",
709                                                   n?")":"");
710
711         for (gl = my_global->networks; gl; gl = gl->next) {
712                 struct network *network = gl->data;
713
714                 if (network->connection.data.virtual.ops != &admin_network)
715                         continue;
716
717                 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->info.name);
718                 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL); 
719                 g_free(hostmask);
720                 
721                 virtual_network_recv_line(network, l);
722
723                 free_line(l);
724         }
725
726         g_free(tmp);
727
728         entered = FALSE;
729 }
730
731 const static struct admin_command builtin_commands[] = {
732         { "ADDNETWORK", add_network },
733         { "ADDSERVER", add_server },
734         { "BACKLOG", repl_command },
735         { "CONNECT", com_connect_network },
736         { "DELNETWORK", del_network },
737         { "ECHO", cmd_echo },
738         { "LOG_LEVEL", cmd_log_level },
739         { "NEXTSERVER", com_next_server },
740         { "CHARSET", handle_charset },
741         { "DIE", handle_die },
742         { "DISCONNECT", com_disconnect_network },
743         { "LISTNETWORKS", list_networks },
744         { "SAVECONFIG", com_save_config },
745         { "DETACH", detach_client },
746         { "HELP", cmd_help },
747         { "DUMPJOINEDCHANNELS", dump_joined_channels },
748 #ifdef DEBUG
749         { "ABORT", do_abort },
750 #endif
751         { NULL }
752 };
753
754 void init_admin(void) 
755 {
756         int i;
757         for(i = 0; builtin_commands[i].name; i++) {
758                 register_admin_command(&builtin_commands[i]);
759         }
760
761         register_virtual_network(&admin_network);
762
763         markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);
764 }