From 1a5727c568e36b927ef2088b2b02bae4c84933f3 Mon Sep 17 00:00:00 2001 From: Fredrik Thulin Date: Mon, 16 May 2016 11:09:30 +0200 Subject: 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 #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 #include #include #include +#ifndef CRYPTECH_NO_MEMORY_H #include -#if !defined(__APPLE__) && !defined(__FreeBSD__) -#include -#endif +#endif /* CRYPTECH_NO_MEMORY_H */ #include -#include #include -#include -#ifndef WIN32 +#if !defined(WIN32) && !defined(CRYPTECH_NO_REGEXP) #include #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, " "); - } - - 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. -- cgit v1.2.3