[Cryptech-Commits] [user/paul/libcli] 02/02: Conditionalize feature sets, to make it easier to build for an embedded environment.

git at cryptech.is git at cryptech.is
Thu Jul 21 20:30:18 UTC 2016


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

paul at psgd.org pushed a commit to branch master
in repository user/paul/libcli.

commit 96e89d34eb2acfd71de6ed38d7029d7f662b9ad5
Author: Paul Selkirk <paul at psgd.org>
AuthorDate: Thu Jul 21 16:12:38 2016 -0400

    Conditionalize feature sets, to make it easier to build for an embedded environment.
    
    The biggest things we need are
      1) No dynamic memory allocations
      2) No socket oriented I/O
    
    While doing so, I've tried to make sure it still works in other build
    scenarios (hosted Linux build with malloc).
---
 Makefile  |  38 ++-
 clitest.c |   6 +
 libcli.c  | 827 +++++++++++++++++++++++++++++++++++++++++++++++++++-----------
 libcli.h  |  21 +-
 4 files changed, 747 insertions(+), 145 deletions(-)

diff --git a/Makefile b/Makefile
index 3062635..8c61c59 100644
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,36 @@ DYNAMIC_LIB ?= 1
 STATIC_LIB ?= 1
 # Run tests by default
 TESTS ?= 1
+# Store passwords with minimal crypto protection
+CRYPT ?= 1
+CFLAGS += -DDO_CRYPT=$(CRYPT)
+# Read and execute commands from a file
+FILE ?= 1
+CFLAGS += -DDO_FILE=$(FILE)
+# Filter commands
+FILTER ?= 1
+CFLAGS += -DDO_FILTER=$(FILTER)
+# Support idle timeout
+IDLE_TIMEOUT ?= 1
+CFLAGS += -DDO_IDLE_TIMEOUT=$(IDLE_TIMEOUT)
+# Use dynamic memory in the library
+MALLOC ?= 1
+CFLAGS += -DDO_MALLOC=$(MALLOC)
+# buffered print
+PRINT_BUFFERED ?= 1
+CFLAGS += -DDO_PRINT_BUFFERED=$(PRINT_BUFFERED)
+# Support regular/periodic events
+REGULAR ?= 1
+CFLAGS += -DDO_REGULAR=$(REGULAR)
+# I/O over sockets
+SOCKET ?= 1
+CFLAGS += -DDO_SOCKET=$(SOCKET)
+# Tab completion of commandsd
+TAB_COMPLETION ?= 1
+CFLAGS += -DDO_TAB_COMPLETION=$(TAB_COMPLETION)
+# Telnet option negotiation
+TELNET ?= 1
+CFLAGS += -DDO_TELNET=$(TELNET)
 
 UNAME = $(shell sh -c 'uname -s 2>/dev/null || echo not')
 DESTDIR =
@@ -17,7 +47,7 @@ LIB_STATIC = libcli.a
 
 CC = gcc
 AR = ar
-ARFLAGS = rcs
+ARFLAGS ?= rcs
 DEBUG = -g
 OPTIM = -O3
 override CFLAGS += $(DEBUG) $(OPTIM) -Wall -std=c99 -pedantic -Wformat-security -Wno-format-zero-length -Werror -Wwrite-strings -Wformat -fdiagnostics-show-option -Wextra -Wsign-compare -Wcast-align -Wno-unused-parameter
@@ -28,8 +58,10 @@ ifeq ($(UNAME),Darwin)
 override LDFLAGS += -Wl,-install_name,$(LIB).$(MAJOR).$(MINOR)
 else
 override LDFLAGS += -Wl,-soname,$(LIB).$(MAJOR).$(MINOR)
+ifeq (1,$(CRYPT))
 LIBS = -lcrypt
 endif
+endif
 
 ifeq (1,$(DYNAMIC_LIB))
 TARGET_LIBS += $(LIB)
@@ -54,8 +86,8 @@ $(LIB_STATIC): libcli.o
 
 libcli.o: libcli.h
 
-clitest: clitest.o $(LIB)
-	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -L. -lcli
+clitest: clitest.o $(TARGET_LIBS)
+	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -L. -lcli $(LIBS)
 
 clitest.exe: clitest.c libcli.o
 	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< libcli.o -lws2_32
diff --git a/clitest.c b/clitest.c
index ace87bc..bff0866 100644
--- a/clitest.c
+++ b/clitest.c
@@ -227,10 +227,14 @@ int main()
     cli = cli_init();
     cli_set_banner(cli, "libcli test environment");
     cli_set_hostname(cli, "router");
+#if DO_TELNET
     cli_telnet_protocol(cli, 1);
+#endif
+#if DO_REGULAR
     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
+#endif
     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);
@@ -269,6 +273,7 @@ int main()
 
     cli_set_auth_callback(cli, check_auth);
     cli_set_enable_callback(cli, check_enable);
+#if DO_FILE
     // Test reading from a file
     {
         FILE *fh;
@@ -282,6 +287,7 @@ int main()
             fclose(fh);
         }
     }
+#endif
 
     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
     {
diff --git a/libcli.c b/libcli.c
index 3893b2a..1bde589 100644
--- a/libcli.c
+++ b/libcli.c
@@ -1,3 +1,57 @@
+/* enable all features by default */
+#ifndef DO_CRYPT
+#define DO_CRYPT 1
+#endif
+#ifndef DO_FILE
+#define DO_FILE 1
+#endif
+#ifndef DO_FILTER
+#define DO_FILTER 1
+#endif
+#ifndef DO_IDLE_TIMEOUT
+#define DO_IDLE_TIMEOUT 1
+#endif
+#ifndef DO_MALLOC
+#define DO_MALLOC 1
+#endif
+#ifndef DO_PRINT_BUFFERED
+#define DO_PRINT_BUFFERED 1
+#endif
+#ifndef DO_REGULAR
+#define DO_REGULAR 1
+#endif
+#ifndef DO_SOCKET
+#define DO_SOCKET 1
+#endif
+#ifndef DO_TAB_COMPLETION
+#define DO_TAB_COMPLETION 1
+#endif
+#ifndef DO_TELNET
+#define DO_TELNET 1
+#endif
+
+#if DO_REGULAR && !DO_SOCKET
+#error DO_REGULAR requires DO_SOCKET
+#endif
+
+#if DO_IDLE_TIMEOUT && !DO_SOCKET
+#error DO_IDLE_TIMEOUT requires DO_SOCKET
+#endif
+
+#if DO_FILTER && !DO_MALLOC
+#error DO_FILTER requires DO_MALLOC
+#endif
+
+#if !DO_MALLOC
+#ifndef CLI_MAX_USERS
+#define CLI_MAX_USERS 1
+#endif
+#ifndef CLI_MAX_COMMANDS
+#define CLI_MAX_COMMANDS 64
+#endif
+#endif
+
+
 #ifdef WIN32
 #include <winsock2.h>
 #include <windows.h>
@@ -8,12 +62,15 @@
 #include <errno.h>
 #include <stdarg.h>
 #include <stdlib.h>
+#if 0
 #include <memory.h>
+#endif
+#if DO_MALLOC
 #if !defined(__APPLE__) && !defined(__FreeBSD__)
 #include <malloc.h>
 #endif
+#endif
 #include <string.h>
-#include <ctype.h>
 #include <unistd.h>
 #include <time.h>
 #ifndef WIN32
@@ -21,6 +78,16 @@
 #endif
 #include "libcli.h"
 
+#ifdef __arm__
+/* arm-none-eabi libc version of isspace() causes a hard fault */
+static inline int isspace(int c)
+{
+    return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v');
+}
+#else
+#include <ctype.h>
+#endif
+
 // vim:sw=4 tw=120 et
 
 #ifdef __GNUC__
@@ -29,8 +96,10 @@
 # define UNUSED(d) d
 #endif
 
+#if DO_FILTER
 #define MATCH_REGEX     1
 #define MATCH_INVERT    2
+#endif
 
 #ifdef WIN32
 /*
@@ -97,7 +166,7 @@ int regex_dummy() {return 0;};
 #define REG_NOSUB       0
 #define REG_EXTENDED    0
 #define REG_ICASE       0
-#endif
+#endif /* WIN32 */
 
 enum cli_states {
     STATE_LOGIN,
@@ -113,15 +182,20 @@ struct unp {
     struct unp *next;
 };
 
+#if DO_FILTER
 struct cli_filter_cmds
 {
     const char *cmd;
     const char *help;
 };
+#endif
 
+#if DO_MALLOC
 /* free and zero (to avoid double-free) */
 #define free_z(p) do { if (p) { free(p); (p) = 0; } } while (0)
+#endif
 
+#if DO_FILTER
 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);
@@ -140,14 +214,16 @@ static struct cli_filter_cmds filter_cmds[] =
     { "egrep",   "Include lines that match extended regex" },
     { NULL, NULL}
 };
