2 ctrlproxy: A modular IRC proxy
3 admin: module for remote administration.
5 (c) 2003-2006 Jelmer Vernooij <jelmer@nl.linux.org>
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.
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.
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.
22 #include "internals.h"
30 #define ADMIN_CHANNEL "#ctrlproxy"
32 GList *admin_commands = NULL;
33 guint longest_command = 0;
35 static void privmsg_admin_out(admin_handle h, const char *data)
37 struct client *c = h->client;
41 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
42 if (c->network->state) nick = c->network->state->me.nick;
43 client_send_args_ex(c, hostmask, "NOTICE", nick, data, NULL);
48 static void network_admin_out(admin_handle h, const char *data)
50 struct client *c = h->client;
53 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
54 virtual_network_recv_args(c->network, hostmask, "PRIVMSG", ADMIN_CHANNEL,
60 static void cmd_help(admin_handle h, char **args, void *userdata)
64 s = help_get(help, args[1] != NULL?args[1]:"index");
68 admin_out(h, "Sorry, help not available");
70 admin_out(h, "Sorry, no help for %s available", args[1]);
74 while (strncmp(s, "%\n", 2) != 0) {
76 admin_out(h, "%s", tmp = g_strndup(s, strchr(s, '\n')-s));
79 s = strchr(s, '\n')+1;
83 struct client *admin_get_client(admin_handle h)
88 struct global *admin_get_global(admin_handle h)
93 struct network *admin_get_network(admin_handle h)
98 void admin_out(admin_handle h, const char *fmt, ...)
103 msg = g_strdup_vprintf(fmt, ap);
111 static void add_network (admin_handle h, char **args, void *userdata)
113 struct network_config *nc;
115 if (args[1] == NULL) {
116 admin_out(h, "No name specified");
120 nc = network_config_init(admin_get_global(h)->config);
121 g_free(nc->name); nc->name = g_strdup(args[1]);
122 load_network(admin_get_global(h), nc);
124 admin_out(h, "Network `%s' added. Use ADDSERVER to add a server to this network.", args[1]);
127 static void del_network (admin_handle h, char **args, void *userdata)
131 if (args[1] == NULL) {
132 admin_out(h, "Not enough parameters");
136 n = find_network(admin_get_global(h), args[1]);
138 admin_out(h, "No such network `%s'", args[1]);
142 disconnect_network(n);
144 admin_out(h, "Network `%s' deleted", args[1]);
147 static void add_server (admin_handle h, char **args, void *userdata)
150 struct tcp_server_config *s;
153 if(!args[1] || !args[2]) {
154 admin_out(h, "Not enough parameters");
158 n = find_network(admin_get_global(h), args[1]);
161 admin_out(h, "No such network '%s'", args[1]);
165 if (n->config->type != NETWORK_TCP) {
166 admin_out(h, "Not a TCP/IP network!");
170 s = g_new0(struct tcp_server_config, 1);
172 s->host = g_strdup(args[2]);
173 if ((t = strchr(s->host, ':'))) {
175 s->port = g_strdup(t+1);
177 s->port = g_strdup("6667");
180 s->password = args[3]?g_strdup(args[3]):NULL;
182 n->config->type_settings.tcp_servers = g_list_append(n->config->type_settings.tcp_servers, s);
184 admin_out(h, "Server added to `%s'", args[1]);
187 static void com_connect_network (admin_handle h, char **args, void *userdata)
191 admin_out(h, "No network specified");
195 s = find_network(admin_get_global(h), args[1]);
198 admin_out(h, "No such network `%s'", args[1]);
202 switch (s->connection.state) {
203 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
204 admin_out(h, "Connecting to `%s'", args[1]);
207 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
208 admin_out(h, "Forcing reconnect to `%s'", args[1]);
209 disconnect_network(s);
210 network_select_next_server(s);
213 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
214 admin_out(h, "Connect to `%s' already in progress", args[1]);
216 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
217 admin_out(h, "Already connected to `%s'", args[1]);
222 static void com_disconnect_network (admin_handle h, char **args, void *userdata)
226 n = admin_get_network(h);
228 if (args[1] != NULL) {
229 n = find_network(admin_get_global(h), args[1]);
231 admin_out(h, "Can't find active network with that name");
236 if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
237 admin_out(h, "Already disconnected from `%s'", args[1]);
239 admin_out(h, "Disconnecting from `%s'", args[1]);
240 disconnect_network(n);
244 static void com_next_server (admin_handle h, char **args, void *userdata)
250 if(args[1] != NULL) {
252 n = find_network(admin_get_global(h), args[1]);
254 n = admin_get_network(h);
258 admin_out(h, "%s: Not connected", name);
260 admin_out(h, "%s: Reconnecting", name);
261 disconnect_network(n);
262 network_select_next_server(n);
267 static void com_save_config (admin_handle h, char **args, void *userdata)
270 global_update_config(admin_get_global(h));
271 adm_dir = args[1]?args[1]:admin_get_global(h)->config->config_dir;
272 save_configuration(admin_get_global(h)->config, adm_dir);
273 admin_out(h, "Configuration saved in %s", adm_dir);
278 static void list_networks(admin_handle h, char **args, void *userdata)
281 for (gl = admin_get_global(h)->networks; gl; gl = gl->next) {
282 struct network *n = gl->data;
284 switch (n->connection.state) {
285 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
286 if (n->connection.data.tcp.last_disconnect_reason)
287 admin_out(h, "%s: Not connected: %s", n->name,
288 n->connection.data.tcp.last_disconnect_reason);
290 admin_out(h, "%s: Not connected", n->name);
292 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
293 admin_out(h, "%s: Reconnecting", n->name);
295 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
296 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
297 admin_out(h, "%s: connected", n->name);
303 static void detach_client(admin_handle h, char **args, void *userdata)
305 struct client *c = admin_get_client(h);
307 disconnect_client(c, "Client exiting");
310 static void dump_joined_channels(admin_handle h, char **args, void *userdata)
315 if (args[1] != NULL) {
316 n = find_network(admin_get_global(h), args[1]);
318 admin_out(h, "Can't find network '%s'", args[1]);
322 n = admin_get_network(h);
326 admin_out(h, "Network '%s' not connected", n->name);
330 for (gl = n->state->channels; gl; gl = gl->next) {
331 struct channel_state *ch = (struct channel_state *)gl->data;
332 admin_out(h, "%s", ch->name);
337 static void do_abort(admin_handle h, char **args, void *userdata)
343 static void handle_die(admin_handle h, char **args, void *userdata)
348 static GHashTable *markers = NULL;
350 static void repl_command(admin_handle h, char **args, void *userdata)
352 struct linestack_marker *lm;
355 n = admin_get_network(h);
357 lm = g_hash_table_lookup(markers, n);
359 if (n->linestack == NULL) {
360 admin_out(h, "No backlog available. Perhaps the connection to the network is down?");
365 admin_out(h, "Sending backlog for network '%s'", n->name);
367 linestack_send(n->linestack, lm, NULL, admin_get_client(h));
369 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
374 /* Backlog for specific nick/channel */
375 admin_out(h, "Sending backlog for channel %s", args[1]);
377 if (n->global->config->report_time)
378 linestack_send_object_timed(n->linestack, args[1], lm, NULL,
379 admin_get_client(h));
381 linestack_send_object(n->linestack, args[1], lm, NULL,
382 admin_get_client(h));
384 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
387 static void cmd_log_level(admin_handle h, char **args, void *userdata)
389 extern enum log_level current_log_level;
392 admin_out(h, "Current log level: %d", current_log_level);
394 int x = atoi(args[1]);
396 admin_out(h, "Invalid log level %d", x);
398 current_log_level = x;
399 admin_out(h, "Log level changed to %d", x);
404 static void handle_charset(admin_handle h, char **args, void *userdata)
408 if (args[1] == NULL) {
409 admin_out(h, "No charset specified");
413 c = admin_get_client(h);
415 if (!client_set_charset(c, args[1])) {
416 admin_out(h, "Error setting charset: %s", args[1]);
420 static void cmd_echo(admin_handle h, char **args, void *userdata)
422 admin_out(h, "%s", args[1]);
425 static gint cmp_cmd(gconstpointer a, gconstpointer b)
427 const struct admin_command *cmda = a, *cmdb = b;
429 return g_strcasecmp(cmda->name, cmdb->name);
432 void register_admin_command(const struct admin_command *cmd)
434 admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
435 if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
438 void unregister_admin_command(const struct admin_command *cmd)
440 admin_commands = g_list_remove(admin_commands, cmd);
443 gboolean process_cmd(admin_handle h, const char *cmd)
449 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
453 args = g_strsplit(cmd, " ", 0);
456 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
460 /* Ok, arguments are processed now. Execute the corresponding command */
461 for (gl = admin_commands; gl; gl = gl->next) {
462 struct admin_command *cmd = (struct admin_command *)gl->data;
463 if(!g_strcasecmp(cmd->name, args[0])) {
464 cmd->handler(h, args, cmd->userdata);
470 admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
477 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
480 char *tmp = g_strdup(l->args[cmdoffset]);
482 struct admin_handle ah;
484 /* Add everything after l->args[cmdoffset] to tmp */
485 for(i = cmdoffset+1; l->args[i]; i++) {
487 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
491 ah.send_fn = privmsg_admin_out;
493 ah.network = c->network;
494 ah.global = c->network->global;
495 ret = process_cmd(&ah, tmp);
502 static gboolean admin_net_init(struct network *n)
507 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->name);
509 virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
510 virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL,
511 "CtrlProxy administration channel | Type `help' for more information",
513 nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
515 virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
517 virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
524 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
526 if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
527 !g_strcasecmp(l->args[0], "NOTICE")) {
528 struct admin_handle ah;
530 if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
531 virtual_network_recv_args(n, n->state->me.hostmask, l->args[0], l->args[1], NULL);
535 if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) &&
536 g_strcasecmp(l->args[1], "ctrlproxy")) {
537 virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
541 ah.send_fn = network_admin_out;
545 ah.global = n->global;
547 return process_cmd(&ah, l->args[2]);
548 } else if (!g_strcasecmp(l->args[0], "ISON")) {
553 if (l->args[1] == NULL) {
554 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
558 for (i = 1; l->args[i]; i++) {
559 if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
560 !g_strcasecmp(l->args[i], n->state->me.nick)) {
561 gl = g_list_append(gl, l->args[i]);
564 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
568 } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
573 if (l->args[1] == NULL) {
574 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
578 for (i = 1; l->args[i]; i++) {
579 if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
580 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], get_my_hostname()));
582 if (!g_strcasecmp(l->args[i], n->state->me.nick)) {
583 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], n->state->me.hostname));
587 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
591 gl = g_list_remove(gl, gl->data);
594 } else if (!g_strcasecmp(l->args[0], "QUIT")) {
596 } else if (!g_strcasecmp(l->args[0], "MODE")) {
597 /* FIXME: Do something here ? */
600 virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
601 log_global(LOG_TRACE, "Unhandled command `%s' to admin network",
607 struct virtual_network_ops admin_network = {
608 "admin", admin_net_init, admin_to_server, NULL
612 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
614 extern struct global *my_global;
616 char *tmp, *hostmask;
618 static gboolean entered = FALSE;
620 if (!my_global || !my_global->config ||
621 !my_global->config->admin_log)
624 if (level < LOG_INFO)
628 return; /* Prevent inifinite recursion.. */
632 tmp = g_strdup_printf("%s%s%s%s%s%s",
640 for (gl = my_global->networks; gl; gl = gl->next) {
641 struct network *network = gl->data;
643 if (network->connection.data.virtual.ops != &admin_network)
646 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->name);
647 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL);
650 virtual_network_recv_line(network, l);
660 const static struct admin_command builtin_commands[] = {
661 { "ADDNETWORK", add_network },
662 { "ADDSERVER", add_server },
663 { "BACKLOG", repl_command },
664 { "CONNECT", com_connect_network },
665 { "DELNETWORK", del_network },
666 { "ECHO", cmd_echo },
667 { "LOG_LEVEL", cmd_log_level },
668 { "NEXTSERVER", com_next_server },
669 { "CHARSET", handle_charset },
670 { "DIE", handle_die },
671 { "DISCONNECT", com_disconnect_network },
672 { "LISTNETWORKS", list_networks },
673 { "SAVECONFIG", com_save_config },
674 { "DETACH", detach_client },
675 { "HELP", cmd_help },
676 { "DUMPJOINEDCHANNELS", dump_joined_channels },
678 { "ABORT", do_abort },
683 void init_admin(void)
686 for(i = 0; builtin_commands[i].name; i++) {
687 register_admin_command(&builtin_commands[i]);
690 register_virtual_network(&admin_network);
692 markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);