[Cryptech-Commits] [user/ft/libcli] 02/02: Update to work in our embedded ARM environment.

git at cryptech.is git at cryptech.is
Mon May 16 09:13:06 UTC 2016


This is an automated email from the git hooks/post-receive script.

fredrik at thulin.net pushed a commit to branch master
in repository user/ft/libcli.

commit 1a5727c568e36b927ef2088b2b02bae4c84933f3
Author: Fredrik Thulin <fredrik at thulin.net>
AuthorDate: Mon May 16 11:09:30 2016 +0200

    Update to work in our embedded ARM environment.
    
    The main areas of change are:
    
      1) No dynamic memory allocations
      2) Not socket oriented anymore
    
    There are some areas that might need revisiting (such as string
    formatting), and I think we ought to fuzz this code to make sure
    I did not break it. There are surely some bugs in there, like it
    seems one has to press CTRL+A twice to jump to the beginning of
    lines.
---
 Makefile  |   14 +-
 clitest.c |   99 ++-
 libcli.c  | 2328 +++++++++++++++++++++++--------------------------------------
 libcli.h  |   61 +-
 4 files changed, 995 insertions(+), 1507 deletions(-)

diff --git a/Makefile b/Makefile
index 3062635..c4d9425 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,9 @@
 # Build dynamic library by default
-DYNAMIC_LIB ?= 1
+DYNAMIC_LIB ?= 0
 # Build static library by default
 STATIC_LIB ?= 1
 # Run tests by default
-TESTS ?= 1
+TESTS ?= 0
 
 UNAME = $(shell sh -c 'uname -s 2>/dev/null || echo not')
 DESTDIR =
@@ -15,7 +15,13 @@ REVISION = 7
 LIB = libcli.so
 LIB_STATIC = libcli.a
 
-CC = gcc
+CC = arm-none-eabi-gcc
+CFLAGS += -mcpu=cortex-m4 -mthumb -mlittle-endian -mthumb-interwork
+CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16
+CFLAGS += -ffunction-sections -fdata-sections
+CFLAGS += -DCRYPTECH_NO_FDOPEN -DCRYPTECH_NO_SELECT
+CFLAGS += -ggdb -O2 -Wall -Wextra -Warray-bounds -Wl,--gc-sections -specs=nosys.specs -g
+#CC = gcc
 AR = ar
 ARFLAGS = rcs
 DEBUG = -g
@@ -28,7 +34,7 @@ ifeq ($(UNAME),Darwin)
 override LDFLAGS += -Wl,-install_name,$(LIB).$(MAJOR).$(MINOR)
 else
 override LDFLAGS += -Wl,-soname,$(LIB).$(MAJOR).$(MINOR)
-LIBS = -lcrypt
+#LIBS = -lcrypt
 endif
 
 ifeq (1,$(DYNAMIC_LIB))