+#endif
 
-static ssize_t _write(int fd, const void *buf, size_t count)
+static ssize_t _write(struct cli_def *cli, const void *buf, size_t count)
 {
+#if DO_SOCKET
     size_t written = 0;
     ssize_t thisTime =0;
     while (count != written)
     {
-        thisTime = write(fd, (char*)buf + written, count - written);
+        thisTime = write(cli->sockfd, (char*)buf + written, count - written);
         if (thisTime == -1)
         {
             if (errno == EINTR)
@@ -158,9 +234,28 @@ static ssize_t _write(int fd, const void *buf, size_t count)
         written += thisTime;
     }
     return written;
+#else
+    /* No default, user will have to provide write callback */
+    return 0;
+#endif
+}
+
+static ssize_t _read(struct cli_def *cli, void *buf, size_t count)
+{
+#if DO_SOCKET
+    return read(cli->sockfd, buf, count);
+#else
+    /* No default, user will have to provide write callback */
+    return 0;
+#endif
 }
+
+/*
+ * Construct the full name of the current command.
+ */
 char *cli_command_name(struct cli_def *cli, struct cli_command *command)
 {
+#if DO_MALLOC
     char *name = cli->commandname;
     char *o;
 
@@ -180,23 +275,49 @@ char *cli_command_name(struct cli_def *cli, struct cli_command *command)
         command = command->parent;
         free(o);
     }
+#else
+    static char name[CLI_MAX_LINE_LENGTH];
+
+    memset(name, 0, sizeof(name));
+    while (command)
+    {
+        size_t len = strlen(command->command);
+        if (name[0]) {
+            /* shift command string right to make space for parent */
+            memmove(&name[len + 1], &name[0], strlen(name));
+            name[len] = ' ';
+        }
+        memcpy(&name[0], command->command, len);
+        command = command->parent;
+    }
+#endif
     cli->commandname = name;
     return name;
 }
 
+/*
+ * Set authentication callback for login.
+ */
 void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *))
 {
     cli->auth_callback = auth_callback;
 }
 
+/*
+ * Set authentication callback for 'enable'.
+ */
 void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *))
 {
     cli->enable_callback = enable_callback;
 }
 
+/*
+ * Add a user to the list of users allowed to log in (will not call auth_callback).
+ */
 void cli_allow_user(struct cli_def *cli, const char *username, const char *password)
 {
     struct unp *u, *n;
+#if DO_MALLOC
     if (!(n = malloc(sizeof(struct unp))))
     {
         fprintf(stderr, "Couldn't allocate memory for user: %s", strerror(errno));
@@ -215,6 +336,19 @@ void cli_allow_user(struct cli_def *cli, const char *username, const char *passw
         free(n);
         return;
     }
+#else
+    static struct unp __users[CLI_MAX_USERS];
+    int i;
+    for (i = 0; i < CLI_MAX_USERS; ++i) {
+        n = &__users[i];
+        if (! n->username) {
+            n->username = (char *)username;
+            n->password = (char *)password;
+            break;
+        }
+    }
+#endif
+
     n->next = NULL;
 
     if (!cli->users)
@@ -228,15 +362,25 @@ void cli_allow_user(struct cli_def *cli, const char *username, const char *passw
     }
 }
 
+/*
+ * Set a the password for the 'enable' command.
+ */
 void cli_allow_enable(struct cli_def *cli, const char *password)
 {
+#if DO_MALLOC
     free_z(cli->enable_password);
     if (!(cli->enable_password = strdup(password)))
     {
         fprintf(stderr, "Couldn't allocate memory for enable password: %s", strerror(errno));
     }
+#else
+    cli->enable_password = (char *)password;
+#endif
 }
 
+/*
+ * Remove a user from the list of users allowed to log in.
+ */
 void cli_deny_user(struct cli_def *cli, const char *username)
 {
     struct unp *u, *p = NULL;
@@ -249,35 +393,68 @@ void cli_deny_user(struct cli_def *cli, const char *username)
                 p->next = u->next;
             else
                 cli->users = u->next;
+#if DO_MALLOC
             free(u->username);
             free(u->password);
             free(u);
+#else
+            u->username = u->password = NULL;
+#endif
             break;
         }
         p = u;
     }
 }
 
+/*
+ * Set the banner to be displayed before the login prompt.
+ */
 void cli_set_banner(struct cli_def *cli, const char *banner)
 {
+#if DO_MALLOC
     free_z(cli->banner);
     if (banner && *banner)
         cli->banner = strdup(banner);
+#else
+    cli->banner = (char *)banner;
+#endif
 }
 
+/*
+ * Set the name to display as part of the prompt.
+ */
 void cli_set_hostname(struct cli_def *cli, const char *hostname)
 {
+#if DO_MALLOC
     free_z(cli->hostname);
     if (hostname && *hostname)
         cli->hostname = strdup(hostname);
+#else
+    cli->hostname = (char *)hostname;
+#endif
 }
 
+/*
+ * Set the character (actually string) to display after the hostname in
+ * the prompt. This will be "> " for normal commands, or "# " for
+ * privileged commands.
+ */
 void cli_set_promptchar(struct cli_def *cli, const char *promptchar)
 {
+#if DO_MALLOC
     free_z(cli->promptchar);
     cli->promptchar = strdup(promptchar);
+#else
+    cli->promptchar = (char *)promptchar;
+#endif
 }
 
+#if DO_TAB_COMPLETION
+/*
+ * Determine the minimum length that distinguishes each command from its
+ * visible neighbors, used for tab completion. This gets recalculated when
+ * the mode or privilege level changes.
+ */
 static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands)
 {
     struct cli_command *c, *p;
@@ -316,7 +493,13 @@ static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands)
 
     return CLI_OK;
 }
+#endif
 
+/*
+ * Set the privilege level. While it's theoretically possible to set it to
+ * any level, the code only really supports "unprivileged" (0) and
+ * "privileged" (15).
+ */
 int cli_set_privilege(struct cli_def *cli, int priv)
 {
     int old = cli->privilege;
@@ -331,13 +514,26 @@ int cli_set_privilege(struct cli_def *cli, int priv)
     return old;
 }
 
+/*
+ * Set the "mode" string to display in the command prompt. While this is
+ * public, cli_set_configmode() stomps all over the user's mode string, so
+ * it really ought to be static.
+ */
 void cli_set_modestring(struct cli_def *cli, const char *modestring)
 {
+#if DO_MALLOC
     free_z(cli->modestring);
     if (modestring)
         cli->modestring = strdup(modestring);
+#else
+    cli->modestring = (char *)modestring;
+#endif
 }
 
+/*
+ * Set the execution mode. While it's theoretically possible to set it to
+ * any level, the code only really supports "exec" (normal) and "config".
+ */
 int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
 {
     int old = cli->mode;
@@ -352,7 +548,11 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
         }
         else if (config_desc && *config_desc)
         {
+#if DO_MALLOC
             char string[64];
+#else
+            static char string[64];
+#endif
             snprintf(string, sizeof(string), "(config-%s)", config_desc);
             cli_set_modestring(cli, string);
         }
@@ -367,6 +567,9 @@ int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc)
     return old;
 }
 
+/*
+ * Add a new command to the command tree.
+ */
 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)
@@ -374,17 +577,41 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command
     struct cli_command *c, *p;
 
     if (!command) return NULL;
