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