diff --git a/clitest.c b/clitest.c
index ace87bc..b6260f7 100644
--- a/clitest.c
+++ b/clitest.c
@@ -202,8 +202,39 @@ void pc(UNUSED(struct cli_def *cli), const char *string)
 
 int main()
 {
-    struct cli_command *c;
     struct cli_def *cli;
+    struct cli_command cmd_test_s = {(char *) "test", cmd_test, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_simple_s = {(char *) "simple", NULL, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_simon_s = {(char *) "simon", NULL, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_set_s = {(char *) "set", NULL, 0, NULL, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+
+    struct cli_command cmd_show_s = {(char *) "show", NULL, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_show_regular_s = {(char *) "regular", cmd_show_regular, 0,
+					     (char *) "Show the how many times cli_regular has run",
+					     PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_show_counters_s = {(char *) "counters", cmd_test, 0,
+					      (char *) "Show the counters that the system uses",
+					      PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_show_junk_s = {(char *) "junk", cmd_test, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+
+    struct cli_command cmd_config_int_s = {(char *) "interface", cmd_config_int, 0,
+					   (char *) "Configure an interface",
+					   PRIVILEGE_PRIVILEGED, MODE_CONFIG, NULL, NULL, NULL};
+    struct cli_command cmd_config_int_exit_s = {(char *) "exit", cmd_config_int_exit, 0,
+						(char *) "Exit from interface configuration",
+						PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, NULL, NULL, NULL};
+    struct cli_command cmd_config_int_address_s = {(char *) "address", cmd_test, 0,
+						   (char *) "Set IP address",
+						   PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, NULL, NULL, NULL};
+    struct cli_command cmd_debug_s = {(char *) "debug", NULL, 0, NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_debug_regular_s = {(char *) "regular", cmd_debug_regular, 0,
+					      (char *) "Enable cli_regular() callback debugging",
+					      PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    struct cli_command cmd_context_s = {(char *) "context", cmd_context, 0,
+					(char *) "Test a user-specified context",
+					PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+
+
     int s, x;
     struct sockaddr_in addr;
     int on = 1;
@@ -219,57 +250,51 @@ int main()
 #endif
 
     // Prepare a small user context
-    char mymessage[] = "I contain user data!";
+    static char mymessage[] = "I contain user data!";
+    static char banner[] = "libcli test environment";
+    static char hostname[] = "router";
     struct my_context myctx;
     myctx.value = 5;
     myctx.message = mymessage;
 
-    cli = cli_init();
-    cli_set_banner(cli, "libcli test environment");
-    cli_set_hostname(cli, "router");
+    if (!(cli = calloc(sizeof(struct cli_def), 1)))
+        return 0;
+
+    if (! cli_init(cli)) {
+        printf("Error initialising CLI\n");
+        return 1;
+    }
+    cli_set_banner(cli, banner);
+    cli_set_hostname(cli, hostname);
     cli_telnet_protocol(cli, 1);
-    cli_regular(cli, regular_callback);
-    cli_regular_interval(cli, 5); // Defaults to 1 second
+    //cli_regular(cli, regular_callback);
+    //cli_regular_interval(cli, 5); // Defaults to 1 second
     cli_set_idle_timeout_callback(cli, 60, idle_timeout); // 60 second idle timeout
-    cli_register_command(cli, NULL, "test", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
-
-    cli_register_command(cli, NULL, "simple", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
-
-    cli_register_command(cli, NULL, "simon", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
-
-    cli_register_command(cli, NULL, "set", cmd_set, PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL);
+    cli_register_command2(cli, &cmd_test_s, NULL);
+    cli_register_command2(cli, &cmd_simple_s, NULL);
+    cli_register_command2(cli, &cmd_simon_s, NULL);
+    cli_register_command2(cli, &cmd_set_s, NULL);
 
-    c = cli_register_command(cli, NULL, "show", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
+    cli_register_command2(cli, &cmd_show_s, NULL);
+    cli_register_command2(cli, &cmd_show_regular_s, &cmd_show_s);
+    cli_register_command2(cli, &cmd_show_counters_s, &cmd_show_s);
+    cli_register_command2(cli, &cmd_show_junk_s, &cmd_show_s);
 
-    cli_register_command(cli, c, "regular", cmd_show_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
-                         "Show the how many times cli_regular has run");
+    cli_register_command2(cli, &cmd_config_int_s, NULL);
+    cli_register_command2(cli, &cmd_config_int_exit_s, NULL);
+    cli_register_command2(cli, &cmd_config_int_address_s, NULL);
 
-    cli_register_command(cli, c, "counters", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
-                         "Show the counters that the system uses");
-
-    cli_register_command(cli, c, "junk", cmd_test, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
-
-    cli_register_command(cli, NULL, "interface", cmd_config_int, PRIVILEGE_PRIVILEGED, MODE_CONFIG,
-                         "Configure an interface");
-
-    cli_register_command(cli, NULL, "exit", cmd_config_int_exit, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT,
-                         "Exit from interface configuration");
-
-    cli_register_command(cli, NULL, "address", cmd_test, PRIVILEGE_PRIVILEGED, MODE_CONFIG_INT, "Set IP address");
-
-    c = cli_register_command(cli, NULL, "debug", NULL, PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL);
-
-    cli_register_command(cli, c, "regular", cmd_debug_regular, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
-                         "Enable cli_regular() callback debugging");
+    cli_register_command2(cli, &cmd_debug_s, NULL);
+    cli_register_command2(cli, &cmd_debug_regular_s, &cmd_debug_s);
 
     // Set user context and its command
     cli_set_context(cli, (void*)&myctx);
-    cli_register_command(cli, NULL, "context", cmd_context, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
-                         "Test a user-specified context");
+    cli_register_command2(cli, &cmd_context_s, NULL);
 
     cli_set_auth_callback(cli, check_auth);
     cli_set_enable_callback(cli, check_enable);
     // Test reading from a file
+    /*
     {
         FILE *fh;
 
@@ -282,7 +307,7 @@ int main()
             fclose(fh);
         }
     }
-
+    */
     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
     {
         perror("socket");
diff --git a/libcli.c b/libcli.c
index 3893b2a..3e33bd2 100644
--- a/libcli.c
+++ b/libcli.c
@@ -3,20 +3,23 @@
 #include <windows.h>
 #endif
 
+#define CRYPTECH_NO_CRYPT
+#define CRYPTECH_NO_MEMORY_H
+//#define CRYPTECH_NO_FDOPEN
+//#define CRYPTECH_NO_SELECT
+#define CRYPTECH_NO_REGEXP
+
 #define _GNU_SOURCE
 #include <stdio.h>
 #include <errno.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#ifndef CRYPTECH_NO_MEMORY_H
 #include <memory.h>
-#if !defined(__APPLE__) && !defined(__FreeBSD__)
-#include <malloc.h>
-#endif
+#endif /* CRYPTECH_NO_MEMORY_H */
 #include <string.h>
-#include <ctype.h>
 #include <unistd.h>
-#include <time.h>
-#ifndef WIN32
+#if !defined(WIN32) && !defined(CRYPTECH_NO_REGEXP)
 #include <regex.h>
 #endif
 #include "libcli.h"
@@ -32,64 +35,11 @@
 #define MATCH_REGEX     1
 #define MATCH_INVERT    2
 
-#ifdef WIN32
-/*
- * Stupid windows has multiple namespaces for filedescriptors, with different
- * read/write functions required for each ..
- */
-int read(int fd, void *buf, unsigned int count) {
-    return recv(fd, buf, count, 0);
-}
-
-int write(int fd, const void *buf, unsigned int count) {
-    return send(fd, buf, count, 0);
-}
-
-int vasprintf(char **strp, const char *fmt, va_list args) {
-    int size;
-
-    size = vsnprintf(NULL, 0, fmt, args);
-    if ((*strp = malloc(size + 1)) == NULL) {
-        return -1;
-    }
-
-    size = vsnprintf(*strp, size + 1, fmt, args);
-    return size;
-}
-
-int asprintf(char **strp, const char *fmt, ...) {
-    va_list args;
-    int size;
-
-    va_start(args, fmt);
-    size = vasprintf(strp, fmt, args);
-
-    va_end(args);
-    return size;
-}
-
-int fprintf(FILE *stream, const char *fmt, ...) {
-    va_list args;
-    int size;
-    char *buf;
-
-    va_start(args, fmt);
-    size = vasprintf(&buf, fmt, args);
-    if (size < 0) {
-        goto out;
-    }
-    size = write(stream->_file, buf, size);
-    free(buf);
-
-out:
-    va_end(args);
-    return size;
-}
-
+#ifdef CRYPTECH_NO_REGEXP
 /*
- * Dummy definitions to allow compilation on Windows
+ * Dummy definitions to allow compilation on Cryptech STM32 MCU
  */
-int regex_dummy() {return 0;};
+int regex_dummy() {return 0;}
 #define regfree(...) regex_dummy()
 #define regexec(...) regex_dummy()
 #define regcomp(...) regex_dummy()
@@ -97,15 +47,7 @@ int regex_dummy() {return 0;};
 #define REG_NOSUB       0
 #define REG_EXTENDED    0
 #define REG_ICASE       0
-#endif
-
-enum cli_states {
-    STATE_LOGIN,
-    STATE_PASSWORD,
-    STATE_NORMAL,
-    STATE_ENABLE_PASSWORD,
-    STATE_ENABLE
-};
+#endif /* CRYPTECH_NO_REGEXP */
 
 struct unp {
     char *username;
@@ -119,35 +61,32 @@ struct cli_filter_cmds
     const char *help;
 };
 
-/* free and zero (to avoid double-free) */
-#define free_z(p) do { if (p) { free(p); (p) = 0; } } while (0)
-
-int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
-int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
-int cli_count_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt);
-int cli_match_filter(struct cli_def *cli, const char *string, void *data);
-int cli_range_filter(struct cli_def *cli, const char *string, void *data);
-int cli_count_filter(struct cli_def *cli, const char *string, void *data);
-
 static struct cli_filter_cmds filter_cmds[] =
 {
-    { "begin",   "Begin with lines that match" },
-    { "between", "Between lines that match" },
-    { "count",   "Count of lines"   },
-    { "exclude", "Exclude lines that match" },
-    { "include", "Include lines that match" },
-    { "grep",    "Include lines that match regex (options: -v, -i, -e)" },
-    { "egrep",   "Include lines that match extended regex" },
+    /* cryptech: removed all filters, was using dynamic memory and seemed an unneccessarily big attack surface */
     { NULL, NULL}
 };
 
-static ssize_t _write(int fd, const void *buf, size_t count)
+static int isspace(int c)
+{
+    /* isspace() not provided with gcc-arm-none-eabi 4.9.3 */
+    return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
+}
+
+static ssize_t _write(struct cli_def *cli, struct cli_loop_ctx *ctx, const void *buf, size_t count)
 {
     size_t written = 0;
-    ssize_t thisTime =0;
+    ssize_t thisTime = 0;
+
+    if (! cli->write_callback && ! ctx->sockfd)
+        return -1;
+
     while (count != written)
     {
-        thisTime = write(fd, (char*)buf + written, count - written);
+        if (cli->write_callback)
+	    thisTime = cli->write_callback(cli, (char*)buf + written, count - written);
+	else
+	    thisTime = write(ctx->sockfd, (char*)buf + written, count - written);
         if (thisTime == -1)
         {
             if (errno == EINTR)
@@ -159,29 +98,34 @@ static ssize_t _write(int fd, const void *buf, size_t count)
     }
     return written;
 }
+
+/* cryptech: made this function use a static buffer instead of dynamically allocated memory
+   for command "show counters" this function is called on command "counters", so the string
+   is built backwards while there still are parent nodes.
+ */
 char *cli_command_name(struct cli_def *cli, struct cli_command *command)
 {
-    char *name = cli->commandname;
-    char *o;
-
-    if (name) free(name);
-    if (!(name = calloc(1, 1)))
-        return NULL;
+    static char buf[CLI_MAX_CMD_NAME_LEN];
+    buf[CLI_MAX_CMD_NAME_LEN - 1] = 0;
+    int idx = CLI_MAX_CMD_NAME_LEN - 1;
 
     while (command)
     {
-        o = name;
-        if (asprintf(&name, "%s%s%s", command->command, *o ? " " : "", o) == -1)
+      /* XXX what to do if there is no room left in buf? */
+      if (idx > (int) strlen(command->command) + 1)
         {
-            fprintf(stderr, "Couldn't allocate memory for command_name: %s", strerror(errno));
-            free(o);
-            return NULL;
+	    if (buf[idx])
+	    {
+	        /* this is not the first command, need to add a space */
+		buf[--idx] = ' ';
+	    }
+	    idx -= strlen(command->command);
+	    memcpy(buf + idx, command->command, strlen(command->command));
         }
         command = command->parent;
-        free(o);
     }
-    cli->commandname = name;
-    return name;
+
+    return buf + idx;
 }
 
 void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *))
@@ -194,88 +138,24 @@ void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const c
     cli->enable_callback = enable_callback;
 }
 
-void cli_allow_user(struct cli_def *cli, const char *username, const char *password)
-{
-    struct unp *u, *n;
-    if (!(n = malloc(sizeof(struct unp))))
-    {
-        fprintf(stderr, "Couldn't allocate memory for user: %s", strerror(errno));
-        return;
-    }
-    if (!(n->username = strdup(username)))
-    {
-        fprintf(stderr, "Couldn't allocate memory for username: %s", strerror(errno));
-        free(n);
-        return;
-    }
-    if (!(n->password = strdup(password)))
-    {
-        fprintf(stderr, "Couldn't allocate memory for password: %s", strerror(errno));
-        free(n->username);
-        free(n);
-        return;
-    }
-    n->next = NULL;
-
-    if (!cli->users)
-    {
-        cli->users = n;
-    }
-    else
-    {
-        for (u = cli->users; u && u->next; u = u->next);
-        if (u) u->next = n;
-    }
-}
-
-void cli_allow_enable(struct cli_def *cli, const char *password)
-{
-    free_z(cli->enable_password);
-    if (!(cli->enable_password = strdup(password)))
-    {
-        fprintf(stderr, "Couldn't allocate memory for enable password: %s", strerror(errno));
-    }
-}
 
-void cli_deny_user(struct cli_def *cli, const char *username)
-{
-    struct unp *u, *p = NULL;
-    if (!cli->users) return;
-    for (u = cli->users; u; u = u->next)
-    {
-        if (strcmp(username, u->username) == 0)
-        {
-            if (p)
-                p->next = u->next;
-            else
-                cli->users = u->next;
-            free(u->username);
-            free(u->password);
-            free(u);
-            break;
-        }
-        p = u;
-    }
-}
+/* cryptech: removed unused function: cli_allow_user */
+/* cryptech: removed unused function: cli_allow_enable */
+/* cryptech: removed unused function: cli_deny_user */
 
-void cli_set_banner(struct cli_def *cli, const char *banner)
+void cli_set_banner(struct cli_def *cli, char *banner)
 {
-    free_z(cli->banner);
-    if (banner && *banner)
-        cli->banner = strdup(banner);
+    cli->banner = banner;
 }
 
-void cli_set_hostname(struct cli_def *cli, const char *hostname)
+void cli_set_hostname(struct cli_def *cli, char *hostname)
 {
-    free_z(cli->hostname);
-    if (hostname && *hostname)
-        cli->hostname = strdup(hostname);
+    cli->hostname = hostname;
 }
 
-void cli_set_promptchar(struct cli_def *cli, const char *promptchar)
+void cli_set_promptchar(struct cli_def *cli, char *promptchar)
 {
-    free_z(cli->promptchar);
-    cli->promptchar = strdup(promptchar);
+    cli->promptchar = promptchar;
 }
 
 static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands)
@@ -324,23 +204,23 @@ int cli_set_privilege(struct cli_def *cli, int priv)
 
     if (priv != old)
     {
-        cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? "# " : "> ");
+        static char priv_prompt[] = "# ", nopriv_prompt[] = "> ";
+        cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? priv_prompt : nopriv_prompt);
         cli_build_shortest(cli, cli->commands);
     }
 
     return old;
 }
 
-void cli_set_modestring(struct cli_def *cli, const char *modestring)
+void cli_set_modestring(struct cli_def *cli, char *modestring)
 {
-    free_z(cli->modestring);
-    if (modestring)
-        cli->modestring = strdup(modestring);
+    cli->modestring = modestring;
 }
 
 int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
 {
     int old = cli->mode;
+    static char string[64];
     cli->mode = mode;
 
     if (mode != old)
@@ -352,13 +232,13 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
         }
         else if (config_desc && *config_desc)
         {
-            char string[64];
             snprintf(string, sizeof(string), "(config-%s)", config_desc);
             cli_set_modestring(cli, string);
         }
         else
         {
-            cli_set_modestring(cli, "(config)");
+	    snprintf(string, sizeof(string), "(config)");
+            cli_set_modestring(cli, string);
         }
 
         cli_build_shortest(cli, cli->commands);
@@ -367,67 +247,38 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
     return old;
 }
 
-struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command, int
-                                         (*callback)(struct cli_def *cli, const char *, char **, int), int privilege,
-                                         int mode, const char *help)
+void cli_register_command2(struct cli_def *cli, struct cli_command *cmd, struct cli_command *parent)
 {
-    struct cli_command *c, *p;
-
-    if (!command) return NULL;
-    if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
-
-    c->callback = callback;
-    c->next = NULL;
-    if (!(c->command = strdup(command)))
-        return NULL;
-    c->parent = parent;
-    c->privilege = privilege;
-    c->mode = mode;
-    if (help && !(c->help = strdup(help)))
-        return NULL;
+    struct cli_command *p;
 
     if (parent)
     {
+        cmd->parent = parent;
         if (!parent->children)
         {
-            parent->children = c;
+            parent->children = cmd;
         }
         else
         {
             for (p = parent->children; p && p->next; p = p->next);
-            if (p) p->next = c;
+            if (p) p->next = cmd;
         }
     }
     else
     {
         if (!cli->commands)
         {
-            cli->commands = c;
+            cli->commands = cmd;
         }
         else
         {
-            for (p = cli->commands; p && p->next; p = p->next);
-            if (p) p->next = c;
+	    for (p = cli->commands; p && p->next; p = p->next);
+            if (p) p->next = cmd;
         }
     }
-    return c;
 }
 
-static void cli_free_command(struct cli_command *cmd)
-{
-    struct cli_command *c, *p;
-
-    for (c = cmd->children; c;)
-    {
-        p = c->next;
-        cli_free_command(c);
-        c = p;
-    }
-
-    free(cmd->command);
-    if (cmd->help) free(cmd->help);
-    free(cmd);
-}
+/* cryptech: removed unused function cli_free_command */
 
 int cli_unregister_command(struct cli_def *cli, const char *command)
 {
@@ -445,7 +296,6 @@ int cli_unregister_command(struct cli_def *cli, const char *command)
             else
                 cli->commands = c->next;
 
-            cli_free_command(c);
             return CLI_OK;
         }
         p = c;
@@ -487,7 +337,7 @@ int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char
     else
     {
         /* require password entry */
-        cli->state = STATE_ENABLE_PASSWORD;
+        cli->state = CLI_STATE_ENABLE_PASSWORD;
     }
 
     return CLI_OK;
@@ -514,7 +364,7 @@ int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(cha
     cli_error(cli, "\nCommand history:");
     for (i = 0; i < MAX_HISTORY; i++)
     {
-        if (cli->history[i])
+        if (cli->history[i][0])
             cli_error(cli, "%3d. %s", i, cli->history[i]);
     }
 
@@ -554,101 +404,60 @@ int cli_int_configure_terminal(struct cli_def *cli, UNUSED(const char *command),
     return CLI_OK;
 }
 
-struct cli_def *cli_init()
+int cli_init(struct cli_def *cli)
 {
-    struct cli_def *cli;
-    struct cli_command *c;
+    static struct cli_command cmd_int_help_s = {(char *) "help", cli_int_help, 0,
+						(char *) "Show available commands",
+						PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL};
+    static struct cli_command cmd_int_quit_s = {(char *) "quit", cli_int_quit, 0,
+						(char *) "Disconnect",
+						PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL};
+    static struct cli_command cmd_int_logout_s = {(char *) "logout", cli_int_quit, 0,
+						  (char *) "Disconnect",
+						  PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL};
+    static struct cli_command cmd_int_exit_s = {(char *) "exit", cli_int_exit, 0,
+						(char *) "Exit from current mode",
+						PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL};
+    static struct cli_command cmd_int_history_s = {(char *) "history", cli_int_history, 0,
+						   (char *) "Show a list of previously run commands",
+						   PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL};
+    static struct cli_command cmd_int_enable_s = {(char *) "enable", cli_int_enable, 0,
+						  (char *) "Turn on privileged commands",
+						  PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    static struct cli_command cmd_int_disable_s = {(char *) "disable", cli_int_disable, 0,
+						   (char *) "Turn off privileged commands",
+						   PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    static struct cli_command cmd_int_configure_s = {(char *) "configure", NULL, 0,
+						     (char *) "Enter configuration mode",
+						     PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
+    static struct cli_command cmd_int_configure_terminal_s = {(char *) "terminal", cli_int_configure_terminal, 0,
+							      (char *) "Configure from the terminal",
+							      PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL};
 
-    if (!(cli = calloc(sizeof(struct cli_def), 1)))
-        return 0;
-
-    cli->buf_size = 1024;
-    if (!(cli->buffer = calloc(cli->buf_size, 1)))
-    {
-        free_z(cli);
-        return 0;
-    }
     cli->telnet_protocol = 1;
 
-    cli_register_command(cli, 0, "help", cli_int_help, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Show available commands");
-    cli_register_command(cli, 0, "quit", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
-    cli_register_command(cli, 0, "logout", cli_int_quit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Disconnect");
-    cli_register_command(cli, 0, "exit", cli_int_exit, PRIVILEGE_UNPRIVILEGED, MODE_ANY, "Exit from current mode");
-    cli_register_command(cli, 0, "history", cli_int_history, PRIVILEGE_UNPRIVILEGED, MODE_ANY,
-                         "Show a list of previously run commands");
-    cli_register_command(cli, 0, "enable", cli_int_enable, PRIVILEGE_UNPRIVILEGED, MODE_EXEC,
-                         "Turn on privileged commands");
-    cli_register_command(cli, 0, "disable", cli_int_disable, PRIVILEGE_PRIVILEGED, MODE_EXEC,
-                         "Turn off privileged commands");
-
-    c = cli_register_command(cli, 0, "configure", 0, PRIVILEGE_PRIVILEGED, MODE_EXEC, "Enter configuration mode");
-    cli_register_command(cli, c, "terminal", cli_int_configure_terminal, PRIVILEGE_PRIVILEGED, MODE_EXEC,
-                         "Configure from the terminal");
+    cli_register_command2(cli, &cmd_int_help_s, NULL);
+    cli_register_command2(cli, &cmd_int_quit_s, NULL);
+    cli_register_command2(cli, &cmd_int_logout_s, NULL);
+    cli_register_command2(cli, &cmd_int_exit_s, NULL);
+    cli_register_command2(cli, &cmd_int_history_s, NULL);
+    cli_register_command2(cli, &cmd_int_enable_s, NULL);
+    cli_register_command2(cli, &cmd_int_disable_s, NULL);
+
+    cli_register_command2(cli, &cmd_int_configure_s, NULL);
+    cli_register_command2(cli, &cmd_int_configure_terminal_s, &cmd_int_configure_s);
 
     cli->privilege = cli->mode = -1;
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
     cli_set_configmode(cli, MODE_EXEC, 0);
 
-    // Default to 1 second timeout intervals
-    cli->timeout_tm.tv_sec = 1;
-    cli->timeout_tm.tv_usec = 0;
-
-    // Set default idle timeout callback, but no timeout
-    cli_set_idle_timeout_callback(cli, 0, cli_int_idle_timeout);
-    return cli;
+    return 1;
 }
 
-void cli_unregister_all(struct cli_def *cli, struct cli_command *command)
-{
-    struct cli_command *c, *p = NULL;
-
-    if (!command) command = cli->commands;
-    if (!command) return;
-
-    for (c = command; c; )
-    {
-        p = c->next;
-
-        // Unregister all child commands
-        if (c->children)
-            cli_unregister_all(cli, c->children);
-
-        if (c->command) free(c->command);
-        if (c->help) free(c->help);
-        free(c);
-
-        c = p;
-    }
-}
+/* cryptech: removed unused function cli_unregister_all */
 
 int cli_done(struct cli_def *cli)
 {
-    struct unp *u = cli->users, *n;
-
-    if (!cli) return CLI_OK;
-    cli_free_history(cli);
-
-    // Free all users
-    while (u)
-    {
-        if (u->username) free(u->username);
-        if (u->password) free(u->password);
-        n = u->next;
-        free(u);
-        u = n;
-    }
-
-    /* free all commands */
-    cli_unregister_all(cli, 0);
-
-    free_z(cli->commandname);
-    free_z(cli->modestring);
-    free_z(cli->banner);
-    free_z(cli->promptchar);
-    free_z(cli->hostname);
-    free_z(cli->buffer);
-    free_z(cli);
-
     return CLI_OK;
 }
 
@@ -657,32 +466,25 @@ static int cli_add_history(struct cli_def *cli, const char *cmd)
     int i;
     for (i = 0; i < MAX_HISTORY; i++)
     {
-        if (!cli->history[i])
+        if (!cli->history[i][0])
         {
             if (i == 0 || strcasecmp(cli->history[i-1], cmd))
-            if (!(cli->history[i] = strdup(cmd)))
-                return CLI_ERROR;
+	    {
+	      strncpy(cli->history[i], cmd, HISTORY_CMD_LEN - 1);
+	      cli->history[i][HISTORY_CMD_LEN - 1] = 0;
+	    }
             return CLI_OK;
         }
     }
     // No space found, drop one off the beginning of the list
-    free(cli->history[0]);
     for (i = 0; i < MAX_HISTORY-1; i++)
-        cli->history[i] = cli->history[i+1];
-    if (!(cli->history[MAX_HISTORY - 1] = strdup(cmd)))
-        return CLI_ERROR;
+        memcpy(cli->history[i], cli->history[i+1], HISTORY_CMD_LEN);
+    strncpy(cli->history[MAX_HISTORY-1], cmd, HISTORY_CMD_LEN - 1);
+    cli->history[MAX_HISTORY-1][HISTORY_CMD_LEN - 1] = 0;
     return CLI_OK;
 }
 
-void cli_free_history(struct cli_def *cli)
-{
-    int i;
-    for (i = 0; i < MAX_HISTORY; i++)
-    {
-        if (cli->history[i])
-            free_z(cli->history[i]);
-    }
-}
+/* cryptech: removed unused cli_free_history */
 
 static int cli_parse_line(const char *line, char *words[], int max_words)
 {
@@ -690,10 +492,12 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
     const char *p = line;
     const char *word_start = 0;
     int inquote = 0;
+    static char buf[CLI_MAX_LINE_LENGTH];
+    char *ptr = buf;
 
     while (*p)
     {
-        if (!isspace(*p))
+      if (!isspace((unsigned char) *p))
         {
             word_start = p;
             break;
@@ -701,16 +505,19 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
         p++;
     }
 
+    memset(buf, 0, sizeof(buf));
+
     while (nwords < max_words - 1)
     {
-        if (!*p || *p == inquote || (word_start && !inquote && (isspace(*p) || *p == '|')))
+      if (!*p || *p == inquote || (word_start && !inquote && (isspace((unsigned char) *p) || *p == '|')))
         {
             if (word_start)
             {
                 int len = p - word_start;
 
-                memcpy(words[nwords] = malloc(len + 1), word_start, len);
-                words[nwords++][len] = 0;
+		memcpy(ptr, word_start, len);
+                words[nwords++] = ptr;
+		ptr += len + 1;
             }
 
             if (!*p)
@@ -736,7 +543,7 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
                     if (!(words[nwords++] = strdup("|")))
                         return 0;
                 }
-                else if (!isspace(*p))
+                else if (!isspace((unsigned char) *p))
                     word_start = p;
             }
 
@@ -747,33 +554,7 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
     return nwords;
 }
 
-static char *join_words(int argc, char **argv)
-{
-    char *p;
-    int len = 0;
-    int i;
-
-    for (i = 0; i < argc; i++)
-    {
-        if (i)
-            len += 1;
-
-        len += strlen(argv[i]);
-    }
-
-    p = malloc(len + 1);
-    p[0] = 0;
-
-    for (i = 0; i < argc; i++)
-    {
-        if (i)
-            strcat(p, " ");
-
-        strcat(p, argv[i]);
-    }
-
-    return p;
-}
+/* cryptech: removed unused function join_words */
 
 static int cli_find_command(struct cli_def *cli, struct cli_command *commands, int num_words, char *words[],
                             int start_word, int filters[])
@@ -823,8 +604,6 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
         if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL))
         {
             int rc = CLI_OK;
-            int f;
-            struct cli_filter **filt = &cli->filters;
 
             // Found a word!
             if (!c->children)
@@ -863,95 +642,20 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
                 return rc;
             }
 
+	    CORRECT_CHECKS:
+
             if (!c->callback)
             {
                 cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c));
                 return CLI_ERROR;
             }
 
-            CORRECT_CHECKS:
-            for (f = 0; rc == CLI_OK && filters[f]; f++)
-            {
-                int n = num_words;
-                char **argv;
-                int argc;
-                int len;
-
-                if (filters[f+1])
-                n = filters[f+1];
-
-                if (filters[f] == n - 1)
-                {
-                    cli_error(cli, "Missing filter");
-                    return CLI_ERROR;
-                }
-
-                argv = words + filters[f] + 1;
-                argc = n - (filters[f] + 1);
-                len = strlen(argv[0]);
-                if (argv[argc - 1][strlen(argv[argc - 1]) - 1] == '?')
-                {
-                    if (argc == 1)
-                    {
-                        int i;
-                        for (i = 0; filter_cmds[i].cmd; i++)
-                            cli_error(cli, "  %-20s %s", filter_cmds[i].cmd, filter_cmds[i].help );
-                    }
-                    else
-                    {
-                        if (argv[0][0] != 'c') // count
-                            cli_error(cli, "  WORD");
-
-                        if (argc > 2 || argv[0][0] == 'c') // count
-                            cli_error(cli, "  <cr>");
-                    }
-
-                    return CLI_OK;
-                }
-
-                if (argv[0][0] == 'b' && len < 3) // [beg]in, [bet]ween
-                {
-                    cli_error(cli, "Ambiguous filter \"%s\" (begin, between)", argv[0]);
-                    return CLI_ERROR;
-                }
-                *filt = calloc(sizeof(struct cli_filter), 1);
-
-                if (!strncmp("include", argv[0], len) || !strncmp("exclude", argv[0], len) ||
-                    !strncmp("grep", argv[0], len) || !strncmp("egrep", argv[0], len))
-                    rc = cli_match_filter_init(cli, argc, argv, *filt);
-                else if (!strncmp("begin", argv[0], len) || !strncmp("between", argv[0], len))
-                    rc = cli_range_filter_init(cli, argc, argv, *filt);
-                else if (!strncmp("count", argv[0], len))
-                    rc = cli_count_filter_init(cli, argc, argv, *filt);
-                else
-                {
-                    cli_error(cli, "Invalid filter \"%s\"", argv[0]);
-                    rc = CLI_ERROR;
-                }
-
-                if (rc == CLI_OK)
-                {
-                    filt = &(*filt)->next;
-                }
-                else
-                {
-                    free(*filt);
-                    *filt = 0;
-                }
-            }
+	    /* cryptech: removed filter checking here */
 
             if (rc == CLI_OK)
                 rc = c->callback(cli, cli_command_name(cli, c), words + start_word + 1, c_words - start_word - 1);
 
-            while (cli->filters)
-            {
-                struct cli_filter *filt = cli->filters;
-
-                // call one last time to clean up
-                filt->filter(cli, NULL, filt->data);
-                cli->filters = filt->next;
-                free(filt);
-            }
+	    /* cryptech: removed filter cleanup here */
 
             return rc;
         }
@@ -996,8 +700,9 @@ int cli_run_command(struct cli_def *cli, const char *command)
     int filters[CLI_MAX_LINE_WORDS] = {0};
 
     if (!command) return CLI_ERROR;
-    while (isspace(*command))
+    while (isspace((int) *command)) {
         command++;
+    }
 
     if (!*command) return CLI_OK;
 
@@ -1015,9 +720,6 @@ int cli_run_command(struct cli_def *cli, const char *command)
     else
         r = CLI_ERROR;
 
-    for (i = 0; i < num_words; i++)
-        free(words[i]);
-
     if (r == CLI_QUIT)
         return r;
 
@@ -1028,15 +730,15 @@ static int cli_get_completions(struct cli_def *cli, const char *command, char **
 {
     struct cli_command *c;
     struct cli_command *n;
-    int num_words, save_words, i, k=0;
+    int num_words, i, k=0;
     char *words[CLI_MAX_LINE_WORDS] = {0};
     int filter = 0;
 
     if (!command) return 0;
-    while (isspace(*command))
+    while (isspace((unsigned char) *command))
         command++;
 
-    save_words = num_words = cli_parse_line(command, words, sizeof(words)/sizeof(words[0]));
+    num_words = cli_parse_line(command, words, sizeof(words)/sizeof(words[0]));
     if (!command[0] || command[strlen(command)-1] == ' ')
         num_words++;
 
@@ -1096,19 +798,17 @@ static int cli_get_completions(struct cli_def *cli, const char *command, char **
     }
 
 out:
-    for (i = 0; i < save_words; i++)
-        free(words[i]);
 
     return k;
 }
 
-static void cli_clear_line(int sockfd, char *cmd, int l, int cursor)
+static void cli_clear_line(struct cli_def *cli, struct cli_loop_ctx *ctx, char *cmd, int l, int cursor)
 {
     int i;
     if (cursor < l)
     {
         for (i = 0; i < (l - cursor); i++)
-            _write(sockfd, " ", 1);
+	  _write(cli, ctx, " ", 1);
     }
     for (i = 0; i < l; i++)
         cmd[i] = '\b';
@@ -1116,7 +816,7 @@ static void cli_clear_line(int sockfd, char *cmd, int l, int cursor)
         cmd[i] = ' ';
     for (; i < l * 3; i++)
         cmd[i] = '\b';
-    _write(sockfd, cmd, i);
+    _write(cli, ctx, cmd, i);
     memset((char *)cmd, 0, i);
     l = cursor = 0;
 }
@@ -1127,66 +827,40 @@ void cli_reprompt(struct cli_def *cli)
     cli->showprompt = 1;
 }
 
-void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli))
-{
-    if (!cli) return;
-    cli->regular_callback = callback;
-}
-
-void cli_regular_interval(struct cli_def *cli, int seconds)
-{
-    if (seconds < 1) seconds = 1;
-    cli->timeout_tm.tv_sec = seconds;
-    cli->timeout_tm.tv_usec = 0;
-}
-
-#define DES_PREFIX "{crypt}"        /* to distinguish clear text from DES crypted */
-#define MD5_PREFIX "$1$"
-
 static int pass_matches(const char *pass, const char *try)
 {
-    int des;
-    if ((des = !strncasecmp(pass, DES_PREFIX, sizeof(DES_PREFIX)-1)))
-        pass += sizeof(DES_PREFIX)-1;
-
-#ifndef WIN32
-    /*
-     * TODO - find a small crypt(3) function for use on windows
-     */
-    if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX)-1))
-        try = crypt(try, pass);
-#endif
-
+    /* cryptech: removed DES mode here */
     return !strcmp(pass, try);
 }
 
 #define CTRL(c) (c - '@')
 
-static int show_prompt(struct cli_def *cli, int sockfd)
+static int show_prompt(struct cli_def *cli, struct cli_loop_ctx *ctx)
 {
     int len = 0;
 
     if (cli->hostname)
-        len += write(sockfd, cli->hostname, strlen(cli->hostname));
+        len += _write(cli, ctx, cli->hostname, strlen(cli->hostname));
 
     if (cli->modestring)
-        len += write(sockfd, cli->modestring, strlen(cli->modestring));
+        len += _write(cli, ctx, cli->modestring, strlen(cli->modestring));
 
-    return len + write(sockfd, cli->promptchar, strlen(cli->promptchar));
+    return len + _write(cli, ctx, cli->promptchar, strlen(cli->promptchar));
 }
 
 int cli_loop(struct cli_def *cli, int sockfd)
 {
     unsigned char c;
-    int n, l, oldl = 0, is_telnet_option = 0, skip = 0, esc = 0;
-    int cursor = 0, insertmode = 1;
-    char *cmd = NULL, *oldcmd = 0;
-    char *username = NULL, *password = NULL;
+    int n = 0;
+    struct cli_loop_ctx ctx;
+
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.insertmode = 1;
+    ctx.sockfd = sockfd;
 
     cli_build_shortest(cli, cli->commands);
-    cli->state = STATE_LOGIN;
+    cli->state = CLI_STATE_LOGIN;
 
-    cli_free_history(cli);
     if (cli->telnet_protocol)
     {
         static const char *negotiate =
@@ -1194,12 +868,10 @@ int cli_loop(struct cli_def *cli, int sockfd)
             "\xFF\xFB\x01"
             "\xFF\xFD\x03"
             "\xFF\xFD\x01";
-        _write(sockfd, negotiate, strlen(negotiate));
+        _write(cli, &ctx, negotiate, strlen(negotiate));
     }
 
-    if ((cmd = malloc(CLI_MAX_LINE_LENGTH)) == NULL)
-        return CLI_ERROR;
-
+#ifndef CRYPTECH_NO_FDOPEN
 #ifdef WIN32
     /*
      * OMG, HACK
@@ -1211,774 +883,783 @@ int cli_loop(struct cli_def *cli, int sockfd)
     if (!(cli->client = fdopen(sockfd, "w+")))
         return CLI_ERROR;
 #endif
+#endif /* CRYPTECH_NO_FDOPEN */
 
     setbuf(cli->client, NULL);
     if (cli->banner)
         cli_error(cli, "%s", cli->banner);
 
-    // Set the last action now so we don't time immediately
-    if (cli->idle_timeout)
-        time(&cli->last_action);
-
     /* start off in unprivileged mode */
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
     cli_set_configmode(cli, MODE_EXEC, NULL);
 
     /* no auth required? */
     if (!cli->users && !cli->auth_callback)
-        cli->state = STATE_NORMAL;
+        cli->state = CLI_STATE_NORMAL;
 
     while (1)
     {
-        signed int in_history = 0;
-        int lastchar = 0;
-        struct timeval tm;
-
-        cli->showprompt = 1;
+	cli_loop_start_new_command(cli, &ctx);
 
-        if (oldcmd)
-        {
-            l = cursor = oldl;
-            oldcmd[l] = 0;
-            cli->showprompt = 1;
-            oldcmd = NULL;
-            oldl = 0;
-        }
-        else
+        while (1)
         {
-            memset(cmd, 0, CLI_MAX_LINE_LENGTH);
-            l = 0;
-            cursor = 0;
+	    cli_loop_show_prompt(cli, &ctx);
+
+	    n = cli_loop_read_next_char(cli, &ctx, &c);
+	    if (n == CLI_LOOP_CTRL_BREAK)
+	      break;
+	    if (n == CLI_LOOP_CTRL_CONTINUE)
+	      continue;
+
+	    n = cli_loop_process_char(cli, &ctx, c);
+	    if (n == CLI_LOOP_CTRL_BREAK)
+	      break;
+	    if (n == CLI_LOOP_CTRL_CONTINUE)
+	      continue;
         }
 
-        memcpy(&tm, &cli->timeout_tm, sizeof(tm));
-
-        while (1)
-        {
-            int sr;
-            fd_set r;
-            if (cli->showprompt)
-            {
-                if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                    _write(sockfd, "\r\n", 2);
 
-                switch (cli->state)
-                {
-                    case STATE_LOGIN:
-                        _write(sockfd, "Username: ", strlen("Username: "));
-                        break;
-
-                    case STATE_PASSWORD:
-                        _write(sockfd, "Password: ", strlen("Password: "));
-                        break;
-
-                    case STATE_NORMAL:
-                    case STATE_ENABLE:
-                        show_prompt(cli, sockfd);
-                        _write(sockfd, cmd, l);
-                        if (cursor < l)
-                        {
-                            int n = l - cursor;
-                            while (n--)
-                                _write(sockfd, "\b", 1);
-                        }
-                        break;
-
-                    case STATE_ENABLE_PASSWORD:
-                        _write(sockfd, "Password: ", strlen("Password: "));
-                        break;
+        if (ctx.l < 0) break;
 
-                }
+	n = cli_loop_process_cmd(cli, &ctx);
+	if (n == CLI_LOOP_CTRL_BREAK)
+	  break;
+    }
 
-                cli->showprompt = 0;
-            }
+    fclose(cli->client);
+    cli->client = 0;
+    return CLI_OK;
+}
 
-            FD_ZERO(&r);
-            FD_SET(sockfd, &r);
+void cli_loop_start_new_command(struct cli_def *cli, struct cli_loop_ctx *ctx)
+{
+    ctx->in_history = 0;
+    ctx->lastchar = 0;
 
-            if ((sr = select(sockfd + 1, &r, NULL, NULL, &tm)) < 0)
-            {
-                /* select error */
-                if (errno == EINTR)
-                    continue;
+    cli->showprompt = 1;
 
-                perror("select");
-                l = -1;
-                break;
-            }
+    if (ctx->restore_cmd_l > 0)
+    {
+	ctx->l = ctx->cursor = ctx->restore_cmd_l;
+	ctx->cmd[ctx->l] = 0;
+	ctx->restore_cmd_l = 0;
+    }
+    else
+    {
+	memset(ctx->cmd, 0, CLI_MAX_LINE_LENGTH);
+	ctx->l = 0;
+	ctx->cursor = 0;
+    }
 
-            if (sr == 0)
-            {
-                /* timeout every second */
-                if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK)
-                {
-                    l = -1;
-                    break;
-                }
+}
 
-                if (cli->idle_timeout)
-                {
-                    if (time(NULL) - cli->last_action >= cli->idle_timeout)
-                    {
-                        if (cli->idle_timeout_callback)
-                        {
-                            // Call the callback and continue on if successful
-                            if (cli->idle_timeout_callback(cli) == CLI_OK)
-                            {
-                                // Reset the idle timeout counter
-                                time(&cli->last_action);
-                                continue;
-                            }
-                        }
-                        // Otherwise, break out of the main loop
-                        l = -1;
-                        break;
-                    }
-                }
+int cli_loop_read_next_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char *c)
+{
+    int n;
+#ifndef CRYPTECH_NO_SELECT
+    struct timeval tm;
+    int sr;
+    fd_set r;
 
-                memcpy(&tm, &cli->timeout_tm, sizeof(tm));
-                continue;
-            }
+    FD_ZERO(&r);
+    FD_SET(ctx->sockfd, &r);
+    memcpy(&tm, &cli->timeout_tm, sizeof(tm));
 
-            if ((n = read(sockfd, &c, 1)) < 0)
-            {
-                if (errno == EINTR)
-                    continue;
+    if ((sr = select(ctx->sockfd + 1, &r, NULL, NULL, &tm)) < 0)
+    {
+	/* select error */
+	if (errno == EINTR)
+	    return CLI_LOOP_CTRL_CONTINUE;
 
-                perror("read");
-                l = -1;
-                break;
-            }
+	perror("select");
+	ctx->l = -1;
+	return CLI_LOOP_CTRL_BREAK;
+    }
 
-            if (cli->idle_timeout)
-                time(&cli->last_action);
+    if (sr == 0)
+    {
+	/* timeout every second */
+	if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK)
+	{
+	    ctx->l = -1;
+	    return CLI_LOOP_CTRL_BREAK;
+	}
+
+	if (cli->idle_timeout)
+	{
+	    if (time(NULL) - cli->last_action >= cli->idle_timeout)
+	    {
+		if (cli->idle_timeout_callback)
+		{
+		    // Call the callback and continue on if successful
+		    if (cli->idle_timeout_callback(cli) == CLI_OK)
+		    {
+			// Reset the idle timeout counter
+			time(&cli->last_action);
+			return CLI_LOOP_CTRL_CONTINUE;
+		    }
+		}
+		// Otherwise, break out of the main loop
+		ctx->l = -1;
+		return CLI_LOOP_CTRL_BREAK;
+	    }
+	}
+
+	memcpy(&tm, &cli->timeout_tm, sizeof(tm));
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
+#endif /* CRYPTECH_NO_SELECT */
 
-            if (n == 0)
-            {
-                l = -1;
-                break;
-            }
+    if (cli->read_callback)
+    {
+        if ((n = cli->read_callback(cli, c, (size_t) 1)) < 0)
+	{
+	    perror("read_callback");
+	    ctx->l = -1;
+	    return CLI_LOOP_CTRL_BREAK;
+	}
+    }
+    else
+    {
+	if ((n = read(ctx->sockfd, c, 1)) < 0)
+	{
+	    if (errno == EINTR)
+		return CLI_LOOP_CTRL_CONTINUE;
+
+	    perror("read");
+	    ctx->l = -1;
+	    return CLI_LOOP_CTRL_BREAK;
+	}
+    }
 
-            if (skip)
-            {
-                skip--;
-                continue;
-            }
+    if (n == 0)
+    {
+	ctx->l = -1;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            if (c == 255 && !is_telnet_option)
-            {
-                is_telnet_option++;
-                continue;
-            }
+    return 0;
+}
 
-            if (is_telnet_option)
-            {
-                if (c >= 251 && c <= 254)
-                {
-                    is_telnet_option = c;
-                    continue;
-                }
 
-                if (c != 255)
-                {
-                    is_telnet_option = 0;
-                    continue;
-                }
+void cli_loop_show_prompt(struct cli_def *cli, struct cli_loop_ctx *ctx)
+{
+    if (cli->showprompt)
+    {
+	if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+	    _write(cli, ctx, "\r\n", 2);
+
+	switch (cli->state)
+	{
+	case CLI_STATE_LOGIN:
+	    _write(cli, ctx, "Username: ", strlen("Username: "));
+	    break;
+
+	case CLI_STATE_PASSWORD:
+	    _write(cli, ctx, "Password: ", strlen("Password: "));
+	    break;
+
+	case CLI_STATE_NORMAL:
+	case CLI_STATE_ENABLE:
+	    show_prompt(cli, ctx);
+	    _write(cli, ctx, ctx->cmd, ctx->l);
+	    if (ctx->cursor < ctx->l)
+	    {
+		int n = ctx->l - ctx->cursor;
+		while (n--)
+		    _write(cli, ctx, "\b", 1);
+	    }
+	    break;
+
+	case CLI_STATE_ENABLE_PASSWORD:
+	    _write(cli, ctx, "Password: ", strlen("Password: "));
+	    break;
+
+	}
+
+	cli->showprompt = 0;
+    }
+}
 
-                is_telnet_option = 0;
-            }
 
-            /* handle ANSI arrows */
-            if (esc)
-            {
-                if (esc == '[')
-                {
-                    /* remap to readline control codes */
-                    switch (c)
-                    {
-                        case 'A': /* Up */
-                            c = CTRL('P');
-                            break;
-
-                        case 'B': /* Down */
-                            c = CTRL('N');
-                            break;
-
-                        case 'C': /* Right */
-                            c = CTRL('F');
-                            break;
-
-                        case 'D': /* Left */
-                            c = CTRL('B');
-                            break;
-
-                        default:
-                            c = 0;
-                    }
-
-                    esc = 0;
-                }
-                else
-                {
-                    esc = (c == '[') ? c : 0;
-                    continue;
-                }
-            }
-
-            if (c == 0) continue;
-            if (c == '\n') continue;
-
-            if (c == '\r')
-            {
-                if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                    _write(sockfd, "\r\n", 2);
-                break;
-            }
-
-            if (c == 27)
-            {
-                esc = 1;
-                continue;
-            }
-
-            if (c == CTRL('C'))
-            {
-                _write(sockfd, "\a", 1);
-                continue;
-            }
-
-            /* back word, backspace/delete */
-            if (c == CTRL('W') || c == CTRL('H') || c == 0x7f)
-            {
-                int back = 0;
-
-                if (c == CTRL('W')) /* word */
-                {
-                    int nc = cursor;
-
-                    if (l == 0 || cursor == 0)
-                        continue;
-
-                    while (nc && cmd[nc - 1] == ' ')
-                    {
-                        nc--;
-                        back++;
-                    }
-
-                    while (nc && cmd[nc - 1] != ' ')
-                    {
-                        nc--;
-                        back++;
-                    }
-                }
-                else /* char */
-                {
-                    if (l == 0 || cursor == 0)
-                    {
-                        _write(sockfd, "\a", 1);
-                        continue;
-                    }
-
-                    back = 1;
-                }
-
-                if (back)
-                {
-                    while (back--)
-                    {
-                        if (l == cursor)
-                        {
-                            cmd[--cursor] = 0;
-                            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                                _write(sockfd, "\b \b", 3);
-                        }
-                        else
-                        {
-                            int i;
-                            cursor--;
-                            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                            {
-                                for (i = cursor; i <= l; i++) cmd[i] = cmd[i+1];
-                                _write(sockfd, "\b", 1);
-                                _write(sockfd, cmd + cursor, strlen(cmd + cursor));
-                                _write(sockfd, " ", 1);
-                                for (i = 0; i <= (int)strlen(cmd + cursor); i++)
-                                    _write(sockfd, "\b", 1);
-                            }
-                        }
-                        l--;
-                    }
-
-                    continue;
-                }
-            }
-
-            /* redraw */
-            if (c == CTRL('L'))
-            {
-                int i;
-                int cursorback = l - cursor;
-
-                if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
-                    continue;
-
-                _write(sockfd, "\r\n", 2);
-                show_prompt(cli, sockfd);
-                _write(sockfd, cmd, l);
-
-                for (i = 0; i < cursorback; i++)
-                    _write(sockfd, "\b", 1);
-
-                continue;
-            }
-
-            /* clear line */
-            if (c == CTRL('U'))
-            {
-                if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
-                    memset(cmd, 0, l);
-                else
-                    cli_clear_line(sockfd, cmd, l, cursor);
-
-                l = cursor = 0;
-                continue;
-            }
-
-            /* kill to EOL */
-            if (c == CTRL('K'))
-            {
-                if (cursor == l)
-                    continue;
-
-                if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                {
-                    int c;
-                    for (c = cursor; c < l; c++)
-                        _write(sockfd, " ", 1);
-
-                    for (c = cursor; c < l; c++)
-                        _write(sockfd, "\b", 1);
-                }
-
-                memset(cmd + cursor, 0, l - cursor);
-                l = cursor;
-                continue;
-            }
-
-            /* EOT */
-            if (c == CTRL('D'))
-            {
-                if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
-                    break;
-
-                if (l)
-                    continue;
-
-                l = -1;
-                break;
-            }
-
-            /* disable */
-            if (c == CTRL('Z'))
-            {
-                if (cli->mode != MODE_EXEC)
-                {
-                    cli_clear_line(sockfd, cmd, l, cursor);
-                    cli_set_configmode(cli, MODE_EXEC, NULL);
-                    cli->showprompt = 1;
-                }
-
-                continue;
-            }
-
-            /* TAB completion */
-            if (c == CTRL('I'))
-            {
-                char *completions[CLI_MAX_LINE_WORDS];
-                int num_completions = 0;
-
-                if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
-                    continue;
-
-                if (cursor != l) continue;
-
-                num_completions = cli_get_completions(cli, cmd, completions, CLI_MAX_LINE_WORDS);
-                if (num_completions == 0)
-                {
-                    _write(sockfd, "\a", 1);
-                }
-                else if (num_completions == 1)
-                {
-                    // Single completion
-                    for (; l > 0; l--, cursor--)
-                    {
-                        if (cmd[l-1] == ' ' || cmd[l-1] == '|')
-                            break;
-                        _write(sockfd, "\b", 1);
-                    }
-                    strcpy((cmd + l), completions[0]);
-                    l += strlen(completions[0]);
-                    cmd[l++] = ' ';
-                    cursor = l;
-                    _write(sockfd, completions[0], strlen(completions[0]));
-                    _write(sockfd, " ", 1);
-                }
-                else if (lastchar == CTRL('I'))
-                {
-                    // double tab
-                    int i;
-                    _write(sockfd, "\r\n", 2);
-                    for (i = 0; i < num_completions; i++)
-                    {
-                        _write(sockfd, completions[i], strlen(completions[i]));
-                        if (i % 4 == 3)
-                            _write(sockfd, "\r\n", 2);
-                        else
-                            _write(sockfd, "     ", 1);
-                    }
-                    if (i % 4 != 3) _write(sockfd, "\r\n", 2);
-                        cli->showprompt = 1;
-                }
-                else
-                {
-                    // More than one completion
-                    lastchar = c;
-                    _write(sockfd, "\a", 1);
-                }
-                continue;
-            }
-
-            /* history */
-            if (c == CTRL('P') || c == CTRL('N'))
-            {
-                int history_found = 0;
-
-                if (cli->state == STATE_LOGIN || cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
-                    continue;
+/*
+ * This function should be called once for every character received from the user.
+ *
+ * It will return CLI_LOOP_CTRL_BREAK if the command is now ready to be processed (or the
+ * session should be terminated, and CLI_LOOP_CTRL_CONTINUE if it should be called again
+ * for the next character received.
+ */
+int cli_loop_process_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char c)
+{
+    if (ctx->skip)
+    {
+	ctx->skip--;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-                if (c == CTRL('P')) // Up
-                {
-                    in_history--;
-                    if (in_history < 0)
-                    {
-                        for (in_history = MAX_HISTORY-1; in_history >= 0; in_history--)
-                        {
-                            if (cli->history[in_history])
-                            {
-                                history_found = 1;
-                                break;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        if (cli->history[in_history]) history_found = 1;
-                    }
-                }
-                else // Down
-                {
-                    in_history++;
-                    if (in_history >= MAX_HISTORY || !cli->history[in_history])
-                    {
-                        int i = 0;
-                        for (i = 0; i < MAX_HISTORY; i++)
-                        {
-                            if (cli->history[i])
-                            {
-                                in_history = i;
-                                history_found = 1;
-                                break;
-                            }
-                        }
-                    }
-                    else
-                    {
-                        if (cli->history[in_history]) history_found = 1;
-                    }
-                }
-                if (history_found && cli->history[in_history])
-                {
-                    // Show history item
-                    cli_clear_line(sockfd, cmd, l, cursor);
-                    memset(cmd, 0, CLI_MAX_LINE_LENGTH);
-                    strncpy(cmd, cli->history[in_history], CLI_MAX_LINE_LENGTH - 1);
-                    l = cursor = strlen(cmd);
-                    _write(sockfd, cmd, l);
-                }
+    if (c == 255 && !ctx->is_telnet_option)
+    {
+	ctx->is_telnet_option++;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-                continue;
-            }
+    if (ctx->is_telnet_option)
+    {
+	if (c >= 251 && c <= 254)
+	{
+	    ctx->is_telnet_option = c;
+	    return CLI_LOOP_CTRL_CONTINUE;
+	}
+
+	if (c != 255)
+	{
+	    ctx->is_telnet_option = 0;
+	    return CLI_LOOP_CTRL_CONTINUE;
+	}
+
+	ctx->is_telnet_option = 0;
+    }
 
-            /* left/right cursor motion */
-            if (c == CTRL('B') || c == CTRL('F'))
-            {
-                if (c == CTRL('B')) /* Left */
-                {
-                    if (cursor)
-                    {
-                        if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                            _write(sockfd, "\b", 1);
+    /* handle ANSI arrows */
+    if (ctx->esc)
+    {
+	if (ctx->esc == '[')
+	{
+	    /* remap to readline control codes */
+	    switch (c)
+	    {
+	    case 'A': /* Up */
+		c = CTRL('P');
+		break;
+
+	    case 'B': /* Down */
+		c = CTRL('N');
+		break;
+
+	    case 'C': /* Right */
+		c = CTRL('F');
+		break;
+
+	    case 'D': /* Left */
+		c = CTRL('B');
+		break;
+
+	    default:
+		c = 0;
+	    }
+
+	    ctx->esc = 0;
+	}
+	else
+	{
+	    ctx->esc = (c == '[') ? c : 0;
+	    return CLI_LOOP_CTRL_CONTINUE;
+	}
+    }
 
-                        cursor--;
-                    }
-                }
-                else /* Right */
-                {
-                    if (cursor < l)
-                    {
-                        if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                            _write(sockfd, &cmd[cursor], 1);
+    if (c == 0) return CLI_LOOP_CTRL_CONTINUE;
+    if (c == '\n') return CLI_LOOP_CTRL_CONTINUE;
 
-                        cursor++;
-                    }
-                }
+    if (c == '\r')
+    {
+	if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+	    _write(cli, ctx, "\r\n", 2);
+	return CLI_LOOP_CTRL_BREAK;
+    }
 
-                continue;
-            }
+    if (c == 27)
+    {
+	ctx->esc = 1;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            /* start of line */
-            if (c == CTRL('A'))
-            {
-                if (cursor)
-                {
-                    if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                    {
-                        _write(sockfd, "\r", 1);
-                        show_prompt(cli, sockfd);
-                    }
+    if (c == CTRL('C'))
+    {
+	_write(cli, ctx, "\a", 1);
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-                    cursor = 0;
-                }
+    /* back word, backspace/delete */
+    if (c == CTRL('W') || c == CTRL('H') || c == 0x7f)
+    {
+	int back = 0;
+
+	if (c == CTRL('W')) /* word */
+	{
+	    int nc = ctx->cursor;
+
+	    if (ctx->l == 0 || ctx->cursor == 0)
+		return CLI_LOOP_CTRL_CONTINUE;
+
+	    while (nc && ctx->cmd[nc - 1] == ' ')
+	    {
+		nc--;
+		back++;
+	    }
+
+	    while (nc && ctx->cmd[nc - 1] != ' ')
+	    {
+		nc--;
+		back++;
+	    }
+	}
+	else /* char */
+	{
+	    if (ctx->l == 0 || ctx->cursor == 0)
+	    {
+		_write(cli, ctx, "\a", 1);
+		return CLI_LOOP_CTRL_CONTINUE;
+	    }
+
+	    back = 1;
+	}
+
+	if (back)
+	{
+	    while (back--)
+	    {
+		if (ctx->l == ctx->cursor)
+		{
+		    ctx->cmd[--ctx->cursor] = 0;
+		    if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+			_write(cli, ctx, "\b \b", 3);
+		}
+		else
+		{
+		    int i;
+		    ctx->cursor--;
+		    if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+		    {
+			for (i = ctx->cursor; i <= ctx->l; i++) ctx->cmd[i] = ctx->cmd[i+1];
+			_write(cli, ctx, "\b", 1);
+			_write(cli, ctx, ctx->cmd + ctx->cursor, strlen(ctx->cmd + ctx->cursor));
+			_write(cli, ctx, " ", 1);
+			for (i = 0; i <= (int)strlen(ctx->cmd + ctx->cursor); i++)
+			    _write(cli, ctx, "\b", 1);
+		    }
+		}
+		ctx->l--;
+	    }
+
+	    return CLI_LOOP_CTRL_CONTINUE;
+	}
+    }
 
-                continue;
-            }
+    /* redraw */
+    if (c == CTRL('L'))
+    {
+	int i;
+	int cursorback = ctx->l - ctx->cursor;
 
-            /* end of line */
-            if (c == CTRL('E'))
-            {
-                if (cursor < l)
-                {
-                    if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                        _write(sockfd, &cmd[cursor], l - cursor);
+	if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD)
+	    return CLI_LOOP_CTRL_CONTINUE;
 
-                    cursor = l;
-                }
+	_write(cli, ctx, "\r\n", 2);
+	show_prompt(cli, ctx);
+	_write(cli, ctx, ctx->cmd, ctx->l);
 
-                continue;
-            }
+	for (i = 0; i < cursorback; i++)
+	    _write(cli, ctx, "\b", 1);
 
-            /* normal character typed */
-            if (cursor == l)
-            {
-                 /* append to end of line */
-                cmd[cursor] = c;
-                if (l < CLI_MAX_LINE_LENGTH - 1)
-                {
-                    l++;
-                    cursor++;
-                }
-                else
-                {
-                    _write(sockfd, "\a", 1);
-                    continue;
-                }
-            }
-            else
-            {
-                // Middle of text
-                if (insertmode)
-                {
-                    int i;
-                    // Move everything one character to the right
-                    if (l >= CLI_MAX_LINE_LENGTH - 2) l--;
-                    for (i = l; i >= cursor; i--)
-                        cmd[i + 1] = cmd[i];
-                    // Write what we've just added
-                    cmd[cursor] = c;
-
-                    _write(sockfd, &cmd[cursor], l - cursor + 1);
-                    for (i = 0; i < (l - cursor + 1); i++)
-                        _write(sockfd, "\b", 1);
-                    l++;
-                }
-                else
-                {
-                    cmd[cursor] = c;
-                }
-                cursor++;
-            }
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-            {
-                if (c == '?' && cursor == l)
-                {
-                    _write(sockfd, "\r\n", 2);
-                    oldcmd = cmd;
-                    oldl = cursor = l - 1;
-                    break;
-                }
-                _write(sockfd, &c, 1);
-            }
+    /* clear line */
+    if (c == CTRL('U'))
+    {
+	if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD)
+	    memset(ctx->cmd, 0, ctx->l);
+	else
+	    cli_clear_line(cli, ctx, ctx->cmd, ctx->l, ctx->cursor);
 
-            oldcmd = 0;
-            oldl = 0;
-            lastchar = c;
-        }
+	ctx->l = ctx->cursor = 0;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-        if (l < 0) break;
+    /* kill to EOL */
+    if (c == CTRL('K'))
+    {
+	if (ctx->cursor == ctx->l)
+	    return CLI_LOOP_CTRL_CONTINUE;
+
+	if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+	{
+	    int c;
+	    for (c = ctx->cursor; c < ctx->l; c++)
+		_write(cli, ctx, " ", 1);
+
+	    for (c = ctx->cursor; c < ctx->l; c++)
+		_write(cli, ctx, "\b", 1);
+	}
+
+	memset(ctx->cmd + ctx->cursor, 0, ctx->l - ctx->cursor);
+	ctx->l = ctx->cursor;
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-        if (cli->state == STATE_LOGIN)
-        {
-            if (l == 0) continue;
-
-            /* require login */
-            free_z(username);
-            if (!(username = strdup(cmd)))
-                return 0;
-            cli->state = STATE_PASSWORD;
-            cli->showprompt = 1;
-        }
-        else if (cli->state == STATE_PASSWORD)
-        {
-            /* require password */
-            int allowed = 0;
+    /* EOT */
+    if (c == CTRL('D'))
+    {
+	if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD)
+	    return CLI_LOOP_CTRL_BREAK;
 
-            free_z(password);
-            if (!(password = strdup(cmd)))
-                return 0;
-            if (cli->auth_callback)
-            {
-                if (cli->auth_callback(username, password) == CLI_OK)
-                    allowed++;
-            }
+	if (ctx->l)
+	    return CLI_LOOP_CTRL_CONTINUE;
 
-            if (!allowed)
-            {
-                struct unp *u;
-                for (u = cli->users; u; u = u->next)
-                {
-                    if (!strcmp(u->username, username) && pass_matches(u->password, password))
-                    {
-                        allowed++;
-                        break;
-                    }
-                }
-            }
-
-            if (allowed)
-            {
-                cli_error(cli, " ");
-                cli->state = STATE_NORMAL;
-            }
-            else
-            {
-                cli_error(cli, "\n\nAccess denied");
-                free_z(username);
-                free_z(password);
-                cli->state = STATE_LOGIN;
-            }
+	ctx->l = -1;
+	return CLI_LOOP_CTRL_BREAK;
+    }
 
-            cli->showprompt = 1;
-        }
-        else if (cli->state == STATE_ENABLE_PASSWORD)
-        {
-            int allowed = 0;
-            if (cli->enable_password)
-            {
-                /* check stored static enable password */
-                if (pass_matches(cli->enable_password, cmd))
-                    allowed++;
-            }
+    /* disable */
+    if (c == CTRL('Z'))
+    {
+	if (cli->mode != MODE_EXEC)
+	{
+	    cli_clear_line(cli, ctx, ctx->cmd, ctx->l, ctx->cursor);
+	    cli_set_configmode(cli, MODE_EXEC, NULL);
+	    cli->showprompt = 1;
+	}
+
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            if (!allowed && cli->enable_callback)
-            {
-                /* check callback */
-                if (cli->enable_callback(cmd))
-                    allowed++;
-            }
+    /* TAB completion */
+    if (c == CTRL('I'))
+    {
+	char *completions[CLI_MAX_LINE_WORDS];
+	int num_completions = 0;
+
+	if (cli->state == CLI_STATE_LOGIN || cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD)
+	    return CLI_LOOP_CTRL_CONTINUE;
+
+	if (ctx->cursor != ctx->l) return CLI_LOOP_CTRL_CONTINUE;
+
+	num_completions = cli_get_completions(cli, ctx->cmd, completions, CLI_MAX_LINE_WORDS);
+	if (num_completions == 0)
+	{
+	    _write(cli, ctx, "\a", 1);
+	}
+	else if (num_completions == 1)
+	{
+	    // Single completion
+	    for (; ctx->l > 0; ctx->l--, ctx->cursor--)
+	    {
+		if (ctx->cmd[ctx->l-1] == ' ' || ctx->cmd[ctx->l-1] == '|')
+		    break;
+		_write(cli, ctx, "\b", 1);
+	    }
+	    strcpy((ctx->cmd + ctx->l), completions[0]);
+	    ctx->l += strlen(completions[0]);
+	    ctx->cmd[ctx->l++] = ' ';
+	    ctx->cursor = ctx->l;
+	    _write(cli, ctx, completions[0], strlen(completions[0]));
+	    _write(cli, ctx, " ", 1);
+	}
+	else if (ctx->lastchar == CTRL('I'))
+	{
+	    // double tab
+	    int i;
+	    _write(cli, ctx, "\r\n", 2);
+	    for (i = 0; i < num_completions; i++)
+	    {
+		_write(cli, ctx, completions[i], strlen(completions[i]));
+		if (i % 4 == 3)
+		    _write(cli, ctx, "\r\n", 2);
+		else
+		    _write(cli, ctx, "     ", 1);
+	    }
+	    if (i % 4 != 3) _write(cli, ctx, "\r\n", 2);
+	    cli->showprompt = 1;
+	}
+	else
+	{
+	    // More than one completion
+	    ctx->lastchar = c;
+	    _write(cli, ctx, "\a", 1);
+	}
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            if (allowed)
-            {
-                cli->state = STATE_ENABLE;
-                cli_set_privilege(cli, PRIVILEGE_PRIVILEGED);
-            }
-            else
-            {
-                cli_error(cli, "\n\nAccess denied");
-                cli->state = STATE_NORMAL;
-            }
-        }
-        else
-        {
-            if (l == 0) continue;
-            if (cmd[l - 1] != '?' && strcasecmp(cmd, "history") != 0)
-                cli_add_history(cli, cmd);
+    /* history */
+    if (c == CTRL('P') || c == CTRL('N'))
+    {
+	int history_found = 0;
+
+	if (cli->state == CLI_STATE_LOGIN || cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD)
+	    return CLI_LOOP_CTRL_CONTINUE;
+
+	if (c == CTRL('P')) // Up
+	{
+	    ctx->in_history--;
+	    if (ctx->in_history < 0)
+	    {
+		for (ctx->in_history = MAX_HISTORY-1; ctx->in_history >= 0; ctx->in_history--)
+		{
+		    if (cli->history[ctx->in_history][0])
+		    {
+			history_found = 1;
+			break;
+		    }
+		}
+	    }
+	    else
+	    {
+		if (cli->history[ctx->in_history]) history_found = 1;
+	    }
+	}
+	else // Down
+	{
+	    ctx->in_history++;
+	    if (ctx->in_history >= MAX_HISTORY || !cli->history[ctx->in_history])
+	    {
+		int i = 0;
+		for (i = 0; i < MAX_HISTORY; i++)
+		{
+		    if (cli->history[i])
+		    {
+			ctx->in_history = i;
+			history_found = 1;
+			break;
+		    }
+		}
+	    }
+	    else
+	    {
+		if (cli->history[ctx->in_history]) history_found = 1;
+	    }
+	}
+	if (history_found && cli->history[ctx->in_history])
+	{
+	    // Show history item
+	    cli_clear_line(cli, ctx, ctx->cmd, ctx->l, ctx->cursor);
+	    memset(ctx->cmd, 0, CLI_MAX_LINE_LENGTH);
+	    strncpy(ctx->cmd, cli->history[ctx->in_history], CLI_MAX_LINE_LENGTH - 1);
+	    /* cryptech: not sure if needed, but ensure we don't disclose memory after buf */
+	    ctx->cmd[CLI_MAX_LINE_LENGTH - 1] = 0;
+	    ctx->l = ctx->cursor = strlen(ctx->cmd);
+	    _write(cli, ctx, ctx->cmd, ctx->l);
+	}
+
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-            if (cli_run_command(cli, cmd) == CLI_QUIT)
-                break;
-        }
+    /* left/right cursor motion */
+    if (c == CTRL('B') || c == CTRL('F'))
+    {
+	if (c == CTRL('B')) /* Left */
+	{
+	    if (ctx->cursor)
+	    {
+		if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+		    _write(cli, ctx, "\b", 1);
+
+		ctx->cursor--;
+	    }
+	}
+	else /* Right */
+	{
+	    if (ctx->cursor < ctx->l)
+	    {
+		if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+		    _write(cli, ctx, &ctx->cmd[ctx->cursor], 1);
+
+		ctx->cursor++;
+	    }
+	}
+
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-        // Update the last_action time now as the last command run could take a
-        // long time to return
-        if (cli->idle_timeout)
-            time(&cli->last_action);
+    /* start of line */
+    if (c == CTRL('A'))
+    {
+	if (ctx->cursor)
+	{
+	    if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+	    {
+		_write(cli, ctx, "\r", 1);
+		show_prompt(cli, ctx);
+	    }
+
+	    ctx->cursor = 0;
+	}
+
+	return CLI_LOOP_CTRL_CONTINUE;
     }
 
-    cli_free_history(cli);
-    free_z(username);
-    free_z(password);
-    free_z(cmd);
+    /* end of line */
+    if (c == CTRL('E'))
+    {
+	if (ctx->cursor < ctx->l)
+	{
+	    if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+		_write(cli, ctx, &ctx->cmd[ctx->cursor], ctx->l - ctx->cursor);
 
-    fclose(cli->client);
-    cli->client = 0;
-    return CLI_OK;
-}
+	    ctx->cursor = ctx->l;
+	}
 
-int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode)
-{
-    int oldpriv = cli_set_privilege(cli, privilege);
-    int oldmode = cli_set_configmode(cli, mode, NULL);
-    char buf[CLI_MAX_LINE_LENGTH];
+	return CLI_LOOP_CTRL_CONTINUE;
+    }
 
-    while (1)
+    /* normal character typed */
+    if (ctx->cursor == ctx->l)
     {
-        char *p;
-        char *cmd;
-        char *end;
-
-        if (fgets(buf, CLI_MAX_LINE_LENGTH - 1, fh) == NULL)
-            break; /* end of file */
+	/* append to end of line */
+	ctx->cmd[ctx->cursor] = c;
+	if (ctx->l < CLI_MAX_LINE_LENGTH - 1)
+	{
+	    ctx->l++;
+	    ctx->cursor++;
+	}
+	else
+	{
+	    _write(cli, ctx, "\a", 1);
+	    return CLI_LOOP_CTRL_CONTINUE;
+	}
+    }
+    else
+    {
+	// Middle of text
+	if (ctx->insertmode)
+	{
+	    int i;
+	    // Move everything one character to the right
+	    if (ctx->l >= CLI_MAX_LINE_LENGTH - 2) ctx->l--;
+	    for (i = ctx->l; i >= ctx->cursor; i--)
+		ctx->cmd[i + 1] = ctx->cmd[i];
+	    // Write what we've just added
+	    ctx->cmd[ctx->cursor] = c;
+
+	    _write(cli, ctx, &ctx->cmd[ctx->cursor], ctx->l - ctx->cursor + 1);
+	    for (i = 0; i < (ctx->l - ctx->cursor + 1); i++)
+		_write(cli, ctx, "\b", 1);
+	    ctx->l++;
+	}
+	else
+	{
+	    ctx->cmd[ctx->cursor] = c;
+	}
+	ctx->cursor++;
+    }
 
-        if ((p = strpbrk(buf, "#\r\n")))
-            *p = 0;
+    if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD)
+    {
+	if (c == '?' && ctx->cursor == ctx->l)
+	{
+	    _write(cli, ctx, "\r\n", 2);
+	    ctx->restore_cmd_l = ctx->l -1;
+	    return CLI_LOOP_CTRL_BREAK;
+	}
+	_write(cli, ctx, &c, 1);
+    }
 
-        cmd = buf;
-        while (isspace(*cmd))
-            cmd++;
+    ctx->restore_cmd_l = 0;
+    ctx->lastchar = c;
 
-        if (!*cmd)
-            continue;
+    return 0;
+}
 
-        for (p = end = cmd; *p; p++)
-            if (!isspace(*p))
-                end = p;
 
-        *++end = 0;
-        if (strcasecmp(cmd, "quit") == 0)
-            break;
 
-        if (cli_run_command(cli, cmd) == CLI_QUIT)
-            break;
+int cli_loop_process_cmd(struct cli_def *cli, struct cli_loop_ctx *ctx)
+{
+    if (cli->state == CLI_STATE_LOGIN)
+    {
+	if (ctx->l == 0) return 0;
+
+	/* require login */
+	if (strlen(ctx->cmd) > sizeof(ctx->username))
+	    return CLI_LOOP_CTRL_BREAK;
+	strncpy(ctx->username, ctx->cmd, sizeof(ctx->username) - 1);
+	cli->state = CLI_STATE_PASSWORD;
+	cli->showprompt = 1;
     }
+    else if (cli->state == CLI_STATE_PASSWORD)
+    {
+	/* require password */
+	int allowed = 0;
+
+	if (cli->auth_callback)
+	{
+	    if (cli->auth_callback(ctx->username, ctx->cmd) == CLI_OK)
+		allowed++;
+	}
+
+	if (!allowed)
+	{
+	    struct unp *u;
+	    for (u = cli->users; u; u = u->next)
+	    {
+		if (!strcmp(u->username, ctx->username) && pass_matches(u->password, ctx->cmd))
+		{
+		    allowed++;
+		    break;
+		}
+	    }
+	}
+	memset(ctx->cmd, 0, sizeof(ctx->cmd));  // XXX verify this sizeof
+
+	if (allowed)
+	{
+	    cli_error(cli, " ");
+	    cli->state = CLI_STATE_NORMAL;
+	}
+	else
+	{
+	    cli_error(cli, "\n\nAccess denied");
+	    cli->state = CLI_STATE_LOGIN;
+	}
+
+	cli->showprompt = 1;
+    }
+    else if (cli->state == CLI_STATE_ENABLE_PASSWORD)
+    {
+	int allowed = 0;
+	if (cli->enable_password)
+	{
+	    /* check stored static enable password */
+	    if (pass_matches(cli->enable_password, ctx->cmd))
+		allowed++;
+	}
+
+	if (!allowed && cli->enable_callback)
+	{
+	    /* check callback */
+	    if (cli->enable_callback(ctx->cmd))
+		allowed++;
+	}
+
+	if (allowed)
+	{
+	    cli->state = CLI_STATE_ENABLE;
+	    cli_set_privilege(cli, PRIVILEGE_PRIVILEGED);
+	}
+	else
+	{
+	    cli_error(cli, "\n\nAccess denied");
+	    cli->state = CLI_STATE_NORMAL;
+	}
+    }
+    else
+    {
+	if (ctx->l == 0) return 0;
+	/* XXX also don't add to history if command equals the last history entry? */
+	if (ctx->cmd[ctx->l - 1] != '?' && strcasecmp(ctx->cmd, "history") != 0)
+	    cli_add_history(cli, ctx->cmd);
 
-    cli_set_privilege(cli, oldpriv);
-    cli_set_configmode(cli, oldmode, NULL /* didn't save desc */);
+	if (cli_run_command(cli, ctx->cmd) == CLI_QUIT)
+	    return CLI_LOOP_CTRL_BREAK;
+    }
 
-    return CLI_OK;
+    return 0;
 }
 
+
+/* cryptech: removed unused file mode */
+
 static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap)
 {
+    static char buf[1024];
     va_list aq;
     int n;
     char *p;
@@ -1988,57 +1669,34 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l
     while (1)
     {
         va_copy(aq, ap);
-        if ((n = vsnprintf(cli->buffer, cli->buf_size, format, ap)) == -1)
+        if ((n = vsnprintf(buf, sizeof(buf), format, ap)) == -1)
             return;
 
-        if ((unsigned)n >= cli->buf_size)
+        if ((unsigned)n >= sizeof(buf))
         {
-            cli->buf_size = n + 1;
-            cli->buffer = realloc(cli->buffer, cli->buf_size);
-            if (!cli->buffer)
-                return;
-            va_end(ap);
-            va_copy(ap, aq);
-            continue;
+	  strncpy(buf, "_print buffer would have overflown", sizeof(buf));
+            return;
         }
         break;
     }
 
-
-    p = cli->buffer;
+    p = buf;
     do
     {
         char *next = strchr(p, '\n');
-        struct cli_filter *f = (print_mode & PRINT_FILTERED) ? cli->filters : 0;
-        int print = 1;
 
         if (next)
             *next++ = 0;
         else if (print_mode & PRINT_BUFFERED)
             break;
 
-        while (print && f)
-        {
-            print = (f->filter(cli, p, f->data) == CLI_OK);
-            f = f->next;
-        }
-        if (print)
-        {
-            if (cli->print_callback)
-                cli->print_callback(cli, p);
-            else if (cli->client)
-                fprintf(cli->client, "%s\r\n", p);
-        }
+        if (cli->print_callback)
+           cli->print_callback(cli, p);
+        else if (cli->client)
+            fprintf(cli->client, "%s\r\n", p);
 
         p = next;
     } while (p);
-
-    if (p && *p)
-    {
-        if (p != cli->buffer)
-        memmove(cli->buffer, p, strlen(p));
-    }
-    else *cli->buffer = 0;
 }
 
 void cli_bufprint(struct cli_def *cli, const char *format, ...)
@@ -2082,244 +1740,7 @@ struct cli_match_filter_state
     } match;
 };
 
-int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt)
-{
-    struct cli_match_filter_state *state;
-    int rflags;
-    int i;
-    char *p;
-
-    if (argc < 2)
-    {
-        if (cli->client)
-            fprintf(cli->client, "Match filter requires an argument\r\n");
-
-        return CLI_ERROR;
-    }
-
-    filt->filter = cli_match_filter;
-    filt->data = state = calloc(sizeof(struct cli_match_filter_state), 1);
-
-    if (argv[0][0] == 'i' || (argv[0][0] == 'e' && argv[0][1] == 'x'))  // include/exclude
-    {
-        if (argv[0][0] == 'e')
-            state->flags = MATCH_INVERT;
-
-        state->match.string = join_words(argc-1, argv+1);
-        return CLI_OK;
-    }
-
-#ifdef WIN32
-    /*
-     * No regex functions in windows, so return an error
-     */
-    return CLI_ERROR;
-#endif
-
-    state->flags = MATCH_REGEX;
-
-    // grep/egrep
-    rflags = REG_NOSUB;
-    if (argv[0][0] == 'e') // egrep
-        rflags |= REG_EXTENDED;
-
-    i = 1;
-    while (i < argc - 1 && argv[i][0] == '-' && argv[i][1])
-    {
-        int last = 0;
-        p = &argv[i][1];
-
-        if (strspn(p, "vie") != strlen(p))
-            break;
-
-        while (*p)
-        {
-            switch (*p++)
-            {
-                case 'v':
-                    state->flags |= MATCH_INVERT;
-                    break;
-
-                case 'i':
-                    rflags |= REG_ICASE;
-                    break;
-
-                case 'e':
-                    last++;
-                    break;
-            }
-        }
-
-        i++;
-        if (last)
-            break;
-    }
-
-    p = join_words(argc-i, argv+i);
-    if ((i = regcomp(&state->match.re, p, rflags)))
-    {
-        if (cli->client)
-            fprintf(cli->client, "Invalid pattern \"%s\"\r\n", p);
-
-        free_z(p);
-        return CLI_ERROR;
-    }
-
-    free_z(p);
-    return CLI_OK;
-}
-
-int cli_match_filter(UNUSED(struct cli_def *cli), const char *string, void *data)
-{
-    struct cli_match_filter_state *state = data;
-    int r = CLI_ERROR;
-
-    if (!string) // clean up
-    {
-        if (state->flags & MATCH_REGEX)
-            regfree(&state->match.re);
-        else
-            free(state->match.string);
-
-        free(state);
-        return CLI_OK;
-    }
-
-    if (state->flags & MATCH_REGEX)
-    {
-        if (!regexec(&state->match.re, string, 0, NULL, 0))
-            r = CLI_OK;
-    }
-    else
-    {
-        if (strstr(string, state->match.string))
-            r = CLI_OK;
-    }
-
-    if (state->flags & MATCH_INVERT)
-    {
-        if (r == CLI_OK)
-            r = CLI_ERROR;
-        else
-            r = CLI_OK;
-    }
-
-    return r;
-}
-
-struct cli_range_filter_state {
-    int matched;
-    char *from;
-    char *to;
-};
-
-int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli_filter *filt)
-{
-    struct cli_range_filter_state *state;
-    char *from = 0;
-    char *to = 0;
-
-    if (!strncmp(argv[0], "bet", 3)) // between
-    {
-        if (argc < 3)
-        {
-            if (cli->client)
-                fprintf(cli->client, "Between filter requires 2 arguments\r\n");
-
-            return CLI_ERROR;
-        }
-
-        if (!(from = strdup(argv[1])))
-            return CLI_ERROR;
-        to = join_words(argc-2, argv+2);
-    }
-    else // begin
-    {
-        if (argc < 2)
-        {
-            if (cli->client)
-                fprintf(cli->client, "Begin filter requires an argument\r\n");
-
-            return CLI_ERROR;
-        }
-
-        from = join_words(argc-1, argv+1);
-    }
-
-    filt->filter = cli_range_filter;
-    filt->data = state = calloc(sizeof(struct cli_range_filter_state), 1);
-
-    state->from = from;
-    state->to = to;
-
-    return CLI_OK;
-}
-
-int cli_range_filter(UNUSED(struct cli_def *cli), const char *string, void *data)
-{
-    struct cli_range_filter_state *state = data;
-    int r = CLI_ERROR;
-
-    if (!string) // clean up
-    {
-        free_z(state->from);
-        free_z(state->to);
-        free_z(state);
-        return CLI_OK;
-    }
-
-    if (!state->matched)
-    state->matched = !!strstr(string, state->from);
-
-    if (state->matched)
-    {
-        r = CLI_OK;
-        if (state->to && strstr(string, state->to))
-            state->matched = 0;
-    }
-
-    return r;
-}
-
-int cli_count_filter_init(struct cli_def *cli, int argc, UNUSED(char **argv), struct cli_filter *filt)
-{
-    if (argc > 1)
-    {
-        if (cli->client)
-            fprintf(cli->client, "Count filter does not take arguments\r\n");
-
-        return CLI_ERROR;
-    }
-
-    filt->filter = cli_count_filter;
-    if (!(filt->data = calloc(sizeof(int), 1)))
-        return CLI_ERROR;
-
-    return CLI_OK;
-}
-
-int cli_count_filter(struct cli_def *cli, const char *string, void *data)
-{
-    int *count = data;
-
-    if (!string) // clean up
-    {
-        // print count
-        if (cli->client)
-            fprintf(cli->client, "%d\r\n", *count);
-
-        free(count);
-        return CLI_OK;
-    }
-
-    while (isspace(*string))
-        string++;
-
-    if (*string)
-        (*count)++;  // only count non-blank lines
-
-    return CLI_ERROR; // no output
-}
+/* cryptech: removed unused filter functions */
 
 void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *))
 {
@@ -2331,7 +1752,6 @@ void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds)
     if (seconds < 1)
         seconds = 0;
     cli->idle_timeout = seconds;
-    time(&cli->last_action);
 }
 
 void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *))
@@ -2351,3 +1771,13 @@ void cli_set_context(struct cli_def *cli, void *context) {
 void *cli_get_context(struct cli_def *cli) {
     return cli->user_context;
 }
+
+void cli_read_callback(struct cli_def *cli, int (*callback)(struct cli_def *, void *, size_t))
+{
+    cli->read_callback = callback;
+}
+
+void cli_write_callback(struct cli_def *cli, int (*callback)(struct cli_def *, const void *, size_t))
+{
+    cli->write_callback = callback;
+}
diff --git a/libcli.h b/libcli.h
index e978526..01b334f 100644
--- a/libcli.h
+++ b/libcli.h
@@ -16,7 +16,8 @@ extern "C" {
 #define CLI_QUIT                -2
 #define CLI_ERROR_ARG           -3
 
-#define MAX_HISTORY             256
+#define MAX_HISTORY             5  /* testing, used to be 256 */
+#define HISTORY_CMD_LEN         128
 
 #define PRIVILEGE_UNPRIVILEGED  0
 #define PRIVILEGE_PRIVILEGED    15
@@ -30,8 +31,20 @@ extern "C" {
 #define PRINT_FILTERED          0x01
 #define PRINT_BUFFERED          0x02
 
-#define CLI_MAX_LINE_LENGTH     4096
-#define CLI_MAX_LINE_WORDS      128
+#define CLI_MAX_LINE_LENGTH     64
+#define CLI_MAX_LINE_WORDS      16
+#define CLI_MAX_CMD_NAME_LEN    32
+
+#define CLI_LOOP_CTRL_CONTINUE 1
+#define CLI_LOOP_CTRL_BREAK 2
+
+enum cli_states {
+    CLI_STATE_LOGIN,
+    CLI_STATE_PASSWORD,
+    CLI_STATE_NORMAL,
+    CLI_STATE_ENABLE_PASSWORD,
+    CLI_STATE_ENABLE
+};
 
 struct cli_def {
     int completion_callback;
@@ -42,29 +55,28 @@ struct cli_def {
     char *banner;
     struct unp *users;
     char *enable_password;
-    char *history[MAX_HISTORY];
+    char history[MAX_HISTORY][HISTORY_CMD_LEN];
     char showprompt;
     char *promptchar;
     char *hostname;
     char *modestring;
     int privilege;
     int mode;
-    int state;
+    enum cli_states state;
     struct cli_filter *filters;
     void (*print_callback)(struct cli_def *cli, const char *string);
     FILE *client;
     /* internal buffers */
     void *conn;
     void *service;
-    char *commandname;  // temporary buffer for cli_command_name() to prevent leak
-    char *buffer;
-    unsigned buf_size;
     struct timeval timeout_tm;
     time_t idle_timeout;
     int (*idle_timeout_callback)(struct cli_def *);
     time_t last_action;
     int telnet_protocol;
     void *user_context;
+    int (*read_callback)(struct cli_def *cli, void *buf, const size_t count);
+    int (*write_callback)(struct cli_def *cli, const void *buf, const size_t count);
 };
 
 struct cli_filter {
@@ -85,24 +97,31 @@ struct cli_command {
     struct cli_command *parent;
 };
 
-struct cli_def *cli_init();
+struct cli_loop_ctx {
+    char cmd[CLI_MAX_LINE_LENGTH];
+    char username[64];
+    int l, restore_cmd_l;
+    int cursor, insertmode;
+    int lastchar, is_telnet_option, skip, esc;
+    signed int in_history;
+    int sockfd;
+};
+
+int cli_init(struct cli_def *cli);
 int cli_done(struct cli_def *cli);
-struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command,
-                                         int (*callback)(struct cli_def *, const char *, char **, int), int privilege,
-                                         int mode, const char *help);
+void cli_register_command2(struct cli_def *cli, struct cli_command *cmd, struct cli_command *parent);
 int cli_unregister_command(struct cli_def *cli, const char *command);
 int cli_run_command(struct cli_def *cli, const char *command);
 int cli_loop(struct cli_def *cli, int sockfd);
 int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode);
 void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *));
 void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *));
-void cli_allow_user(struct cli_def *cli, const char *username, const char *password);
 void cli_allow_enable(struct cli_def *cli, const char *password);
 void cli_deny_user(struct cli_def *cli, const char *username);
-void cli_set_banner(struct cli_def *cli, const char *banner);
-void cli_set_hostname(struct cli_def *cli, const char *hostname);
-void cli_set_promptchar(struct cli_def *cli, const char *promptchar);
-void cli_set_modestring(struct cli_def *cli, const char *modestring);
+void cli_set_banner(struct cli_def *cli, char *banner);
+void cli_set_hostname(struct cli_def *cli, char *hostname);
+void cli_set_promptchar(struct cli_def *cli, char *promptchar);
+void cli_set_modestring(struct cli_def *cli, char *modestring);
 int cli_set_privilege(struct cli_def *cli, int privilege);
 int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc);
 void cli_reprompt(struct cli_def *cli);
@@ -116,6 +135,14 @@ void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *,
 void cli_free_history(struct cli_def *cli);
 void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds);
 void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *));
+void cli_read_callback(struct cli_def *cli, int (*callback)(struct cli_def *cli, void *buf, size_t count));
+void cli_write_callback(struct cli_def *cli, int (*callback)(struct cli_def *cli, const void *buf, size_t count));
+
+void cli_loop_start_new_command(struct cli_def *cli, struct cli_loop_ctx *ctx);
+void cli_loop_show_prompt(struct cli_def *cli, struct cli_loop_ctx *ctx);
+int cli_loop_read_next_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char *c);
+int cli_loop_process_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char c);
+int cli_loop_process_cmd(struct cli_def *cli, struct cli_loop_ctx *ctx);
 
 // Enable or disable telnet protocol negotiation.
 // Note that this is enabled by default and must be changed before cli_loop() is run.



More information about the Commits mailing list