+#if DO_MALLOC
     if (!(c = calloc(sizeof(struct cli_command), 1))) return NULL;
+#else
+    /* This is implicitly zeroed by being in placed in bss. We could be
+     * more explict by making it global, and having cli_init zero it.
+     */
+    static struct cli_command __commands[CLI_MAX_COMMANDS];
+    int i;
+    for (i = 0; i < CLI_MAX_COMMANDS; ++i) {
+        c = &__commands[i];
+        if (c->command == NULL)
+            break;
+    }
+    if (i >= CLI_MAX_COMMANDS)
+        return NULL;
+    memset(c, 0, sizeof(*c));
+#endif
 
     c->callback = callback;
     c->next = NULL;
+#if DO_MALLOC
     if (!(c->command = strdup(command)))
         return NULL;
+#else
+    c->command = (char *)command;
+#endif
     c->parent = parent;
     c->privilege = privilege;
     c->mode = mode;
+#if DO_MALLOC
     if (help && !(c->help = strdup(help)))
         return NULL;
+#else
+    c->help = (char *)help;
+#endif
 
     if (parent)
     {
@@ -413,6 +640,9 @@ struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command
     return c;
 }
 
+/*
+ * Free dynamic memory used in a command structure, after unregistering it.
+ */
 static void cli_free_command(struct cli_command *cmd)
 {
     struct cli_command *c, *p;
@@ -424,11 +654,18 @@ static void cli_free_command(struct cli_command *cmd)
         c = p;
     }
 
+#if DO_MALLOC
     free(cmd->command);
     if (cmd->help) free(cmd->help);
     free(cmd);
+#else
+    cmd->command = cmd->help = NULL;
+#endif
 }
 
+/*
+ * Remove a command from the command tree.
+ */
 int cli_unregister_command(struct cli_def *cli, const char *command)
 {
     struct cli_command *c, *p = NULL;
@@ -454,6 +691,11 @@ int cli_unregister_command(struct cli_def *cli, const char *command)
     return CLI_OK;
 }
 
+/*
+ * Show the name and help string for commands. This is called recursively
+ * to show all commands that are visible given the current mode and
+ * privilege level.
+ */
 int cli_show_help(struct cli_def *cli, struct cli_command *c)
 {
     struct cli_command *p;
@@ -463,7 +705,7 @@ int cli_show_help(struct cli_def *cli, struct cli_command *c)
         if (p->command && p->callback && cli->privilege >= p->privilege &&
             (p->mode == cli->mode || p->mode == MODE_ANY))
         {
-            cli_error(cli, "  %-20s %s", cli_command_name(cli, p), (p->help != NULL ? p->help : ""));
+            cli_error(cli, "  %-30s %s", cli_command_name(cli, p), (p->help != NULL ? p->help : ""));
         }
 
         if (p->children)
@@ -473,6 +715,9 @@ int cli_show_help(struct cli_def *cli, struct cli_command *c)
     return CLI_OK;
 }
 
+/*
+ * Internal command 'enable'.
+ */
 int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     if (cli->privilege == PRIVILEGE_PRIVILEGED)
@@ -493,6 +738,9 @@ int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char
     return CLI_OK;
 }
 
+/*
+ * Internal command 'disable'.
+ */
 int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
@@ -500,6 +748,9 @@ int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(cha
     return CLI_OK;
 }
 
+/*
+ * Internal command 'help'.
+ */
 int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     cli_error(cli, "\nCommands available:");
@@ -507,12 +758,15 @@ int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *
     return CLI_OK;
 }
 
+/*
+ * Internal command 'history'.
+ */
 int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     int i;
 
     cli_error(cli, "\nCommand history:");
-    for (i = 0; i < MAX_HISTORY; i++)
+    for (i = 0; i < CLI_MAX_HISTORY; i++)
     {
         if (cli->history[i])
             cli_error(cli, "%3d. %s", i, cli->history[i]);
@@ -521,6 +775,9 @@ int cli_int_history(struct cli_def *cli, UNUSED(const char *command), UNUSED(cha
     return CLI_OK;
 }
 
+/*
+ * Internal command 'quit'.
+ */
 int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
@@ -528,6 +785,9 @@ int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *
     return CLI_QUIT;
 }
 
+/*
+ * Internal command 'exit'.
+ */
 int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc)
 {
     if (cli->mode == MODE_EXEC)
@@ -542,23 +802,37 @@ int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int arg
     return CLI_OK;
 }
 
+#if DO_IDLE_TIMEOUT
+/*
+ * Idle-timeout callback function.
+ * This is named as if it was an internal command, but it's not.
+ */
 int cli_int_idle_timeout(struct cli_def *cli)
 {
     cli_print(cli, "Idle timeout");
     return CLI_QUIT;
 }
+#endif
 
+/*
+ * Internal command 'configure terminal' - set the mode to "config", which
+ * changes the prompt, disables some comands, enables other commands.
+ */
 int cli_int_configure_terminal(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc))
 {
     cli_set_configmode(cli, MODE_CONFIG, NULL);
     return CLI_OK;
 }
 
+/*
+ * Initialize libcli structures, and register the internal commands.
+ */
 struct cli_def *cli_init()
 {
     struct cli_def *cli;
     struct cli_command *c;
 
+#if DO_MALLOC
     if (!(cli = calloc(sizeof(struct cli_def), 1)))
         return 0;
 
@@ -568,7 +842,23 @@ struct cli_def *cli_init()
         free_z(cli);
         return 0;
     }
+#else
+    static struct cli_def __cli;
+    static char __buffer[1024];
+    cli = &__cli;
+    if (cli->buffer) {
+        fprintf(stderr, "Cannot start a second instance of cli with static memory\n");
+        return NULL;
+    }
+    memset(cli, 0, sizeof(*cli));       /* should already be zeroed */
+    cli->buf_size = sizeof(__buffer);
+    cli->buffer = __buffer;
+    memset(cli->buffer, 0, cli->buf_size);
+#endif
+
+#if DO_TELNET
     cli->telnet_protocol = 1;
+#endif
 
     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");
@@ -587,17 +877,24 @@ struct cli_def *cli_init()
 
     cli->privilege = cli->mode = -1;
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
-    cli_set_configmode(cli, MODE_EXEC, 0);
+    cli_set_configmode(cli, MODE_EXEC, NULL);
 
     // Default to 1 second timeout intervals
     cli->timeout_tm.tv_sec = 1;
     cli->timeout_tm.tv_usec = 0;
 
+#if DO_IDLE_TIMEOUT
     // Set default idle timeout callback, but no timeout
     cli_set_idle_timeout_callback(cli, 0, cli_int_idle_timeout);
+#endif
     return cli;
 }
 
+/*
+ * Unregister all commands, as part of cli_done().
+ * While this is a public function, it seems extremely unwise to call it
+ * from user code.
+ */
 void cli_unregister_all(struct cli_def *cli, struct cli_command *command)
 {
     struct cli_command *c, *p = NULL;
@@ -613,17 +910,27 @@ void cli_unregister_all(struct cli_def *cli, struct cli_command *command)
         if (c->children)
             cli_unregister_all(cli, c->children);
 
+#if DO_MALLOC
         if (c->command) free(c->command);
         if (c->help) free(c->help);
         free(c);
+#else
+        c->command = c->help = NULL;
+#endif
 
         c = p;
     }
 }
 
+/*
+ * Clean up all data structures, and free all dynamic memory.
+ */
 int cli_done(struct cli_def *cli)
 {
-    struct unp *u = cli->users, *n;
+    struct unp *u = cli->users;
+#if DO_MALLOC
+    struct unp *n;
+#endif
 
     if (!cli) return CLI_OK;
     cli_free_history(cli);
@@ -631,16 +938,21 @@ int cli_done(struct cli_def *cli)
     // Free all users
     while (u)
     {
+#if DO_MALLOC
         if (u->username) free(u->username);
         if (u->password) free(u->password);
         n = u->next;
         free(u);
         u = n;
+#else
+        u->username = u->password = NULL;
+#endif
     }
 
     /* free all commands */
     cli_unregister_all(cli, 0);
 
+#if DO_MALLOC
     free_z(cli->commandname);
     free_z(cli->modestring);
     free_z(cli->banner);
@@ -648,52 +960,88 @@ int cli_done(struct cli_def *cli)
     free_z(cli->hostname);
     free_z(cli->buffer);
     free_z(cli);
+#else
+    memset(cli, 0, sizeof(*cli));
+#endif
 
     return CLI_OK;
 }
 
