Implement WHO for the admin network.
[jelmer/ctrlproxy.git] / src / admin.c
index e44bf3df2d02b79ccf3e2cdc011b00b2a88e8ac8..29831cc74c9b77d79ca9919d305eaf407c95ea01 100644 (file)
 #include "internals.h"
 #include <string.h>
 #include "admin.h"
+#include "help.h"
+#include "irc.h"
 
-#define ADMIN_CHANNEL "#ctrlproxy"
+help_t *help;
 
-static GList *commands = NULL;
-static guint longest_command = 0;
+#define ADMIN_CHANNEL "#ctrlproxy"
 
-struct admin_handle
-{
-       struct global *global;
-       struct client *client;
-       struct network *network;
-       void *user_data;
-       void (*send_fn) (struct admin_handle *, const char *data);
-};
+GList *admin_commands = NULL;
+guint longest_command = 0;
 
 static void privmsg_admin_out(admin_handle h, const char *data)
 {
@@ -43,7 +38,7 @@ static void privmsg_admin_out(admin_handle h, const char *data)
        char *nick = c->nick;
        char *hostmask;
 
-       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
+       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->info.name);
        if (c->network->state) nick = c->network->state->me.nick;
        client_send_args_ex(c, hostmask, "NOTICE", nick, data, NULL);
 
@@ -55,13 +50,36 @@ static void network_admin_out(admin_handle h, const char *data)
        struct client *c = h->client;
        char *hostmask;
 
-       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->name);
+       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", c->network->info.name);
        virtual_network_recv_args(c->network, hostmask, "PRIVMSG", ADMIN_CHANNEL, 
                                                          data, NULL);
 
        g_free(hostmask);
 }
 
+static void cmd_help(admin_handle h, char **args, void *userdata)
+{
+       const char *s;
+
+       s = help_get(help, args[1] != NULL?args[1]:"index");
+
+       if (s == NULL) {
+               if (args[1] == NULL)
+                       admin_out(h, "Sorry, help not available");
+               else
+                       admin_out(h, "Sorry, no help for %s available", args[1]);
+               return;
+       }
+
+       while (strncmp(s, "%\n", 2) != 0) {
+               char *tmp;
+               admin_out(h, "%s", tmp = g_strndup(s, strchr(s, '\n')-s));
+               g_free(tmp);
+                       
+               s = strchr(s, '\n')+1;
+       }
+}
+
 struct client *admin_get_client(admin_handle h)
 {
        return h->client;
@@ -99,6 +117,11 @@ static void add_network (admin_handle h, char **args, void *userdata)
                return;
        }
 
+       if (find_network(admin_get_global(h), args[1]) != NULL) {
+               admin_out(h, "Network with name `%s' already exists", args[1]);
+               return;
+       }
+
        nc = network_config_init(admin_get_global(h)->config);
        g_free(nc->name); nc->name = g_strdup(args[1]);
        load_network(admin_get_global(h), nc);
@@ -234,7 +257,7 @@ static void com_next_server (admin_handle h, char **args, void *userdata)
                n = find_network(admin_get_global(h), args[1]);
        } else {
                n = admin_get_network(h);
-               name = n->name;
+               name = n->info.name;
        }
        if(!n) {
                admin_out(h, "%s: Not connected", name);
@@ -255,47 +278,7 @@ static void com_save_config (admin_handle h, char **args, void *userdata)
        admin_out(h, "Configuration saved in %s", adm_dir);
 }
 
