aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 *));