+/*
+ * Add the most recent command to the history buffer.
+ */
 static int cli_add_history(struct cli_def *cli, const char *cmd)
 {
+#if ! DO_MALLOC
+    static char __history[CLI_MAX_HISTORY][CLI_MAX_LINE_LENGTH];
+#endif
     int i;
-    for (i = 0; i < MAX_HISTORY; i++)
+    for (i = 0; i < CLI_MAX_HISTORY; i++)
     {
         if (!cli->history[i])
         {
             if (i == 0 || strcasecmp(cli->history[i-1], cmd))
+#if DO_MALLOC
             if (!(cli->history[i] = strdup(cmd)))
                 return CLI_ERROR;
+#else
+            cli->history[i] = __history[i];
+            strncpy(cli->history[i], cmd, CLI_MAX_LINE_LENGTH);
+#endif
             return CLI_OK;
         }
     }
     // No space found, drop one off the beginning of the list
+#if DO_MALLOC
     free(cli->history[0]);
-    for (i = 0; i < MAX_HISTORY-1; i++)
+    for (i = 0; i < CLI_MAX_HISTORY-1; i++)
         cli->history[i] = cli->history[i+1];
-    if (!(cli->history[MAX_HISTORY - 1] = strdup(cmd)))
+    if (!(cli->history[CLI_MAX_HISTORY - 1] = strdup(cmd)))
         return CLI_ERROR;
+#else
+    memmove(__history[0], __history[1], (CLI_MAX_HISTORY - 1) * CLI_MAX_LINE_LENGTH);
+    strncpy(cli->history[CLI_MAX_HISTORY - 1], cmd, CLI_MAX_LINE_LENGTH);
+#endif
     return CLI_OK;
 }
 
+/*
+ * Free the history buffer. This is normally called internally, but it's a
+ * public function, and I suppose user code could call it to clean up at
+ * random moments.
+ */
 void cli_free_history(struct cli_def *cli)
 {
     int i;
-    for (i = 0; i < MAX_HISTORY; i++)
+    for (i = 0; i < CLI_MAX_HISTORY; i++)
     {
         if (cli->history[i])
+#if DO_MALLOC
             free_z(cli->history[i]);
+#else
+            cli->history[i] = NULL;
+#endif
     }
 }
 