-static void help (admin_handle h, char **args, void *userdata)
-{
-       GList *gl = commands;
-       char *tmp;
-       char **details;
-       int i;
 
-       if(args[1]) {
-               admin_out(h, "Details for command %s:", args[1]);
-       } else {
-               admin_out(h, "The following commands are available:");
-       }
-       while(gl) {
-               struct admin_command *cmd = (struct admin_command *)gl->data;
-               if(args[1]) {
-                       if(!g_strcasecmp(args[1], cmd->name)) {
-                               if(cmd->help_details != NULL) {
-                                       details = g_strsplit(cmd->help_details, "\n", 0);
-                                       for(i = 0; details[i] != NULL; i++) {
-                                               admin_out(h, details[i]);
-                                       }
-                                       return;
-                               } else {
-                                       admin_out(h, "Sorry, no help for %s available", args[1]);
-                               }
-                       }
-               } else {
-                       if(cmd->help != NULL) {
-                               tmp = g_strdup_printf("%s%s     %s",cmd->name,g_strnfill(longest_command - strlen(cmd->name),' '),cmd->help);
-                               admin_out(h, tmp);
-                               g_free(tmp);
-                       } else {
-                               admin_out(h, cmd->name);
-                       }
-               }
-               gl = gl->next;
-       }
-       if(args[1]) {
-               admin_out(h, "Unknown command");
-       }
-}
 
 static void list_networks(admin_handle h, char **args, void *userdata)
 {
@@ -305,14 +288,18 @@ static void list_networks(admin_handle h, char **args, void *userdata)
 
                switch (n->connection.state) {
                case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
-                       admin_out(h, ("%s: Not connected"), n->name);
+                       if (n->connection.data.tcp.last_disconnect_reason)
+                               admin_out(h, "%s: Not connected: %s", n->info.name, 
+                                                 n->connection.data.tcp.last_disconnect_reason);
+                       else
+                               admin_out(h, "%s: Not connected", n->info.name);
                        break;
                case NETWORK_CONNECTION_STATE_RECONNECT_PENDING:
-                       admin_out(h, ("%s: Reconnecting"), n->name);
+                       admin_out(h, "%s: Reconnecting", n->info.name);
                        break;
                case NETWORK_CONNECTION_STATE_LOGIN_SENT:
                case NETWORK_CONNECTION_STATE_MOTD_RECVD:
-                       admin_out(h, ("%s: connected"), n->name);
+                       admin_out(h, "%s: connected", n->info.name);
                        break;
                }
        }
@@ -341,7 +328,7 @@ static void dump_joined_channels(admin_handle h, char **args, void *userdata)
        }
 
        if (!n->state) {
-               admin_out(h, "Network '%s' not connected", n->name);
+               admin_out(h, "Network '%s' not connected", n->info.name);
                return;
        }
 
@@ -380,9 +367,12 @@ static void repl_command(admin_handle h, char **args, void *userdata)
        }
 
        if(!args[1]) {
-               admin_out(h, "Sending backlog for network '%s'", n->name);
+               admin_out(h, "Sending backlog for network '%s'", n->info.name);
 
-               linestack_send(n->linestack, lm, NULL, admin_get_client(h));
+               if (n->global->config->report_time)
+                       linestack_send_timed(n->linestack, lm, NULL, admin_get_client(h));
+               else
+                       linestack_send(n->linestack, lm, NULL, admin_get_client(h));
 
                g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
 
@@ -402,23 +392,44 @@ static void repl_command(admin_handle h, char **args, void *userdata)
        g_hash_table_replace(markers, n, linestack_get_marker(n->linestack));
 }
 
+static void cmd_log_level(admin_handle h, char **args, void *userdata)
+{
+       extern enum log_level current_log_level;
+       
+       if (args[1] == NULL) 
+               admin_out(h, "Current log level: %d", current_log_level);
+       else {
+               int x = atoi(args[1]);
+               if (x < 0 || x > 5) 
+                       admin_out(h, "Invalid log level %d", x);
+               else { 
+                       current_log_level = x;
+                       admin_out(h, "Log level changed to %d", x);
+               }
+       }
+}
+
 static void handle_charset(admin_handle h, char **args, void *userdata)
 {
-       GError *error = NULL;
        struct client *c;
 
-       c = admin_get_client(h);
-
        if (args[1] == NULL) {
                admin_out(h, "No charset specified");
                return;
        }
 
+       c = admin_get_client(h);
+
        if (!client_set_charset(c, args[1])) {
-               admin_out(h, "Error setting charset: %s", error->message);
+               admin_out(h, "Error setting charset: %s", args[1]);
        }
 }
 
