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 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->info.name,
288 n->connection.data.tcp.last_disconnect_reason);
290 admin_out(h, "%s: Not connected", n->info.name);
292 case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
293 admin_out(h, "%s: Reconnecting", n->info.name);
295 case NETWORK_CONNECTION_STATE_LOGIN_SENT:
296 case NETWORK_CONNECTION_STATE_MOTD_RECVD:
297 admin_out(h, "%s: connected", n->info.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->info.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->info.name);
367 if (n->global->config->report_time)
368 linestack_send_timed(n->linestack, lm, NULL, admin_get_client(h));
370 linestack_send(n->linestack, lm, NULL, admin_get_client(h));
372 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
377 /* Backlog for specific nick/channel */
378 admin_out(h, "Sending backlog for channel %s", args[1]);
380 if (n->global->config->report_time)
381 linestack_send_object_timed(n->linestack, args[1], lm, NULL,
382 admin_get_client(h));
384 linestack_send_object(n->linestack, args[1], lm, NULL,
385 admin_get_client(h));
387 g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
390 static void cmd_log_level(admin_handle h, char **args, void *userdata)
392 extern enum log_level current_log_level;
395 admin_out(h, "Current log level: %d", current_log_level);
397 int x = atoi(args[1]);
399 admin_out(h, "Invalid log level %d", x);
401 current_log_level = x;
402 admin_out(h, "Log level changed to %d", x);
407 static void handle_charset(admin_handle h, char **args, void *userdata)
411 if (args[1] == NULL) {
412 admin_out(h, "No charset specified");
416 c = admin_get_client(h);
418 if (!client_set_charset(c, args[1])) {
419 admin_out(h, "Error setting charset: %s", args[1]);
423 static void cmd_echo(admin_handle h, char **args, void *userdata)
425 admin_out(h, "%s", args[1]);
428 static gint cmp_cmd(gconstpointer a, gconstpointer b)
430 const struct admin_command *cmda = a, *cmdb = b;
432 return g_strcasecmp(cmda->name, cmdb->name);
435 void register_admin_command(const struct admin_command *cmd)
437 admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
438 if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
441 void unregister_admin_command(const struct admin_command *cmd)
443 admin_commands = g_list_remove(admin_commands, cmd);
446 gboolean process_cmd(admin_handle h, const char *cmd)
452 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
456 args = g_strsplit(cmd, " ", 0);
459 admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
463 /* Ok, arguments are processed now. Execute the corresponding command */
464 for (gl = admin_commands; gl; gl = gl->next) {
465 struct admin_command *cmd = (struct admin_command *)gl->data;
466 if(!g_strcasecmp(cmd->name, args[0])) {
467 cmd->handler(h, args, cmd->userdata);
473 admin_out(h, "Can't find command '%s'. Type 'help' for a list of available commands. ", args[0]);
480 gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
483 char *tmp = g_strdup(l->args[cmdoffset]);
485 struct admin_handle ah;
487 /* Add everything after l->args[cmdoffset] to tmp */
488 for(i = cmdoffset+1; l->args[i]; i++) {
490 tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
494 ah.send_fn = privmsg_admin_out;
496 ah.network = c->network;
497 ah.global = c->network->global;
498 ret = process_cmd(&ah, tmp);
505 static gboolean admin_net_init(struct network *n)
510 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->info.name);
512 virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
513 virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL,
514 "CtrlProxy administration channel | Type `help' for more information",
516 nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
518 virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
520 virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
527 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
529 if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
530 !g_strcasecmp(l->args[0], "NOTICE")) {
531 struct admin_handle ah;
533 if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
534 virtual_network_recv_args(n, n->state->me.hostmask, l->args[0], l->args[1], NULL);
538 if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) &&
539 g_strcasecmp(l->args[1], "ctrlproxy")) {
540 virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
544 ah.send_fn = network_admin_out;
548 ah.global = n->global;
550 return process_cmd(&ah, l->args[2]);
551 } else if (!g_strcasecmp(l->args[0], "ISON")) {
556 if (l->args[1] == NULL) {
557 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
561 for (i = 1; l->args[i]; i++) {
562 if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
563 !g_strcasecmp(l->args[i], n->state->me.nick)) {
564 gl = g_list_append(gl, l->args[i]);
567 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
571 } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
576 if (l->args[1] == NULL) {
577 virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
581 for (i = 1; l->args[i]; i++) {
582 if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
583 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], get_my_hostname()));
585 if (!g_strcasecmp(l->args[i], n->state->me.nick)) {
586 gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], n->state->me.hostname));
590 virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
594 gl = g_list_remove(gl, gl->data);
597 } else if (!g_strcasecmp(l->args[0], "QUIT")) {
599 } else if (!g_strcasecmp(l->args[0], "MODE")) {
600 /* FIXME: Do something here ? */
603 virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
604 log_global(LOG_TRACE, "Unhandled command `%s' to admin network",
610 struct virtual_network_ops admin_network = {
611 "admin", admin_net_init, admin_to_server, NULL
615 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
617 extern struct global *my_global;
619 char *tmp, *hostmask;
621 static gboolean entered = FALSE;
623 if (!my_global || !my_global->config ||
624 !my_global->config->admin_log)
627 if (level < LOG_INFO)
631 return; /* Prevent inifinite recursion.. */
635 tmp = g_strdup_printf("%s%s%s%s%s%s",
643 for (gl = my_global->networks; gl; gl = gl->next) {
644 struct network *network = gl->data;
646 if (network->connection.data.virtual.ops != &admin_network)
649 hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->info.name);
650 l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL);
653 virtual_network_recv_line(network, l);
663 const static struct admin_command builtin_commands[] = {
664 { "ADDNETWORK", add_network },
665 { "ADDSERVER", add_server },
666 { "BACKLOG", repl_command },
667 { "CONNECT", com_connect_network },
668 { "DELNETWORK", del_network },
669 { "ECHO", cmd_echo },
670 { "LOG_LEVEL", cmd_log_level },
671 { "NEXTSERVER", com_next_server },
672 { "CHARSET", handle_charset },
673 { "DIE", handle_die },
674 { "DISCONNECT", com_disconnect_network },
675 { "LISTNETWORKS", list_networks },
676 { "SAVECONFIG", com_save_config },
677 { "DETACH", detach_client },
678 { "HELP", cmd_help },
679 { "DUMPJOINEDCHANNELS", dump_joined_channels },
681 { "ABORT", do_abort },
686 void init_admin(void)
689 for(i = 0; builtin_commands[i].name; i++) {
690 register_admin_command(&builtin_commands[i]);
693 register_virtual_network(&admin_network);
695 markers = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)linestack_free_marker);