+/*
+ * Split a command line into words, with quoting and filtering
+ */
 static int cli_parse_line(const char *line, char *words[], int max_words)
 {
     int nwords = 0;
     const char *p = line;
     const char *word_start = 0;
     int inquote = 0;
+#if ! DO_MALLOC
+    static char __line[CLI_MAX_LINE_LENGTH];
+    memset(__line, 0, sizeof(__line));
+    char *__line_ptr = __line;
+#endif
 
     while (*p)
     {
-        if (!isspace(*p))
+        if (!isspace((int)*p))
         {
             word_start = p;
             break;
@@ -703,13 +1051,19 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
 
     while (nwords < max_words - 1)
     {
-        if (!*p || *p == inquote || (word_start && !inquote && (isspace(*p) || *p == '|')))
+        if (!*p || *p == inquote || (word_start && !inquote && (isspace((int)*p) || *p == '|')))
         {
             if (word_start)
             {
                 int len = p - word_start;
 
-                memcpy(words[nwords] = malloc(len + 1), word_start, len);
+#if DO_MALLOC
+                words[nwords] = malloc(len + 1);
+#else
+                words[nwords] = __line_ptr;
+                __line_ptr += len + 1;
+#endif
+                memcpy(words[nwords], word_start, len);
                 words[nwords++][len] = 0;
             }
 
@@ -733,10 +1087,16 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
             {
                 if (*p == '|')
                 {
+#if DO_MALLOC
                     if (!(words[nwords++] = strdup("|")))
                         return 0;
+#else
+                    words[nwords++] = __line_ptr;
+                    *__line_ptr = '|';
+                    __line_ptr += 2;
+#endif
                 }
-                else if (!isspace(*p))
+                else if (!isspace((int)*p))
                     word_start = p;
             }
 
@@ -747,6 +1107,10 @@ static int cli_parse_line(const char *line, char *words[], int max_words)
     return nwords;
 }
 
+#if DO_FILTER
+/*
+ * Join a set of words into a string.
+ */
 static char *join_words(int argc, char **argv)
 {
     char *p;
@@ -774,15 +1138,22 @@ static char *join_words(int argc, char **argv)
 
     return p;
 }
+#endif
 
+/*
+ * Find the command structure that matches a command string, and call the
+ * callback function, with remaining tokens of the command line as argv[].
+ */
 static int cli_find_command(struct cli_def *cli, struct cli_command *commands, int num_words, char *words[],
                             int start_word, int filters[])
 {
     struct cli_command *c, *again_config = NULL, *again_any = NULL;
     int c_words = num_words;
 
+#if DO_FILTER
     if (filters[0])
         c_words = filters[0];
+#endif
 
     // Deal with ? for help
     if (!words[start_word])
@@ -813,9 +1184,6 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
         if (cli->privilege < c->privilege)
             continue;
 
-        if (strncasecmp(c->command, words[start_word], c->unique_len))
-            continue;
-
         if (strncasecmp(c->command, words[start_word], strlen(words[start_word])))
             continue;
 
@@ -823,8 +1191,10 @@ 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;
+#if DO_FILTER
             int f;
             struct cli_filter **filt = &cli->filters;
+#endif
 
             // Found a word!
             if (!c->children)
@@ -870,6 +1240,7 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
             }
 
             CORRECT_CHECKS:
+#if DO_FILTER
             for (f = 0; rc == CLI_OK && filters[f]; f++)
             {
                 int n = num_words;
@@ -939,10 +1310,12 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
                     *filt = 0;
                 }
             }
+#endif /* DO_FILTER */
 
             if (rc == CLI_OK)
                 rc = c->callback(cli, cli_command_name(cli, c), words + start_word + 1, c_words - start_word - 1);
 
+#if DO_FILTER
             while (cli->filters)
             {
                 struct cli_filter *filt = cli->filters;
@@ -952,6 +1325,7 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
                 cli->filters = filt->next;
                 free(filt);
             }
+#endif
 
             return rc;
         }
@@ -988,35 +1362,49 @@ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, i
     return CLI_ERROR_ARG;
 }
 
+/*
+ * Find and call the function associated with a command.
+ */
 int cli_run_command(struct cli_def *cli, const char *command)
 {
     int r;
-    unsigned int num_words, i, f;
+    unsigned int num_words;
     char *words[CLI_MAX_LINE_WORDS] = {0};
+#if DO_MALLOC
+    unsigned int i;
+#endif
+#if DO_FILTER
     int filters[CLI_MAX_LINE_WORDS] = {0};
+    unsigned int f;
+#else
+    int *filters = NULL;
+#endif
 
     if (!command) return CLI_ERROR;
-    while (isspace(*command))
+    while (isspace((int)*command))
         command++;
 
     if (!*command) return CLI_OK;
 
     num_words = cli_parse_line(command, words, CLI_MAX_LINE_WORDS);
+#if DO_FILTER
     for (i = f = 0; i < num_words && f < CLI_MAX_LINE_WORDS - 1; i++)
     {
         if (words[i][0] == '|')
         filters[f++] = i;
     }
-
     filters[f] = 0;
+#endif
 
     if (num_words)
         r = cli_find_command(cli, cli->commands, num_words, words, 0, filters);
     else
         r = CLI_ERROR;
 
+#if DO_MALLOC
     for (i = 0; i < num_words; i++)
         free(words[i]);
+#endif
 
     if (r == CLI_QUIT)
         return r;
@@ -1024,25 +1412,38 @@ int cli_run_command(struct cli_def *cli, const char *command)
     return CLI_OK;
 }
 
+#if DO_TAB_COMPLETION
+/*
+ * Build a list of possible completions for a partial command.
+ */
 static int cli_get_completions(struct cli_def *cli, const char *command, char **completions, int max_completions)
 {
     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};
+#if DO_MALLOC
+    int save_words;
+#endif
+#if DO_FILTER
     int filter = 0;
+#endif
 
     if (!command) return 0;
-    while (isspace(*command))
+    while (isspace((int)*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 DO_MALLOC
+    save_words = num_words;
+#endif
     if (!command[0] || command[strlen(command)-1] == ' ')
         num_words++;
 
     if (!num_words)
         goto out;
 
+#if DO_FILTER
     for (i = 0; i < num_words; i++)
     {
         if (words[i] && words[i][0] == '|')
@@ -1068,6 +1469,7 @@ static int cli_get_completions(struct cli_def *cli, const char *command, char **
         completions[k] = NULL;
         goto out;
     }
+#endif
 
     for (c = cli->commands, i = 0; c && i < num_words && k < max_completions; c = n)
     {
@@ -1096,55 +1498,85 @@ static int cli_get_completions(struct cli_def *cli, const char *command, char **
     }
 
 out:
+#if DO_MALLOC
     for (i = 0; i < save_words; i++)
         free(words[i]);
+#endif
 
     return k;
 }
+#endif /* DO_TAB_COMPLETION */
 
-static void cli_clear_line(int sockfd, char *cmd, int l, int cursor)
+/*
+ * Clear the current command line on the console.
+ */
+static void cli_clear_line(struct cli_def *cli, char *cmd, int l, int cursor)
 {
     int i;
+    /* If user is editing, cursor may be in the middle of the line */
     if (cursor < l)
     {
         for (i = 0; i < (l - cursor); i++)
-            _write(sockfd, " ", 1);
+            cli->write_callback(cli, " ", 1);
     }
+    /* Send the cursor back to the beginning of the line, overwrite with
+     * spaces, and return the cursor to the beginning.
+     */
     for (i = 0; i < l; i++)
         cmd[i] = '\b';
-    for (; i < l * 2; i++)
+    cli->write_callback(cli, cmd, l);
+    for (i = 0; i < l; i++)
         cmd[i] = ' ';
-    for (; i < l * 3; i++)
+    cli->write_callback(cli, cmd, l);
+    for (i = 0; i < l; i++)
         cmd[i] = '\b';
-    _write(sockfd, cmd, i);
-    memset((char *)cmd, 0, i);
+    cli->write_callback(cli, cmd, l);
+    memset((char *)cmd, 0, l);
     l = cursor = 0;
 }
 
+/*
+ * Set a flag to show the prompt (and the line in progress) at the next
+ * opportunity. In the example code, this is called after displaying text
+ * from a periodic callback, though I suppose it might have other uses.
+ */
 void cli_reprompt(struct cli_def *cli)
 {
     if (!cli) return;
     cli->showprompt = 1;
 }
 
+#if DO_REGULAR
+/*
+ * Set a regular (periodic) callback.
+ */
 void cli_regular(struct cli_def *cli, int (*callback)(struct cli_def *cli))
 {
     if (!cli) return;
     cli->regular_callback = callback;
 }
 
+/*
+ * Set the interval for regular (periodic) callbacks.
+ */
 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;
 }
+#endif
 
+/*
+ * Check the entered password against a stored password.
+ * This supports hashed passwords.
+ */
+static int pass_matches(const char *pass, const char *try)
+{
+#if DO_CRYPT
 #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;
@@ -1156,37 +1588,61 @@ static int pass_matches(const char *pass, const char *try)
     if (des || !strncmp(pass, MD5_PREFIX, sizeof(MD5_PREFIX)-1))
         try = crypt(try, pass);
 #endif
+#endif /* DO_CRYPT */
 
     return !strcmp(pass, try);
 }
 
 #define CTRL(c) (c - '@')
 
-static int show_prompt(struct cli_def *cli, int sockfd)
+/*
+ * Show the command prompt.
+ */
+static int show_prompt(struct cli_def *cli)
 {
     int len = 0;
 
     if (cli->hostname)
-        len += write(sockfd, cli->hostname, strlen(cli->hostname));
+        len += cli->write_callback(cli, cli->hostname, strlen(cli->hostname));
 
     if (cli->modestring)
-        len += write(sockfd, cli->modestring, strlen(cli->modestring));
+        len += cli->write_callback(cli, cli->modestring, strlen(cli->modestring));
 
-    return len + write(sockfd, cli->promptchar, strlen(cli->promptchar));
+    return len + cli->write_callback(cli, cli->promptchar, strlen(cli->promptchar));
 }
 
+/*
+ * Main processing loop. Massive and threatening.
+ */
 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 n, l, oldl = 0, esc = 0;
+#if DO_TELNET
+    int is_telnet_option = 0;
+#endif
     int cursor = 0, insertmode = 1;
+#if DO_MALLOC
     char *cmd = NULL, *oldcmd = 0;
     char *username = NULL, *password = NULL;
+#else
+    char cmd[CLI_MAX_LINE_LENGTH];
+    char *oldcmd = NULL;
+    char username[CLI_MAX_LINE_LENGTH];
+    char *password = NULL;
+#endif
+
+    cli->sockfd = sockfd;
+    if (cli->read_callback == NULL)
+        cli->read_callback = _read;
+    if (cli->write_callback == NULL)
+        cli->write_callback = _write;
 
     cli_build_shortest(cli, cli->commands);
     cli->state = STATE_LOGIN;
 
     cli_free_history(cli);
+#if DO_TELNET
     if (cli->telnet_protocol)
     {
         static const char *negotiate =
@@ -1194,31 +1650,23 @@ int cli_loop(struct cli_def *cli, int sockfd)
             "\xFF\xFB\x01"
             "\xFF\xFD\x03"
             "\xFF\xFD\x01";
-        _write(sockfd, negotiate, strlen(negotiate));
+        cli->write_callback(cli, negotiate, strlen(negotiate));
     }
+#endif
 
+#if DO_MALLOC
     if ((cmd = malloc(CLI_MAX_LINE_LENGTH)) == NULL)
         return CLI_ERROR;
-
-#ifdef WIN32
-    /*
-     * OMG, HACK
-     */
-    if (!(cli->client = fdopen(_open_osfhandle(sockfd, 0), "w+")))
-        return CLI_ERROR;
-    cli->client->_file = sockfd;
-#else
-    if (!(cli->client = fdopen(sockfd, "w+")))
-        return CLI_ERROR;
 #endif
 
-    setbuf(cli->client, NULL);
     if (cli->banner)
         cli_error(cli, "%s", cli->banner);
 
+#if DO_IDLE_TIMEOUT
     // Set the last action now so we don't time immediately
     if (cli->idle_timeout)
         time(&cli->last_action);
+#endif
 
     /* start off in unprivileged mode */
     cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED);
@@ -1228,15 +1676,18 @@ int cli_loop(struct cli_def *cli, int sockfd)
     if (!cli->users && !cli->auth_callback)
         cli->state = STATE_NORMAL;
 
+    /* process commands */
     while (1)
     {
         signed int in_history = 0;
         int lastchar = 0;
+#if DO_SOCKET
         struct timeval tm;
+#endif
 
         cli->showprompt = 1;
 
-        if (oldcmd)
+        if (oldcmd)     /* previous command ended with '?', resume */
         {
             l = cursor = oldl;
             oldcmd[l] = 0;
@@ -1244,48 +1695,53 @@ int cli_loop(struct cli_def *cli, int sockfd)
             oldcmd = NULL;
             oldl = 0;
         }
-        else
+        else            /* start a new command */
         {
             memset(cmd, 0, CLI_MAX_LINE_LENGTH);
             l = 0;
             cursor = 0;
         }
 
+#if DO_SOCKET
         memcpy(&tm, &cli->timeout_tm, sizeof(tm));
+#endif
 
+        /* accumulate one command */
         while (1)
         {
+#if DO_SOCKET
             int sr;
             fd_set r;
+#endif
             if (cli->showprompt)
             {
                 if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                    _write(sockfd, "\r\n", 2);
+                    cli->write_callback(cli, "\r\n", 2);
 
                 switch (cli->state)
                 {
                     case STATE_LOGIN:
-                        _write(sockfd, "Username: ", strlen("Username: "));
+                        cli->write_callback(cli, "Username: ", strlen("Username: "));
                         break;
 
                     case STATE_PASSWORD:
-                        _write(sockfd, "Password: ", strlen("Password: "));
+                        cli->write_callback(cli, "Password: ", strlen("Password: "));
                         break;
 
                     case STATE_NORMAL:
                     case STATE_ENABLE:
-                        show_prompt(cli, sockfd);
-                        _write(sockfd, cmd, l);
+                        show_prompt(cli);
+                        cli->write_callback(cli, cmd, l);
                         if (cursor < l)
                         {
                             int n = l - cursor;
                             while (n--)
-                                _write(sockfd, "\b", 1);
+                                cli->write_callback(cli, "\b", 1);
                         }
                         break;
 
                     case STATE_ENABLE_PASSWORD:
-                        _write(sockfd, "Password: ", strlen("Password: "));
+                        cli->write_callback(cli, "Password: ", strlen("Password: "));
                         break;
 
                 }
@@ -1293,6 +1749,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 cli->showprompt = 0;
             }
 
+#if DO_SOCKET
             FD_ZERO(&r);
             FD_SET(sockfd, &r);
 
@@ -1307,15 +1764,24 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 break;
             }
 
+            /* 
+             * There are other, system-specific, ways to deal with
+             * timing-related functions, but I don't know if they're
+             * desirable outside of a unix-y environment. For now, let's
+             * just disable the whole thing for non-sockets builds.
+             */
             if (sr == 0)
             {
+#if DO_REGULAR
                 /* timeout every second */
                 if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK)
                 {
                     l = -1;
                     break;
                 }
+#endif
 
+#if DO_IDLE_TIMEOUT
                 if (cli->idle_timeout)
                 {
                     if (time(NULL) - cli->last_action >= cli->idle_timeout)
@@ -1335,12 +1801,19 @@ int cli_loop(struct cli_def *cli, int sockfd)
                         break;
                     }
                 }
+#endif
 
                 memcpy(&tm, &cli->timeout_tm, sizeof(tm));
                 continue;
             }
-
-            if ((n = read(sockfd, &c, 1)) < 0)
+#endif /* DO_SOCKET */
+
+            /*
+             * Read the next character(s) from the input. If the previous
+             * code block is enabled, this should have data, but don't
+             * freak out if it doesn't.
+             */
+            if ((n = cli->read_callback(cli, &c, 1)) < 0)
             {
                 if (errno == EINTR)
                     continue;
@@ -1350,8 +1823,10 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 break;
             }
 
+#if DO_IDLE_TIMEOUT
             if (cli->idle_timeout)
                 time(&cli->last_action);
+#endif
 
             if (n == 0)
             {
@@ -1359,12 +1834,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 break;
             }
 
-            if (skip)
-            {
-                skip--;
-                continue;
-            }
-
+#if DO_TELNET
             if (c == 255 && !is_telnet_option)
             {
                 is_telnet_option++;
@@ -1387,6 +1857,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
 
                 is_telnet_option = 0;
             }
+#endif
 
             /* handle ANSI arrows */
             if (esc)
@@ -1431,7 +1902,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
             if (c == '\r')
             {
                 if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                    _write(sockfd, "\r\n", 2);
+                    cli->write_callback(cli, "\r\n", 2);
                 break;
             }
 
@@ -1443,7 +1914,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
 
             if (c == CTRL('C'))
             {
-                _write(sockfd, "\a", 1);
+                cli->write_callback(cli, "\a", 1);
                 continue;
             }
 
@@ -1475,7 +1946,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 {
                     if (l == 0 || cursor == 0)
                     {
-                        _write(sockfd, "\a", 1);
+                        cli->write_callback(cli, "\a", 1);
                         continue;
                     }
 
@@ -1490,7 +1961,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                         {
                             cmd[--cursor] = 0;
                             if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                                _write(sockfd, "\b \b", 3);
+                                cli->write_callback(cli, "\b \b", 3);
                         }
                         else
                         {
@@ -1499,11 +1970,11 @@ int cli_loop(struct cli_def *cli, int sockfd)
                             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);
+                                cli->write_callback(cli, "\b", 1);
+                                cli->write_callback(cli, cmd + cursor, strlen(cmd + cursor));
+                                cli->write_callback(cli, " ", 1);
                                 for (i = 0; i <= (int)strlen(cmd + cursor); i++)
-                                    _write(sockfd, "\b", 1);
+                                    cli->write_callback(cli, "\b", 1);
                             }
                         }
                         l--;
