From 96e89d34eb2acfd71de6ed38d7029d7f662b9ad5 Mon Sep 17 00:00:00 2001 From: Paul Selkirk Date: Thu, 21 Jul 2016 16:12:38 -0400 Subject: 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 #include @@ -8,12 +62,15 @@ #include #include #include +#if 0 #include +#endif +#if DO_MALLOC #if !defined(__APPLE__) && !defined(__FreeBSD__) #include #endif +#endif #include -#include #include #include #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 +#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 *)); -- cgit v1.2.3