#ifdef WIN32 #include #include #endif #define CRYPTECH_NO_CRYPT #define CRYPTECH_NO_MEMORY_H //#define CRYPTECH_NO_FDOPEN //#define CRYPTECH_NO_SELECT #define CRYPTECH_NO_REGEXP #define _GNU_SOURCE #include #include #include #include #ifndef CRYPTECH_NO_MEMORY_H #include #endif /* CRYPTECH_NO_MEMORY_H */ #include #include #include #if !defined(WIN32) && !defined(CRYPTECH_NO_REGEXP) #include #endif #include "libcli.h" // vim:sw=4 tw=120 et #ifdef __GNUC__ # define UNUSED(d) d __attribute__ ((unused)) #else # define UNUSED(d) d #endif #define MATCH_REGEX 1 #define MATCH_INVERT 2 #ifdef CRYPTECH_NO_REGEXP /* * Dummy definitions to allow compilation on Cryptech STM32 MCU */ int regex_dummy() {return 0;} #define regfree(...) regex_dummy() #define regexec(...) regex_dummy() #define regcomp(...) regex_dummy() #define regex_t int #define REG_NOSUB 0 #define REG_EXTENDED 0 #define REG_ICASE 0 #endif /* CRYPTECH_NO_REGEXP */ struct unp { char *username; char *password; struct unp *next; }; struct cli_filter_cmds { const char *cmd; const char *help; }; static struct cli_filter_cmds filter_cmds[] = { /* cryptech: removed all filters, was using dynamic memory and seemed an unneccessarily big attack surface */ { NULL, NULL} }; static int isspace(int c) { /* isspace() not provided with gcc-arm-none-eabi 4.9.3 */ return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'); } static ssize_t _write(struct cli_def *cli, struct cli_loop_ctx *ctx, const void *buf, size_t count) { size_t written = 0; ssize_t thisTime = 0; if (! cli->write_callback && ! ctx->sockfd) return -1; while (count != written) { if (cli->write_callback) thisTime = cli->write_callback(cli, (char*)buf + written, count - written); else thisTime = write(ctx->sockfd, (char*)buf + written, count - written); if (thisTime == -1) { if (errno == EINTR) continue; else return -1; } written += thisTime; } return written; } /* cryptech: made this function use a static buffer instead of dynamically allocated memory for command "show counters" this function is called on command "counters", so the string is built backwards while there still are parent nodes. */ char *cli_command_name(struct cli_def *cli, struct cli_command *command) { static char buf[CLI_MAX_CMD_NAME_LEN]; buf[CLI_MAX_CMD_NAME_LEN - 1] = 0; int idx = CLI_MAX_CMD_NAME_LEN - 1; while (command) { /* XXX what to do if there is no room left in buf? */ if (idx > (int) strlen(command->command) + 1) { if (buf[idx]) { /* this is not the first command, need to add a space */ buf[--idx] = ' '; } idx -= strlen(command->command); memcpy(buf + idx, command->command, strlen(command->command)); } command = command->parent; } return buf + idx; } void cli_set_auth_callback(struct cli_def *cli, int (*auth_callback)(const char *, const char *)) { cli->auth_callback = auth_callback; } void cli_set_enable_callback(struct cli_def *cli, int (*enable_callback)(const char *)) { cli->enable_callback = enable_callback; } /* cryptech: removed unused function: cli_allow_user */ /* cryptech: removed unused function: cli_allow_enable */ /* cryptech: removed unused function: cli_deny_user */ void cli_set_banner(struct cli_def *cli, char *banner) { cli->banner = banner; } void cli_set_hostname(struct cli_def *cli, char *hostname) { cli->hostname = hostname; } void cli_set_promptchar(struct cli_def *cli, char *promptchar) { cli->promptchar = promptchar; } static int cli_build_shortest(struct cli_def *cli, struct cli_command *commands) { struct cli_command *c, *p; char *cp, *pp; unsigned len; for (c = commands; c; c = c->next) { c->unique_len = strlen(c->command); if ((c->mode != MODE_ANY && c->mode != cli->mode) || c->privilege > cli->privilege) continue; c->unique_len = 1; for (p = commands; p; p = p->next) { if (c == p) continue; if ((p->mode != MODE_ANY && p->mode != cli->mode) || p->privilege > cli->privilege) continue; cp = c->command; pp = p->command; len = 1; while (*cp && *pp && *cp++ == *pp++) len++; if (len > c->unique_len) c->unique_len = len; } if (c->children) cli_build_shortest(cli, c->children); } return CLI_OK; } int cli_set_privilege(struct cli_def *cli, int priv) { int old = cli->privilege; cli->privilege = priv; if (priv != old) { static char priv_prompt[] = "# ", nopriv_prompt[] = "> "; cli_set_promptchar(cli, priv == PRIVILEGE_PRIVILEGED ? priv_prompt : nopriv_prompt); cli_build_shortest(cli, cli->commands); } return old; } void cli_set_modestring(struct cli_def *cli, char *modestring) { cli->modestring = modestring; } int cli_set_configmode(struct cli_def *cli, int mode, const char *config_desc) { int old = cli->mode; static char string[64]; cli->mode = mode; if (mode != old) { if (!cli->mode) { // Not config mode cli_set_modestring(cli, NULL); } else if (config_desc && *config_desc) { snprintf(string, sizeof(string), "(config-%s)", config_desc); cli_set_modestring(cli, string); } else { snprintf(string, sizeof(string), "(config)"); cli_set_modestring(cli, string); } cli_build_shortest(cli, cli->commands); } return old; } void cli_register_command2(struct cli_def *cli, struct cli_command *cmd, struct cli_command *parent) { struct cli_command *p; if (parent) { cmd->parent = parent; if (!parent->children) { parent->children = cmd; } else { for (p = parent->children; p && p->next; p = p->next); if (p) p->next = cmd; } } else { if (!cli->commands) { cli->commands = cmd; } else { for (p = cli->commands; p && p->next; p = p->next); if (p) p->next = cmd; } } } /* cryptech: removed unused function cli_free_command */ int cli_unregister_command(struct cli_def *cli, const char *command) { struct cli_command *c, *p = NULL; if (!command) return -1; if (!cli->commands) return CLI_OK; for (c = cli->commands; c; c = c->next) { if (strcmp(c->command, command) == 0) { if (p) p->next = c->next; else cli->commands = c->next; return CLI_OK; } p = c; } return CLI_OK; } int cli_show_help(struct cli_def *cli, struct cli_command *c) { struct cli_command *p; for (p = c; p; p = p->next) { if (p->command && p->callback && cli->privilege >= p->privilege && (p->mode == cli->mode || p->mode == MODE_ANY)) { cli_error(cli, " %-35s %s", cli_command_name(cli, p), (p->help != NULL ? p->help : "")); } if (p->children) cli_show_help(cli, p->children); } return CLI_OK; } int cli_int_enable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { if (cli->privilege == PRIVILEGE_PRIVILEGED) return CLI_OK; if (!cli->enable_password && !cli->enable_callback) { /* no password required, set privilege immediately */ cli_set_privilege(cli, PRIVILEGE_PRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); } else { /* require password entry */ cli->state = CLI_STATE_ENABLE_PASSWORD; } return CLI_OK; } int cli_int_disable(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_OK; } int cli_int_help(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_error(cli, "\nCommands available:"); cli_show_help(cli, cli->commands); return CLI_OK; } 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++) { if (cli->history[i][0]) cli_error(cli, "%3d. %s", i, cli->history[i]); } return CLI_OK; } int cli_int_quit(struct cli_def *cli, UNUSED(const char *command), UNUSED(char *argv[]), UNUSED(int argc)) { cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); return CLI_QUIT; } int cli_int_exit(struct cli_def *cli, const char *command, char *argv[], int argc) { if (cli->mode == MODE_EXEC) return cli_int_quit(cli, command, argv, argc); if (cli->mode > MODE_CONFIG) cli_set_configmode(cli, MODE_CONFIG, NULL); else cli_set_configmode(cli, MODE_EXEC, NULL); cli->service = NULL; return CLI_OK; } int cli_int_idle_timeout(struct cli_def *cli) { cli_print(cli, "Idle timeout"); return CLI_QUIT; } 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; } int cli_init(struct cli_def *cli) { static struct cli_command cmd_int_help_s = {(char *) "help", cli_int_help, 0, (char *) "Show available commands", PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL}; static struct cli_command cmd_int_quit_s = {(char *) "quit", cli_int_quit, 0, (char *) "Disconnect", PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL}; static struct cli_command cmd_int_logout_s = {(char *) "logout", cli_int_quit, 0, (char *) "Disconnect", PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL}; static struct cli_command cmd_int_exit_s = {(char *) "exit", cli_int_exit, 0, (char *) "Exit from current mode", PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL}; static struct cli_command cmd_int_history_s = {(char *) "history", cli_int_history, 0, (char *) "Show a list of previously run commands", PRIVILEGE_UNPRIVILEGED, MODE_ANY, NULL, NULL, NULL}; static struct cli_command cmd_int_enable_s = {(char *) "enable", cli_int_enable, 0, (char *) "Turn on privileged commands", PRIVILEGE_UNPRIVILEGED, MODE_EXEC, NULL, NULL, NULL}; static struct cli_command cmd_int_disable_s = {(char *) "disable", cli_int_disable, 0, (char *) "Turn off privileged commands", PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL}; static struct cli_command cmd_int_configure_s = {(char *) "configure", NULL, 0, (char *) "Enter configuration mode", PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL}; static struct cli_command cmd_int_configure_terminal_s = {(char *) "terminal", cli_int_configure_terminal, 0, (char *) "Configure from the terminal", PRIVILEGE_PRIVILEGED, MODE_EXEC, NULL, NULL, NULL}; cli->telnet_protocol = 1; cli_register_command2(cli, &cmd_int_help_s, NULL); cli_register_command2(cli, &cmd_int_quit_s, NULL); cli_register_command2(cli, &cmd_int_logout_s, NULL); cli_register_command2(cli, &cmd_int_exit_s, NULL); cli_register_command2(cli, &cmd_int_history_s, NULL); cli_register_command2(cli, &cmd_int_enable_s, NULL); cli_register_command2(cli, &cmd_int_disable_s, NULL); cli_register_command2(cli, &cmd_int_configure_s, NULL); cli_register_command2(cli, &cmd_int_configure_terminal_s, &cmd_int_configure_s); cli->privilege = cli->mode = -1; cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, 0); return 1; } /* cryptech: removed unused function cli_unregister_all */ int cli_done(struct cli_def *cli) { return CLI_OK; } static int cli_add_history(struct cli_def *cli, const char *cmd) { int i; for (i = 0; i < MAX_HISTORY; i++) { if (!cli->history[i][0]) { if (i == 0 || strcasecmp(cli->history[i-1], cmd)) { strncpy(cli->history[i], cmd, HISTORY_CMD_LEN - 1); cli->history[i][HISTORY_CMD_LEN - 1] = 0; } return CLI_OK; } } // No space found, drop one off the beginning of the list for (i = 0; i < MAX_HISTORY-1; i++) memcpy(cli->history[i], cli->history[i+1], HISTORY_CMD_LEN); strncpy(cli->history[MAX_HISTORY-1], cmd, HISTORY_CMD_LEN - 1); cli->history[MAX_HISTORY-1][HISTORY_CMD_LEN - 1] = 0; return CLI_OK; } /* cryptech: removed unused cli_free_history */ 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; static char buf[CLI_MAX_LINE_LENGTH]; char *ptr = buf; while (*p) { if (!isspace((unsigned char) *p)) { word_start = p; break; } p++; } memset(buf, 0, sizeof(buf)); while (nwords < max_words - 1) { if (!*p || *p == inquote || (word_start && !inquote && (isspace((unsigned char) *p) || *p == '|'))) { if (word_start) { int len = p - word_start; if (len > 1) { if ((ptr + len + 1) > buf + sizeof(buf) - 1) break; memcpy(ptr, word_start, len); words[nwords++] = ptr; ptr += len; ptr++; /* NULL terminate through memset above */ } } if (!*p) break; if (inquote) p++; /* skip over trailing quote */ inquote = 0; word_start = 0; } else if (*p == '"' || *p == '\'') { inquote = *p++; word_start = p; } else { if (!word_start) { if (*p == '|') { if ((ptr + 1 + 1) > buf + sizeof(buf) - 1) break; *ptr = '|'; words[nwords++] = ptr; ptr += strlen("|"); ptr++; /* NULL terminate through memset above */ } else if (!isspace((unsigned char) *p)) word_start = p; } p++; } } return nwords; } /* cryptech: removed unused function join_words */ static int cli_find_command(struct cli_def *cli, struct cli_command *commands, int num_words, char *words[], int start_word, int filters[]) { struct cli_command *c, *again_config = NULL, *again_any = NULL; int c_words = num_words; if (filters[0]) c_words = filters[0]; // Deal with ? for help if (!words[start_word]) return CLI_ERROR; if (words[start_word][strlen(words[start_word]) - 1] == '?') { int l = strlen(words[start_word])-1; if (commands->parent && commands->parent->callback) cli_error(cli, "%-20s %s", cli_command_name(cli, commands->parent), (commands->parent->help != NULL ? commands->parent->help : "")); for (c = commands; c; c = c->next) { if (strncasecmp(c->command, words[start_word], l) == 0 && (c->callback || c->children) && cli->privilege >= c->privilege && (c->mode == cli->mode || c->mode == MODE_ANY)) cli_error(cli, " %-20s %s", c->command, (c->help != NULL ? c->help : "")); } return CLI_OK; } for (c = commands; c; c = c->next) { 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; AGAIN: if (c->mode == cli->mode || (c->mode == MODE_ANY && again_any != NULL)) { int rc = CLI_OK; // Found a word! if (!c->children) { // Last word if (!c->callback) { cli_error(cli, "No callback for \"%s\"", cli_command_name(cli, c)); return CLI_ERROR; } } else { if (start_word == c_words - 1) { if (c->callback) goto CORRECT_CHECKS; cli_error(cli, "Incomplete command"); return CLI_ERROR; } rc = cli_find_command(cli, c->children, num_words, words, start_word + 1, filters); if (rc == CLI_ERROR_ARG) { if (c->callback) { rc = CLI_OK; goto CORRECT_CHECKS; } else { cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", words[start_word]); } } return rc; } CORRECT_CHECKS: if (!c->callback) { cli_error(cli, "Internal server error processing \"%s\"", cli_command_name(cli, c)); return CLI_ERROR; } /* cryptech: removed filter checking here */ if (rc == CLI_OK) rc = c->callback(cli, cli_command_name(cli, c), words + start_word + 1, c_words - start_word - 1); /* cryptech: removed filter cleanup here */ return rc; } else if (cli->mode > MODE_CONFIG && c->mode == MODE_CONFIG) { // command matched but from another mode, // remember it if we fail to find correct command again_config = c; } else if (c->mode == MODE_ANY) { // command matched but for any mode, // remember it if we fail to find correct command again_any = c; } } // drop out of config submode if we have matched command on MODE_CONFIG if (again_config) { c = again_config; cli_set_configmode(cli, MODE_CONFIG, NULL); goto AGAIN; } if (again_any) { c = again_any; goto AGAIN; } if (start_word == 0) cli_error(cli, "Invalid %s \"%s\"", commands->parent ? "argument" : "command", words[start_word]); return CLI_ERROR_ARG; } int cli_run_command(struct cli_def *cli, const char *command) { int r; unsigned int num_words, i, f; char *words[CLI_MAX_LINE_WORDS] = {0}; int filters[CLI_MAX_LINE_WORDS] = {0}; if (!command) return CLI_ERROR; while (isspace((int) *command)) { command++; } if (!*command) return CLI_OK; num_words = cli_parse_line(command, words, CLI_MAX_LINE_WORDS); for (i = f = 0; i < num_words && f < CLI_MAX_LINE_WORDS - 1; i++) { if (words[i][0] == '|') filters[f++] = i; } filters[f] = 0; if (num_words) r = cli_find_command(cli, cli->commands, num_words, words, 0, filters); else r = CLI_ERROR; if (r == CLI_QUIT) return r; return CLI_OK; } 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, i, k=0; char *words[CLI_MAX_LINE_WORDS] = {0}; int filter = 0; if (!command) return 0; while (isspace((unsigned char) *command)) command++; num_words = cli_parse_line(command, words, sizeof(words)/sizeof(words[0])); if (!command[0] || command[strlen(command)-1] == ' ') num_words++; if (!num_words) goto out; for (i = 0; i < num_words; i++) { if (words[i] && words[i][0] == '|') filter = i; } if (filter) // complete filters { unsigned len = 0; if (filter < num_words - 1) // filter already completed goto out; if (filter == num_words - 1) len = strlen(words[num_words-1]); for (i = 0; filter_cmds[i].cmd && k < max_completions; i++) { if (!len || (len < strlen(filter_cmds[i].cmd) && !strncmp(filter_cmds[i].cmd, words[num_words - 1], len))) completions[k++] = (char *)filter_cmds[i].cmd; } completions[k] = NULL; goto out; } for (c = cli->commands, i = 0; c && i < num_words && k < max_completions; c = n) { n = c->next; if (cli->privilege < c->privilege) continue; if (c->mode != cli->mode && c->mode != MODE_ANY) continue; if (words[i] && strncasecmp(c->command, words[i], strlen(words[i]))) continue; if (i < num_words - 1) { if (strlen(words[i]) < c->unique_len) continue; n = c->children; i++; continue; } completions[k++] = c->command; } out: return k; } static void cli_clear_line(struct cli_def *cli, struct cli_loop_ctx *ctx) { int i; if (ctx->cursor < ctx->l) { for (i = 0; i < (ctx->l - ctx->cursor); i++) _write(cli, ctx, " ", 1); } for (i = 0; i < ctx->l; i++) ctx->cmd[i] = '\b'; _write(cli, ctx, ctx->cmd, i); for (i = 0; i < ctx->l; i++) ctx->cmd[i] = ' '; _write(cli, ctx, ctx->cmd, i); for (i = 0; i < ctx->l; i++) ctx->cmd[i] = '\b'; _write(cli, ctx, ctx->cmd, i); memset(ctx->cmd, 0, i); ctx->l = ctx->cursor = 0; } void cli_reprompt(struct cli_def *cli) { if (!cli) return; cli->showprompt = 1; } static int pass_matches(const char *pass, const char *try) { /* cryptech: removed DES mode here */ return !strcmp(pass, try); } #define CTRL(c) (c - '@') static int show_prompt(struct cli_def *cli, struct cli_loop_ctx *ctx) { int len = 0; if (cli->hostname) len += _write(cli, ctx, cli->hostname, strlen(cli->hostname)); if (cli->modestring) len += _write(cli, ctx, cli->modestring, strlen(cli->modestring)); return len + _write(cli, ctx, cli->promptchar, strlen(cli->promptchar)); } int cli_loop(struct cli_def *cli, int sockfd) { unsigned char c; int n = 0; struct cli_loop_ctx ctx; memset(&ctx, 0, sizeof(ctx)); ctx.insertmode = 1; ctx.sockfd = sockfd; cli_build_shortest(cli, cli->commands); cli->state = CLI_STATE_LOGIN; if (cli->telnet_protocol) { static const char *negotiate = "\xFF\xFB\x03" "\xFF\xFB\x01" "\xFF\xFD\x03" "\xFF\xFD\x01"; _write(cli, &ctx, negotiate, strlen(negotiate)); } #ifndef CRYPTECH_NO_FDOPEN #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 #endif /* CRYPTECH_NO_FDOPEN */ setbuf(cli->client, NULL); if (cli->banner) cli_error(cli, "%s", cli->banner); /* start off in unprivileged mode */ cli_set_privilege(cli, PRIVILEGE_UNPRIVILEGED); cli_set_configmode(cli, MODE_EXEC, NULL); /* no auth required? */ if (!cli->users && !cli->auth_callback) cli->state = CLI_STATE_NORMAL; while (1) { cli_loop_start_new_command(cli, &ctx); while (1) { cli_loop_show_prompt(cli, &ctx); n = cli_loop_read_next_char(cli, &ctx, &c); if (n == CLI_LOOP_CTRL_BREAK) break; if (n == CLI_LOOP_CTRL_CONTINUE) continue; n = cli_loop_process_char(cli, &ctx, c); if (n == CLI_LOOP_CTRL_BREAK) break; if (n == CLI_LOOP_CTRL_CONTINUE) continue; } if (ctx.l < 0) break; n = cli_loop_process_cmd(cli, &ctx); if (n == CLI_LOOP_CTRL_BREAK) break; } fclose(cli->client); cli->client = 0; return CLI_OK; } void cli_loop_start_new_command(struct cli_def *cli, struct cli_loop_ctx *ctx) { ctx->in_history = 0; ctx->lastchar = 0; cli->showprompt = 1; if (ctx->restore_cmd_l > 0) { ctx->l = ctx->cursor = ctx->restore_cmd_l; ctx->cmd[ctx->l] = 0; ctx->restore_cmd_l = 0; } else { memset(ctx->cmd, 0, CLI_MAX_LINE_LENGTH); ctx->l = 0; ctx->cursor = 0; } } int cli_loop_read_next_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char *c) { int n; #ifndef CRYPTECH_NO_SELECT struct timeval tm; int sr; fd_set r; FD_ZERO(&r); FD_SET(ctx->sockfd, &r); memcpy(&tm, &cli->timeout_tm, sizeof(tm)); if ((sr = select(ctx->sockfd + 1, &r, NULL, NULL, &tm)) < 0) { /* select error */ if (errno == EINTR) return CLI_LOOP_CTRL_CONTINUE; perror("select"); ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } if (sr == 0) { /* timeout every second */ if (cli->regular_callback && cli->regular_callback(cli) != CLI_OK) { ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } if (cli->idle_timeout) { if (time(NULL) - cli->last_action >= cli->idle_timeout) { if (cli->idle_timeout_callback) { // Call the callback and continue on if successful if (cli->idle_timeout_callback(cli) == CLI_OK) { // Reset the idle timeout counter time(&cli->last_action); return CLI_LOOP_CTRL_CONTINUE; } } // Otherwise, break out of the main loop ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } } memcpy(&tm, &cli->timeout_tm, sizeof(tm)); return CLI_LOOP_CTRL_CONTINUE; } #endif /* CRYPTECH_NO_SELECT */ if (cli->read_callback) { if ((n = cli->read_callback(cli, c, (size_t) 1)) < 0) { perror("read_callback"); ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } if (n == 0) return CLI_LOOP_CTRL_CONTINUE; } else { if ((n = read(ctx->sockfd, c, 1)) < 0) { if (errno == EINTR) return CLI_LOOP_CTRL_CONTINUE; perror("read"); ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } } if (n == 0) { ctx->l = -1; return CLI_LOOP_CTRL_CONTINUE; } return 0; } void cli_loop_show_prompt(struct cli_def *cli, struct cli_loop_ctx *ctx) { if (cli->showprompt) { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, "\r\n", 2); switch (cli->state) { case CLI_STATE_LOGIN: _write(cli, ctx, "Username: ", strlen("Username: ")); break; case CLI_STATE_PASSWORD: _write(cli, ctx, "Password: ", strlen("Password: ")); break; case CLI_STATE_NORMAL: case CLI_STATE_ENABLE: show_prompt(cli, ctx); _write(cli, ctx, ctx->cmd, ctx->l); if (ctx->cursor < ctx->l) { int n = ctx->l - ctx->cursor; while (n--) _write(cli, ctx, "\b", 1); } break; case CLI_STATE_ENABLE_PASSWORD: _write(cli, ctx, "Password: ", strlen("Password: ")); break; } cli->showprompt = 0; } } /* * This function should be called once for every character received from the user. * * It will return CLI_LOOP_CTRL_BREAK if the command is now ready to be processed (or the * session should be terminated, and CLI_LOOP_CTRL_CONTINUE if it should be called again * for the next character received. */ int cli_loop_process_char(struct cli_def *cli, struct cli_loop_ctx *ctx, unsigned char c) { if (ctx->skip) { ctx->skip--; return CLI_LOOP_CTRL_CONTINUE; } if (c == 255 && !ctx->is_telnet_option) { ctx->is_telnet_option++; return CLI_LOOP_CTRL_CONTINUE; } if (ctx->is_telnet_option) { if (c >= 251 && c <= 254) { ctx->is_telnet_option = c; return CLI_LOOP_CTRL_CONTINUE; } if (c != 255) { ctx->is_telnet_option = 0; return CLI_LOOP_CTRL_CONTINUE; } ctx->is_telnet_option = 0; } /* handle ANSI arrows */ if (ctx->esc) { if (ctx->esc == '[') { /* remap to readline control codes */ switch (c) { case 'A': /* Up */ c = CTRL('P'); break; case 'B': /* Down */ c = CTRL('N'); break; case 'C': /* Right */ c = CTRL('F'); break; case 'D': /* Left */ c = CTRL('B'); break; default: c = 0; } ctx->esc = 0; } else { ctx->esc = (c == '[') ? c : 0; return CLI_LOOP_CTRL_CONTINUE; } } if (c == 0) return CLI_LOOP_CTRL_CONTINUE; if (c == '\n') return CLI_LOOP_CTRL_CONTINUE; if (c == '\r') { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, "\r\n", 2); return CLI_LOOP_CTRL_BREAK; } if (c == 27) { ctx->esc = 1; return CLI_LOOP_CTRL_CONTINUE; } if (c == CTRL('C')) { _write(cli, ctx, "\a", 1); return CLI_LOOP_CTRL_CONTINUE; } /* back word, backspace/delete */ if (c == CTRL('W') || c == CTRL('H') || c == 0x7f) { int back = 0; if (c == CTRL('W')) /* word */ { int nc = ctx->cursor; if (ctx->l == 0 || ctx->cursor == 0) return CLI_LOOP_CTRL_CONTINUE; while (nc && ctx->cmd[nc - 1] == ' ') { nc--; back++; } while (nc && ctx->cmd[nc - 1] != ' ') { nc--; back++; } } else /* char */ { if (ctx->l == 0 || ctx->cursor == 0) { _write(cli, ctx, "\a", 1); return CLI_LOOP_CTRL_CONTINUE; } back = 1; } if (back) { while (back--) { if (ctx->l == ctx->cursor) { ctx->cmd[--ctx->cursor] = 0; if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, "\b \b", 3); } else { int i; ctx->cursor--; if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) { for (i = ctx->cursor; i <= ctx->l; i++) ctx->cmd[i] = ctx->cmd[i+1]; _write(cli, ctx, "\b", 1); _write(cli, ctx, ctx->cmd + ctx->cursor, strlen(ctx->cmd + ctx->cursor)); _write(cli, ctx, " ", 1); for (i = 0; i <= (int)strlen(ctx->cmd + ctx->cursor); i++) _write(cli, ctx, "\b", 1); } } ctx->l--; } return CLI_LOOP_CTRL_CONTINUE; } } /* redraw */ if (c == CTRL('L')) { int i; int cursorback = ctx->l - ctx->cursor; if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD) return CLI_LOOP_CTRL_CONTINUE; _write(cli, ctx, "\r\n", 2); show_prompt(cli, ctx); _write(cli, ctx, ctx->cmd, ctx->l); for (i = 0; i < cursorback; i++) _write(cli, ctx, "\b", 1); return CLI_LOOP_CTRL_CONTINUE; } /* clear line */ if (c == CTRL('U')) { if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD) memset(ctx->cmd, 0, ctx->l); else cli_clear_line(cli, ctx); ctx->l = ctx->cursor = 0; return CLI_LOOP_CTRL_CONTINUE; } /* kill to EOL */ if (c == CTRL('K')) { if (ctx->cursor == ctx->l) return CLI_LOOP_CTRL_CONTINUE; if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) { int c; for (c = ctx->cursor; c < ctx->l; c++) _write(cli, ctx, " ", 1); for (c = ctx->cursor; c < ctx->l; c++) _write(cli, ctx, "\b", 1); } memset(ctx->cmd + ctx->cursor, 0, ctx->l - ctx->cursor); ctx->l = ctx->cursor; return CLI_LOOP_CTRL_CONTINUE; } /* EOT */ if (c == CTRL('D')) { if (cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD) return CLI_LOOP_CTRL_BREAK; if (ctx->l) return CLI_LOOP_CTRL_CONTINUE; ctx->l = -1; return CLI_LOOP_CTRL_BREAK; } /* disable */ if (c == CTRL('Z')) { if (cli->mode != MODE_EXEC) { cli_clear_line(cli, ctx); cli_set_configmode(cli, MODE_EXEC, NULL); cli->showprompt = 1; } return CLI_LOOP_CTRL_CONTINUE; } /* TAB completion */ if (c == CTRL('I')) { char *completions[CLI_MAX_LINE_WORDS]; int num_completions = 0; if (cli->state == CLI_STATE_LOGIN || cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD) return CLI_LOOP_CTRL_CONTINUE; if (ctx->cursor != ctx->l) return CLI_LOOP_CTRL_CONTINUE; num_completions = cli_get_completions(cli, ctx->cmd, completions, CLI_MAX_LINE_WORDS); if (num_completions == 0) { _write(cli, ctx, "\a", 1); } else if (num_completions == 1) { // Single completion for (; ctx->l > 0; ctx->l--, ctx->cursor--) { if (ctx->cmd[ctx->l-1] == ' ' || ctx->cmd[ctx->l-1] == '|') break; _write(cli, ctx, "\b", 1); } strcpy((ctx->cmd + ctx->l), completions[0]); ctx->l += strlen(completions[0]); ctx->cmd[ctx->l++] = ' '; ctx->cursor = ctx->l; _write(cli, ctx, completions[0], strlen(completions[0])); _write(cli, ctx, " ", 1); } else if (ctx->lastchar == CTRL('I')) { // double tab int i; _write(cli, ctx, "\r\n", 2); for (i = 0; i < num_completions; i++) { _write(cli, ctx, completions[i], strlen(completions[i])); if (i % 4 == 3) _write(cli, ctx, "\r\n", 2); else _write(cli, ctx, " ", 1); } if (i % 4 != 3) _write(cli, ctx, "\r\n", 2); cli->showprompt = 1; } else { // More than one completion ctx->lastchar = c; _write(cli, ctx, "\a", 1); } return CLI_LOOP_CTRL_CONTINUE; } /* history */ if (c == CTRL('P') || c == CTRL('N')) { int history_found = 0; if (cli->state == CLI_STATE_LOGIN || cli->state == CLI_STATE_PASSWORD || cli->state == CLI_STATE_ENABLE_PASSWORD) return CLI_LOOP_CTRL_CONTINUE; if (c == CTRL('P')) // Up { ctx->in_history--; if (ctx->in_history < 0) { for (ctx->in_history = MAX_HISTORY-1; ctx->in_history >= 0; ctx->in_history--) { if (cli->history[ctx->in_history][0]) { history_found = 1; break; } } } else { if (cli->history[ctx->in_history]) history_found = 1; } } else // Down { ctx->in_history++; if (ctx->in_history >= MAX_HISTORY || !cli->history[ctx->in_history]) { int i = 0; for (i = 0; i < MAX_HISTORY; i++) { if (cli->history[i]) { ctx->in_history = i; history_found = 1; break; } } } else { if (cli->history[ctx->in_history]) history_found = 1; } } if (history_found && cli->history[ctx->in_history]) { // Show history item cli_clear_line(cli, ctx); memset(ctx->cmd, 0, CLI_MAX_LINE_LENGTH); strncpy(ctx->cmd, cli->history[ctx->in_history], CLI_MAX_LINE_LENGTH - 1); /* cryptech: not sure if needed, but ensure we don't disclose memory after buf */ ctx->cmd[CLI_MAX_LINE_LENGTH - 1] = 0; ctx->l = ctx->cursor = strlen(ctx->cmd); _write(cli, ctx, ctx->cmd, ctx->l); } return CLI_LOOP_CTRL_CONTINUE; } /* left/right cursor motion */ if (c == CTRL('B') || c == CTRL('F')) { if (c == CTRL('B')) /* Left */ { if (ctx->cursor) { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, "\b", 1); ctx->cursor--; } } else /* Right */ { if (ctx->cursor < ctx->l) { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, &ctx->cmd[ctx->cursor], 1); ctx->cursor++; } } return CLI_LOOP_CTRL_CONTINUE; } /* start of line */ if (c == CTRL('A')) { if (ctx->cursor) { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) { _write(cli, ctx, "\r", 1); show_prompt(cli, ctx); } ctx->cursor = 0; } return CLI_LOOP_CTRL_CONTINUE; } /* end of line */ if (c == CTRL('E')) { if (ctx->cursor < ctx->l) { if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) _write(cli, ctx, &ctx->cmd[ctx->cursor], ctx->l - ctx->cursor); ctx->cursor = ctx->l; } return CLI_LOOP_CTRL_CONTINUE; } /* normal character typed */ if (ctx->cursor == ctx->l) { /* append to end of line */ ctx->cmd[ctx->cursor] = c; if (ctx->l < CLI_MAX_LINE_LENGTH - 1) { ctx->l++; ctx->cursor++; } else { _write(cli, ctx, "\a", 1); return CLI_LOOP_CTRL_CONTINUE; } } else { // Middle of text if (ctx->insertmode) { int i; // Move everything one character to the right if (ctx->l >= CLI_MAX_LINE_LENGTH - 2) ctx->l--; for (i = ctx->l; i >= ctx->cursor; i--) ctx->cmd[i + 1] = ctx->cmd[i]; // Write what we've just added ctx->cmd[ctx->cursor] = c; _write(cli, ctx, &ctx->cmd[ctx->cursor], ctx->l - ctx->cursor + 1); for (i = 0; i < (ctx->l - ctx->cursor + 1); i++) _write(cli, ctx, "\b", 1); ctx->l++; } else { ctx->cmd[ctx->cursor] = c; } ctx->cursor++; } if (cli->state != CLI_STATE_PASSWORD && cli->state != CLI_STATE_ENABLE_PASSWORD) { if (c == '?' && ctx->cursor == ctx->l) { _write(cli, ctx, "\r\n", 2); ctx->restore_cmd_l = ctx->l -1; return CLI_LOOP_CTRL_BREAK; } _write(cli, ctx, &c, 1); } ctx->restore_cmd_l = 0; ctx->lastchar = c; return 0; } int cli_loop_process_cmd(struct cli_def *cli, struct cli_loop_ctx *ctx) { if (cli->state == CLI_STATE_LOGIN) { if (ctx->l == 0) return 0; /* require login */ if (strlen(ctx->cmd) > sizeof(ctx->username)) return CLI_LOOP_CTRL_BREAK; strncpy(ctx->username, ctx->cmd, sizeof(ctx->username) - 1); cli->state = CLI_STATE_PASSWORD; cli->showprompt = 1; } else if (cli->state == CLI_STATE_PASSWORD) { /* require password */ int allowed = 0; if (cli->auth_callback) { if (cli->auth_callback(ctx->username, ctx->cmd) == CLI_OK) allowed++; } if (!allowed) { struct unp *u; for (u = cli->users; u; u = u->next) { if (!strcmp(u->username, ctx->username) && pass_matches(u->password, ctx->cmd)) { allowed++; break; } } } memset(ctx->cmd, 0, sizeof(ctx->cmd)); // XXX verify this sizeof if (allowed) { cli_error(cli, " "); cli->state = CLI_STATE_NORMAL; } else { cli_error(cli, "\n\nAccess denied"); cli->state = CLI_STATE_LOGIN; } cli->showprompt = 1; } else if (cli->state == CLI_STATE_ENABLE_PASSWORD) { int allowed = 0; if (cli->enable_password) { /* check stored static enable password */ if (pass_matches(cli->enable_password, ctx->cmd)) allowed++; } if (!allowed && cli->enable_callback) { /* check callback */ if (cli->enable_callback(ctx->cmd)) allowed++; } if (allowed) { cli->state = CLI_STATE_ENABLE; cli_set_privilege(cli, PRIVILEGE_PRIVILEGED); } else { cli_error(cli, "\n\nAccess denied"); cli->state = CLI_STATE_NORMAL; } } else { if (ctx->l == 0) return 0; /* XXX also don't add to history if command equals the last history entry? */ if (ctx->cmd[ctx->l - 1] != '?' && strcasecmp(ctx->cmd, "history") != 0) cli_add_history(cli, ctx->cmd); if (cli_run_command(cli, ctx->cmd) == CLI_QUIT) return CLI_LOOP_CTRL_BREAK; } return 0; } /* cryptech: removed unused file mode */ static void _print(struct cli_def *cli, int print_mode, const char *format, va_list ap) { static char buf[1024]; va_list aq; int n; char *p; if (!cli) return; // sanity check while (1) { va_copy(aq, ap); if ((n = vsnprintf(buf, sizeof(buf), format, ap)) == -1) return; if ((unsigned)n >= sizeof(buf)) { strncpy(buf, "_print buffer would have overflown", sizeof(buf)); return; } break; } p = buf; do { char *next = strchr(p, '\n'); if (next) *next++ = 0; else if (print_mode & PRINT_BUFFERED) break; if (cli->print_callback) cli->print_callback(cli, p); else if (cli->client) fprintf(cli->client, "%s\r\n", p); p = next; } while (p); } void cli_bufprint(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_BUFFERED|PRINT_FILTERED, format, ap); va_end(ap); } void cli_vabufprint(struct cli_def *cli, const char *format, va_list ap) { _print(cli, PRINT_BUFFERED, format, ap); } void cli_print(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_FILTERED, format, ap); va_end(ap); } void cli_error(struct cli_def *cli, const char *format, ...) { va_list ap; va_start(ap, format); _print(cli, PRINT_PLAIN, format, ap); va_end(ap); } struct cli_match_filter_state { int flags; union { char *string; regex_t re; } match; }; /* cryptech: removed unused filter functions */ void cli_print_callback(struct cli_def *cli, void (*callback)(struct cli_def *, const char *)) { cli->print_callback = callback; } void cli_set_idle_timeout(struct cli_def *cli, unsigned int seconds) { if (seconds < 1) seconds = 0; cli->idle_timeout = seconds; } 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; } void cli_telnet_protocol(struct cli_def *cli, int telnet_protocol) { cli->telnet_protocol = !!telnet_protocol; } void cli_set_context(struct cli_def *cli, void *context) { cli->user_context = context; } void *cli_get_context(struct cli_def *cli) { return cli->user_context; } void cli_read_callback(struct cli_def *cli, int (*callback)(struct cli_def *, void *, size_t)) { cli->read_callback = callback; } void cli_write_callback(struct cli_def *cli, int (*callback)(struct cli_def *, const void *, size_t)) { cli->write_callback = callback; }