@@ -1522,12 +1993,12 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
                     continue;
 
-                _write(sockfd, "\r\n", 2);
-                show_prompt(cli, sockfd);
-                _write(sockfd, cmd, l);
+                cli->write_callback(cli, "\r\n", 2);
+                show_prompt(cli);
+                cli->write_callback(cli, cmd, l);
 
                 for (i = 0; i < cursorback; i++)
-                    _write(sockfd, "\b", 1);
+                    cli->write_callback(cli, "\b", 1);
 
                 continue;
             }
@@ -1538,7 +2009,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 if (cli->state == STATE_PASSWORD || cli->state == STATE_ENABLE_PASSWORD)
                     memset(cmd, 0, l);
                 else
-                    cli_clear_line(sockfd, cmd, l, cursor);
+                    cli_clear_line(cli, cmd, l, cursor);
 
                 l = cursor = 0;
                 continue;
@@ -1554,10 +2025,10 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 {
                     int c;
                     for (c = cursor; c < l; c++)
-                        _write(sockfd, " ", 1);
+                        cli->write_callback(cli, " ", 1);
 
                     for (c = cursor; c < l; c++)
-                        _write(sockfd, "\b", 1);
+                        cli->write_callback(cli, "\b", 1);
                 }
 
                 memset(cmd + cursor, 0, l - cursor);
@@ -1583,7 +2054,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
             {
                 if (cli->mode != MODE_EXEC)
                 {
-                    cli_clear_line(sockfd, cmd, l, cursor);
+                    cli_clear_line(cli, cmd, l, cursor);
                     cli_set_configmode(cli, MODE_EXEC, NULL);
                     cli->showprompt = 1;
                 }
@@ -1591,6 +2062,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 continue;
             }
 
