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->info.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->info.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 if (find_network(admin_get_global(h), args[1]) != NULL) {
121 admin_out(h, "Network with name `%s' already exists", args[1]);
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);
129 admin_out(h, "Network `%s' added. Use ADDSERVER to add a server to this network.", args[1]);
132 static void del_network (admin_handle h, char **args, void *userdata)
136 if (args[1] == NULL) {
137 admin_out(h, "Not enough parameters");
141 n = find_network(admin_get_global(h), args[1]);
143 admin_out(h, "No such network `%s'", args[1]);
147 disconnect_network(n);
151 admin_out(h, "Network `%s' deleted", args[1]);
154 static void add_server (admin_handle h, char **args, void *userdata)
157 struct tcp_server_config *s;
160 if(!args[1] || !args[2]) {
161 admin_out(h, "Not enough parameters");
165 n = find_network(admin_get_global(h), args[1]);
168 admin_out(h, "No such network '%s'", args[1]);
172 if (n->config->type != NETWORK_TCP) {
173 admin_out(h, "Not a TCP/IP network!");
177 s = g_new0(struct tcp_server_config, 1);
179 s->host = g_strdup(args[2]);
180 if ((t = strchr(s->host, ':'))) {
182 s->port = g_strdup(t+1);
184 s->port = g_strdup("6667");
187 s->password = args[3]?g_strdup(args[3]):NULL;
189 n->config->type_settings.tcp_servers = g_list_append(n->config->type_settings.tcp_servers, s);
191 admin_out(h, "Server added to `%s'", args[1]);
194 static void com_connect_network (admin_handle h, char **args, void *userdata)
198 admin_out(h, "No network specified");
202 s = find_network(admin_get_global(h), args[1]);
205 admin_out(h, "No such network `%s'", args[1]);
209 switch (s->connection.state) {
210 case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
211 admin_out(h, "Connecting to `%s'", args[1]);
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);
220 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
221 admin_out(h, "Connect to `%s' already in progress", args[1]);
223 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
224 admin_out(h, "Already connected to `%s'", args[1]);
229 static void com_disconnect_network (admin_handle h, char **args, void *userdata)
233 n = admin_get_network(h);
235 if (args[1] != NULL) {
236 n = find_network(admin_get_global(h), args[1]);
238 admin_out(h, "Can't find active network with that name");
243 if (n->connection.state == NETWORK_CONNECTION_STATE_NOT_CONNECTED) {
244 admin_out(h, "Already disconnected from `%s'", args[1]);
246 admin_out(h, "Disconnecting from `%s'", args[1]);
247 disconnect_network(n);
251 static void com_next_server (admin_handle h, char **args, void *userdata)
257 if(args[1] != NULL) {
259 n = find_network(admin_get_global(h), args[1]);
261 n = admin_get_network(h);
265 admin_out(h, "%s: Not connected", name);
267 admin_out(h, "%s: Reconnecting", name);
268 disconnect_network(n);
269 network_select_next_server(n);
274 static void com_save_config (admin_handle h, char **args, void *userdata)
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);
285 static void list_networks(admin_handle h, char **args, void *userdata)
288 for (gl = admin_get_global(h)->networks; gl; gl = gl->next) {
289 struct network *n = gl->data;
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);
297 admin_out(h, "%s: Not connected", n->info.name);
299 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
300 admin_out(h, "%s: Reconnecting", n->info.name);
302 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
303 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
304 admin_out(h, "%s: connected", n->info.name);
310 static void detach_client(admin_handle h, char **args, void *userdata)
312 struct client *c = admin_get_client(h);
314 disconnect_client(c, "Client exiting");
317 static void dump_joined_channels(admin_handle h, char **args, void *userdata)
322 if (args[1] != NULL) {
323 n = find_network(admin_get_global(h), args[1]);
325 admin_out(h, "Can't find network '%s'", args[1]);
329 n = admin_get_network(h);
333 admin_out(h, "Network '%s' not connected", n->info.name);
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);
344 static void do_abort(admin_handle h, char **args, void *userdata)
350 static void handle_die(admin_handle h, char **args, void *userdata)
355 static GHashTable *markers = NULL;
357 static void repl_command(admin_handle h, char **args, void *userdata)
359 struct linestack_marker *lm;
362 n = admin_get_network(h);
364 lm = g_hash_table_lookup(markers, n);
366 if (n->linestack == NULL) {
367 admin_out(h, "No backlog available. Perhaps the connection to the network is down?");
372 admin_out(h, "Sending backlog for network '%s'", n->info.name);
374 if (n->global->config->report_time)
375 linestack_send_timed(n->linestack, lm, NULL, admin_get_client(h));
377 linestack_send(n->linestack, lm, NULL, admin_get_client(h));
379 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
384 /* Backlog for specific nick/channel */
385 admin_out(h, "Sending backlog for channel %s", args[1]);
387 if (n->global->config->report_time)
388 linestack_send_object_timed(n->linestack, args[1], lm, NULL,
389 admin_get_client(h));
391 linestack_send_object(n->linestack, args[1], lm, NULL,
392 admin_get_client(h));
394 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
397 static void cmd_log_level(admin_handle h, char **args, void *userdata)
399 extern enum log_level current_log_level;
402 admin_out(h, "Current log level: %d", current_log_level);
404 int x = atoi(args[1]);
406 admin_out(h, "Invalid log level %d", x);
408 current_log_level = x;
409 admin_out(h, "Log level changed to %d", x);
414 static void handle_charset(admin_handle h, char **args, void *userdata)
418 if (args[1] == NULL) {
419 admin_out(h, "No charset specified");
423 c = admin_get_client(h);
425 if (!client_set_charset(c, args[1])) {
426 admin_out(h, "Error setting charset: %s", args[1]);
430 static void cmd_echo(admin_handle h, char **args, void *userdata)
432 admin_out(h, "%s", args[1]);
435 static gint cmp_cmd(gconstpointer a, gconstpointer b)
437 const struct admin_command *cmda = a, *cmdb = b;
439 return g_strcasecmp(cmda->name, cmdb->name);
442 void register_admin_command(const struct admin_command *cmd)
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);
448 void unregister_admin_command(const struct admin_command *cmd)
450 admin_commands = g_list_remove(admin_commands, cmd);
453 gboolean process_cmd(admin_handle h, const char *cmd)
459 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
463 args = g_strsplit(cmd, " ", 0);
466 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
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);
480 admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
487 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
492 struct admin_handle ah;
494 if (l->args[cmdoffset] == NULL) {
495 client_send_response(c, ERR_NEEDMOREPARAMS, l->args[0], "Not enough parameters", NULL);
499 tmp = g_strdup(l->args[cmdoffset]);
501 /* Add everything after l->args[cmdoffset] to tmp */
502 for(i = cmdoffset+1; l->args[i]; i++) {
504 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
508 ah.send_fn = privmsg_admin_out;
510 ah.network = c->network;
511 ah.global = c->network->global;
512 ret = process_cmd(&ah, tmp);
519 static gboolean admin_net_init(struct network *n)
524 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->info.name);
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",
530 nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
532 virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
534 virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
541 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
543 if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
544 !g_strcasecmp(l->args[0], "NOTICE")) {
545 struct admin_handle ah;
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);
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);
558 ah.send_fn = network_admin_out;
562 ah.global = n->global;
564 return process_cmd(&ah, l->args[2]);
565 } else if (!g_strcasecmp(l->args[0], "ISON")) {
570 if (l->args[1] == NULL) {
571 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
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]);
581 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
585 } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
590 if (l->args[1] == NULL) {
591 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
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()));
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));
604 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
608 gl = g_list_remove(gl, gl->data);
611 } else if (!g_strcasecmp(l->args[0], "QUIT")) {
613 } else if (!g_strcasecmp(l->args[0], "MODE")) {
614 /* FIXME: Do something here ? */
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,
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,
642 virtual_network_recv_response(n, RPL_ENDOFWHO, l->args[1], "End of /WHO list.", NULL);
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);
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);
654 virtual_network_recv_args(n, n->state->me.hostmask, "PART", l->args[1], NULL);
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);
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);
667 virtual_network_recv_response(n, RPL_UNAWAY, "You are no longer marked as being away", NULL);
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",
678 struct virtual_network_ops admin_network = {
679 "admin", admin_net_init, admin_to_server, NULL
683 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
685 extern struct global *my_global;
687 char *tmp, *hostmask;
689 static gboolean entered = FALSE;
691 if (!my_global || !my_global->config ||
692 !my_global->config->admin_log)
695 if (level < LOG_INFO)
699 return; /* Prevent inifinite recursion.. */
703 tmp = g_strdup_printf("%s%s%s%s%s%s",
711 for (gl = my_global->networks; gl; gl = gl->next) {
712 struct network *network = gl->data;
714 if (network->connection.data.virtual.ops != &admin_network)
717 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->info.name);
718 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL);
721 virtual_network_recv_line(network, l);
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 },
749 { "ABORT", do_abort },
754 void init_admin(void)
757 for(i = 0; builtin_commands[i].name; i++) {
758 register_admin_command(&builtin_commands[i]);
761 register_virtual_network(&admin_network);
763 markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);