+static void cmd_echo(admin_handle h, char **args, void *userdata)
+{
+       admin_out(h, "%s", args[1]);
+}
+
 static gint cmp_cmd(gconstpointer a, gconstpointer b)
 {
        const struct admin_command *cmda = a, *cmdb = b;
@@ -428,16 +439,16 @@ static gint cmp_cmd(gconstpointer a, gconstpointer b)
 
 void register_admin_command(const struct admin_command *cmd)
 {
-       commands = g_list_insert_sorted(commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
+       admin_commands = g_list_insert_sorted(admin_commands, g_memdup(cmd, sizeof(*cmd)), cmp_cmd);
        if (strlen(cmd->name) > longest_command) longest_command = strlen(cmd->name);
 }
 
 void unregister_admin_command(const struct admin_command *cmd)
 {
-       commands = g_list_remove(commands, cmd);
+       admin_commands = g_list_remove(admin_commands, cmd);
 }
 
-static gboolean process_cmd(admin_handle h, const char *cmd)
+gboolean process_cmd(admin_handle h, const char *cmd)
 {
        char **args = NULL;
        GList *gl;
@@ -449,8 +460,13 @@ static gboolean process_cmd(admin_handle h, const char *cmd)
 
        args = g_strsplit(cmd, " ", 0);
 
+       if (!args[0]) {
+               admin_out(h, "Please specify a command. Use the 'help' command to get a list of available commands");
+               return TRUE;
+       }
+
        /* Ok, arguments are processed now. Execute the corresponding command */
-       for (gl = commands; gl; gl = gl->next) {
+       for (gl = admin_commands; gl; gl = gl->next) {
                struct admin_command *cmd = (struct admin_command *)gl->data;
                if(!g_strcasecmp(cmd->name, args[0])) {
                        cmd->handler(h, args, cmd->userdata);
@@ -479,7 +495,6 @@ gboolean admin_process_command(struct client *c, struct line *l, int cmdoffset)
                tmp = g_strdup_printf("%s %s", oldtmp, l->args[i]);
                g_free(oldtmp);
        }
-       l->is_private = 1;
 
        ah.send_fn = privmsg_admin_out;
        ah.client = c;
@@ -497,17 +512,17 @@ static gboolean admin_net_init(struct network *n)
        char *hostmask;
        char *nicks;
 
-       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->name);
+       hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", n->info.name);
        
        virtual_network_recv_args(n, n->state->me.hostmask, "JOIN", ADMIN_CHANNEL, NULL);
-       virtual_network_recv_args(n, NULL, "332", n->config->nick, ADMIN_CHANNEL, 
+       virtual_network_recv_response(n, RPL_TOPIC, ADMIN_CHANNEL, 
                "CtrlProxy administration channel | Type `help' for more information",
                                                          NULL);
        nicks = g_strdup_printf("@ctrlproxy %s", n->config->nick);
 
-       virtual_network_recv_args(n, NULL, "353", n->config->nick, "=", ADMIN_CHANNEL, nicks, NULL);
+       virtual_network_recv_response(n, RPL_NAMREPLY, "=", ADMIN_CHANNEL, nicks, NULL);
        g_free(nicks);
-       virtual_network_recv_args(n, NULL, "366", n->config->nick, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
+       virtual_network_recv_response(n, RPL_ENDOFNAMES, ADMIN_CHANNEL, "End of /NAMES list.", NULL);
 
        g_free(hostmask);
 
@@ -516,39 +531,121 @@ static gboolean admin_net_init(struct network *n)
 
 static gboolean admin_to_server (struct network *n, struct client *c, const struct line *l)
 {
-       struct admin_handle ah;
+       if (!g_strcasecmp(l->args[0], "PRIVMSG") ||
+               !g_strcasecmp(l->args[0], "NOTICE")) {
+               struct admin_handle ah;
 
-       if (g_strcasecmp(l->args[0], "PRIVMSG") && 
-               g_strcasecmp(l->args[0], "NOTICE")) {
-               log_global(LOG_TRACE, "Unhandled command `%s' to admin network", 
-                                  l->args[0]);
-               return TRUE;
-       }
+               if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
+                       virtual_network_recv_args(n, n->state->me.hostmask, l->args[0], l->args[1], NULL);
+                       return TRUE;
+               }
+
+               if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) && 
+                       g_strcasecmp(l->args[1], "ctrlproxy")) {
+                       virtual_network_recv_response(n, ERR_NOSUCHNICK, l->args[1], "No such nick/channel", NULL);
+                       return TRUE;
+               }
 
-       if (g_strcasecmp(l->args[0], n->state->me.nick) == 0) {
-               virtual_network_recv_args(c->network, n->state->me.hostmask, l->args[0], l->args[1], NULL);
+               ah.send_fn = network_admin_out;
+               ah.user_data = NULL;
+               ah.client = c;
+               ah.network = n;
+               ah.global = n->global;
+
+               return process_cmd(&ah, l->args[2]);
+       } else if (!g_strcasecmp(l->args[0], "ISON")) {
+               int i;
+               char *tmp;
+               GList *gl = NULL;
+
+               if (l->args[1] == NULL) {
+                       virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
+                       return TRUE;
+               }
+
+               for (i = 1; l->args[i]; i++) {
+                       if (!g_strcasecmp(l->args[i], "ctrlproxy") ||
+                               !g_strcasecmp(l->args[i], n->state->me.nick)) {
+                               gl = g_list_append(gl, l->args[i]);
+                       }
+               }
+               virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
+               g_free(tmp);
+               g_list_free(gl);
                return TRUE;
-       }
+       } else if (!g_strcasecmp(l->args[0], "USERHOST")) {
+               GList *gl = NULL;
+               char *tmp;
+               int i;
+
+               if (l->args[1] == NULL) {
+                       virtual_network_recv_response(n, ERR_NEEDMOREPARAMS, l->args[0], "Not enough params", NULL);
+                       return TRUE;
+               }
+
+               for (i = 1; l->args[i]; i++) {
+                       if (!g_strcasecmp(l->args[i], "ctrlproxy")) {
+                               gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], get_my_hostname()));
+                       }
+                       if (!g_strcasecmp(l->args[i], n->state->me.nick)) {
+                               gl = g_list_append(gl, g_strdup_printf("%s=+%s", l->args[i], n->state->me.hostname));
+                       }
+               }
 
-       if (g_strcasecmp(l->args[1], ADMIN_CHANNEL) && 
-               g_strcasecmp(l->args[1], "ctrlproxy")) {
-               virtual_network_recv_args(c->network, NULL, "401", l->args[1], "No such nick/channel", NULL);
+               virtual_network_recv_response(n, RPL_ISON, tmp = list_make_string(gl), NULL);
+               g_free(tmp);
+               while (gl) {
+                       g_free(gl->data);
+                       gl = g_list_remove(gl, gl->data);
+               }
                return TRUE;
-       }
+       } else if (!g_strcasecmp(l->args[0], "QUIT")) {
+               return TRUE;
+       } else if (!g_strcasecmp(l->args[0], "MODE")) {
+               /* FIXME: Do something here ? */
+               return TRUE;
+       } else if (!g_strcasecmp(l->args[0], "WHO")) {
+               if (!strcmp(l->args[1], ADMIN_CHANNEL) || 
+                       !strcmp(l->args[1], "ctrlproxy")) {
+                       virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
+                                                                         "ctrlproxy",
+                                                                         get_my_hostname(),
+                                                                         get_my_hostname(),
+                                                                         "ctrlproxy",
+                                                                         "H",
+                                                                         "0 CtrlProxy user",
+                                                                         NULL);
+               }
+               if (!strcmp(l->args[1], ADMIN_CHANNEL) ||
+                       !strcmp(l->args[1], n->state->me.nick)) {
+                       char *fullname = g_strdup_printf("0 %s", n->state->me.fullname);
+                       virtual_network_recv_response(n, RPL_WHOREPLY, ADMIN_CHANNEL, 
+                                                                         n->state->me.username,
+                                                                         n->state->me.hostname,
+                                                                         get_my_hostname(),
+                                                                         n->state->me.nick,
+                                                                         "H",
+                                                                         fullname,
+                                                                         NULL);
+                       g_free(fullname);
+               }
 
-       ah.send_fn = network_admin_out;
-       ah.user_data = NULL;
-       ah.client = c;
-       ah.network = n;
-       ah.global = n->global;
+               virtual_network_recv_response(n, RPL_ENDOFWHO, l->args[1], "End of /WHO list.", NULL);
 
-       return process_cmd(&ah, l->args[2]);
+               return TRUE;
+       } else {
+               virtual_network_recv_response(n, ERR_UNKNOWNCOMMAND, l->args[0], "Unknown command", NULL);
+               log_global(LOG_TRACE, "Unhandled command `%s' to admin network", 
+                                  l->args[0]);
+               return TRUE;
+       }
 }
 
 struct virtual_network_ops admin_network = {
        "admin", admin_net_init, admin_to_server, NULL
 };
 