+#if DO_TAB_COMPLETION
             /* TAB completion */
             if (c == CTRL('I'))
             {
@@ -1605,7 +2077,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 num_completions = cli_get_completions(cli, cmd, completions, CLI_MAX_LINE_WORDS);
                 if (num_completions == 0)
                 {
-                    _write(sockfd, "\a", 1);
+                    cli->write_callback(cli, "\a", 1);
                 }
                 else if (num_completions == 1)
                 {
@@ -1614,39 +2086,40 @@ int cli_loop(struct cli_def *cli, int sockfd)
                     {
                         if (cmd[l-1] == ' ' || cmd[l-1] == '|')
                             break;
-                        _write(sockfd, "\b", 1);
+                        cli->write_callback(cli, "\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);
+                    cli->write_callback(cli, completions[0], strlen(completions[0]));
+                    cli->write_callback(cli, " ", 1);
                 }
                 else if (lastchar == CTRL('I'))
                 {
                     // double tab
                     int i;
-                    _write(sockfd, "\r\n", 2);
+                    cli->write_callback(cli, "\r\n", 2);
                     for (i = 0; i < num_completions; i++)
                     {
-                        _write(sockfd, completions[i], strlen(completions[i]));
+                        cli->write_callback(cli, completions[i], strlen(completions[i]));
                         if (i % 4 == 3)
-                            _write(sockfd, "\r\n", 2);
+                            cli->write_callback(cli, "\r\n", 2);
                         else
-                            _write(sockfd, "     ", 1);
+                            cli->write_callback(cli, "     ", 1);
                     }
-                    if (i % 4 != 3) _write(sockfd, "\r\n", 2);
+                    if (i % 4 != 3) cli->write_callback(cli, "\r\n", 2);
                         cli->showprompt = 1;
                 }
                 else
                 {
                     // More than one completion
                     lastchar = c;
-                    _write(sockfd, "\a", 1);
+                    cli->write_callback(cli, "\a", 1);
                 }
                 continue;
             }
+#endif /* DO_TAB_COMPLETION */
 
             /* history */
             if (c == CTRL('P') || c == CTRL('N'))
@@ -1661,7 +2134,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                     in_history--;
                     if (in_history < 0)
                     {
-                        for (in_history = MAX_HISTORY-1; in_history >= 0; in_history--)
+                        for (in_history = CLI_MAX_HISTORY-1; in_history >= 0; in_history--)
                         {
                             if (cli->history[in_history])
                             {
@@ -1678,10 +2151,10 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 else // Down
                 {
                     in_history++;
-                    if (in_history >= MAX_HISTORY || !cli->history[in_history])
+                    if (in_history >= CLI_MAX_HISTORY || !cli->history[in_history])
                     {
                         int i = 0;
-                        for (i = 0; i < MAX_HISTORY; i++)
+                        for (i = 0; i < CLI_MAX_HISTORY; i++)
                         {
                             if (cli->history[i])
                             {
@@ -1699,11 +2172,11 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 if (history_found && cli->history[in_history])
                 {
                     // Show history item
-                    cli_clear_line(sockfd, cmd, l, cursor);
+                    cli_clear_line(cli, 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);
+                    cli->write_callback(cli, cmd, l);
                 }
 
                 continue;
@@ -1717,7 +2190,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                     if (cursor)
                     {
                         if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                            _write(sockfd, "\b", 1);
+                            cli->write_callback(cli, "\b", 1);
 
                         cursor--;
                     }
@@ -1727,7 +2200,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                     if (cursor < l)
                     {
                         if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                            _write(sockfd, &cmd[cursor], 1);
+                            cli->write_callback(cli, &cmd[cursor], 1);
 
                         cursor++;
                     }
@@ -1743,8 +2216,8 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 {
                     if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
                     {
-                        _write(sockfd, "\r", 1);
-                        show_prompt(cli, sockfd);
+                        cli->write_callback(cli, "\r", 1);
+                        show_prompt(cli);
                     }
 
                     cursor = 0;
@@ -1759,7 +2232,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 if (cursor < l)
                 {
                     if (cli->state != STATE_PASSWORD && cli->state != STATE_ENABLE_PASSWORD)
-                        _write(sockfd, &cmd[cursor], l - cursor);
+                        cli->write_callback(cli, &cmd[cursor], l - cursor);
 
                     cursor = l;
                 }
@@ -1779,7 +2252,7 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 }
                 else
                 {
-                    _write(sockfd, "\a", 1);
+                    cli->write_callback(cli, "\a", 1);
                     continue;
                 }
             }
@@ -1796,9 +2269,9 @@ int cli_loop(struct cli_def *cli, int sockfd)
                     // Write what we've just added
                     cmd[cursor] = c;
 
-                    _write(sockfd, &cmd[cursor], l - cursor + 1);
+                    cli->write_callback(cli, &cmd[cursor], l - cursor + 1);
                     for (i = 0; i < (l - cursor + 1); i++)
-                        _write(sockfd, "\b", 1);
+                        cli->write_callback(cli, "\b", 1);
                     l++;
                 }
                 else
@@ -1812,18 +2285,18 @@ int cli_loop(struct cli_def *cli, int sockfd)
             {
                 if (c == '?' && cursor == l)
                 {
-                    _write(sockfd, "\r\n", 2);
+                    cli->write_callback(cli, "\r\n", 2);
                     oldcmd = cmd;
                     oldl = cursor = l - 1;
                     break;
                 }
-                _write(sockfd, &c, 1);
+                cli->write_callback(cli, &c, 1);
             }
 
             oldcmd = 0;
             oldl = 0;
             lastchar = c;
-        }
+        } /* while (1) */
 
         if (l < 0) break;
 
@@ -1832,9 +2305,13 @@ int cli_loop(struct cli_def *cli, int sockfd)
             if (l == 0) continue;
 
             /* require login */
+#if DO_MALLOC
             free_z(username);
             if (!(username = strdup(cmd)))
-                return 0;
+                return CLI_ERROR;
+#else
+            strncpy(username, cmd, sizeof(username));
+#endif
             cli->state = STATE_PASSWORD;
             cli->showprompt = 1;
         }
@@ -1843,9 +2320,13 @@ int cli_loop(struct cli_def *cli, int sockfd)
             /* require password */
             int allowed = 0;
 
+#if DO_MALLOC
             free_z(password);
             if (!(password = strdup(cmd)))
-                return 0;
+                return CLI_ERROR;
+#else
+            password = cmd;
+#endif
             if (cli->auth_callback)
             {
                 if (cli->auth_callback(username, password) == CLI_OK)
@@ -1873,8 +2354,10 @@ int cli_loop(struct cli_def *cli, int sockfd)
             else
             {
                 cli_error(cli, "\n\nAccess denied");
+#if DO_MALLOC
                 free_z(username);
                 free_z(password);
+#endif
                 cli->state = STATE_LOGIN;
             }
 
@@ -1918,22 +2401,28 @@ int cli_loop(struct cli_def *cli, int sockfd)
                 break;
         }
 
+#if DO_IDLE_TIMEOUT
         // 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);
+#endif
     }
 
     cli_free_history(cli);
+#if DO_MALLOC
     free_z(username);
     free_z(password);
     free_z(cmd);
+#endif
 
-    fclose(cli->client);
-    cli->client = 0;
     return CLI_OK;
 }
 
+#if DO_FILE
+/*
+ * Read and execute commands from a file.
+ */
 int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode)
 {
     int oldpriv = cli_set_privilege(cli, privilege);
@@ -1953,14 +2442,14 @@ int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode)
             *p = 0;
 
         cmd = buf;
-        while (isspace(*cmd))
+        while (isspace((int)*cmd))
             cmd++;
 
         if (!*cmd)
             continue;
 
         for (p = end = cmd; *p; p++)
-            if (!isspace(*p))
+            if (!isspace((int)*p))
                 end = p;
 
         *++end = 0;
@@ -1976,7 +2465,12 @@ int cli_file(struct cli_def *cli, FILE *fh, int privilege, int mode)
 
     return CLI_OK;
 }
+#endif
 
+/*
+ * Print a varargs string to the console.
+ * Public print functions are built on top of this.
+ */
 static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap)
 {
     va_list aq;
@@ -1991,6 +2485,7 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l
         if ((n = vsnprintf(cli->buffer, cli->buf_size, format, ap)) == -1)
             return;
 
+#if DO_MALLOC
         if ((unsigned)n >= cli->buf_size)
         {
             cli->buf_size = n + 1;
@@ -2001,6 +2496,9 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l
             va_copy(ap, aq);
             continue;
         }
+#else
+        /* Just don't call _print with a large amount of data, okay? */
+#endif
         break;
     }
 
@@ -2009,25 +2507,33 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l
     do
     {
         char *next = strchr(p, '\n');
+#if DO_FILTER
         struct cli_filter *f = (print_mode & PRINT_FILTERED) ? cli->filters : 0;
+#endif
         int print = 1;
 
         if (next)
             *next++ = 0;
+#if DO_PRINT_BUFFERED
         else if (print_mode & PRINT_BUFFERED)
             break;
+#endif
 
+#if DO_FILTER
         while (print && f)
         {
             print = (f->filter(cli, p, f->data) == CLI_OK);
             f = f->next;
         }
+#endif
         if (print)
         {
             if (cli->print_callback)
                 cli->print_callback(cli, p);
-            else if (cli->client)
-                fprintf(cli->client, "%s\r\n", p);
+            else {
+                cli->write_callback(cli, p, strlen(p));
+                cli->write_callback(cli, "\r\n", 2);
+            }
         }
 
         p = next;
@@ -2041,6 +2547,7 @@ static void _print(struct cli_def *cli, int print_mode, const char *format, va_l
     else *cli->buffer = 0;
 }
 
+#if DO_FILTER || DO_PRINT_BUFFERED
 void cli_bufprint(struct cli_def *cli, const char *format, ...)
 {
     va_list ap;
@@ -2049,21 +2556,34 @@ void cli_bufprint(struct cli_def *cli, const char *format, ...)
     _print(cli, PRINT_BUFFERED|PRINT_FILTERED, format, ap);
     va_end(ap);
 }
+#endif
 
+#if DO_PRINT_BUFFERED
 void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap)
 {
     _print(cli, PRINT_BUFFERED, format, ap);
 }
