summaryrefslogtreecommitdiffstats
path: root/lib/vty_cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vty_cli.c')
-rw-r--r--lib/vty_cli.c2308
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 ;
+ } ;
+} ;