diff options
Diffstat (limited to 'lib/vty_cli.c')
-rw-r--r-- | lib/vty_cli.c | 2308 |
1 files changed, 2308 insertions, 0 deletions
diff --git a/lib/vty_cli.c b/lib/vty_cli.c new file mode 100644 index 00000000..de9bb53c --- /dev/null +++ b/lib/vty_cli.c @@ -0,0 +1,2308 @@ +/* VTY Command Line Handler + * Copyright (C) 1997, 98 Kunihiro Ishiguro + * + * Revisions: Copyright (C) 2010 Chris Hall (GMCH), Highwayman + * + * This file is part of GNU Zebra. + * + * GNU Zebra is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * GNU Zebra is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Zebra; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include <zebra.h> + +#include "keystroke.h" +#include "vty.h" +#include "uty.h" +#include "vty_io.h" +#include "vty_cli.h" + +#include "command.h" + +#include "memory.h" + +/*============================================================================== + * Host name handling + * + * The host name is used in the command line prompt. The name used is either + * the name set by "hostname" command, or the current machine host name. + * + * Static variables -- under the VTY_LOCK ! + */ + +static char* vty_host_name = NULL ; +int vty_host_name_set = 0 ; + +static void uty_new_host_name(const char* name) ; + +/*------------------------------------------------------------------------------ + * Update vty_host_name as per "hostname" or "no hostname" command + */ +extern void +uty_set_host_name(const char* name) +{ + VTY_ASSERT_LOCKED() ; + + vty_host_name_set = (name != NULL) ; + + if (vty_host_name_set) + uty_new_host_name(name) ; + else + uty_check_host_name() ; +} ; + +/*------------------------------------------------------------------------------ + * If vty_host_name is set, free it. + */ +extern void +uty_free_host_name(void) +{ + if (vty_host_name != NULL) + XFREE (MTYPE_HOST, vty_host_name) ; /* sets vty_host_name = NULL */ +} ; + +/*------------------------------------------------------------------------------ + * If the host name is not set by command, see if the actual host name has + * changed, and if so change it. + * + * This is done periodically in case the actual host name changes ! + */ +extern void +uty_check_host_name(void) +{ + struct utsname names ; + + VTY_ASSERT_LOCKED() ; + + if (vty_host_name_set) + return ; /* nothing to do if set by command */ + + uname (&names) ; + + if ((vty_host_name == NULL) || (strcmp(vty_host_name, names.nodename) != 0)) + uty_new_host_name(names.nodename) ; +} ; + +/*------------------------------------------------------------------------------ + * Set new vty_host_name and run along list of VTYs to mark the change. + */ +static void +uty_new_host_name(const char* name) +{ + vty_io vio ; + + VTY_ASSERT_LOCKED() ; + + uty_free_host_name() ; + vty_host_name = XSTRDUP(MTYPE_HOST, name) ; + + vio = vio_list_base ; + while (vio != NULL) + { + vio->cli_prompt_set = 0 ; + vio = sdl_next(vio, vio_list) ; + } ; +} ; + +/*============================================================================== + * General mechanism for command execution. + * + * Command execution is driven by select/pselect -- which means that the + * processing of commands is multiplexed with all other activity. In the + * following: + * + * -- read_ready and write_ready are events signalled by select/pselect + * + * -- setting read or write on, means enabling the file for select/pselect to + * consider it for read_ready or write_ready, respectively. + * + * State of the CLI: + * + * cli_blocked -- a command has been dispatched, and CLI is now waiting + * for it and/or its output to complete. + * + * cmd_in_progress -- a command has been dispatched (and may have been + * queued). + * + * If not blocked: can continue in the CLI until another + * command is ready to be executed. + * + * There are two output FIFOs: + * + * 1. for the CLI itself -- uty_cli_out and friends + * + * 2. for output generated by commands -- vty_out and friends. + * + * The CLI FIFO is emptied whenever possible, in preference to the command + * FIFO. The command FIFO is only emptied when !cmd_in_progress -- this means + * that all the output from a given command is collected together before being + * sent to the file. + * + * The CLI starts with cli_blocked and !cmd_in_progress, with the socket set + * write on. The CLI progresses as follows: + * + * * on read_ready, if cli_blocked: do nothing. + * + * * on write_ready: + * + * - empty out the CLI buffer + * + * - if ! cmd_in_progress: + * + * * empty out the command buffer + * + * * if the command buffer is empty, clear cli_blocked (if was set) + * + * - generate a read_ready event unless write() would block. + * + * So the CLI is kicked once all available output completes. + * + * * on read_ready, if !cli_blocked: + * + * - process keystrokes into command line under construction. + * + * - when required, dispatch the command: + * + * * set cmd_in_progress + * + * * execute the command -- which may generate output + * + * * clear cmd_in_progress + * + * * set cli_blocked + * + * - set write on (or read on) as required. + * + * Note that only sets read on when the keystroke stream is empty and has not + * yet hit eof. The CLI process is driven mostly by write_ready -- which + * invokes read_ready. + * + * Note also that after each command dispatch the CLI processor exits, to be + * re-entered again on write_ready/read_ready -- so does one command line at + * a time, yielding the processor after each one. + * + * Note that select/pselect treat a socket which is at "EOF", or has seen an + * error, or has been half closed, etc. as readable and writable. This means + * that the CLI will continue to move forward even after the socket is no + * longer delivering any data. + * + *------------------------------------------------------------------------------ + * The "--more--" handling. + * + * This is largely buried in the output handling. + * + * While cmd_in_progress is true, all output to the command output FIFO is held + * there -- no actual write() is performed. + * + * When the command completes: + * + * * cmd_in_progress is cleared + * + * * cli_blocked will be set + * + * * the line_control structure is reset + * + * * the output process is kicked off by setting write on + * + * The output process used the line_control structure to manage the output, and + * occasionally enter the trivial "--more--" CLI. This is invisible to the + * main CLI. (See the cmd_wait_more flag and its handling.) + * + * When all the output has completed, cli_blocked is cleared and the CLI will + * be kicked. + * + * It is expected that the output will end with a newline -- so that when the + * CLI is kicked, the cursor will be at the start of an empty line. + * + * This mechanism means that the command output FIFO only ever contains the + * output from the last command executed. + * + * If the user decides to abandon output at the "--more--" prompt, then the + * contents of the command output FIFO are discarded. + * + *------------------------------------------------------------------------------ + * The qpthreads/qpnexus extension. + * + * When running in qnexus mode, many commands are not executed directly in the + * CLI, but are queued for execution by the main "routeing" nexus. + * + * The parsing of commands is context sensitive. The context depends may + * change during the execution of a command. So... it is not possible to + * dispatch a command until the previous one has completed. + * + * In qnexus mode, when a command is queued, the CLI does not go cli_blocked, + * even if some output has already been generated. This allows a further + * command to be entered while the previous one is executed. However, if the + * command is dispatched before the previous one completes, then the cli will + * block. + * + * While the previous command is executing, the current command line has a + * minimal prompt -- to show that the context is not known. When the previous + * command completes, the command line is redrawn in the new context. + * + *------------------------------------------------------------------------------ + * Command line drawn state. + * + * When the cli_drawn flag is set, the current console line contains the + * current prompt and the user input to date. The cursor is positioned where + * the user last placed it. + * + * The command line can be "wiped" -- see uty_cli_wipe() -- which removes all + * output and prompt, and leaves the console at the start of an empty line + * where the command line used to be. + * + * On entry to the CLI, it will draw the command line again if it has been + * wiped. + * + * This mechanism is used to support the partial command line that may be + * entered while a queued command executes. + * + * It is also used for the command help/completion system. + * + * It is also used to support the "monitor" output. + */ + +/*============================================================================== + * Command Line Interface + * + * State of the CLI: + * + * cli_blocked -- a command has been dispatched, and now waiting + * for it and/or its output to complete. + * + * cmd_in_progress -- a command has been dispatched (and may have been + * queued). + * + * Can continue in the CLI until another command is + * ready to be executed. + */ + +static void uty_cli_draw(vty_io vio, enum node_type node) ; +static enum cli_do uty_cli_process(vty_io vio, enum node_type node) ; +static void uty_cli_response(vty_io vio, enum cli_do cli_do) ; +static int uty_cli_dispatch(vty_io vio) ; + +/*------------------------------------------------------------------------------ + * Run the CLI until: + * + * * becomes blocked + * * runs out of keystrokes + * * executes a command + * + * When exits will (in general): + * + * * set write on if there is anything to be written or have something + * in the keystroke stream to be processed. + * + * * set read on if does not set write on, if not yet hit EOF on the + * keystroke stream. + * + * Note that this executes at most one command each time it is called. This + * is to allow for a modicum of sharing of the system. For real keyboard input + * this will make no difference at all ! + */ +extern void +uty_cli(vty_io vio) +{ + bool won ; + + VTY_ASSERT_LOCKED() ; + + /* cli_blocked is set when is waiting for a command, or its output to + * complete. + * + * There is no good reason to arrive here in that state, and nothing to be + * done if that happens. + */ + if (vio->cli_blocked) + return ; + + /* If there is something to do, that is because a previous command has + * now completed, which may have wiped the pending command. + * + * If there is nothing pending, then can run the CLI until there is + * something to do, or runs out of input. + */ + if (vio->cli_do != cli_do_nothing) + uty_cli_draw(vio, vio->vty->node) ; + else + vio->cli_do = uty_cli_process(vio, vio->vty->node) ; + + /* If have something to do, do it. */ + if (vio->cli_do != cli_do_nothing) + { + /* Reflect immediate response */ + uty_cli_response(vio, vio->cli_do) ; + + /* If command not already in progress, dispatch this one, which may + * set the CLI blocked. + * + * Otherwise is now blocked until queued command completes. + */ + if (!vio->cmd_in_progress) + vio->cli_blocked = uty_cli_dispatch(vio) ; + else + vio->cli_blocked = 1 ; + } ; + + /* If there is anything in the CLI output FIFO, must set write on to clear + * it. + * + * If there is anything in the command output FIFO *and* is !cmd_in_progress, + * must set write on to clear it. + * + * Otherwise: if cmd_in_progress, then the command buffer is not to be + * output yet, and the command completion will kick things into action -- so + * nothing further is required here. + * + * Otherwise: if there is anything in the command output buffer, must set + * write on to clear it. + * + * Otherwise: if there is something in the keystroke stream, then must set + * write on... the write_ready acts as a proxy for keystroke stream is ready. + * + * Otherwise: set read on -- so if further input arrives, will get on it. + */ + do + { + won = 1 ; /* generally the case */ + + /* If have some CLI output -- do that */ + if (!vio_fifo_empty(&vio->cli_obuf)) + break ; + + /* If is blocked with command in progress -- do nothing. + * + * This state <=> that there is a queued command, and the CLI is now + * blocked until the command completes. When it does, things will + * progress. + */ + if (vio->cli_blocked && vio->cmd_in_progress) + { + won = 0 ; + break ; + } ; + + /* If have some command output which is not held because there is + * a command in progress -- do that. + */ + if (!vio_fifo_empty(&vio->cmd_obuf) && !vio->cmd_in_progress) + break ; + + /* If the keystroke buffer is not empty then write_ready is used as a + * proxy for keystroke ready. + */ + if (!keystroke_stream_empty(vio->key_stream)) + break ; + + /* Finally... if not at keystroke EOF, set read on */ + won = 0 ; + + if (!keystroke_stream_eof(vio->key_stream)) + uty_file_set_read(&vio->file, on) ; + + } while (0) ; + + if (won) + uty_file_set_write(&vio->file, on) ; +} ; + +/* TODO: dealing with EOF and timeout and closing and such + * + * uty_cout (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE); + * + */ +/*------------------------------------------------------------------------------ + * Dispatch the current vio->cli_do -- queueing it if necessary. + * + * Requires that are NOT blocked and NO command is queued. + * + * Expects to be on new blank line, and when returns will be on new, blank + * line. + * + * Returns: true <=> output is pending and command completed + * false => no output pending or command was queued + * + * Generally sets vio->cl_do = cli_do_nothing and clears vio->cl to empty. + * + * Can set vio->cl_do = and vio->cl to be a follow-on command. + */ +static int +uty_cli_dispatch(vty_io vio) +{ + qstring_t tmp ; + enum cli_do cli_do ; + int queued ; + + struct vty* vty = vio->vty ; + + VTY_ASSERT_LOCKED() ; + assert(!vio->cli_blocked && !vio->cmd_in_progress) ; + + /* Set vio->clx to the command about to execute. + * + * Clear vio->cl and vio->cl_do. + */ + vio->cmd_in_progress = 1 ; /* => vty->buf is valid */ + + tmp = vio->clx ; /* swap clx and cl */ + vio->clx = vio->cl ; + vio->cl = tmp ; + + qs_term(&vio->clx) ; /* ensure string is terminated */ + vty->buf = qs_chars(&vio->clx) ; /* terminated command line */ + cli_do = vio->cli_do ; /* current operation */ + + vio->cli_do = cli_do_nothing ; /* clear */ + qs_set_empty(&vio->cl) ; /* set cl empty (with '\0') */ + + /* Reset the command output FIFO and line_control */ + + assert(vio_fifo_empty(&vio->cmd_obuf)) ; + /* TODO: reset line_control */ + + /* Dispatch command */ + if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) + { + /* AUTH_NODE and AUTH_ENABLE_NODE are unique */ + queued = uty_auth(vty, vty->buf, cli_do) ; + } + else + { + /* All other nodes... */ + switch (cli_do) + { + case cli_do_nothing: + break ; + + case cli_do_command: + queued = uty_command(vty, vty->buf) ; + break ; + + case cli_do_ctrl_c: + queued = uty_stop_input(vty) ; + break ; + + case cli_do_ctrl_d: + queued = uty_down_level(vty) ; + break ; + + case cli_do_ctrl_z: + queued = uty_command(vty, vty->buf) ; + if (queued) + vio->cli_do = cli_do_ctrl_z ; + else + queued = uty_end_config(vty) ; + break ; + + default: + zabort("unknown cli_do_xxx value") ; + } ; + } ; + + if (!queued) + { + vio->cmd_in_progress = 0 ; /* command complete */ + vty->buf = NULL ; /* finished with command line */ + } ; + + return ! (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) ; +} ; + +/*------------------------------------------------------------------------------ + * Queued command has completed. + * + * Note that sets write on whether there is anything in the output buffer + * or not... write_ready will kick read_ready. + */ +extern void +vty_queued_result(struct vty *vty, int result, int action) +{ + vty_io vio ; + + VTY_LOCK() ; + + vio = vty->vio ; + + vio->cmd_in_progress = 0 ; /* command complete */ + vty->buf = NULL ; /* finished with command line */ + + vio->cli_blocked = !vio_fifo_empty(&vio->cmd_obuf) ; + /* blocked if output is now pending */ + + uty_cli_wipe(vio) ; /* wipe any partly constructed line */ + + uty_file_set_write(&vty->vio->file, on) ; + /* flush any output -- which will do a + * read_ready when all finished */ + VTY_UNLOCK() ; +} + +/*============================================================================== + * CLI VTY output + * + * This is buffered separately from the general (command) VTY output above. + * + * Has a dedicated buffer in the struct vty, which is flushed regularly during + * command processing. + * + * It is expected that can flush straight to the file, since this is running at + * CLI speed. However, if the CLI is being driven by something other than a + * keyboard, or "monitor" output has filled the buffers, then may need to + * have intermediate buffering. + * + * No actual I/O takes place here-- all "output" is to vio->cli_obuf + * and/or vio->cli_ex_obuf + * + * The "cli_echo" functions discard the output if vio->cli_echo_suppress != 0. + * This is used while passwords are entered and to allow command line changes + * to be made while the line is not visible. + */ + +enum { cli_rep_count = 32 } ; + +typedef const char cli_rep_char[cli_rep_count] ; + +static const char telnet_backspaces[] = + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + } ; +CONFIRM(sizeof(telnet_backspaces) == sizeof(cli_rep_char)) ; + +static const char telnet_spaces[] = + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' + } ; +CONFIRM(sizeof(telnet_spaces) == sizeof(cli_rep_char)) ; + +static const char* telnet_newline = "\r\n" ; + +static void uty_cli_write(vty_io vio, const char *this, int len) ; +static void uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf fprintf() + */ +static void +uty_cli_out (vty_io vio, const char *format, ...) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->file.write_open) + { + va_list args; + int len ; + + va_start (args, format); + len = qs_vprintf(&vio->cli_vbuf, format, args) ; + va_end(args); + + if (len > 0) + uty_cli_write(vio, qs_chars(&vio->cli_vbuf), len) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo user input + * + * Do nothing if echo suppressed (eg in AUTH_NODE) or not write_open + */ +static void +uty_cli_echo (vty_io vio, const char *this, size_t len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->file.write_open) + return ; + + uty_cli_write(vio, this, len) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- echo 'n' characters using a cli_rep_char string + * + * Do nothing if echo suppressed (eg in AUTH_NODE) + */ +static void +uty_cli_echo_n(vty_io vio, cli_rep_char chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->cli_echo_suppress || !vio->file.write_open) + return ; + + uty_cli_write_n(vio, chars, n) ; +} + +/*------------------------------------------------------------------------------ + * CLI VTY output -- cf write() + */ +inline static void +uty_cli_write(vty_io vio, const char *this, int len) +{ + VTY_ASSERT_LOCKED() ; + + if (vio->file.write_open) + vio_fifo_put(&vio->cli_obuf, this, len) ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write 'n' characters using a cli_rep_char string + */ +static void +uty_cli_write_n(vty_io vio, cli_rep_char chars, int n) +{ + int len ; + + len = sizeof(cli_rep_char) ; + while (n > 0) + { + if (n < len) + len = n ; + uty_cli_write(vio, chars, len) ; + n -= len ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * CLI VTY output -- write given string + * + * Returns length of string. + */ +static int +uty_cli_write_s(vty_io vio, const char *str) +{ + int len ; + + len = strlen(str) ; + if (len != 0) + uty_cli_write(vio, str, len) ; + + return len ; +} ; + +/*============================================================================== + * Standard Messages + */ + +/*------------------------------------------------------------------------------ + * Send newline to the console. + * + * Clears the cli_drawn flag. + */ +static void +uty_cli_out_newline(vty_io vio) +{ + uty_cli_write(vio, telnet_newline, 2) ; + vio->cli_drawn = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Wipe 'n' characters. + * + * If 'n' < 0, wipes characters backwards and moves cursor back. + * 'n' > 0, wipes characters forwards, leaving cursor where it is + */ +static void +uty_cli_out_wipe_n(vty_io vio, int n) +{ + if (n < 0) + { + n = abs(n) ; + uty_cli_write_n(vio, telnet_backspaces, n); + } ; + + if (n > 0) + { + uty_cli_write_n(vio, telnet_spaces, n) ; + uty_cli_write_n(vio, telnet_backspaces, n) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send response to the given cli_do + * + * If no command is in progress, then will send newline to signal that the + * command is about to be dispatched. + * + * If command is in progress, then leaves cursor on '^' to signal that is now + * waiting for previous command to complete. + */ +static const char* cli_response [2][cli_do_count] = +{ + { /* when not waiting for previous command to complete */ + [cli_do_command] = "", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + }, + { /* when waiting for a previous command to complete */ + [cli_do_command] = "^", + [cli_do_ctrl_c] = "^C", + [cli_do_ctrl_d] = "^D", + [cli_do_ctrl_z] = "^Z", + } +} ; + +static void +uty_cli_response(vty_io vio, enum cli_do cli_do) +{ + const char* str ; + int len ; + + if (cli_do == cli_do_nothing) + return ; + + str = (cli_do < cli_do_count) + ? cli_response[vio->cmd_in_progress ? 1 : 0][cli_do] : NULL ; + assert(str != NULL) ; + + len = uty_cli_write_s(vio, str) ; + + if (vio->cmd_in_progress) + { + vio->cli_extra_len = len ; + uty_cli_write_n(vio, telnet_backspaces, len) ; + } + else + { + uty_cli_out_newline(vio) ; + } ; +} ; + +/*------------------------------------------------------------------------------ + * Send various messages with trailing newline. + */ + +static void +uty_cli_out_CMD_ERR_AMBIGUOUS(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_AMBIGUOUS ".") ; + uty_cli_out_newline(vio) ; +} ; + +static void +uty_cli_out_CMD_ERR_NO_MATCH(vty_io vio) +{ + uty_cli_write_s(vio, "% " MSG_CMD_ERR_NO_MATCH ".") ; + uty_cli_out_newline(vio) ; +} ; + +/*============================================================================== + * Command line draw and wipe + */ + +/*------------------------------------------------------------------------------ + * Wipe the current console line -- if any. + */ +extern void +uty_cli_wipe(vty_io vio) +{ + int a ; + int b ; + + if (!vio->cli_drawn == 0) + return ; /* quit if already wiped */ + + assert(vio->cl.cp <= vio->cl.len) ; + + /* deal with echo suppression first */ + if (vio->cli_echo_suppress) + { + b = 0 ; + a = 0 ; + } + else + { + b = vio->cl.cp ; /* behind cursor */ + a = vio->cl.len - b ; /* ahead of cursor */ + } + + /* Stuff ahead of the current position */ + uty_cli_out_wipe_n(vio, a + vio->cli_extra_len) ; + + /* Stuff behind the current position */ + uty_cli_out_wipe_n(vio, vio->cli_prompt_len + b) ; + + vio->cli_drawn = 0 ; +} ; + +/*------------------------------------------------------------------------------ + * Draw prompt and entire command line, leaving current position where it + * should be. + * + * If command line is currently drawn, this wipes and redraws. + * + * Otherwise, assumes is positioned at start of an empty line. + * + * If there is a command queued, uses a dummy prompt -- because by the time + * the command is executed, the node may have changed, so the current prompt + * may be invalid. + * + * Sets: cli_drawn = true + * cli_prompt_len = length of prompt used + * cli_extra_len = 0 + * cli_echo_suppress = (AUTH_NODE or AUTH_ENABLE_NODE) + */ +static void +uty_cli_draw(vty_io vio, enum node_type node) +{ + const char* prompt ; + + if (vio->cli_drawn) + uty_cli_wipe(vio) ; + + vio->cli_drawn = 1 ; + vio->cli_extra_len = 0 ; + vio->cli_echo_suppress = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + /* Sort out what the prompt is. */ + + if (vio->cmd_in_progress) + { + /* If there is a queued command, the prompt is a minimal affair. */ + prompt = "~ " ; + vio->cli_prompt_len = strlen(prompt) ; + } + else + { + /* The prompt depends on the node, and is expected to include the + * host name. + * + * Caches the prompt so doesn't re-evaluate it every time. + * + * If the host name changes, the cli_prompt_set flag is cleared. + */ + if (!vio->cli_prompt_set || (node != vio->cli_prompt_node)) + { + const char* prompt ; + + if (vty_host_name == NULL) + uty_check_host_name() ; /* should never be required */ + + prompt = cmd_prompt(node) ; + if (prompt == NULL) + { + zlog_err("vty %s has node %d", uty_get_name(vio), node) ; + prompt = "%s ???: " ; + } ; + + qs_printf(&vio->cli_prompt_for_node, prompt, vty_host_name); + + vio->cli_prompt_node = node ; + vio->cli_prompt_set = 1 ; + } ; + + prompt = qs_chars(&vio->cli_prompt_for_node) ; + vio->cli_prompt_len = vio->cli_prompt_for_node.len ; + } ; + + uty_cli_write(vio, prompt, vio->cli_prompt_len) ; + + if ((vio->cl.len != 0) && !vio->cli_echo_suppress) + { + uty_cli_write(vio, qs_chars(&vio->cl), vio->cl.len) ; + if (vio->cl.cp < vio->cl.len) + uty_cli_write_n(vio, telnet_backspaces, vio->cl.len - vio->cl.cp) ; + } ; +} ; + +/*============================================================================== + * Command line processing loop + */ + +#define CONTROL(X) ((X) - '@') + +static void uty_telnet_command(vty_io vio, keystroke stroke) ; +static int uty_cli_insert (vty_io vio, const char* chars, int n) ; +static int uty_cli_overwrite (vty_io vio, char* chars, int n) ; +static int uty_cli_word_overwrite (vty_io vio, char *str) ; +static int uty_cli_forwards(vty_io vio, int n) ; +static int uty_cli_backwards(vty_io vio, int n) ; +static int uty_cli_del_forwards(vty_io vio, int n) ; +static int uty_cli_del_backwards(vty_io vio, int n) ; +static int uty_cli_bol (vty_io vio) ; +static int uty_cli_eol (vty_io vio) ; +static int uty_cli_word_forwards_delta(vty_io vio) ; +static int uty_cli_word_forwards(vty_io vio) ; +static int uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) ; +static int uty_cli_word_backwards_pure (vty_io vio) ; +static int uty_cli_word_backwards (vty_io vio) ; +static int uty_cli_del_word_forwards(vty_io vio) ; +static int uty_cli_del_word_backwards(vty_io vio) ; +static int uty_cli_del_to_eol (vty_io vio) ; +static int uty_cli_clear_line(vty_io vio) ; +static int uty_cli_transpose_chars(vty_io vio) ; +static void uty_cli_history_use(vty_io vio, int step) ; +static void uty_cli_next_line(vty_io vio) ; +static void uty_cli_previous_line (vty_io vio) ; +static void uty_cli_complete_command (vty_io vio, enum node_type node) ; +static void uty_cli_describe_command (vty_io vio, enum node_type node) ; + +/*------------------------------------------------------------------------------ + * Fetch next keystroke, reading from the file if required. + */ +static inline bool +uty_cli_get_keystroke(vty_io vio, keystroke stroke) +{ + if (keystroke_get(vio->key_stream, stroke)) + return 1 ; + + uty_read(vio, NULL) ; /* not stealing */ + + return keystroke_get(vio->key_stream, stroke) ; +} ; + +/*------------------------------------------------------------------------------ + * Process keystrokes until run out of input, or get something to cli_do. + * + * If required, draw the prompt and command line. + * + * Process keystrokes until run out of stuff to do, or have a "command line" + * that must now be executed. + * + * Processes the contents of the keystroke stream. If exhausts that, will set + * ready to read and return. (To give some "sharing".) + * + * Returns: cli_do_xxxx + * + * When returns the cl is '\0' terminated. + */ +static enum cli_do +uty_cli_process(vty_io vio, enum node_type node) +{ + struct keystroke stroke ; + uint8_t u ; + int auth ; + enum cli_do ret ; + + auth = (node == AUTH_NODE || node == AUTH_ENABLE_NODE) ; + + /* Now process as much as possible of what there is */ + ret = cli_do_nothing ; + while (1) + { + if (!vio->cli_drawn) + uty_cli_draw(vio, node) ; + + if (!uty_cli_get_keystroke(vio, &stroke)) + break ; + + if (stroke.flags != 0) + { + /* TODO: deal with broken keystrokes */ + continue ; + } ; + + switch (stroke.type) + { + /* Straightforward character -----------------------------------*/ + /* Note: only interested in 8-bit characters ! */ + case ks_char: + u = (uint8_t)stroke.value ; + + switch (stroke.value) + { + case CONTROL('A'): + uty_cli_bol (vio); + break; + + case CONTROL('B'): + uty_cli_backwards(vio, 1); + break; + + case CONTROL('C'): + ret = cli_do_ctrl_c ; /* Exit on ^C ..................*/ + break ; + + case CONTROL('D'): + if (vio->cl.len == 0) /* if at start of empty line */ + ret = cli_do_ctrl_d ; /* Exit on ^D ..................*/ + else + uty_cli_del_forwards(vio, 1); + break; + + case CONTROL('E'): + uty_cli_eol (vio); + break; + + case CONTROL('F'): + uty_cli_forwards(vio, 1); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_backwards(vio, 1); + break; + + case CONTROL('K'): + uty_cli_del_to_eol (vio); + break; + + case CONTROL('N'): + uty_cli_next_line (vio); + break; + + case CONTROL('P'): + uty_cli_previous_line (vio); + break; + + case CONTROL('T'): + uty_cli_transpose_chars (vio); + break; + + case CONTROL('U'): + uty_cli_clear_line(vio); + break; + + case CONTROL('W'): + uty_cli_del_word_backwards (vio); + break; + + case CONTROL('Z'): + ret = cli_do_ctrl_z ; /* Exit on ^Z ..................*/ + break; + + case '\n': + case '\r': + ret = cli_do_command ; /* Exit on CR or LF.............*/ + break ; + + case '\t': + if (auth) + break ; + else + uty_cli_complete_command (vio, node); + break; + + case '?': + if (auth) + uty_cli_insert (vio, (char*)&u, 1); + else + uty_cli_describe_command (vio, node); + break; + + default: + if ((stroke.value >= 0x20) && (stroke.value < 0x7F)) + uty_cli_insert (vio, (char*)&u, 1) ; + break; + } + break ; + + /* ESC X -------------------------------------------------------------*/ + case ks_esc: + switch (stroke.value) + { + case 'b': + uty_cli_word_backwards (vio); + break; + + case 'f': + uty_cli_word_forwards (vio); + break; + + case 'd': + uty_cli_del_word_forwards (vio); + break; + + case CONTROL('H'): + case 0x7f: + uty_cli_del_word_backwards (vio); + break; + + default: + break; + } ; + break ; + + /* ESC [ X -----------------------------------------------------------*/ + case ks_csi: + if (stroke.len != 0) + break ; /* only recognise 3 byte sequences */ + + switch (stroke.value) + { + case ('A'): + uty_cli_previous_line (vio); + break; + + case ('B'): + uty_cli_next_line (vio); + break; + + case ('C'): + uty_cli_forwards(vio, 1); + break; + + case ('D'): + uty_cli_backwards(vio, 1); + break; + default: + break ; + } ; + break ; + + /* Telnet Command ----------------------------------------------------*/ + case ks_iac: + uty_telnet_command(vio, &stroke) ; + break ; + + /* Single byte escape ------------------------------------------------*/ + default: + zabort("unknown keystroke type") ; + } ; + + /* After each keystroke..... */ + + if (ret != cli_do_nothing) + { + uty_cli_eol (vio) ; /* go to the end of the line */ + break ; /* stop processing */ + } ; + } ; + + /* Tidy up and return where got to. */ + + qs_term(&vio->cl) ; /* add '\0' */ + + return ret ; +} ; + +/*============================================================================== + * Support for the "--More--" handling + */ + +#define MSG_MORE_PROMPT "--more--" + +/*------------------------------------------------------------------------------ + * Output the "--more--" prompt and enter waiting for more state. + * + * As enters the waiting state, empties all the available input into the + * keystroke FIFO... so doesn't treat anything that arrived before the + * prompt was issued as a response to the prompt ! + * + * Enables read -- now waiting for response to --more-- + * + * NB: it is the caller's responsibility to clear the "--more--" prompt from + * the CLI buffer. + */ +extern void +uty_cli_want_more(vty_io vio) +{ + int get ; + + /* Empty the input buffers into the keystroke FIFO */ + do + { + get = uty_read(vio, NULL) ; + } while (get > 0) ; + + /* Output the prompt and update the state */ + uty_cli_write_s(vio, MSG_MORE_PROMPT) ; + + vio->cmd_wait_more = 1 ; + + uty_file_set_read(&vio->file, on) ; +} ; + +/*------------------------------------------------------------------------------ + * Waiting for response to "--more--" prompt. + * + * Steal the next available keystroke and process it. Error and EOF appear as + * a null keystroke. + * + * Wipes the prompt and exits cli_wait_more state when has stolen keystroke. + * + * Enables write -- need to clear the prompt from the cli buffers and may + * now continue command output, or signal it's done. + */ +extern void +uty_cli_wait_more(vty_io vio) +{ + struct keystroke steal ; + + /* The read fetches a reasonable lump from the I/O -- so if there is a + * complete keystroke available, expect to get it. + * + * If no complete keystroke available to steal, returns ks_null. + * + * If has hit EOF (or error etc), returns knull_eof. + */ + uty_read(vio, &steal) ; + + if ((steal.type == ks_null) || (steal.value != knull_eof)) + return ; + + /* Stolen a keystroke -- a (very) few terminate all output */ + if (steal.type == ks_char) + { + switch (steal.value) + { + case CONTROL('C'): + case 'q': + case 'Q': + uty_out_discard(vio) ; + break; + + default: /* evrything else, thrown away */ + break ; + } ; + } ; + + /* Wipe out the prompt and update state. */ + uty_cli_out_wipe_n(vio, - (int)strlen(MSG_MORE_PROMPT)) ; + + vio->cmd_wait_more = 0 ; + + uty_file_set_write(&vio->file, on) ; +} ; + +/*============================================================================== + * Command line operations + */ + +/*------------------------------------------------------------------------------ + * Insert 'n' characters at current position in the command line + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_insert (vty_io vio, const char* chars, int n) +{ + int after ; + + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len)&& (n >= 0)) ; + + if (n <= 0) + return n ; /* avoid trouble */ + + after = qs_insert(&vio->cl, chars, n) ; + + uty_cli_echo(vio, qs_cp_char(&vio->cl), after + n) ; + + if (after != 0) + uty_cli_echo_n(vio, telnet_backspaces, after) ; + + vio->cl.cp += n ; + + return n ; +} ; + +/*------------------------------------------------------------------------------ + * Overstrike 'n' characters at current position in the command line + * + * Move current position forwards. + * + * Returns number of characters inserted -- ie 'n' + */ +static int +uty_cli_overwrite (vty_io vio, char* chars, int n) +{ + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.cp <= vio->cl.len) && (n >= 0)) ; + + if (n > 0) + { + qs_replace(&vio->cl, chars, n) ; + uty_cli_echo(vio, chars, n) ; + + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Insert a word into vty interface with overwrite mode. + * + * NB: Assumes result will then be the end of the line. + * + * Returns number of characters inserted -- ie length of string + */ +static int +uty_cli_word_overwrite (vty_io vio, char *str) +{ + int n ; + VTY_ASSERT_LOCKED() ; + + n = uty_cli_overwrite(vio, str, strlen(str)) ; + + vio->cl.len = vio->cl.cp ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Forward 'n' characters -- stop at end of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_forwards(vty_io vio, int n) +{ + int c ; + VTY_ASSERT_LOCKED() ; + + c = vio->cl.len - vio->cl.cp ; + if (n > c) + n = c ; + + assert(n >= 0) ; + + if (n > 0) + { + uty_cli_echo(vio, qs_cp_char(&vio->cl), n) ; + vio->cl.cp += n ; + } ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Backwards 'n' characters -- stop at start of line. + * + * Returns number of characters actually moved + */ +static int +uty_cli_backwards(vty_io vio, int n) +{ + int c ; + VTY_ASSERT_LOCKED() ; + + c = vio->cl.len - vio->cl.cp ; + if (n > c) + n = c ; + + assert(n >= 0) ; + + uty_cli_echo_n(vio, telnet_backspaces, n) ; + vio->cl.cp -= n ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters -- forwards -- stop at end of line. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_forwards(vty_io vio, int n) +{ + int after ; + + VTY_ASSERT_LOCKED() ; + + assert((vio->cl.len - vio->cl.cp) && (n >= 0)) ; + + if (n <= 0) + return 0 ; + + after = qs_delete(&vio->cl, n) ; + + if (after > 0) + uty_cli_echo(vio, qs_cp_char(&vio->cl), after) ; + + uty_cli_echo_n(vio, telnet_spaces, n) ; + uty_cli_echo_n(vio, telnet_backspaces, after + n) ; + + vio->cl.len -= n ; + + return n ; +} + +/*------------------------------------------------------------------------------ + * Delete 'n' characters before the point. + * + * Returns number of characters actually deleted. + */ +static int +uty_cli_del_backwards(vty_io vio, int n) +{ + return uty_cli_del_forwards(vio, uty_cli_backwards(vio, n)) ; +} + +/*------------------------------------------------------------------------------ + * Move to the beginning of the line. + * + * Returns number of characters moved over. + */ +static int +uty_cli_bol (vty_io vio) +{ + return uty_cli_backwards(vio, vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Move to the end of the line. + * + * Returns number of characters moved over + */ +static int +uty_cli_eol (vty_io vio) +{ + return uty_cli_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word delta -- distance to start of next word. + * + * Return number of characters to step over to reach next word. + * + * Steps over non-space characters and then any spaces. + */ +static int +uty_cli_word_forwards_delta(vty_io vio) +{ + char* cp ; + char* tp ; + char* ep ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + ep = qs_ep_char(&vio->cl) ; + + tp = cp ; + + while ((tp < ep) && (*tp != ' ')) + ++tp ; + + while ((tp < ep) && (*tp == ' ')) + ++tp ; + + return tp - cp ; +} ; + +/*------------------------------------------------------------------------------ + * Forward word -- move to start of next word. + * + * Moves past any non-spaces, then past any spaces. + */ +static int +uty_cli_word_forwards(vty_io vio) +{ + return uty_cli_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word delta -- distance to start of next word, back. + * + * Return number of characters to step over to reach next word. + * + * If "eat_spaces", starts by stepping over spaces. + * Steps back until next (backwards) character is space, or hits start of line. + */ +static int +uty_cli_word_backwards_delta(vty_io vio, int eat_spaces) +{ + char* cp ; + char* tp ; + char* sp ; + + VTY_ASSERT_LOCKED() ; ; + + assert(vio->cl.cp <= vio->cl.len) ; + + cp = qs_cp_char(&vio->cl) ; + sp = qs_chars(&vio->cl) ; + + tp = cp ; + + if (eat_spaces) + while ((tp > sp) && (*(tp - 1) == ' ')) + --tp ; + + while ((tp > sp) && (*(tp - 1) != ' ')) + --tp ; + + return cp - tp ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word, but not trailing spaces. + * + * Move back until next (backwards) character is space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards_pure (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 0)) ; +} ; + +/*------------------------------------------------------------------------------ + * Backward word -- move to start of previous word. + * + * Moves past any spaces, then move back until next (backwards) character is + * space or start of line. + * + * Returns number of characters stepped over. + */ +static int +uty_cli_word_backwards (vty_io vio) +{ + return uty_cli_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Delete to end of word -- forwards. + * + * Deletes any leading spaces, then deletes upto next space or end of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_forwards(vty_io vio) +{ + return uty_cli_del_forwards(vio, uty_cli_word_forwards_delta(vio)) ; +} + +/*------------------------------------------------------------------------------ + * Delete to start of word -- backwards. + * + * Deletes any trailing spaces, then deletes upto next space or start of line. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_word_backwards(vty_io vio) +{ + return uty_cli_del_backwards(vio, uty_cli_word_backwards_delta(vio, 1)) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill rest of line from current point. + * + * Returns number of characters deleted. + */ +static int +uty_cli_del_to_eol (vty_io vio) +{ + return uty_cli_del_forwards(vio, vio->cl.len - vio->cl.cp) ; +} ; + +/*------------------------------------------------------------------------------ + * Kill line from the beginning. + * + * Returns number of characters deleted. + */ +static int +uty_cli_clear_line(vty_io vio) +{ + uty_cli_bol(vio) ; + return uty_cli_del_to_eol(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Transpose chars before or at the point. + * + * Return number of characters affected. + */ +static int +uty_cli_transpose_chars(vty_io vio) +{ + char chars[2] ; + char* cp ; + + VTY_ASSERT_LOCKED() ; + + /* Give up if < 2 characters or at start of line. */ + if ((vio->cl.len < 2) || (vio->cl.cp < 1)) + return 0 ; + + /* Move back to first of characters to exchange */ + if (vio->cl.cp == vio->cl.len) + uty_cli_backwards(vio, 2) ; + else + uty_cli_backwards(vio, 1) ; + + /* Pick up in the new order */ + cp = qs_cp_char(&vio->cl) ; + chars[1] = *cp++ ; + chars[0] = *cp ; + + /* And overwrite */ + return uty_cli_overwrite(vio, chars, 2) ; +} ; + +/*============================================================================== + * Command line history handling + */ + +/*------------------------------------------------------------------------------ + * Add given command line to the history buffer. + * + * This is inserting the vty->buf line into the history. + */ +extern void +uty_cli_hist_add (vty_io vio, const char* cmd_line) +{ + char* prev_line ; + char* line ; + char* e ; + int prev_index ; + + VTY_ASSERT_LOCKED() ; + + /* make sure have a suitable history vector */ + vector_set_min_length(&vio->hist, VTY_MAXHIST) ; + + /* trim leading and trailing spaces before considering for history */ + + while (*cmd_line == ' ') + ++cmd_line ; /* hack off leading spaces */ + + if (*cmd_line == '\0') + return ; /* ignore empty lines */ + + line = XSTRDUP(MTYPE_VTY_HIST, cmd_line) ; + + e = line + strlen(line) ; + while (*(e - 1) == ' ') + --e ; + *e = '\0' ; /* hack off any trailing spaces */ + + /* find the previous command line in the history */ + prev_index = vio->hindex - 1 ; + if (prev_index < 0) + prev_index = VTY_MAXHIST - 1 ; + + prev_line = vector_get_item(&vio->hist, prev_index) ; + + /* Insert unless same as previous line */ + if ((prev_line == NULL) || (strcmp(line, prev_line) != 0)) + { + /* Insert history entry. */ + if (prev_line != NULL) + XFREE (MTYPE_VTY_HIST, prev_line) ; + + vector_set_item(&vio->hist, vio->hindex, line) ; + + /* History index rotation. */ + vio->hindex++; + if (vio->hindex == VTY_MAXHIST) + vio->hindex = 0; + } + else + { + XFREE(MTYPE_VTY_HIST, line) ; + } ; + + vio->hp = vio->hindex; +} ; + +/*------------------------------------------------------------------------------ + * Replace command line by current history. + * + * This function is called from vty_next_line and vty_previous_line. + */ +static void +uty_cli_history_use(vty_io vio, int step) +{ + int index ; + unsigned new_len ; + unsigned old_len ; + char* hist ; + + VTY_ASSERT_LOCKED() ; + + /* See if have anything usable */ + index = vio->hp ; + + if ((step > 0) && (index == vio->hindex)) + return ; /* cannot step forward past the insertion point */ + + index += step ; + if (index < 0) + index = VTY_MAXHIST - 1 ; + else if (index >= VTY_MAXHIST) ; + index = 0 ; + + if ((step < 0) && (index == vio->hindex)) + return ; /* cannot step back to the insertion point */ + + hist = vector_get_item(&vio->hist, index) ; + if (hist == NULL) + return ; /* cannot step to unused entry */ + + vio->hp = index; + + /* Move back to the start of the current line */ + uty_cli_bol(vio) ; + + /* Get previous line from history buffer and echo that */ + old_len = vio->cl.len ; + new_len = qs_set(&vio->cl, hist) ; + vio->cl.cp = new_len ; + + uty_cli_echo(vio, hist, new_len) ; + + if (old_len < new_len) + uty_cli_del_to_eol(vio) ; + + return ; +} ; + +/*------------------------------------------------------------------------------ + * Use next history line, if any. + */ +static void +uty_cli_next_line(vty_io vio) +{ + uty_cli_history_use(vio, +1) ; +} + +/*------------------------------------------------------------------------------ + * Use previous history line, if any. + */ +static void +uty_cli_previous_line (vty_io vio) +{ + uty_cli_history_use(vio, -1) ; +} + +/*============================================================================== + * Command Completion and Command Description + * + */ +static void uty_cli_describe_show(vty_io vio, vector describe) ; +static void uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) ; +static void uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) ; + +static vector uty_cli_cmd_prepare(vty_io vio, int help) ; + +/*------------------------------------------------------------------------------ + * Command completion + */ +static void +uty_cli_complete_command (vty_io vio, enum node_type node) +{ + unsigned i ; + int ret ; + vector matched ; + vector vline ; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + matched = cmd_complete_command (vline, node, &ret); + cmd_free_strvec (vline); + + /* Show the result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + case CMD_COMPLETE_FULL_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + uty_cli_insert(vio, " ", 1); + break ; + + case CMD_COMPLETE_MATCH: + uty_cli_eol (vio) ; + uty_cli_word_backwards_pure (vio); + uty_cli_word_overwrite (vio, vector_get_item(matched, 0)); + break ; + + case CMD_COMPLETE_LIST_MATCH: + for (i = 0; i < vector_end(matched); i++) + { + if ((i % 6) == 0) + uty_cli_out_newline(vio) ; /* clears cli_drawn */ + uty_cli_out (vio, "%-10s ", (char*)vector_get_item(matched, i)); + } + + break; + + case CMD_ERR_NOTHING_TODO: + default: + break; + } ; + + cmd_free_strvec(matched); +} ; + +/*------------------------------------------------------------------------------ + * Command Description + */ +static void +uty_cli_describe_command (vty_io vio, enum node_type node) +{ + int ret; + vector vline; + vector describe; + + VTY_ASSERT_LOCKED() ; + + /* Try and match the tokenised command line */ + vline = uty_cli_cmd_prepare(vio, 1) ; + describe = cmd_describe_command (vline, node, &ret); + cmd_free_strvec (vline); + + uty_cli_out_newline(vio); /* clears cli_drawn */ + + /* Deal with result. */ + switch (ret) + { + case CMD_ERR_AMBIGUOUS: + uty_cli_out_CMD_ERR_AMBIGUOUS(vio) ; + break ; + + case CMD_ERR_NO_MATCH: + uty_cli_out_CMD_ERR_NO_MATCH(vio) ; + break ; + + default: + uty_cli_describe_show(vio, describe) ; + break ; + } ; + + if (describe != NULL) + vector_free (describe); +} + +/*------------------------------------------------------------------------------ + * Show the command description. + * + * Generates lines of the form: + * + * word description text + * + * Where the word field is adjusted to suit the longest word, and the + * description text is wrapped if required (if the width of the console is + * known) so that get: + * + * word description .................................. + * .............text + * + * If one of the options is '<cr>', that is always shown last. + */ +static void +uty_cli_describe_show(vty_io vio, vector describe) +{ + unsigned int i, cmd_width, desc_width; + struct desc *desc, *desc_cr ; + + /* Get width of the longest "word" */ + cmd_width = 0; + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + unsigned int len; + + if (desc->cmd[0] == '\0') + continue; + + len = strlen (desc->cmd); + if (desc->cmd[0] == '.') + len--; + + if (cmd_width < len) + cmd_width = len; + } + + /* Set width of description string. */ + desc_width = vio->width - (cmd_width + 6); + + /* Print out description. */ + desc_cr = NULL ; /* put <cr> last if it appears */ + + for (i = 0; i < vector_active (describe); i++) + if ((desc = vector_slot (describe, i)) != NULL) + { + if (desc->cmd[0] == '\0') + continue; + + if (strcmp (desc->cmd, command_cr) == 0) + { + desc_cr = desc; + continue; + } + + uty_cli_describe_fold (vio, cmd_width, desc_width, desc); + } + + if (desc_cr != NULL) + uty_cli_describe_fold (vio, cmd_width, desc_width, desc_cr); +} ; + +/*------------------------------------------------------------------------------ + * Show one word and the description, folding the description as required. + */ +static void +uty_cli_describe_fold (vty_io vio, int cmd_width, + unsigned int desc_width, struct desc *desc) +{ + char *buf; + const char *cmd, *p; + int pos; + + VTY_ASSERT_LOCKED() ; + + cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd; + p = desc->str ; + + /* If have a sensible description width */ + if (desc_width > 20) + { + buf = XCALLOC (MTYPE_TMP, strlen (desc->str) + 1); + + while (strlen (p) > desc_width) + { + /* move back to first space */ + for (pos = desc_width; pos > 0; pos--) + if (*(p + pos) == ' ') + break; + + /* if did not find a space, break at width */ + if (pos == 0) + pos = desc_width ; + + strncpy (buf, p, pos); + buf[pos] = '\0'; + uty_cli_describe_line(vio, cmd_width, cmd, buf) ; + + cmd = ""; /* for 2nd and subsequent lines */ + + p += pos ; /* step past what just wrote */ + while (*p == ' ') + ++p ; /* skip spaces */ + } ; + + XFREE (MTYPE_TMP, buf); + } ; + + uty_cli_describe_line(vio, cmd_width, cmd, p) ; +} ; + +/*------------------------------------------------------------------------------ + * Show one description line. + */ +static void +uty_cli_describe_line(vty_io vio, int cmd_width, const char* cmd, + const char* str) +{ + if (str != NULL) + uty_cli_out (vio, " %-*s %s", cmd_width, cmd, str) ; + else + uty_cli_out (vio, " %-s", cmd) ; + uty_cli_out_newline(vio) ; +} ; + +/*------------------------------------------------------------------------------ + * Prepare "vline" token array for command handler. + * + * For "help" (command completion/description), if the command line is empty, + * or ends in ' ', adds an empty token to the end of the token array. + */ +static vector +uty_cli_cmd_prepare(vty_io vio, int help) +{ + vector vline ; + + vline = cmd_make_strvec(qs_term(&vio->cl)) ; + + /* Note that if there is a vector of tokens, then there is at least one + * token, so can guarantee that vio->cl.len >= 1 ! + */ + if (help) + if ((vline == NULL) || isspace(*qs_chars_at(&vio->cl, vio->cl.len - 1))) + vline = cmd_add_to_strvec(vline, "") ; + + return vline ; +} ; + +/*============================================================================== + * VTY telnet stuff + */ + +#define TELNET_OPTION_DEBUG 1 /* 0 to turn off */ + +static const char* telnet_commands[256] = +{ + [tn_IAC ] = "IAC", + [tn_DONT ] = "DONT", + [tn_DO ] = "DO", + [tn_WONT ] = "WONT", + [tn_WILL ] = "WILL", + [tn_SB ] = "SB", + [tn_GA ] = "GA", + [tn_EL ] = "EL", + [tn_EC ] = "EC", + [tn_AYT ] = "AYT", + [tn_AO ] = "AO", + [tn_IP ] = "IP", + [tn_BREAK] = "BREAK", + [tn_DM ] = "DM", + [tn_NOP ] = "NOP", + [tn_SE ] = "SE", + [tn_EOR ] = "EOR", + [tn_ABORT] = "ABORT", + [tn_SUSP ] = "SUSP", + [tn_EOF ] = "EOF", +} ; + +static const char* telnet_options[256] = +{ + [to_BINARY] = "BINARY", /* 8-bit data path */ + [to_ECHO] = "ECHO", /* echo */ + [to_RCP] = "RCP", /* prepare to reconnect */ + [to_SGA] = "SGA", /* suppress go ahead */ + [to_NAMS] = "NAMS", /* approximate message size */ + [to_STATUS] = "STATUS", /* give status */ + [to_TM] = "TM", /* timing mark */ + [to_RCTE] = "RCTE", /* remote controlled tx and echo */ + [to_NAOL] = "NAOL", /* neg. about output line width */ + [to_NAOP] = "NAOP", /* neg. about output page size */ + [to_NAOCRD] = "NAOCRD", /* neg. about CR disposition */ + [to_NAOHTS] = "NAOHTS", /* neg. about horizontal tabstops */ + [to_NAOHTD] = "NAOHTD", /* neg. about horizontal tab disp. */ + [to_NAOFFD] = "NAOFFD", /* neg. about formfeed disposition */ + [to_NAOVTS] = "NAOVTS", /* neg. about vertical tab stops */ + [to_NAOVTD] = "NAOVTD", /* neg. about vertical tab disp. */ + [to_NAOLFD] = "NAOLFD", /* neg. about output LF disposition */ + [to_XASCII] = "XASCII", /* extended ascii character set */ + [to_LOGOUT] = "LOGOUT", /* force logout */ + [to_BM] = "BM", /* byte macro */ + [to_DET] = "DET", /* data entry terminal */ + [to_SUPDUP] = "SUPDUP", /* supdup protocol */ + [to_SUPDUPOUTPUT] = "SUPDUPOUTPUT",/* supdup output */ + [to_SNDLOC] = "SNDLOC", /* send location */ + [to_TTYPE] = "TTYPE", /* terminal type */ + [to_EOR] = "EOR", /* end or record */ + [to_TUID] = "TUID", /* TACACS user identification */ + [to_OUTMRK] = "OUTMRK", /* output marking */ + [to_TTYLOC] = "TTYLOC", /* terminal location number */ + [to_3270REGIME] = "3270REGIME", /* 3270 regime */ + [to_X3PAD] = "X3PAD", /* X.3 PAD */ + [to_NAWS] = "NAWS", /* window size */ + [to_TSPEED] = "TSPEED", /* terminal speed */ + [to_LFLOW] = "LFLOW", /* remote flow control */ + [to_LINEMODE] = "LINEMODE", /* Linemode option */ + [to_XDISPLOC] = "XDISPLOC", /* X Display Location */ + [to_OLD_ENVIRON] = "OLD_ENVIRON", /* Old - Environment variables */ + [to_AUTHENTICATION] = "AUTHENTICATION", /* Authenticate */ + [to_ENCRYPT] = "ENCRYPT", /* Encryption option */ + [to_NEW_ENVIRON] = "NEW_ENVIRON", /* New - Environment variables */ + [to_EXOPL] = "EXOPL", /* extended-options-list */ +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as decimal. + */ +static void +uty_cli_out_dec(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "%d ", (int)u) ; +} ; + +/*------------------------------------------------------------------------------ + * For debug. Put string or value as hex. + */ +static void +uty_cli_out_hex(vty_io vio, const char* str, unsigned char u) +{ + if (str != NULL) + uty_cli_out(vio, "%s ", str) ; + else + uty_cli_out(vio, "0x%02x ", (unsigned)u) ; +} ; + +/*------------------------------------------------------------------------------ + * Send telnet: "WILL TELOPT_ECHO" + */ +extern void +uty_will_echo (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_ECHO }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "suppress Go-Ahead" + */ +extern void +uty_will_suppress_go_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_WILL, to_SGA }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use linemode" + */ +extern void +uty_dont_linemode (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LINEMODE }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "Use window size" + */ +extern void +uty_do_window_size (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DO, to_NAWS }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Send telnet: "don't use lflow" -- not currently used + */ +extern void +uty_dont_lflow_ahead (vty_io vio) +{ + unsigned char cmd[] = { tn_IAC, tn_DONT, to_LFLOW }; + VTY_ASSERT_LOCKED() ; + uty_cli_write (vio, (char*)cmd, (int)sizeof(cmd)); +} + +/*------------------------------------------------------------------------------ + * Process incoming Telnet Option(s) + * + * In particular: get telnet window size. + */ +static void +uty_telnet_command(vty_io vio, keystroke stroke) +{ + uint8_t* p ; + uint8_t o ; + int left ; + + /* Echo to the other end if required */ + if (TELNET_OPTION_DEBUG) + { + p = stroke->buf ; + left = stroke->len ; + + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_commands[*p], *p) ; + ++p ; + + if (left-- > 0) + uty_cli_out_dec(vio, telnet_options[*p], *p) ; + ++p ; + + if (left > 0) + { + while(left-- > 0) + uty_cli_out_hex(vio, NULL, *p++) ; + + if (stroke->flags & kf_truncated) + uty_cli_out(vio, "... ") ; + + if (!(stroke->flags & kf_broken)) + { + uty_cli_out_hex(vio, telnet_commands[tn_IAC], tn_IAC) ; + uty_cli_out_hex(vio, telnet_commands[tn_SE], tn_SE) ; + } + } ; + + if (!(stroke->flags & kf_broken)) + uty_cli_out (vio, "BROKEN") ; + + uty_cli_out (vio, "\r\n") ; + + } ; + + /* Process the telnet command */ + if (stroke->flags != 0) + return ; /* go no further if broken */ + + p = stroke->buf ; + left = stroke->len ; + + passert(left >= 1) ; /* must be if not broken ! */ + passert(stroke->value == *p) ; /* or something is wrong */ + + ++p ; /* step past X of IAC X */ + --left ; + + /* Decode the one command that is interesting -- "NAWS" */ + switch (stroke->value) + { + case tn_SB: + passert(left > 0) ; /* or parser failed */ + + o = *p++ ; /* the option byte */ + --left ; + switch(o) + { + case to_NAWS: + if (left != 4) + { + uzlog(NULL, LOG_WARNING, + "RFC 1073 violation detected: telnet NAWS option " + "should send %d characters, but we received %d", + (3 + 4 + 2), (3 + left + 2)) ; + } + else + { + vio->width = *p++ << 8 ; + vio->width += *p++ ; + vio->height = *p++ << 8 ; + vio->height += *p ; + + if (TELNET_OPTION_DEBUG) + uty_cli_out(vio, "TELNET NAWS window size received: " + "width %d, height %d%s", + vio->width, vio->height, telnet_newline) ; + } ; + break ; + + default: /* no other IAC SB <option> */ + break ; + } ; + break ; + + default: /* no other IAC X */ + break ; + } ; +} ; |