Added "del client <ipaddr>" command for dynamic clients
[freeradius.git] / src / main / command.c
index b021ab9..1b6e9c3 100644 (file)
 
 #include <freeradius-devel/modpriv.h>
 #include <freeradius-devel/conffile.h>
-#
+#include <freeradius-devel/stats.h>
+#include <freeradius-devel/realms.h>
+
 #ifdef HAVE_SYS_UN_H
 #include <sys/un.h>
+#ifndef SUN_LEN
+#define SUN_LEN(su)  (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
 #endif
 
 #ifdef HAVE_SYS_STAT_H
@@ -46,8 +51,12 @@ typedef struct fr_command_table_t fr_command_table_t;
 
 typedef int (*fr_command_func_t)(rad_listen_t *, int, char *argv[]);
 
+#define FR_READ  (1)
+#define FR_WRITE (2)
+
 struct fr_command_table_t {
        const char *command;
+       int mode;               /* read/write */
        const char *help;
        fr_command_func_t func;
        fr_command_table_t *table;
@@ -57,11 +66,28 @@ struct fr_command_table_t {
 
 typedef struct fr_command_socket_t {
        char    *path;
+       char    *copy;          /* <sigh> */
        uid_t   uid;
        gid_t   gid;
+       int     mode;
        char    *uid_name;
        char    *gid_name;
+       char    *mode_name;
        char user[256];
+
+       /*
+        *      The next few entries handle fake packets injected by
+        *      the control socket.
+        */
+       fr_ipaddr_t     src_ipaddr; /* src_port is always 0 */
+       fr_ipaddr_t     dst_ipaddr;
+       int             dst_port;
+       rad_listen_t    *inject_listener;
+       RADCLIENT       *inject_client;
+
+       /*
+        *      The next few entries do buffer management.
+        */
        ssize_t offset;
        ssize_t next;
        char buffer[COMMAND_BUFFER_SIZE];
@@ -74,10 +100,21 @@ static const CONF_PARSER command_config[] = {
     offsetof(fr_command_socket_t, uid_name), NULL, NULL},
   { "gid",  PW_TYPE_STRING_PTR,
     offsetof(fr_command_socket_t, gid_name), NULL, NULL},
+  { "mode",  PW_TYPE_STRING_PTR,
+    offsetof(fr_command_socket_t, mode_name), NULL, NULL},
 
   { NULL, -1, 0, NULL, NULL }          /* end the list */
 };
 
+static FR_NAME_NUMBER mode_names[] = {
+       { "ro", FR_READ },
+       { "read-only", FR_READ },
+       { "read-write", FR_READ | FR_WRITE },
+       { "rw", FR_READ | FR_WRITE },
+       { NULL, 0 }
+};
+
+
 static ssize_t cprintf(rad_listen_t *listener, const char *fmt, ...)
 #ifdef __GNUC__
                __attribute__ ((format (printf, 2, 3)))
@@ -127,9 +164,9 @@ static int fr_server_domain_socket(const char *path)
 
        memset(&salocal, 0, sizeof(salocal));
         salocal.sun_family = AF_UNIX;
-       memcpy(salocal.sun_path, path, len); /* not zero terminated */
+       memcpy(salocal.sun_path, path, len + 1); /* SUN_LEN does strlen */
        
-       socklen = sizeof(salocal.sun_family) + len;
+       socklen = SUN_LEN(&salocal);
 
        /*
         *      Check the path.
@@ -219,6 +256,18 @@ static int fr_server_domain_socket(const char *path)
 }
 
 
+static void command_close_socket(rad_listen_t *this)
+{
+       this->status = RAD_LISTEN_STATUS_CLOSED;
+
+       /*
+        *      This removes the socket from the event fd, so no one
+        *      will be calling us any more.
+        */
+       event_new_fd(this);
+}
+
+
 static ssize_t cprintf(rad_listen_t *listener, const char *fmt, ...)
 {
        ssize_t len;
@@ -232,10 +281,7 @@ static ssize_t cprintf(rad_listen_t *listener, const char *fmt, ...)
        if (listener->status == RAD_LISTEN_STATUS_CLOSED) return 0;
 
        len = write(listener->fd, buffer, len);
-       if (len < 0) {
-               listener->status = RAD_LISTEN_STATUS_CLOSED;
-               event_new_fd(listener);
-       }
+       if (len <= 0) command_close_socket(listener);
 
        /*
         *      FIXME: Keep writing until done?
@@ -341,7 +387,7 @@ static void cprint_conf_parser(rad_listen_t *listener, int indent, CONF_SECTION
                        data = variables[i].data;;
                        
                } else {
-                       data = (((char *)base) + variables[i].offset);
+                       data = (((const char *)base) + variables[i].offset);
                }
 
                switch (variables[i].type) {
@@ -352,7 +398,7 @@ static void cprint_conf_parser(rad_listen_t *listener, int indent, CONF_SECTION
                        
                case PW_TYPE_INTEGER:
                        cprintf(listener, "%.*s%s = %u\n", indent, tabs,
-                               variables[i].name, *(int *) data);
+                               variables[i].name, *(const int *) data);
                        break;
                        
                case PW_TYPE_IPADDR:
@@ -366,7 +412,7 @@ static void cprint_conf_parser(rad_listen_t *listener, int indent, CONF_SECTION
                case PW_TYPE_BOOLEAN:
                        cprintf(listener, "%.*s%s = %s\n", indent, tabs,
                                variables[i].name, 
-                               ((*(int *) data) == 0) ? "no" : "yes");
+                               ((*(const int *) data) == 0) ? "no" : "yes");
                        break;
                        
                case PW_TYPE_STRING_PTR:
@@ -374,9 +420,9 @@ static void cprint_conf_parser(rad_listen_t *listener, int indent, CONF_SECTION
                        /*
                         *      FIXME: Escape things in the string!
                         */
-                       if (*(char **) data) {
+                       if (*(const char * const *) data) {
                                cprintf(listener, "%.*s%s = \"%s\"\n", indent, tabs,
-                                       variables[i].name, *(char **) data);
+                                       variables[i].name, *(const char * const *) data);
                        } else {
                                cprintf(listener, "%.*s%s = \n", indent, tabs,
                                        variables[i].name);
@@ -528,6 +574,89 @@ static int command_show_modules(rad_listen_t *listener, UNUSED int argc, UNUSED
        return 1;               /* success */
 }
 
+#ifdef WITH_PROXY
+static int command_show_home_servers(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
+{
+       int i;
+       home_server *home;
+       const char *type, *state, *proto;
+
+       char buffer[256];
+
+       for (i = 0; i < 256; i++) {
+               home = home_server_bynumber(i);
+               if (!home) break;
+
+               /*
+                *      Internal "virtual" home server.
+                */
+               if (home->ipaddr.af == AF_UNSPEC) continue;
+
+               if (home->type == HOME_TYPE_AUTH) {
+                       type = "auth";
+
+               } else if (home->type == HOME_TYPE_ACCT) {
+                       type = "acct";
+
+               } else continue;
+
+               if (home->proto == IPPROTO_UDP) {
+                       proto = "udp";
+               }
+#ifdef WITH_TCP
+               else if (home->proto == IPPROTO_TCP) {
+                       proto = "tcp";
+               }
+#endif
+               else proto = "??";
+
+               if (home->state == HOME_STATE_ALIVE) {
+                       state = "alive";
+
+               } else if (home->state == HOME_STATE_ZOMBIE) {
+                       state = "zombie";
+
+               } else if (home->state == HOME_STATE_IS_DEAD) {
+                       state = "dead";
+
+               } else continue;
+
+               cprintf(listener, "%s\t%d\t%s\t%s\t%s\t%d\n",
+                       ip_ntoh(&home->ipaddr, buffer, sizeof(buffer)),
+                       home->port, proto, type, state,
+                       home->currently_outstanding);
+       }
+
+       return 0;
+}
+#endif
+
+static int command_show_clients(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
+{
+       int i;
+       RADCLIENT *client;
+       char buffer[256];
+
+       for (i = 0; i < 256; i++) {
+               client = client_findbynumber(NULL, i);
+               if (!client) break;
+
+               ip_ntoh(&client->ipaddr, buffer, sizeof(buffer));
+
+               if (((client->ipaddr.af == AF_INET) &&
+                    (client->prefix != 32)) ||
+                   ((client->ipaddr.af == AF_INET6) &&
+                    (client->prefix != 128))) {
+                       cprintf(listener, "\t%s/%d\n", buffer, client->prefix);
+               } else {
+                       cprintf(listener, "\t%s\n", buffer);
+               }
+       }
+
+       return 0;
+}
+
+
 static int command_show_xml(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
 {
        CONF_ITEM *ci;
@@ -566,6 +695,12 @@ static int command_show_xml(rad_listen_t *listener, UNUSED int argc, UNUSED char
        return 1;               /* success */
 }
 
+static int command_show_version(rad_listen_t *listener, UNUSED int argc, UNUSED char *argv[])
+{
+       cprintf(listener, "%s\n", radiusd_version);
+       return 1;
+}
+
 static int command_debug_level(rad_listen_t *listener, int argc, char *argv[])
 {
        int number;
@@ -586,201 +721,1187 @@ static int command_debug_level(rad_listen_t *listener, int argc, char *argv[])
        return 0;
 }
 
-extern char *debug_log_file;
+char *debug_log_file = NULL;
+static char debug_log_file_buffer[1024];
+
 static int command_debug_file(rad_listen_t *listener, int argc, char *argv[])
 {
-       if (argc == 0) {
-               cprintf(listener, "ERROR: Must specify <filename>\n");
+       if (debug_flag && mainconfig.radlog_dest == RADLOG_STDOUT) {
+               cprintf(listener, "ERROR: Cannot redirect debug logs to a file when already in debugging mode.\n");
                return -1;
        }
 
-       if (debug_log_file) {
-               free(debug_log_file);
-               debug_log_file = NULL;
+       if ((argc > 0) && (strchr(argv[0], FR_DIR_SEP) != NULL)) {
+               cprintf(listener, "ERROR: Cannot direct debug logs to absolute path.\n");
        }
-       debug_log_file = strdup(argv[1]);
+
+       debug_log_file = NULL;
+
+       if (argc == 0) return 0;
+
+       /*
+        *      This looks weird, but it's here to avoid locking
+        *      a mutex for every log message.
+        */
+       memset(debug_log_file_buffer, 0, sizeof(debug_log_file_buffer));
+
+       /*
+        *      Debug files always go to the logging directory.
+        */
+       snprintf(debug_log_file_buffer, sizeof(debug_log_file_buffer),
+                "%s/%s", radlog_dir, argv[0]);
+
+       debug_log_file = &debug_log_file_buffer[0];
 
        return 0;
 }
 
-static fr_command_table_t command_table_debug[] = {
-       { "level",
-         "debug level <number> - Set debug level to <number>.  Higher is more debugging.",
-         command_debug_level, NULL },
+extern char *debug_condition;
+static int command_debug_condition(UNUSED rad_listen_t *listener, int argc, char *argv[])
+{
+       /*
+        *      Delete old condition.
+        *
+        *      This is thread-safe because the condition is evaluated
+        *      in the main server thread, as is this code.
+        */
+       free(debug_condition);
+       debug_condition = NULL;
 
-       { "file",
-         "debug file <filename> - Send debug output to <filename>",
-         command_debug_file, NULL },
+       /*
+        *      Disable it.
+        */
+       if (argc == 0) {
+               return 0;
+       }
 
-       { NULL, NULL, NULL, NULL }
-};
+       debug_condition = strdup(argv[0]);
 
-static fr_command_table_t command_table_show_module[] = {
-       { "config",
-         "show module config <module> - show configuration for <module>",
-         command_show_module_config, NULL },
-       { "flags",
-         "show module flags <module> - show other module properties",
-         command_show_module_flags, NULL },
-       { "list",
-         "shows list of loaded modules",
-         command_show_modules, NULL },
-       { "methods",
-         "show module methods <module> - show sections where <module> may be used",
-         command_show_module_methods, NULL },
+       return 0;
+}
 
-       { NULL, NULL, NULL, NULL }
-};
+static int command_show_debug_condition(rad_listen_t *listener,
+                                       UNUSED int argc, UNUSED char *argv[])
+{
+       if (!debug_condition) return 0;
 
+       cprintf(listener, "%s\n", debug_condition);
+       return 0;
+}
 
-static fr_command_table_t command_table_show[] = {
-       { "config",
-         "show config <module> - show configuration for module",
-         command_show_module_config, NULL },
-       { "module",
-         "show module <command> - do sub-command of module",
-         NULL, command_table_show_module },
-       { "modules",
-         "show modules - shows list of loaded modules",
-         command_show_modules, NULL },
-       { "uptime",
-         "show uptime - shows time at which server started",
-         command_uptime, NULL },
-       { "xml",
-         "show xml <reference> - Prints out configuration as XML",
-         command_show_xml, NULL },
-       { NULL, NULL, NULL, NULL }
-};
 
+static int command_show_debug_file(rad_listen_t *listener,
+                                       UNUSED int argc, UNUSED char *argv[])
+{
+       if (!debug_log_file) return 0;
 
-static int command_set_module_config(rad_listen_t *listener, int argc, char *argv[])
+       cprintf(listener, "%s\n", debug_log_file);
+       return 0;
+}
+
+
+static int command_show_debug_level(rad_listen_t *listener,
+                                       UNUSED int argc, UNUSED char *argv[])
 {
-       int i, rcode;
-       CONF_PAIR *cp;
-       CONF_SECTION *cs;
-       module_instance_t *mi;
-       const CONF_PARSER *variables;
-       void *data;
+       cprintf(listener, "%d\n", debug_flag);
+       return 0;
+}
 
-       if (argc < 3) {
-               cprintf(listener, "ERROR: No module name or variable was given\n");
-               return 0;
+
+static RADCLIENT *get_client(rad_listen_t *listener, int argc, char *argv[])
+{
+       RADCLIENT *client;
+       fr_ipaddr_t ipaddr;
+       int proto = IPPROTO_UDP;
+
+       if (argc < 1) {
+               cprintf(listener, "ERROR: Must specify <ipaddr>\n");
+               return NULL;
        }
 
-       cs = cf_section_find("modules");
-       if (!cs) return 0;
+       if (ip_hton(argv[0], AF_UNSPEC, &ipaddr) < 0) {
+               cprintf(listener, "ERROR: Failed parsing IP address; %s\n",
+                       fr_strerror());
+               return NULL;
+       }
 
-       mi = find_module_instance(cs, argv[0], 0);
-       if (!mi) {
-               cprintf(listener, "ERROR: No such module \"%s\"\n", argv[0]);
-               return 0;
+#ifdef WITH_TCP
+       if (argc >= 2) {
+               if (strcmp(argv[1], "tcp") == 0) {
+                       proto = IPPROTO_TCP;
+
+               } else if (strcmp(argv[1], "udp") == 0) {
+                       proto = IPPROTO_UDP;
+
+               } else {
+                       cprintf(listener, "ERROR: Unknown protocol %s.  Please use \"udp\" or \"tcp\"\n",
+                               argv[1]);
+                       return NULL;
+               }
        }
+#endif
 
-       if ((mi->entry->module->type & RLM_TYPE_HUP_SAFE) == 0) {
-               cprintf(listener, "ERROR: Cannot change configuration of module as it is cannot be HUP'd.\n");
+       client = client_find(NULL, &ipaddr, proto);
+       if (!client) {
+               cprintf(listener, "ERROR: No such client\n");
+               return NULL;
+       }
+
+       return client;
+}
+
+
+static int command_show_client_config(rad_listen_t *listener, int argc, char *argv[])
+{
+       RADCLIENT *client;
+       FILE *fp;
+
+       client = get_client(listener, argc, argv);
+       if (!client) {
                return 0;
        }
 
-       variables = cf_section_parse_table(mi->cs);
-       if (!variables) {
-               cprintf(listener, "ERROR: Cannot find configuration for module\n");
+       if (!client->cs) return 1;
+
+       fp = fdopen(dup(listener->fd), "a");
+       if (!fp) {
+               cprintf(listener, "ERROR: Can't dup %s\n", strerror(errno));
                return 0;
        }
 
-       rcode = -1;
-       for (i = 0; variables[i].name != NULL; i++) {
-               /*
-                *      FIXME: Recurse into sub-types somehow...
-                */
-               if (variables[i].type == PW_TYPE_SUBSECTION) continue;
+       cf_section2file(fp, client->cs);
+       fclose(fp);
 
-               if (strcmp(variables[i].name, argv[1]) == 0) {
-                       rcode = i;
-                       break;
+       return 1;
+}
+
+#ifdef WITH_PROXY
+static home_server *get_home_server(rad_listen_t *listener, int argc,
+                                   char *argv[], int *last)
+{
+       home_server *home;
+       int port;
+       int proto = IPPROTO_UDP;
+       fr_ipaddr_t ipaddr;
+
+       if (argc < 2) {
+               cprintf(listener, "ERROR: Must specify <ipaddr> <port> [proto]\n");
+               return NULL;
+       }
+
+       if (ip_hton(argv[0], AF_UNSPEC, &ipaddr) < 0) {
+               cprintf(listener, "ERROR: Failed parsing IP address; %s\n",
+                       fr_strerror());
+               return NULL;
+       }
+
+       port = atoi(argv[1]);
+
+       if (last) *last = 2;
+       if (argc > 2) {
+               if (strcmp(argv[2], "udp") == 0) {
+                       proto = IPPROTO_UDP;
+                       if (last) *last = 3;
                }
+#ifdef WITH_TCP
+               if (strcmp(argv[2], "tcp") == 0) {
+                       proto = IPPROTO_TCP;
+                       if (last) *last = 3;
+               }
+#endif
        }
 
-       if (rcode < 0) {
-               cprintf(listener, "ERROR: No such variable \"%s\"\n", argv[1]);
+       home = home_server_find(&ipaddr, port, proto);
+       if (!home) {
+               cprintf(listener, "ERROR: No such home server\n");
+               return NULL;
+       }
+
+       return home;
+}
+
+static int command_show_home_server_config(rad_listen_t *listener, int argc, char *argv[])
+{
+       home_server *home;
+       FILE *fp;
+
+       home = get_home_server(listener, argc, argv, NULL);
+       if (!home) {
                return 0;
        }
 
-       i = rcode;              /* just to be safe */
+       if (!home->cs) return 1;
 
-       /*
-        *      It's not part of the dynamic configuration.  The module
-        *      needs to re-parse && validate things.
-        */
-       if (variables[i].data) {
-               cprintf(listener, "ERROR: Variable cannot be dynamically updated\n");
+       fp = fdopen(dup(listener->fd), "a");
+       if (!fp) {
+               cprintf(listener, "ERROR: Can't dup %s\n", strerror(errno));
                return 0;
        }
 
-       data = ((char *) mi->insthandle) + variables[i].offset;
+       cf_section2file(fp, home->cs);
+       fclose(fp);
 
-       cp = cf_pair_find(mi->cs, argv[1]);
-       if (!cp) return 0;
+       return 1;
+}
 
-       /*
-        *      Replace the OLD value in the configuration file with
-        *      the NEW value.
-        *
-        *      FIXME: Parse argv[2] depending on it's data type!
-        *      If it's a string, look for leading single/double quotes,
-        *      end then call tokenize functions???
-        */
-       cf_pair_replace(mi->cs, cp, argv[2]);
+extern void revive_home_server(void *ctx);
+extern void mark_home_server_dead(home_server *home, struct timeval *when);
 
-       rcode = cf_item_parse(mi->cs, argv[1], variables[i].type,
-                             data, argv[2]);
-       if (rcode < 0) {
-               cprintf(listener, "ERROR: Failed to parse value\n");
+static int command_set_home_server_state(rad_listen_t *listener, int argc, char *argv[])
+{
+       int last;
+       home_server *home;
+
+       if (argc < 3) {
+               cprintf(listener, "ERROR: Must specify <ipaddr> <port> [proto] <state>\n");
                return 0;
        }
 
-       return 1;               /* success */
-}
+       home = get_home_server(listener, argc, argv, &last);
+       if (!home) {
+               return 0;
+       }
 
+       if (strcmp(argv[last], "alive") == 0) {
+               revive_home_server(home);
 
-static fr_command_table_t command_table_set_module[] = {
-       { "config",
-         "set module config <module> variable value - set configuration for <module>",
-         command_set_module_config, NULL },
+       } else if (strcmp(argv[last], "dead") == 0) {
+               struct timeval now;
 
-       { NULL, NULL, NULL, NULL }
-};
+               gettimeofday(&now, NULL); /* we do this WAY too ofetn */
+               mark_home_server_dead(home, &now);
 
+       } else {
+               cprintf(listener, "ERROR: Unknown state \"%s\"\n", argv[last]);
+               return 0;
+       }
 
-static fr_command_table_t command_table_set[] = {
-       { "module", NULL, NULL, command_table_set_module },
+       return 1;
+}
 
-       { NULL, NULL, NULL, NULL }
-};
+static int command_show_home_server_state(rad_listen_t *listener, int argc, char *argv[])
+{
+       home_server *home;
 
+       home = get_home_server(listener, argc, argv, NULL);
+       if (!home) {
+               return 0;
+       }
 
-static fr_command_table_t command_table[] = {
-       { "debug",
-         "debug <command> - debugging commands",
-         NULL, command_table_debug },
-       { "hup",
-         "hup [module] - sends a HUP signal to the server, or optionally to one module",
-         command_hup, NULL },
-       { "terminate",
-         "terminate - terminates the server, and causes it to exit",
-         command_terminate, NULL },
-       { "show", NULL, NULL, command_table_show },
-       { "set", NULL, NULL, command_table_set },
+       switch (home->state) {
+       case HOME_STATE_ALIVE:
+               cprintf(listener, "alive\n");
+               break;
 
-       { NULL, NULL, NULL, NULL }
-};
+       case HOME_STATE_IS_DEAD:
+               cprintf(listener, "dead\n");
+               break;
 
+       case HOME_STATE_ZOMBIE:
+               cprintf(listener, "zombie\n");
+               break;
+
+       default:
+               cprintf(listener, "unknown\n");
+               break;
+       }
+       
+       return 1;
+}
+#endif
 
 /*
- *     FIXME: Unix domain sockets!
+ *     For encode/decode stuff
  */
-static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+static int null_socket_dencode(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
 {
-       fr_command_socket_t *sock;
+       return 0;
+}
+
+static int null_socket_send(UNUSED rad_listen_t *listener, REQUEST *request)
+{
+       char *output_file;
+       FILE *fp;
+       VALUE_PAIR *vp;
+
+       output_file = request_data_reference(request, null_socket_send, 0);
+       if (!output_file) {
+               radlog(L_ERR, "WARNING: No output file for injected packet %d",
+                      request->number);
+               return 0;
+       }
+
+       fp = fopen(output_file, "w");
+       if (!fp) {
+               radlog(L_ERR, "Failed to send injected file to %s: %s",
+                      output_file, strerror(errno));
+               return 0;
+       }
+
+       if (request->reply->code != 0) {
+               const char *what = "reply";
+               char buffer[1024];
+
+               if (request->reply->code < FR_MAX_PACKET_CODE) {
+                       what = fr_packet_codes[request->reply->code];
+               }
+
+               fprintf(fp, "%s\n", what);
+
+               if (debug_flag) {
+                       request->radlog(L_DBG, 0, request,
+                                       "Injected %s packet to host %s port 0 code=%d, id=%d",
+                                       what,
+                                       inet_ntop(request->reply->src_ipaddr.af,
+                                                 &request->reply->src_ipaddr.ipaddr,
+                                                 buffer, sizeof(buffer)),
+                                       request->reply->code, request->reply->id);
+               }
+
+               for (vp = request->reply->vps; vp != NULL; vp = vp->next) {
+                       vp_prints(buffer, sizeof(buffer), vp);
+                       fprintf(fp, "%s\n", buffer);
+                       if (debug_flag) {
+                               request->radlog(L_DBG, 0, request, "\t%s",
+                                               buffer);
+                       }
+               }
+       }
+       fclose(fp);
+
+       return 0;
+}
+
+static int command_inject_to(rad_listen_t *listener, int argc, char *argv[])
+{
+       int port;
+       RAD_LISTEN_TYPE type;
+       fr_command_socket_t *sock = listener->data;
+       fr_ipaddr_t ipaddr;
+       rad_listen_t *found = NULL;
+
+       if (argc < 1) {
+               cprintf(listener, "ERROR: Must specify [auth/acct]\n");
+               return 0;
+       }
+
+       if (strcmp(argv[0], "auth") == 0) {
+               type = RAD_LISTEN_AUTH;
+
+       } else if (strcmp(argv[0], "acct") == 0) {
+#ifdef WITH_ACCOUNTING
+               type = RAD_LISTEN_ACCT;
+#else
+               cprintf(listener, "ERROR: This server was built without accounting support.\n");
+               return 0;
+#endif
+
+       } else {
+               cprintf(listener, "ERROR: Unknown socket type\n");
+               return 0;
+       }
+
+       if (argc < 3) {
+               cprintf(listener, "ERROR: No <ipaddr> <port> was given\n");
+               return 0;
+       }
+
+       /*
+        *      FIXME:  Look for optional arg 4, and bind interface.
+        */
+
+       if (ip_hton(argv[1], AF_UNSPEC, &ipaddr) < 0) {
+               cprintf(listener, "ERROR: Failed parsing IP address; %s\n",
+                       fr_strerror());
+               return 0;
+       }
+       port = atoi(argv[2]);
+
+       found = listener_find_byipaddr(&ipaddr, port);
+       if (!found) {
+               cprintf(listener, "ERROR: Could not find matching listener\n");
+               return 0;
+       }
+
+       sock->inject_listener = found;
+       sock->dst_ipaddr = ipaddr;
+       sock->dst_port = port;
+
+       return 1;
+}
+
+static int command_inject_from(rad_listen_t *listener, int argc, char *argv[])
+{
+       RADCLIENT *client;
+       fr_command_socket_t *sock = listener->data;
+
+       if (argc < 1) {
+               cprintf(listener, "ERROR: No <ipaddr> was given\n");
+               return 0;
+       }
+
+       if (!sock->inject_listener) {
+               cprintf(listener, "ERROR: You must specify \"inject to\" before using \"inject from\"\n");
+               return 0;
+       }
+
+       sock->src_ipaddr.af = AF_UNSPEC;
+       if (ip_hton(argv[0], AF_UNSPEC, &sock->src_ipaddr) < 0) {
+               cprintf(listener, "ERROR: Failed parsing IP address; %s\n",
+                       fr_strerror());
+               return 0;
+       }
+
+       client = client_listener_find(sock->inject_listener, &sock->src_ipaddr,
+                                     0);
+       if (!client) {
+               cprintf(listener, "ERROR: No such client %s\n", argv[0]);
+               return 0;
+       }
+       sock->inject_client = client;
+
+       return 1;
+}
+
+static int command_inject_file(rad_listen_t *listener, int argc, char *argv[])
+{
+       static int inject_id = 0;
+       int filedone;
+       fr_command_socket_t *sock = listener->data;
+       rad_listen_t *fake;
+       REQUEST *request = NULL;
+       RADIUS_PACKET *packet;
+       VALUE_PAIR *vp;
+       FILE *fp;
+       RAD_REQUEST_FUNP fun = NULL;
+       char buffer[2048];
+
+       if (argc < 2) {
+               cprintf(listener, "ERROR: You must specify <input-file> <output-file>\n");
+               return 0;
+       }
+
+       /*
+        *      Output files always go to the logging directory.
+        */
+       snprintf(buffer, sizeof(buffer), "%s/%s", radlog_dir, argv[1]);
+
+       fp = fopen(argv[0], "r");
+       if (!fp ) {
+               cprintf(listener, "ERROR: Failed opening %s: %s\n",
+                       argv[0], strerror(errno));
+               return 0;
+       }
+
+       vp = readvp2(fp, &filedone, "");
+       fclose(fp);
+       if (!vp) {
+               cprintf(listener, "ERROR: Failed reading attributes from %s: %s\n",
+                       argv[0], fr_strerror());
+               return 0;
+       }
+
+       fake = rad_malloc(sizeof(*fake));
+       memcpy(fake, sock->inject_listener, sizeof(*fake));
+
+       /*
+        *      Re-write the IO for the listener.
+        */
+       fake->encode = null_socket_dencode;
+       fake->decode = null_socket_dencode;
+       fake->send = null_socket_send;
+
+       packet = rad_alloc(0);
+       packet->src_ipaddr = sock->src_ipaddr;
+       packet->src_port = 0;
+
+       packet->dst_ipaddr = sock->dst_ipaddr;
+       packet->dst_port = sock->dst_port;
+       packet->vps = vp;
+       packet->id = inject_id++;
+
+       if (fake->type == RAD_LISTEN_AUTH) {
+               packet->code = PW_AUTHENTICATION_REQUEST;
+               fun = rad_authenticate;
+
+       } else {
+#ifdef WITH_ACCOUNTING
+               packet->code = PW_ACCOUNTING_REQUEST;
+               fun = rad_accounting;
+#else
+               cprintf(listener, "ERROR: This server was built without accounting support.\n");
+               rad_free(&packet);
+               free(fake);
+               return 0;
+#endif
+       }
+
+       if (!received_request(fake, packet, &request, sock->inject_client)) {
+               cprintf(listener, "ERROR: Failed to inject request.  See log file for details\n");
+               rad_free(&packet);
+               free(fake);
+               return 0;
+       }
+
+       /*
+        *      Remember what the output file is, and remember to
+        *      delete the fake listener when done.
+        */
+       request_data_add(request, null_socket_send, 0, strdup(buffer), free);
+       request_data_add(request, null_socket_send, 1, fake, free);
+
+       if (debug_flag) {
+               request->radlog(L_DBG, 0, request,
+                               "Injected %s packet from host %s port 0 code=%d, id=%d",
+                               fr_packet_codes[packet->code],
+                               inet_ntop(packet->src_ipaddr.af,
+                                         &packet->src_ipaddr.ipaddr,
+                                         buffer, sizeof(buffer)),
+                               packet->code, packet->id);
+               
+               for (vp = packet->vps; vp != NULL; vp = vp->next) {
+                       vp_prints(buffer, sizeof(buffer), vp);
+                       request->radlog(L_DBG, 0, request, "\t%s", buffer);
+               }
+       }
+
+       /*
+        *      And go process it.
+        */
+       thread_pool_addrequest(request, fun);
+
+       return 1;
+}
+
+
+static fr_command_table_t command_table_inject[] = {
+       { "to", FR_WRITE,
+         "inject to <ipaddr> <port> - Inject packets to the destination IP and port.",
+         command_inject_to, NULL },
+
+       { "from", FR_WRITE,
+         "inject from <ipaddr> - Inject packets as if they came from <ipaddr>",
+         command_inject_from, NULL },
+
+       { "file", FR_WRITE,
+         "inject file <input-file> <output-file> - Inject packet from input-file>, with results sent to <output-file>",
+         command_inject_file, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+static fr_command_table_t command_table_debug[] = {
+       { "condition", FR_WRITE,
+         "debug condition [condition] - Enable debugging for requests matching [condition]",
+         command_debug_condition, NULL },
+
+       { "level", FR_WRITE,
+         "debug level <number> - Set debug level to <number>.  Higher is more debugging.",
+         command_debug_level, NULL },
+
+       { "file", FR_WRITE,
+         "debug file [filename] - Send all debugging output to [filename]",
+         command_debug_file, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+static fr_command_table_t command_table_show_debug[] = {
+       { "condition", FR_READ,
+         "show debug condition - Shows current debugging condition.",
+         command_show_debug_condition, NULL },
+
+       { "level", FR_READ,
+         "show debug level - Shows current debugging level.",
+         command_show_debug_level, NULL },
+
+       { "file", FR_READ,
+         "show debug file - Shows current debugging file.",
+         command_show_debug_file, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+static fr_command_table_t command_table_show_module[] = {
+       { "config", FR_READ,
+         "show module config <module> - show configuration for given module",
+         command_show_module_config, NULL },
+       { "flags", FR_READ,
+         "show module flags <module> - show other module properties",
+         command_show_module_flags, NULL },
+       { "list", FR_READ,
+         "show module list - shows list of loaded modules",
+         command_show_modules, NULL },
+       { "methods", FR_READ,
+         "show module methods <module> - show sections where <module> may be used",
+         command_show_module_methods, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+static fr_command_table_t command_table_show_client[] = {
+       { "config", FR_READ,
+         "show client config <ipaddr> "
+#ifdef WITH_TCP
+         "[proto] "
+#endif
+         "- show configuration for given client",
+         command_show_client_config, NULL },
+       { "list", FR_READ,
+         "show client list - shows list of global clients",
+         command_show_clients, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+#ifdef WITH_PROXY
+static fr_command_table_t command_table_show_home[] = {
+       { "config", FR_READ,
+         "show home_server config <ipaddr> <port> [proto] - show configuration for given home server",
+         command_show_home_server_config, NULL },
+       { "list", FR_READ,
+         "show home_server list - shows list of home servers",
+         command_show_home_servers, NULL },
+       { "state", FR_READ,
+         "show home_server state <ipaddr> <port> [proto] - shows state of given home server",
+         command_show_home_server_state, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+#endif
+
+
+static fr_command_table_t command_table_show[] = {
+       { "client", FR_READ,
+         "show client <command> - do sub-command of client",
+         NULL, command_table_show_client },
+       { "debug", FR_READ,
+         "show debug <command> - show debug properties",
+         NULL, command_table_show_debug },
+#ifdef WITH_PROXY
+       { "home_server", FR_READ,
+         "show home_server <command> - do sub-command of home_server",
+         NULL, command_table_show_home },
+#endif
+       { "module", FR_READ,
+         "show module <command> - do sub-command of module",
+         NULL, command_table_show_module },
+       { "uptime", FR_READ,
+         "show uptime - shows time at which server started",
+         command_uptime, NULL },
+       { "version", FR_READ,
+         "show version - Prints version of the running server",
+         command_show_version, NULL },
+       { "xml", FR_READ,
+         "show xml <reference> - Prints out configuration as XML",
+         command_show_xml, NULL },
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static int command_set_module_config(rad_listen_t *listener, int argc, char *argv[])
+{
+       int i, rcode;
+       CONF_PAIR *cp;
+       CONF_SECTION *cs;
+       module_instance_t *mi;
+       const CONF_PARSER *variables;
+       void *data;
+
+       if (argc < 3) {
+               cprintf(listener, "ERROR: No module name or variable was given\n");
+               return 0;
+       }
+
+       cs = cf_section_find("modules");
+       if (!cs) return 0;
+
+       mi = find_module_instance(cs, argv[0], 0);
+       if (!mi) {
+               cprintf(listener, "ERROR: No such module \"%s\"\n", argv[0]);
+               return 0;
+       }
+
+       if ((mi->entry->module->type & RLM_TYPE_HUP_SAFE) == 0) {
+               cprintf(listener, "ERROR: Cannot change configuration of module as it is cannot be HUP'd.\n");
+               return 0;
+       }
+
+       variables = cf_section_parse_table(mi->cs);
+       if (!variables) {
+               cprintf(listener, "ERROR: Cannot find configuration for module\n");
+               return 0;
+       }
+
+       rcode = -1;
+       for (i = 0; variables[i].name != NULL; i++) {
+               /*
+                *      FIXME: Recurse into sub-types somehow...
+                */
+               if (variables[i].type == PW_TYPE_SUBSECTION) continue;
+
+               if (strcmp(variables[i].name, argv[1]) == 0) {
+                       rcode = i;
+                       break;
+               }
+       }
+
+       if (rcode < 0) {
+               cprintf(listener, "ERROR: No such variable \"%s\"\n", argv[1]);
+               return 0;
+       }
+
+       i = rcode;              /* just to be safe */
+
+       /*
+        *      It's not part of the dynamic configuration.  The module
+        *      needs to re-parse && validate things.
+        */
+       if (variables[i].data) {
+               cprintf(listener, "ERROR: Variable cannot be dynamically updated\n");
+               return 0;
+       }
+
+       data = ((char *) mi->insthandle) + variables[i].offset;
+
+       cp = cf_pair_find(mi->cs, argv[1]);
+       if (!cp) return 0;
+
+       /*
+        *      Replace the OLD value in the configuration file with
+        *      the NEW value.
+        *
+        *      FIXME: Parse argv[2] depending on it's data type!
+        *      If it's a string, look for leading single/double quotes,
+        *      end then call tokenize functions???
+        */
+       cf_pair_replace(mi->cs, cp, argv[2]);
+
+       rcode = cf_item_parse(mi->cs, argv[1], variables[i].type,
+                             data, argv[2]);
+       if (rcode < 0) {
+               cprintf(listener, "ERROR: Failed to parse value\n");
+               return 0;
+       }
+
+       return 1;               /* success */
+}
+
+static int command_set_module_status(rad_listen_t *listener, int argc, char *argv[])
+{
+       CONF_SECTION *cs;
+       module_instance_t *mi;
+
+       if (argc < 2) {
+               cprintf(listener, "ERROR: No module name or status was given\n");
+               return 0;
+       }
+
+       cs = cf_section_find("modules");
+       if (!cs) return 0;
+
+       mi = find_module_instance(cs, argv[0], 0);
+       if (!mi) {
+               cprintf(listener, "ERROR: No such module \"%s\"\n", argv[0]);
+               return 0;
+       }
+
+
+       if (strcmp(argv[1], "alive") == 0) {
+               mi->dead = FALSE;
+
+       } else if (strcmp(argv[1], "dead") == 0) {
+               mi->dead = TRUE;
+
+       } else {
+               cprintf(listener, "ERROR: Unknown status \"%s\"\n", argv[2]);
+               return 0;
+       }
+
+       return 1;               /* success */
+}
+
+static int command_print_stats(rad_listen_t *listener, fr_stats_t *stats,
+                              int auth)
+{
+       cprintf(listener, "\trequests\t%u\n", stats->total_requests);
+       cprintf(listener, "\tresponses\t%u\n", stats->total_responses);
+       
+       if (auth) {
+               cprintf(listener, "\taccepts\t\t%u\n",
+                       stats->total_access_accepts);
+               cprintf(listener, "\trejects\t\t%u\n",
+                       stats->total_access_rejects);
+               cprintf(listener, "\tchallenges\t%u\n",
+                       stats->total_access_challenges);
+       }
+
+       cprintf(listener, "\tdup\t\t%u\n", stats->total_dup_requests);
+       cprintf(listener, "\tinvalid\t\t%u\n", stats->total_invalid_requests);
+       cprintf(listener, "\tmalformed\t%u\n", stats->total_malformed_requests);
+       cprintf(listener, "\tbad_signature\t%u\n", stats->total_bad_authenticators);
+       cprintf(listener, "\tdropped\t\t%u\n", stats->total_packets_dropped);
+       cprintf(listener, "\tunknown_types\t%u\n", stats->total_unknown_types);
+       
+       return 1;
+}
+
+
+#ifdef WITH_DETAIL
+static FR_NAME_NUMBER state_names[] = {
+       { "unopened", STATE_UNOPENED },
+       { "unlocked", STATE_UNLOCKED },
+       { "header", STATE_HEADER },
+       { "reading", STATE_READING },
+       { "queued", STATE_QUEUED },
+       { "running", STATE_RUNNING },
+       { "no-reply", STATE_NO_REPLY },
+       { "replied", STATE_REPLIED },
+
+       { NULL, 0 }
+};
+
+static int command_stats_detail(rad_listen_t *listener, int argc, char *argv[])
+{
+       rad_listen_t *this;
+       listen_detail_t *data;
+       struct stat buf;
+
+       if (argc == 0) {
+               cprintf(listener, "ERROR: Must specify <filename>\n");
+               return 0;
+       }
+
+       data = NULL;
+       for (this = mainconfig.listen; this != NULL; this = this->next) {
+               if (this->type != RAD_LISTEN_DETAIL) continue;
+
+               data = this->data;
+               if (strcmp(argv[1], data->filename) != 0) continue;
+
+               break;
+       }
+
+       cprintf(listener, "\tstate\t%s\n",
+               fr_int2str(state_names, data->state, "?"));
+
+       if ((data->state == STATE_UNOPENED) ||
+           (data->state == STATE_UNLOCKED)) {
+               return 1;
+       }
+
+       /*
+        *      Race conditions: file might not exist.
+        */
+       if (stat(data->filename_work, &buf) < 0) {
+               cprintf(listener, "packets\t0\n");
+               cprintf(listener, "tries\t0\n");
+               cprintf(listener, "offset\t0\n");
+               cprintf(listener, "size\t0\n");
+               return 1;
+       }
+
+       cprintf(listener, "packets\t%d\n", data->packets);
+       cprintf(listener, "tries\t%d\n", data->tries);
+       cprintf(listener, "offset\t%u\n", (unsigned int) data->offset);
+       cprintf(listener, "size\t%u\n", (unsigned int) buf.st_size);
+
+       return 1;
+}
+#endif
+
+#ifdef WITH_PROXY
+static int command_stats_home_server(rad_listen_t *listener, int argc, char *argv[])
+{
+       home_server *home;
+
+       if (argc == 0) {
+               cprintf(listener, "ERROR: Must specify [auth/acct] OR <ipaddr> <port>\n");
+               return 0;
+       }
+
+       if (argc == 1) {
+#ifdef WITH_ACCOUNTING
+               if (strcmp(argv[0], "acct") == 0) {
+                       return command_print_stats(listener,
+                                                  &proxy_acct_stats, 0);
+               }
+#endif
+               if (strcmp(argv[0], "auth") == 0) {
+                       return command_print_stats(listener,
+                                                  &proxy_auth_stats, 1);
+               }
+
+               cprintf(listener, "ERROR: Should specify [auth/acct]\n");
+               return 0;
+       }
+
+       home = get_home_server(listener, argc, argv, NULL);
+       if (!home) {
+               return 0;
+       }
+
+       command_print_stats(listener, &home->stats,
+                           (home->type == HOME_TYPE_AUTH));
+       cprintf(listener, "\toutstanding\t%d\n", home->currently_outstanding);
+       return 1;
+}
+#endif
+
+static int command_stats_client(rad_listen_t *listener, int argc, char *argv[])
+{
+       int auth = TRUE;
+       RADCLIENT *client;
+
+       if (argc < 1) {
+               cprintf(listener, "ERROR: Must specify [auth/acct]\n");
+               return 0;
+       }
+
+       if (strcmp(argv[0], "auth") == 0) {
+               auth = TRUE;
+
+       } else if (strcmp(argv[0], "acct") == 0) {
+#ifdef WITH_ACCOUNTING
+               auth = FALSE;
+#else
+               cprintf(listener, "ERROR: This server was built without accounting support.\n");
+               return 0;
+#endif
+
+       } else {
+               cprintf(listener, "ERROR: Unknown statistics type\n");
+               return 0;
+       }
+
+       /*
+        *      Global results for all client.
+        */
+       if (argc == 1) {
+#ifdef WITH_ACCOUNTING
+               if (!auth) {
+                       return command_print_stats(listener,
+                                                  &radius_acct_stats, auth);
+               }
+#endif
+               return command_print_stats(listener, &radius_auth_stats, auth);
+       }
+
+       client = get_client(listener, argc - 1, argv + 1);
+       if (!client) {
+               return 0;
+       }
+
+#ifdef WITH_ACCOUNTING
+       if (!auth) {
+               return command_print_stats(listener, client->acct, auth);
+       }
+#endif
+
+       return command_print_stats(listener, client->auth, auth);
+}
+
+
+static int command_add_client_file(rad_listen_t *listener, int argc, char *argv[])
+{
+       RADCLIENT *c;
+
+       if (argc < 1) {
+               cprintf(listener, "ERROR: <file> is required\n");
+               return 0;
+       }
+
+       /*
+        *      Read the file and generate the client.
+        */
+       c = client_read(argv[0], FALSE, FALSE);
+       if (!c) {
+               cprintf(listener, "ERROR: Unknown error reading client file.\n");
+               return 0;
+       }
+
+       if (!client_add(NULL, c)) {
+               cprintf(listener, "ERROR: Unknown error inserting new client.\n");
+               client_free(c);
+               return 0;
+       }
+
+       return 1;
+}
+
+
+static int command_del_client(rad_listen_t *listener, int argc, char *argv[])
+{
+#ifdef WITH_DYNAMIC_CLIENTS
+       RADCLIENT *client;
+
+       client = get_client(listener, argc - 1, argv + 1);
+       if (!client) return 0;
+
+       if (!client->dynamic) {
+               cprintf(listener, "ERROR: Client %s was not dynamically defined.\n", argv[1]);
+               return 0;
+       }
+
+       /*
+        *      DON'T delete it.  Instead, mark it as "dead now".  The
+        *      next time we receive a packet for the client, it will
+        *      be deleted.
+        *
+        *      If we don't receive a packet from it, the client
+        *      structure will stick around for a while.  Oh well...
+        */
+       client->lifetime = 1;
+#else
+       cprintf(listener, "ERROR: Dynamic clients are not supported.\n");
+#endif
+
+       return 1;
+}
+
+
+static fr_command_table_t command_table_del_client[] = {
+       { "ipaddr", FR_WRITE,
+         "del client ipaddr <ipaddr> - Delete a dynamically created client",
+         command_del_client, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static fr_command_table_t command_table_del[] = {
+       { "client", FR_WRITE,
+         "del client <command> - Delete client configuration commands",
+         NULL, command_table_del_client },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static fr_command_table_t command_table_add_client[] = {
+       { "file", FR_WRITE,
+         "add client file <filename> - Add new client definition from <filename>",
+         command_add_client_file, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static fr_command_table_t command_table_add[] = {
+       { "client", FR_WRITE,
+         "add client <command> - Add client configuration commands",
+         NULL, command_table_add_client },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+#ifdef WITH_PROXY
+static fr_command_table_t command_table_set_home[] = {
+       { "state", FR_WRITE,
+         "set home_server state <ipaddr> <port> [proto] [alive|dead] - set state for given home server",
+         command_set_home_server_state, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+#endif
+
+static fr_command_table_t command_table_set_module[] = {
+       { "config", FR_WRITE,
+         "set module config <module> variable value - set configuration for <module>",
+         command_set_module_config, NULL },
+
+       { "status", FR_WRITE,
+         "set module status [alive|dead] - set the module to be alive or dead (always return \"fail\")",
+         command_set_module_status, NULL },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static fr_command_table_t command_table_set[] = {
+       { "module", FR_WRITE,
+         "set module <command> - set module commands",
+         NULL, command_table_set_module },
+#ifdef WITH_PROXY
+       { "home_server", FR_WRITE, 
+         "set home_server <command> - set home server commands",
+         NULL, command_table_set_home },
+#endif
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static fr_command_table_t command_table_stats[] = {
+       { "client", FR_READ,
+         "stats client [auth/acct] <ipaddr> "
+#ifdef WITH_TCP
+         "[proto] "
+#endif
+         "- show statistics for given client, or for all clients (auth or acct)",
+         command_stats_client, NULL },
+#ifdef WITH_PROXY
+       { "home_server", FR_READ,
+         "stats home_server [<ipaddr>/auth/acct] <port> - show statistics for given home server (ipaddr and port), or for all home servers (auth or acct)",
+         command_stats_home_server, NULL },
+#endif
+
+#ifdef WITH_DETAIL
+       { "detail", FR_READ,
+         "stats detail <filename> - show statistics for the given detail file",
+         command_stats_detail, NULL },
+#endif
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+static fr_command_table_t command_table[] = {
+       { "add", FR_WRITE, NULL, NULL, command_table_add },
+       { "debug", FR_WRITE,
+         "debug <command> - debugging commands",
+         NULL, command_table_debug },
+       { "del", FR_WRITE, NULL, NULL, command_table_del },
+       { "hup", FR_WRITE,
+         "hup [module] - sends a HUP signal to the server, or optionally to one module",
+         command_hup, NULL },
+       { "inject", FR_WRITE,
+         "inject <command> - commands to inject packets into a running server",
+         NULL, command_table_inject },
+       { "reconnect", FR_READ,
+         "reconnect - reconnect to a running server",
+         NULL, NULL },         /* just here for "help" */
+       { "terminate", FR_WRITE,
+         "terminate - terminates the server, and cause it to exit",
+         command_terminate, NULL },
+       { "set", FR_WRITE, NULL, NULL, command_table_set },
+       { "show",  FR_READ, NULL, NULL, command_table_show },
+       { "stats",  FR_READ, NULL, NULL, command_table_stats },
+
+       { NULL, 0, NULL, NULL, NULL }
+};
+
+
+static void command_socket_free(rad_listen_t *this)
+{
+       fr_command_socket_t *sock = this->data;
+
+       unlink(sock->copy);
+       free(sock->copy);
+       sock->copy = NULL;
+}
+
+
+/*
+ *     Parse the unix domain sockets.
+ *
+ *     FIXME: TCP + SSL, after RadSec is in.
+ */
+static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+       fr_command_socket_t *sock;
+
+       if (check_config) return 0;
 
        sock = this->data;
 
@@ -788,6 +1909,9 @@ static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
                return -1;
        }
 
+       sock->copy = NULL;
+       if (sock->path) sock->copy = strdup(sock->path);
+
 #if defined(HAVE_GETPEEREID) || defined (SO_PEERCRED)
        if (sock->uid_name) {
                struct passwd *pw;
@@ -823,6 +1947,17 @@ static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
 
 #endif
 
+       if (!sock->mode_name) {
+               sock->mode = FR_READ;
+       } else {
+               sock->mode = fr_str2int(mode_names, sock->mode_name, 0);
+               if (!sock->mode) {
+                       radlog(L_ERR, "Invalid mode name \"%s\"",
+                              sock->mode_name);
+                       return -1;
+               }
+       }
+
        /*
         *      FIXME: check for absolute pathnames?
         *      check for uid/gid on the other end...    
@@ -852,6 +1987,8 @@ static int command_socket_print(rad_listen_t *this, char *buffer, size_t bufsize
 static int str2argv(char *str, char **argv, int max_argc)
 {
        int argc = 0;
+       size_t len;
+       char buffer[1024];
 
        while (*str) {
                if (argc >= max_argc) return argc;
@@ -871,7 +2008,29 @@ static int str2argv(char *str, char **argv, int max_argc)
 
                if (!*str) return argc;
 
-               argv[argc] = str;
+               if ((*str == '\'') || (*str == '"')) {
+                       char *p = str;
+                       FR_TOKEN token;
+
+                       token = gettoken((const char **) &p, buffer,
+                                        sizeof(buffer));
+                       if ((token != T_SINGLE_QUOTED_STRING) &&
+                           (token != T_DOUBLE_QUOTED_STRING)) {
+                               return -1;
+                       }
+
+                       len = strlen(buffer);
+                       if (len >= (size_t) (p - str)) {
+                               return -1;
+                       }
+
+                       memcpy(str, buffer, len + 1);
+                       argv[argc] = str;
+                       str = p;
+
+               } else {
+                       argv[argc] = str;
+               }
                argc++;
 
                while (*str &&
@@ -884,6 +2043,26 @@ static int str2argv(char *str, char **argv, int max_argc)
        return argc;
 }
 
+static void print_help(rad_listen_t *listener,
+                      fr_command_table_t *table, int recursive)
+{
+       int i;
+       
+       for (i = 0; table[i].command != NULL; i++) {
+               if (table[i].help) {
+                       cprintf(listener, "%s\n",
+                               table[i].help);
+               } else {
+                       cprintf(listener, "%s <command> - do sub-command of %s\n",
+                               table[i].command, table[i].command);
+               }
+
+               if (recursive && table[i].table) {
+                       print_help(listener, table[i].table, recursive);
+               }
+       }
+}
+
 #define MAX_ARGV (16)
 
 /*
@@ -926,8 +2105,7 @@ static int command_domain_recv(rad_listen_t *listener,
                 */
                if ((co->offset == 0) && (co->buffer[0] == 0x04)) {
                close_socket:
-                       listener->status = RAD_LISTEN_STATUS_CLOSED;
-                       event_new_fd(listener);
+                       command_close_socket(listener);
                        return 0;
                }
 
@@ -975,8 +2153,16 @@ static int command_domain_recv(rad_listen_t *listener,
                co->offset++;
        } while (1);
 
+       DEBUG("radmin> %s", co->buffer);
+
        argc = str2argv(co->buffer, my_argv, MAX_ARGV);
-       if (argc == 0) goto do_next;
+       if (argc == 0) goto do_next; /* empty strings are OK */
+
+       if (argc < 0) {
+               cprintf(listener, "ERROR: Failed parsing command.\n");
+               goto do_next;
+       }
+
        argv = my_argv;
 
        for (len = 0; len <= co->offset; len++) {
@@ -1023,6 +2209,15 @@ static int command_domain_recv(rad_listen_t *listener,
        len = 0;
        for (i = 0; table[i].command != NULL; i++) {
                if (strcmp(table[i].command, argv[0]) == 0) {
+                       /*
+                        *      Check permissions.
+                        */
+                       if (((co->mode & FR_WRITE) == 0) &&
+                           ((table[i].mode & FR_WRITE) != 0)) {
+                               cprintf(listener, "ERROR: You do not have write permission.  See \"mode = rw\" in the \"listen\" section for this socket.\n");
+                               goto do_next;
+                       }
+
                        if (table[i].table) {
                                /*
                                 *      This is the last argument, but
@@ -1040,6 +2235,13 @@ static int command_domain_recv(rad_listen_t *listener,
                                goto retry;
                        }
 
+                       if ((argc == 2) && (strcmp(argv[1], "?") == 0)) goto do_help;
+
+                       if (!table[i].func) {
+                               cprintf(listener, "ERROR: Invalid command\n");
+                               goto do_next;
+                       }
+
                        len = 1;
                        rcode = table[i].func(listener,
                                              argc - 1, argv + 1);
@@ -1053,20 +2255,20 @@ static int command_domain_recv(rad_listen_t *listener,
        if (!len) {
                if ((strcmp(argv[0], "help") == 0) ||
                    (strcmp(argv[0], "?") == 0)) {
+                       int recursive;
+
                do_help:
-                       for (i = 0; table[i].command != NULL; i++) {
-                               if (table[i].help) {
-                                       cprintf(listener, "%s\n",
-                                               table[i].help);
-                               } else {
-                                       cprintf(listener, "%s <command> - do sub-command of %s\n",
-                                               table[i].command, table[i].command);
-                               }
+                       if ((argc > 1) && (strcmp(argv[1], "-r") == 0)) {
+                               recursive = TRUE;
+                       } else {
+                               recursive = FALSE;
                        }
+
+                       print_help(listener, table, recursive);
                        goto do_next;
                }
 
-               cprintf(listener, "ERROR: Unknown command \"%s\"\r\n",
+               cprintf(listener, "ERROR: Unknown command \"%s\"\n",
                        argv[0]);
        }
 
@@ -1099,6 +2301,9 @@ static int command_domain_accept(rad_listen_t *listener,
        salen = sizeof(src);
 
        DEBUG2(" ... new connection request on command socket.");
+
+       *pfun = NULL;
+       *prequest = NULL;
        
        newfd = accept(listener->fd, (struct sockaddr *) &src, &salen);
        if (newfd < 0) {
@@ -1110,7 +2315,7 @@ static int command_domain_accept(rad_listen_t *listener,
                }
 
                DEBUG2(" ... failed to accept connection.");
-               return -1;
+               return 0;
        }
 
        /*
@@ -1124,21 +2329,21 @@ static int command_domain_accept(rad_listen_t *listener,
                        radlog(L_ERR, "Failed getting peer credentials for %s: %s",
                               sock->path, strerror(errno));
                        close(newfd);
-                       return -1;
+                       return 0;
                }
 
                if (sock->uid_name && (sock->uid != uid)) {
                        radlog(L_ERR, "Unauthorized connection to %s from uid %ld",
                               sock->path, (long int) uid);
                        close(newfd);
-                       return -1;
+                       return 0;
                }
 
                if (sock->gid_name && (sock->gid != gid)) {
                        radlog(L_ERR, "Unauthorized connection to %s from gid %ld",
                               sock->path, (long int) gid);
                        close(newfd);
-                       return -1;
+                       return 0;
                }
        }
 
@@ -1150,14 +2355,14 @@ static int command_domain_accept(rad_listen_t *listener,
                radlog(L_ERR, "Failed writing initial data to socket: %s",
                       strerror(errno));
                close(newfd);
-               return -1;
+               return 0;
        }
        magic = htonl(1);       /* protocol version */
        if (write(newfd, &magic, 4) < 0) {
                radlog(L_ERR, "Failed writing initial data to socket: %s",
                       strerror(errno));
                close(newfd);
-               return -1;
+               return 0;
        }
 
 
@@ -1165,7 +2370,7 @@ static int command_domain_accept(rad_listen_t *listener,
         *      Add the new listener.
         */
        this = listen_alloc(listener->type);
-       if (!this) return -1;
+       if (!this) return 0;
 
        /*
         *      Copy everything, including the pointer to the socket
@@ -1180,6 +2385,7 @@ static int command_domain_accept(rad_listen_t *listener,
        sock->offset = 0;
        sock->user[0] = '\0';
        sock->path = ((fr_command_socket_t *) listener->data)->path;
+       sock->mode = ((fr_command_socket_t *) listener->data)->mode;
 
        this->fd = newfd;
        this->recv = command_domain_recv;