+
 void admin_log(enum log_level level, const struct network *n, const struct client *c, const char *data)
 {
        extern struct global *my_global;
@@ -572,7 +669,7 @@ void admin_log(enum log_level level, const struct network *n, const struct clien
        tmp = g_strdup_printf("%s%s%s%s%s%s", 
                                                  data, 
                                                  n?" (":"",
-                                                 n?n->name:"", 
+                                                 n?n->info.name:"", 
                                                  c?"/":"",
                                                  c?c->description:"",
                                                  n?")":"");
@@ -583,7 +680,7 @@ void admin_log(enum log_level level, const struct network *n, const struct clien
                if (network->connection.data.virtual.ops != &admin_network)
                        continue;
 
-               hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->name);
+               hostmask = g_strdup_printf("ctrlproxy!ctrlproxy@%s", network->info.name);
                l = irc_parse_line_args(hostmask, "PRIVMSG", ADMIN_CHANNEL, tmp, NULL); 
                g_free(hostmask);
                
@@ -597,25 +694,25 @@ void admin_log(enum log_level level, const struct network *n, const struct clien
        entered = FALSE;
 }
 
-
-
 const static struct admin_command builtin_commands[] = {
-       { "ADDNETWORK", add_network, "<name>", "Add new network with specified name" },
-       { "ADDSERVER", add_server, "<network> <host>[:<port>] [<password>]", "Add server to network" },
-       { "BACKLOG", repl_command, "[channel]", "Send backlogs for this network or a channel, if specified" },
-       { "CONNECT", com_connect_network, "<network>", "Connect to specified network. Forces reconnect when waiting." },
-       { "DELNETWORK", del_network, "<network>", "Remove specified network" },
-       { "NEXTSERVER", com_next_server, "[network]", "Disconnect and use to the next server in the list" },
-       { "CHARSET", handle_charset, "<charset>", "Change client charset" },
-       { "DIE", handle_die, "", "Exit ctrlproxy" },
-       { "DISCONNECT", com_disconnect_network, "<network>", "Disconnect specified network" },
-       { "LISTNETWORKS", list_networks, "", "List current networks and their status" },
-       { "SAVECONFIG", com_save_config, "<name>", "Save current XML configuration to specified file" },
-       { "DETACH", detach_client, "", "Detach current client" },
-       { "HELP", help, "[command]", "This help command" },
-       { "DUMPJOINEDCHANNELS", dump_joined_channels, "[network]", NULL, NULL },
+       { "ADDNETWORK", add_network },
+       { "ADDSERVER", add_server },
+       { "BACKLOG", repl_command },
+       { "CONNECT", com_connect_network },
+       { "DELNETWORK", del_network },
+       { "ECHO", cmd_echo },
+       { "LOG_LEVEL", cmd_log_level },
+       { "NEXTSERVER", com_next_server },
+       { "CHARSET", handle_charset },
+       { "DIE", handle_die },
+       { "DISCONNECT", com_disconnect_network },
+       { "LISTNETWORKS", list_networks },
+       { "SAVECONFIG", com_save_config },
+       { "DETACH", detach_client },
+       { "HELP", cmd_help },
+       { "DUMPJOINEDCHANNELS", dump_joined_channels },
 #ifdef DEBUG
-       { "ABORT", do_abort, "", NULL, NULL },
+       { "ABORT", do_abort },
 #endif
        { NULL }
 };