+#endif
 
+/*
+ * Print a varargs string to the console.
+ */
 void cli_print(struct cli_def *cli, const char *format, ...)
 {
     va_list ap;
 
     va_start(ap, format);
+#if DO_FILTER
     _print(cli, PRINT_FILTERED, format, ap);
+#else
+    _print(cli, PRINT_PLAIN, format, ap);
+#endif
     va_end(ap);
 }
 
+/*
+ * Print an error string to the console.
+ */
 void cli_error(struct cli_def *cli, const char *format, ...)
 {
     va_list ap;
@@ -2073,6 +2593,7 @@ void cli_error(struct cli_def *cli, const char *format, ...)
     va_end(ap);
 }
 
+#if DO_FILTER
 struct cli_match_filter_state
 {
     int flags;
@@ -2091,8 +2612,7 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli
 
     if (argc < 2)
     {
-        if (cli->client)
-            fprintf(cli->client, "Match filter requires an argument\r\n");
+        cli_error(cli, "Match filter requires an argument");
 
         return CLI_ERROR;
     }
@@ -2158,9 +2678,7 @@ int cli_match_filter_init(struct cli_def *cli, int argc, char **argv, struct cli
     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);
-
+        cli_error(cli, "Invalid pattern \"%s\"", p);
         free_z(p);
         return CLI_ERROR;
     }
@@ -2223,9 +2741,7 @@ int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli
     {
         if (argc < 3)
         {
-            if (cli->client)
-                fprintf(cli->client, "Between filter requires 2 arguments\r\n");
-
+            cli_error(cli, "Between filter requires 2 arguments");
             return CLI_ERROR;
         }
 
@@ -2237,9 +2753,7 @@ int cli_range_filter_init(struct cli_def *cli, int argc, char **argv, struct cli
     {
         if (argc < 2)
         {
-            if (cli->client)
-                fprintf(cli->client, "Begin filter requires an argument\r\n");
-
+            cli_error(cli, "Begin filter requires an argument");
             return CLI_ERROR;
         }
 
@@ -2285,9 +2799,7 @@ int cli_count_filter_init(struct cli_def *cli, int argc, UNUSED(char **argv), st
 {
     if (argc > 1)
     {
-        if (cli->client)
-            fprintf(cli->client, "Count filter does not take arguments\r\n");
-
+        cli_error(cli, "Count filter does not take arguments");
         return CLI_ERROR;
     }
 
@@ -2305,14 +2817,12 @@ int cli_count_filter(struct cli_def *cli, const char *string, void *data)
     if (!string) // clean up
     {
         // print count
-        if (cli->client)
-            fprintf(cli->client, "%d\r\n", *count);
-
+        cli_error(cli, "%d", *count);
         free(count);
         return CLI_OK;
     }
 
-    while (isspace(*string))
+    while (isspace((int)*string))
         string++;
 
     if (*string)
@@ -2320,12 +2830,42 @@ int cli_count_filter(struct cli_def *cli, const char *string, void *data)
 
     return CLI_ERROR; // no output
 }
+#endif /* DO_FILTER */
 
+/*
+ * Set print callback function.
+ */
 void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *))
 {
     cli->print_callback = callback;
 }
 
+/*
+ * Set read callback function.
+ */
+void cli_read_callback(struct cli_def *cli, ssize_t (*callback)(struct cli_def *, void *, size_t))
+{
+    cli->read_callback = callback;
+    if (cli->read_callback == NULL)
+        cli->read_callback = _read;
+}
+
+/*
+ * Set write callback function.
+ */
+void cli_write_callback(struct cli_def *cli, ssize_t (*callback)(struct cli_def *, const void *, size_t))
+{
+    cli->write_callback = callback;
+    if (cli->write_callback == NULL)
+        cli->write_callback = _write;
+}
+
+#if DO_IDLE_TIMEOUT
+/*
+ * Set idle-timeout period. This is usually set by
+ * cli_set_idle_timeout_callback(), but this allows the user to change the
+ * period without resetting the callback - a minor optimization.
+ */
 void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds)
 {
     if (seconds < 1)
@@ -2334,20 +2874,35 @@ void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds)
     time(&cli->last_action);
 }
 
+/*
+ * Set idle-timeout callback function.
+ */
 void cli_set_idle_timeout_callback(struct cli_def *cli, unsigned int seconds, int (*callback)(struct cli_def *))
 {
     cli_set_idle_timeout(cli, seconds);
     cli->idle_timeout_callback = callback;
 }
+#endif
 
+#if DO_TELNET
+/*
+ * Toggle support for telnet option negotiation.
+ */
 void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) {
     cli->telnet_protocol = !!telnet_protocol;
 }
+#endif
 
+/*
+ * Set an opaque user context pointer.
+ */
 void cli_set_context(struct cli_def *cli, void *context) {
     cli->user_context = context;
 }
 
+/*
+ * Get the opaque user context pointer.
+ */
 void *cli_get_context(struct cli_def *cli) {
     return cli->user_context;
 }
diff --git a/libcli.h b/libcli.h
index e978526..46b2f83 100644
--- a/libcli.h
+++ b/libcli.h
@@ -16,8 +16,6 @@ extern "C" {
 #define CLI_QUIT                -2
 #define CLI_ERROR_ARG           -3
 
-#define MAX_HISTORY             256
-
 #define PRIVILEGE_UNPRIVILEGED  0
 #define PRIVILEGE_PRIVILEGED    15
 #define MODE_ANY                -1
@@ -30,8 +28,15 @@ extern "C" {
 #define PRINT_FILTERED          0x01
 #define PRINT_BUFFERED          0x02
 
-#define CLI_MAX_LINE_LENGTH     4096
-#define CLI_MAX_LINE_WORDS      128
+#ifndef CLI_MAX_LINE_LENGTH
+#define CLI_MAX_LINE_LENGTH     128
+#endif
+#ifndef CLI_MAX_LINE_WORDS
+#define CLI_MAX_LINE_WORDS      16
+#endif
+#ifndef CLI_MAX_HISTORY
+#define CLI_MAX_HISTORY         256
+#endif
 
 struct cli_def {
     int completion_callback;
@@ -42,7 +47,7 @@ struct cli_def {
     char *banner;
     struct unp *users;
     char *enable_password;
-    char *history[MAX_HISTORY];
+    char *history[CLI_MAX_HISTORY];
     char showprompt;
     char *promptchar;
     char *hostname;
@@ -52,7 +57,6 @@ struct cli_def {
     int state;
     struct cli_filter *filters;
     void (*print_callback)(struct cli_def *cli, const char *string);
-    FILE *client;
     /* internal buffers */
     void *conn;
     void *service;
@@ -65,6 +69,9 @@ struct cli_def {
     time_t last_action;
     int telnet_protocol;
     void *user_context;
+    int sockfd;
+    ssize_t (*read_callback)(struct cli_def *cli, void *buf, size_t count);
+    ssize_t (*write_callback)(struct cli_def *cli, const void *buf, size_t count);
 };
 
 struct cli_filter {
@@ -113,6 +120,8 @@ void cli_bufprint(struct cli_def *cli, const char *format, ...) __attribute__((f
 void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap);
 void cli_error(struct cli_def *cli, const char *format, ...) __attribute__((format (printf, 2, 3)));
 void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *));
+void cli_read_callback(struct cli_def *cli, ssize_t (*callback)(struct cli_def *, void *, size_t));
+void cli_write_callback(struct cli_def *cli, ssize_t (*callback)(struct cli_def *, const void *, size_t));
 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 *));



More information about the Commits mailing list