summaryrefslogtreecommitdiffstats
path: root/lib/vty_io.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r--lib/vty_io.c2202
1 files changed, 2202 insertions, 0 deletions
diff --git a/lib/vty_io.c b/lib/vty_io.c
new file mode 100644
index 00000000..15f90219
--- /dev/null
+++ b/lib/vty_io.c
@@ -0,0 +1,2202 @@
+/* VTY IO Functions
+ * 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 "vty.h"
+#include "vty_io.h"
+#include "vty_cli.h"
+#include "qstring.h"
+#include "keystroke.h"
+
+#include "memory.h"
+
+#include "prefix.h"
+#include "filter.h"
+#include "privs.h"
+#include "sockunion.h"
+#include "network.h"
+
+#include <arpa/telnet.h>
+#include <sys/un.h> /* for VTYSH */
+#include <sys/socket.h>
+
+#define VTYSH_DEBUG 0
+
+/*==============================================================================
+ * VTY Command Output -- base functions
+ *
+ * ALL vty command output ends up here.
+ *
+ * vty == NULL => vprintf(stdout, ...)
+ * VTY_SHELL => vprintf(stdout, ...)
+ * VTY_STDOUT => vprintf(stdout, ...)
+ *
+ * VTY_FILE => write(fd, ....)
+ *
+ * VTY_TERM => command FIFO
+ * VTY_SHELL_SERV => command FIFO
+ *
+ * During command processing the output sent here is held until the command
+ * completes.
+ */
+
+/*------------------------------------------------------------------------------
+ * VTY output function -- cf fprintf
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
+ */
+extern int
+uty_out (struct vty *vty, const char *format, ...)
+{
+ int result;
+ VTY_ASSERT_LOCKED() ;
+ va_list args;
+ va_start (args, format);
+ result = uty_vout(vty, format, args);
+ va_end (args);
+ return result;
+}
+
+/*------------------------------------------------------------------------------
+ * VTY output function -- cf vfprintf
+ *
+ * Returns: >= 0 => OK
+ * < 0 => failed (see errno)
+ */
+extern int
+uty_vout(struct vty *vty, const char *format, va_list args)
+{
+ enum vty_type type ;
+ vty_io vio ;
+ int len ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Establish type of vty -- if any */
+ if (vty != NULL)
+ {
+ vio = vty->vio ;
+ if (!vio->file.write_open)
+ return 0 ; /* discard output if not open ! */
+
+ type = vio->type ;
+ }
+ else
+ {
+ vio = NULL ;
+ type = VTY_STDOUT ;
+ } ;
+
+ /* Output -- process depends on type */
+ switch (type)
+ {
+ case VTY_STDOUT:
+ case VTY_SHELL:
+ len = vprintf (format, args);
+ break ;
+
+ case VTY_FILE:
+ case VTY_TERM:
+ case VTY_SHELL_SERV:
+
+ len = qs_vprintf(&vio->cmd_vbuf, format, args) ;
+ if (len > 0)
+ {
+ if (type == VTY_FILE)
+ len = write(vio->file.fd, vio->cmd_vbuf.body, len) ;
+ else
+ vio_fifo_put(&vio->cmd_obuf, vio->cmd_vbuf.body, len) ;
+ } ;
+ break ;
+
+ default:
+ zabort("impossible VTY type") ;
+ } ;
+
+ return len;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Discard the current contents of the command FIFO
+ *
+ * TODO: worry about line control ??
+ */
+extern void
+uty_out_discard(vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ vio_fifo_set_empty(&vio->cmd_obuf) ;
+} ;
+
+/*==============================================================================
+ * The watch dog.
+ *
+ * The watch dog starts up every now and checks:
+ *
+ * * for changes to the host name, which should be reflected in the
+ * prompt for any terminals.
+ *
+ * * the death watch list
+ */
+
+enum { vty_watch_dog_interval = 5 } ;
+
+static void vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when) ;
+static int vty_watch_dog_thread(struct thread *thread) ;
+
+/*------------------------------------------------------------------------------
+ * Watch dog action
+ */
+static void
+uty_watch_dog_bark(void)
+{
+ uty_check_host_name() ; /* check for host name change */
+
+ /* TODO: death watch scan */
+
+ /* Set timer to go off again later */
+ if (vty_cli_nexus)
+ qtimer_set(vty_watch_dog.qnexus, qt_add_monotonic(vty_watch_dog_interval),
+ vty_watch_dog_qnexus) ;
+ else
+ {
+ if (vty_watch_dog.thread != NULL)
+ thread_cancel (vty_watch_dog.thread);
+ vty_watch_dog.thread = thread_add_timer (vty_master,
+ vty_watch_dog_thread, NULL, vty_watch_dog_interval) ;
+ } ;
+} ;
+
+static void
+uty_watch_dog_start()
+{
+ if (vty_cli_nexus)
+ vty_watch_dog.qnexus = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ NULL, NULL) ;
+
+ uty_watch_dog_bark() ; /* start up by barking the first time */
+}
+
+extern void
+uty_watch_dog_stop(void)
+{
+ if (vty_watch_dog.anon != NULL)
+ {
+ if (vty_cli_nexus)
+ qtimer_free(vty_watch_dog.qnexus) ;
+ else
+ thread_cancel(vty_watch_dog.thread) ;
+ } ;
+}
+
+/*------------------------------------------------------------------------------
+ * qnexus watch dog action
+ */
+static void
+vty_watch_dog_qnexus(qtimer qtr, void* timer_info, qtime_t when)
+{
+ VTY_LOCK() ;
+ uty_watch_dog_bark() ;
+ VTY_UNLOCK() ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * thread watch dog action
+ */
+static int
+vty_watch_dog_thread(struct thread *thread)
+{
+ VTY_LOCK() ;
+ uty_watch_dog_bark() ;
+ VTY_UNLOCK() ;
+ return 0 ;
+} ;
+
+/*==============================================================================
+ * Prototypes.
+ */
+static void uty_file_init_new(vio_file file, int fd, void* info) ;
+static void uty_file_half_close(vio_file file) ;
+static void uty_file_close(vio_file file) ;
+
+static void vty_read_qnexus (qps_file qf, void* file_info) ;
+static void vty_write_qnexus (qps_file qf, void* file_info) ;
+static void vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when) ;
+
+static int vty_read_thread (struct thread *thread) ;
+static int vty_write_thread (struct thread *thread) ;
+static int vty_timer_thread (struct thread *thread) ;
+
+static void vtysh_read_qnexus (qps_file qf, void* file_info) ;
+static int vtysh_read_thread (struct thread *thread) ;
+
+/*==============================================================================
+ * Creation and destruction of VTY objects
+ */
+
+/*------------------------------------------------------------------------------
+ * Allocate new vty struct
+ *
+ * Allocates and initialises vty_io structure, complete with:
+ *
+ * Output buffer
+ * Input buffer
+ * qpselect file -- added to CLI nexus ) if running CLI nexus
+ * qtimer )
+ *
+ * Adds to the known vty's -- which locks/unlocks momentarily.
+ *
+ * Returns: new vty
+ */
+extern struct vty *
+uty_new (int fd, enum vty_type type)
+{
+ struct vty *vty ;
+ struct vty_io* vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ if (vty_watch_dog.anon == NULL)
+ uty_watch_dog_start() ;
+
+ vty = XCALLOC (MTYPE_VTY, sizeof (struct vty));
+ vio = XCALLOC (MTYPE_VTY, sizeof (struct vty_io)) ;
+
+ vty->vio = vio ;
+ vio->vty = vty ;
+
+ /* Zeroising the vty structure has set:
+ *
+ * node = 0 TODO: something better for node value ????
+ * buf = NULL -- no command line, yet
+ * index = NULL -- nothing, yet
+ * index_sub = NULL -- nothing, yet
+ */
+ confirm(AUTH_NODE == 0) ; /* default node type */
+
+ if (type == VTY_TERM)
+ vty->newline = "\r\n" ;
+ else
+ vty->newline = "\n" ;
+
+ /* Zeroising the vty_io structure has set:
+ *
+ * vio_list both pointers NULL
+ *
+ * half_closed = 0 -- NOT half closed (important !)
+ * timed_out = 0 -- NOT timed out
+ *
+ * mon_list both pointers NULL
+ *
+ * name = NULL -- no name, yet
+ *
+ * cli_drawn = 0 -- not drawn
+ * cli_prompt_len = 0 )
+ * cli_extra_len = 0 ) not material
+ * cli_echo_suppress = 0 )
+ *
+ * cli_prompt_node = 0 -- not material
+ * cli_prompt_set = 0 -- so prompt needs to be constructed
+ *
+ * cli_blocked = 0 -- not blocked
+ * cmd_in_progress = 0 -- no command in progress
+ *
+ * cli_do = 0 = cli_do_nothing
+ *
+ * cmd_wait_more = 0 -- not waiting for response to "--more--"
+ *
+ * fail = 0 -- no login failures yet
+ *
+ * hist = empty vector
+ * hp = 0 -- at the beginning
+ * hindex = 0 -- the beginning
+ *
+ * width = 0 -- unknown console width
+ * height = 0 -- unknown console height
+ *
+ * lines = 0 -- unset
+ *
+ * monitor = 0 -- not a monitor
+ *
+ * config = 0 -- not holder of "config" mode
+ */
+ confirm(cli_do_nothing == 0) ;
+
+ vio->type = type ;
+
+ uty_file_init_new(&vio->file, fd, vio) ;
+
+ vio->key_stream = keystroke_stream_new('\0') ; /* TODO: CSI ?? */
+
+ qs_init_new(&vio->ibuf, 0) ;
+
+ qs_init_new(&vio->cli_prompt_for_node, 0) ;
+
+ qs_init_new(&vio->cl, 0) ;
+ qs_init_new(&vio->clx, 0) ;
+
+ qs_init_new(&vio->cli_vbuf, 0) ;
+ vio_fifo_init_new(&vio->cli_obuf, 4 * 1024) ; /* allocate in 4K lumps */
+
+ qs_init_new(&vio->cmd_vbuf, 0) ;
+ vio_fifo_init_new(&vio->cmd_obuf, 16 * 1024) ;
+
+ /* Place on list of known vio/vty */
+ sdl_push(vio_list_base, vio, vio_list) ;
+
+ return vty;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create new vty of type VTY_TERM -- ie attached to a telnet session.
+ *
+ * Returns: new vty
+ */
+static struct vty *
+uty_new_term(int vty_sock, union sockunion *su)
+{
+ struct vty *vty ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Allocate new vty structure and set up default values. */
+ vty = uty_new (vty_sock, VTY_TERM) ;
+ vio = vty->vio ;
+
+ /* Set the action functions */
+ if (vty_cli_nexus)
+ {
+ vio->file.action.read.qnexus = vty_read_qnexus ;
+ vio->file.action.write.qnexus = vty_write_qnexus ;
+ vio->file.action.timer.qnexus = vty_timer_qnexus ;
+ }
+ else
+ {
+ vio->file.action.read.thread = vty_read_thread ;
+ vio->file.action.write.thread = vty_write_thread ;
+ vio->file.action.timer.thread = vty_timer_thread ;
+ } ;
+
+ /* The text form of the address identifies the VTY */
+ vio->name = sockunion_su2str (su, MTYPE_VTY_NAME);
+
+ /* Set the initial node */
+ if (no_password_check)
+ {
+ if (restricted_mode)
+ vty->node = RESTRICTED_NODE;
+ else if (host.advanced)
+ vty->node = ENABLE_NODE;
+ else
+ vty->node = VIEW_NODE;
+ }
+ else
+ vty->node = AUTH_NODE;
+
+ /* Pick up current timeout setting */
+ vio->file.v_timeout = vty_timeout_val;
+
+ /* Use global 'lines' setting, otherwise is unset */
+ if (host.lines >= 0)
+ vio->lines = host.lines;
+ else
+ vio->lines = -1;
+
+ /* Setting up terminal. */
+ uty_will_echo (vio);
+ uty_will_suppress_go_ahead (vio);
+ uty_dont_linemode (vio);
+ uty_do_window_size (vio);
+ if (0)
+ uty_dont_lflow_ahead (vio) ;
+
+ /* Set CLI into state waiting for output to complete. */
+ vio->cli_blocked = 1 ;
+ uty_file_set_write(&vio->file, on) ;
+
+ /* Reject connection if password isn't set, and not "no password" */
+ if ((host.password == NULL) && (host.password_encrypt == NULL)
+ && ! no_password_check)
+ {
+ uty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
+ uty_half_close (vio);
+ return NULL;
+ }
+
+ /* Say hello to the world. */
+ vty_hello (vty);
+
+ if (! no_password_check)
+ uty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE,
+ VTY_NEWLINE, VTY_NEWLINE);
+
+ return vty;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Create new vty of type VTY_SHELL_SERV -- ie attached to a vtysh session.
+ *
+ * Returns: new vty
+ */
+static struct vty *
+uty_new_shell_serv(int vty_sock)
+{
+ struct vty *vty ;
+ vty_io vio ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Allocate new vty structure and set up default values. */
+ vty = uty_new (vty_sock, VTY_SHELL_SERV) ;
+ vio = vty->vio ;
+
+ /* Set the action functions */
+ if (vty_cli_nexus)
+ {
+ vio->file.action.read.qnexus = vtysh_read_qnexus ;
+ vio->file.action.write.qnexus = vty_write_qnexus ;
+ vio->file.action.timer.qnexus = NULL ;
+ }
+ else
+ {
+ vio->file.action.read.thread = vtysh_read_thread ;
+ vio->file.action.write.thread = vty_write_thread ;
+ vio->file.action.timer.thread = NULL ;
+ } ;
+
+ vty->node = VIEW_NODE;
+
+ /* Enable the command output to clear the output to date, and set cli
+ * state to blocked waiting for that output to complete.
+ */
+ vio->cli_blocked = 1 ;
+ uty_file_set_write(&vio->file, on) ;
+
+ return vty;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set/Clear "monitor" state:
+ *
+ * set: if VTY_TERM and not already "monitor" (and write_open !)
+ * clear: if is "monitor"
+ */
+extern void
+uty_set_monitor(vty_io vio, bool on)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (on && !vio->monitor)
+ {
+ if ((vio->type == VTY_TERM) && vio->file.write_open)
+ {
+ vio->monitor = 1 ;
+ sdl_push(vio_monitors_base, vio, mon_list) ;
+ } ;
+ }
+ else if (!on && vio->monitor)
+ {
+ vio->monitor = 0 ;
+ sdl_del(vio_monitors_base, vio, mon_list) ;
+ }
+} ;
+
+/*------------------------------------------------------------------------------
+ * Return "name" of VTY
+ *
+ * For VTY_TERM this is the IP address of the far end of the telnet connection.
+ */
+extern const char*
+uty_get_name(vty_io vio)
+{
+ return (vio->name != NULL) ? vio->name : "?" ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Closing down VTY for reading.
+ *
+ * Shuts the read side and discards any buffered input.
+ *
+ * Leaves the output running, but places the VTY on "death watch". When
+ * all output completes and there is no queued command or anything else
+ * active, the VTY is finally put to sleep.
+ */
+extern void
+uty_half_close (vty_io vio)
+{
+ char* line ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->half_closed)
+ return ; /* cannot do it again */
+
+ vio->half_closed = 1 ;
+
+ uzlog (NULL, LOG_INFO, "Vty connection (fd %d) close", vio->file.fd) ;
+
+ uty_file_half_close(&vio->file) ;
+ uty_set_monitor(vio, 0) ;
+
+ keystroke_stream_free(vio->key_stream) ;
+ qs_free_body(&vio->ibuf) ;
+
+ uty_cli_wipe(vio) ;
+
+ while ((line = vector_ream_keep(&vio->hist)) != NULL)
+ XFREE(MTYPE_VTY_HIST, line) ;
+
+ /* Hit the width, height and lines so that all output clears without
+ * interruption.
+ */
+ vio->width = 0 ;
+ vio->height = 0 ;
+ vio->lines = 0 ;
+
+ /* Make sure no longer holding the config symbol of power */
+ uty_config_unlock(vio->vty, AUTH_NODE) ;
+
+ /* Move to the death watch list */
+ sdl_del(vio_list_base, vio, vio_list) ;
+ sdl_push(vio_death_watch, vio, vio_list) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Closing down VTY.
+ *
+ * Shuts down everything and discards all buffers etc. etc.
+ *
+ * If not already on it, places the VTY on "death watch". When there is no
+ * queued command or anything else active, the VTY is finally put to sleep.
+ */
+extern void
+uty_close (vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_file_close(&vio->file) ; /* bring the file to a complete stop */
+
+ uty_half_close(vio) ; /* deal with the input side, and place on
+ death watch -- if not already done */
+
+ qs_free_body(&vio->cli_prompt_for_node) ;
+ qs_free_body(&vio->cl) ;
+ qs_free_body(&vio->clx) ;
+ qs_free_body(&vio->cli_vbuf) ;
+ qs_free_body(&vio->cmd_vbuf) ;
+
+ vio_fifo_reset_keep(&vio->cli_obuf) ;
+ vio_fifo_reset_keep(&vio->cmd_obuf) ;
+
+ vio->vty->buf = NULL ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Closing down VTY completely.
+ *
+ * Shuts down everything and discards all buffers etc. etc.
+ *
+ * If not already on it, places the VTY on "death watch". When there is no
+ * queued command or anything else active, the VTY is finally put to sleep.
+ */
+extern void
+uty_full_close (vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ uty_file_close(&vio->file) ; /* bring the file to a complete stop */
+
+ uty_half_close(vio) ; /* deal with the input side, and place on
+ death watch -- if not already done */
+
+ qs_free_body(&vio->cli_prompt_for_node) ;
+ qs_free_body(&vio->cl) ;
+ qs_free_body(&vio->clx) ;
+ qs_free_body(&vio->cli_vbuf) ;
+ qs_free_body(&vio->cmd_vbuf) ;
+
+ vio_fifo_reset_keep(&vio->cli_obuf) ;
+ vio_fifo_reset_keep(&vio->cmd_obuf) ;
+
+ vio->vty->buf = NULL ;
+} ;
+
+/*==============================================================================
+ * Dealing with am I/O error on VTY
+ *
+ * If this is the first error for this VTY, produce suitable log message.
+ *
+ * If is a "monitor", turn that off, *before* issuing log message.
+ */
+static int
+uty_io_error(vty_io vio, const char* what)
+{
+ /* can no longer be a monitor ! */
+ uty_set_monitor(vio, 0) ;
+
+ /* if this is the first error, log it */
+ if (vio->file.error_seen == 0)
+ {
+ const char* type ;
+ switch (vio->type)
+ {
+ case VTY_TERM:
+ type = "VTY Terminal" ;
+ break ;
+ case VTY_SHELL_SERV:
+ type = "VTY Shell Server" ;
+ break ;
+ default:
+ zabort("unknown VTY type for uty_io_error()") ;
+ } ;
+
+ vio->file.error_seen = errno ;
+ uzlog(NULL, LOG_WARNING, "%s: %s failed on fd %d: %s",
+ type, what, vio->file.fd, safe_strerror(vio->file.error_seen)) ;
+ } ;
+
+ return -1 ;
+} ;
+
+/*==============================================================================
+ * vio_file level operations
+ */
+
+/*------------------------------------------------------------------------------
+ * Initialise a new vio_file structure.
+ *
+ * Requires that: the vio_file structure is not currently in use.
+ *
+ * if fd >= 0 then: file is open and ready read and write
+ * otherwise: file is not open
+ *
+ * there are no errors, yet.
+ *
+ * Sets timeout to no timeout at all -- timeout is optional.
+ */
+static void
+uty_file_init_new(vio_file file, int fd, void* info)
+{
+ memset(file, 0, sizeof(struct vio_file)) ;
+
+ /* Zeroising the structure has set:
+ *
+ * action = all the actions set NULL
+ *
+ * error_seen = 0 -- no error, yet
+ *
+ * qf = NULL -- no qfile, yet
+ * t_read = NULL ) no threads, yet
+ * t_write = NULL )
+ *
+ * v_timeout = 0 -- no timeout set
+ * timer_runing = 0 -- not running, yet
+ * t_timer = NULL -- no timer thread, yet
+ * qtr = NULL -- no qtimer, yet
+ */
+ file->fd = fd ;
+ file->info = info ;
+
+ file->read_open = (fd >= 0) ;
+ file->write_open = (fd >= 0) ;
+
+ if (vty_cli_nexus)
+ {
+ file->qf = qps_file_init_new(NULL, NULL);
+ if (fd >= 0)
+ qps_add_file(vty_cli_nexus->selection, file->qf, file->fd, file->info);
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Restart the timer.
+ *
+ * If a timeout time is set, then start or restart the timer with that value.
+ *
+ * If no timeout time is set, and the timer is running, unset it.
+ */
+static void
+uty_file_restart_timer(vio_file file)
+{
+ if (file->v_timeout != 0)
+ {
+ assert(file->action.timer.anon != NULL) ;
+
+ if (vty_cli_nexus)
+ {
+ if (file->qtr == NULL) /* allocate qtr if required */
+ file->qtr = qtimer_init_new(NULL, vty_cli_nexus->pile,
+ NULL, file->info) ;
+ qtimer_set(file->qtr, qt_add_monotonic(QTIME(file->v_timeout)),
+ file->action.timer.qnexus) ;
+ }
+ else
+ {
+ if (file->t_timer != NULL)
+ thread_cancel (file->t_timer);
+ file->t_timer = thread_add_timer (vty_master,
+ file->action.timer.thread, file->info, file->v_timeout) ;
+ } ;
+
+ file->timer_running = 1 ;
+ }
+ else if (file->timer_running)
+ {
+ if (vty_cli_nexus)
+ {
+ if (file->qtr != NULL)
+ qtimer_unset(file->qtr) ;
+ }
+ else
+ {
+ if (file->t_timer != NULL)
+ thread_cancel (file->t_timer) ;
+ } ;
+
+ file->timer_running = 0 ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set a new timer value.
+ */
+extern void
+uty_file_set_timer(vio_file file, unsigned long timeout)
+{
+ file->v_timeout = timeout ;
+ if (file->timer_running)
+ uty_file_restart_timer(file) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set read on/off -- restart timer.
+ */
+extern void
+uty_file_set_read(vio_file file, bool on)
+{
+ if (file->fd < 0)
+ return ;
+
+ if (on)
+ {
+ assert(file->action.read.anon != NULL) ;
+
+ if (vty_cli_nexus)
+ {
+ qps_enable_mode(file->qf, qps_read_mnum, file->action.read.qnexus) ;
+ }
+ else
+ {
+ if (file->t_read != NULL)
+ thread_cancel(file->t_read) ;
+
+ file->t_read = thread_add_read(vty_master,
+ file->action.read.thread, file->info, file->fd) ;
+ } ;
+ }
+ else
+ {
+ if (vty_cli_nexus)
+ {
+ qps_disable_modes(file->qf, qps_read_mbit) ;
+ }
+ else
+ {
+ if (file->t_read != NULL)
+ thread_cancel (file->t_read) ;
+ } ;
+ } ;
+
+ uty_file_restart_timer(file) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Set write on/off -- restart timer.
+ */
+extern void
+uty_file_set_write(vio_file file, bool on)
+{
+ if (file->fd < 0)
+ return ;
+
+ if (on)
+ {
+ assert(file->action.write.anon != NULL) ;
+
+ if (vty_cli_nexus)
+ {
+ qps_enable_mode(file->qf, qps_write_mnum, file->action.write.qnexus) ;
+ }
+ else
+ {
+ if (file->t_write != NULL)
+ thread_cancel(file->t_write) ;
+
+ file->t_write = thread_add_write(vty_master,
+ file->action.write.thread, file->info, file->fd) ;
+ } ;
+ }
+ else
+ {
+ if (vty_cli_nexus)
+ {
+ qps_disable_modes(file->qf, qps_write_mbit) ;
+ }
+ else
+ {
+ if (file->t_write != NULL)
+ thread_cancel (file->t_write) ;
+ } ;
+ } ;
+
+ uty_file_restart_timer(file) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close given vty file for reading.
+ *
+ * Sets timer to timeout for clearing any pending output.
+ */
+static void
+uty_file_half_close(vio_file file)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (file->fd >= 0)
+ {
+ shutdown(file->fd, SHUT_RD) ; /* actual half close */
+
+ file->v_timeout = 30 ; /* for output to clear */
+ uty_file_set_read(file, off) ;
+ } ;
+
+ file->read_open = 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close given vio_file, completely -- shut down any timer.
+ *
+ * Structure is cleared of everything except the last error !
+ */
+static void
+uty_file_close(vio_file file)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (file->fd >= 0)
+ close(file->fd) ;
+
+ if (vty_cli_nexus && (file->fd >= 0))
+ qps_remove_file(file->qf) ;
+
+ if (file->qf != NULL)
+ qps_file_free(file->qf) ;
+
+ if (file->t_read != NULL)
+ thread_cancel(file->t_write) ;
+ if (file->t_write != NULL)
+ thread_cancel(file->t_write) ;
+
+ file->fd = -1 ;
+ file->qf = NULL ;
+ file->t_read = NULL ;
+ file->t_write = NULL ;
+
+ file->info = NULL ;
+ file->action.read.anon = NULL ;
+ file->action.write.anon = NULL ;
+ file->action.timer.anon = NULL ;
+
+ file->read_open = 0 ;
+ file->write_open = 0 ;
+
+ if (file->qtr != NULL)
+ qtimer_free(file->qtr) ;
+ if (file->t_timer != NULL)
+ thread_cancel(file->t_timer) ;
+
+ file->v_timeout = 0 ;
+ file->qtr = NULL ;
+ file->t_timer = NULL ;
+} ;
+
+/*==============================================================================
+ * Reading from the VTY_TERM type file.
+ *
+ * The select/pselect call-back ends up in uty_read_ready().
+ *
+ * Note that uty_write_ready() also calls uty_read_ready, in order to kick the
+ * current CLI.
+ */
+
+/*------------------------------------------------------------------------------
+ * Ready to read -> kicking CLI
+ *
+ * Have two CLI: one (trivial one) when waiting on "--more--",
+ * and the standard one.
+ *
+ * End up here when there is something ready to be read.
+ *
+ * Also ends up here when was write_ready and did not block in uty_write.
+ *
+ * Will also end up here if an error has occurred, the other end has closed,
+ * this end has half closed, etc. This fact is used to kick the CLI even when
+ * there is no data to be read.
+ *
+ * Note that nothing is actually read here -- reading is done in the CLI itself,
+ * if required.
+ *
+ * The CLI decides whether to re-enable read, or enable write, or both.
+ */
+static void
+uty_read_ready(vty_io vio)
+{
+ uty_file_set_read(&vio->file, off) ; /* restarts timer */
+
+ /* Execute the required command processor */
+ if (vio->cmd_wait_more)
+ uty_cli_wait_more(vio) ; /* run "--more--" CLI */
+ else
+ uty_cli(vio) ; /* run standard CLI */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to read -> kicking CLI
+ */
+static void
+vty_read_qnexus(qps_file qf, void* file_info)
+{
+ vty_io vio = file_info;
+
+ VTY_LOCK() ;
+
+ assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ;
+
+ uty_read_ready(vio) ;
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- threads: ready to read -> kicking CLI
+ */
+static int
+vty_read_thread(struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ;
+
+ vio->file.t_read = NULL ; /* implicitly */
+ uty_read_ready(vio);
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Read a lump of bytes and shovel into the keystroke stream
+ *
+ * Steal keystroke if required -- see keystroke_input()
+ *
+ * Returns: 0 => nothing available
+ * > 0 => read at least one byte
+ * -1 => EOF (or not open, or failed)
+ */
+extern int
+uty_read (vty_io vio, keystroke steal)
+{
+ unsigned char buf[500] ;
+ int get ;
+
+ if (!vio->file.read_open)
+ return -1 ; /* at EOF if not open */
+
+ get = read_nb(vio->file.fd, buf, sizeof(buf)) ;
+ if (get > 0)
+ keystroke_input(vio->key_stream, buf, get, steal) ;
+ else if (get < 0)
+ {
+ if (get == -1)
+ uty_io_error(vio, "read") ;
+
+ vio->file.read_open = 0 ;
+ keystroke_input(vio->key_stream, NULL, 0, steal) ;
+
+ get = -1 ;
+ } ;
+
+ return get ;
+} ;
+
+/*==============================================================================
+ * The write file action for VTY_TERM type VTY
+ *
+ * There are two sets of buffering:
+ *
+ * cli -- command line -- which reflects the status of the command line
+ *
+ * cmd -- command output -- which is not written to the file while
+ * cmd_in_progress.
+ *
+ * The cli output takes precedence.
+ *
+ * Output of command stuff is subject to line_control, and may go through the
+ * "--more--" mechanism.
+ */
+
+static bool uty_write(vty_io vio) ;
+static int uty_flush_fifo(vty_io vio, vio_fifo vf,
+ struct vty_line_control* line_control) ;
+static void uty_empty_out_fifos(vty_io vio) ;
+
+/*------------------------------------------------------------------------------
+ * Flush as much as possible of what there is.
+ *
+ * May end up with:
+ *
+ * * something in the buffers waiting to go, but output is currently
+ * threatening to block.
+ *
+ * in this case will have set write on, and things will progress when next
+ * write_ready.
+ *
+ * * otherwise:
+ *
+ * will be set write off, so does a read_ready in order t kick the CLI,
+ * which may wish to set either read or write on.
+ */
+static void
+uty_write_ready(vty_io vio)
+{
+ bool blocked ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ uty_file_set_write(&vio->file, off) ; /* restarts timer, too */
+
+ blocked = uty_write(vio) ;
+
+ if (!blocked)
+ uty_read_ready(vio) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to write -> try to empty buffers
+ */
+static void
+vty_write_qnexus(qps_file qf, void* file_info)
+{
+ vty_io vio = file_info ;
+
+ VTY_LOCK() ;
+
+ assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ;
+
+ uty_write_ready(vio) ;
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- thread: ready to write -> try to empty buffers
+ */
+static int
+vty_write_thread(struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ;
+
+ vio->file.t_write = NULL; /* implicitly */
+ uty_write_ready(vio) ;
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Write as much as possible of what there is.
+ *
+ * If not cmd_in_progress, clears cli_blocked if both FIFOs are, or become,
+ * empty.
+ *
+ * Note that if !write_open, or becomes !write_open, then the FIFOs are empty
+ * and all output instantly successful.
+ *
+ * Sets write on if prevented from outputting everything available for output
+ * by write() threatening to block.
+ *
+ * Sets read on if enters cmd_wait_more state.
+ *
+ * Returns: true <=> blocked by I/O
+ *
+ * Note that this means that returns true iff sets write on.
+ */
+static bool
+uty_write(vty_io vio)
+{
+ int ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* empty the CLI FIFO
+ *
+ * NB: if the file is !write_open, or if it fails during output here
+ * and becomes !write_open, then ret == 0 -- as if everything
+ * has been written.
+ */
+ ret = uty_flush_fifo(vio, &vio->cli_obuf, NULL) ;
+ if (ret != 0)
+ return 1 ; /* blocked by I/O */
+
+ if ((vio->cmd_in_progress) || (vio->cmd_wait_more))
+ return 0 ; /* not blocked by I/O */
+
+ /* write from the command FIFO
+ *
+ * NB: if the file is !write_open, or if it fails during output here
+ * and becomes !write_open, then ret == 0 -- as if everything
+ * has been written.
+ */
+ ret = uty_flush_fifo(vio, &vio->cmd_obuf, &vio->line_control) ;
+ if (ret == 1)
+ return 1 ; /* blocked by I/O */
+
+ if (ret == 2)
+ {
+ /* Want now to wait for "--more--"
+ *
+ * Note that this produces CLI output, which must deal with here.
+ */
+ uty_cli_want_more(vio) ; /* NB: sets cmd_wait_more */
+
+ ret = uty_flush_fifo(vio, &vio->cli_obuf, NULL) ;
+ if (ret == 1)
+ return 1 ; /* blocked by I/O */
+
+ if (vio->file.write_open)
+ return 0 ; /* not blocked by I/O */
+ } ;
+
+ /* Reach here iff both CLI and command FIFOs are empty and is not
+ * cmd_in_progress
+ */
+ vio->cli_blocked = 0 ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Flush the given FIFO to output -- subject to possible line control.
+ *
+ * If ends up needing to write more, sets write on.
+ *
+ * Returns: 0 => written everything there is -- or not (now) write_open
+ * 1 => written everything that could -- needs to write more
+ * 2 => written everything that could -- needs a "--more--"
+ */
+static int
+uty_flush_fifo(vty_io vio, vio_fifo vf, struct vty_line_control* line_control)
+{
+ char* src ;
+ size_t have ;
+ int done ;
+ bool wait_more ;
+
+ if (!vio->file.write_open)
+ {
+ uty_empty_out_fifos(vio) ;
+ return 0 ;
+ } ;
+
+ wait_more = 0 ;
+
+ while ((src = vio_fifo_get_lump(vf, &have)) != NULL)
+ {
+ if (line_control != NULL) /* TODO: line control */
+ {
+ /* Account for what happens if output have bytes from src...
+ * ... and if necessary reduce "have".
+ *
+ * set wait_more if now need to wait
+ */
+ } ;
+
+ done = write_nb(vio->file.fd, src, have) ;
+
+ if (done < 0)
+ {
+ uty_io_error(vio, "write") ;
+
+ vio->file.write_open = 0 ;
+ uty_empty_out_fifos(vio) ;
+ return 0 ; /* no longer open */
+ }
+
+ vio_fifo_got_upto(vf, src + done) ;
+
+ if (done < (int)have)
+ {
+ if (line_control != NULL)
+ {
+ /* "put back" have - done bytes for next time */
+ } ;
+
+ uty_file_set_write(&vio->file, on) ;
+ return 1 ; /* output is full */
+ } ;
+
+ /* If now wants to wait for a "--more--", then exit
+ *
+ * Note that the line_control cannot tell if the place it wants to
+ * stop is, in fact, the end of the FIFO -- can only tell that
+ * now...
+ */
+ if (wait_more)
+ return vio_fifo_empty(vf) ? 0 : 2 ;
+ } ;
+
+ return 0 ; /* all gone */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Empty the output FIFOs
+ *
+ * This is for use when the output has failed or is closed.
+ */
+static void
+uty_empty_out_fifos(vty_io vio)
+{
+ vio_fifo_set_empty(&vio->cli_obuf) ;
+ vio_fifo_set_empty(&vio->cmd_obuf) ;
+
+ vio->cmd_wait_more = 0 ;
+} ;
+
+/*==============================================================================
+ * Timer for VTY_TERM (and VTY_SHELL_SERV).
+ */
+
+/*------------------------------------------------------------------------------
+ * Timer has expired.
+ *
+ * If half_closed, then this is curtains -- have waited long enough !
+ *
+ * Otherwise, half close the VTY and leave it to the death-watch to sweep up.
+ */
+static void
+uty_timer_expired (vty_io vio)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ if (vio->half_closed)
+ return uty_close(vio) ; /* curtains */
+
+ uty_half_close(vio) ; /* bring input side to a halt */
+
+ vio->timed_out = 1 ; /* why stopped */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: deal with timer timeout.
+ */
+static void
+vty_timer_qnexus (qtimer qtr, void* timer_info, qtime_t when)
+{
+ vty_io vio = timer_info ;
+
+ VTY_LOCK() ;
+
+ uty_timer_expired(vio);
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- thread: deal with timer timeout.
+ */
+static int
+vty_timer_thread (struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ vio->file.t_timer = NULL ; /* implicitly */
+
+ uty_timer_expired(vio) ;
+
+ VTY_UNLOCK() ;
+ return 0;
+}
+
+/*==============================================================================
+ * VTY Listener(s)
+ *
+ * Have listeners for VTY_TERM and VTY_SHELL_SERV types of VTY.
+ */
+
+typedef struct vty_listener* vty_listener ;
+
+struct vty_listener
+{
+ vty_listener next ; /* ssl type list */
+
+ enum vty_type type ;
+
+ struct vio_file file ;
+};
+
+/* List of listeners so can tidy up. */
+static vty_listener vty_listeners_list = NULL ;
+
+/* Prototypes for listener stuff */
+static int uty_serv_sock_addrinfo (const char *hostname, unsigned short port) ;
+static int uty_serv_sock(const char* addr, unsigned short port) ;
+static int uty_serv_sock_open(sa_family_t family, int type, int protocol,
+ struct sockaddr* sa, unsigned short port) ;
+static int uty_serv_vtysh(const char *path) ;
+static int vty_accept_thread(struct thread *thread) ;
+static void vty_accept_qnexus(qps_file qf, void* listener) ;
+static int uty_accept(vty_listener listener, int listen_sock) ;
+static int uty_accept_term(vty_listener listener) ;
+static int uty_accept_shell_serv (vty_listener listener) ;
+
+static void uty_serv_start_listener(int fd, enum vty_type type) ;
+
+/*------------------------------------------------------------------------------
+ * If possible, will use getaddrinfo() to find all the things to listen on.
+ */
+
+#if defined(HAVE_IPV6) && !defined(NRL)
+# define VTY_USE_ADDRINFO 1
+#else
+# define VTY_USE_ADDRINFO 0
+#endif
+
+/*------------------------------------------------------------------------------
+ * Open VTY listener(s)
+ *
+ * addr -- address ) to listen for VTY_TERM connections
+ * port -- port )
+ * path -- path for VTYSH connections -- if VTYSH_ENABLED
+ */
+extern void
+uty_open_listeners(const char *addr, unsigned short port, const char *path)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ /* If port is set to 0, do not listen on TCP/IP at all! */
+ if (port)
+ {
+ int n ;
+
+ if (VTY_USE_ADDRINFO)
+ n = uty_serv_sock_addrinfo(addr, port);
+ else
+ n = uty_serv_sock(addr, port);
+
+ if (n == 0)
+ uzlog(NULL, LOG_ERR, "could not open any VTY listeners") ;
+ }
+
+ /* If want to listen for vtysh, set up listener now */
+ if (VTYSH_ENABLED && (path != NULL))
+ uty_serv_vtysh(path) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Close VTY listener
+ *
+ * addr -- address ) to listen for VTY_TERM connections
+ * port -- port )
+ * path -- path for VTYSH connections -- if VTYSH_ENABLED
+ */
+extern void
+uty_close_listeners(void)
+{
+ vty_listener listener ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ while ((listener = ssl_pop(&listener, vty_listeners_list, next)) != NULL)
+ {
+ uty_file_close(&listener->file) ; /* no ceremony, no flowers */
+ XFREE(MTYPE_VTY, listener) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open listener(s) for VTY_TERM -- using getaddrinfo().
+ *
+ * Returns: number of listeners successfully opened.
+ */
+static int
+uty_serv_sock_addrinfo (const char *hostname, unsigned short port)
+{
+#if VTY_USE_ADDRINFO
+
+# ifndef HAVE_IPV6
+# error Using getaddrinfo() but HAVE_IPV6 is not defined ??
+# endif
+
+ int ret;
+ int n ;
+ struct addrinfo req;
+ struct addrinfo *ainfo;
+ struct addrinfo *ainfo_save;
+ char port_str[16];
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Want to listen, TCP-wise, on all available address families, on the
+ * given port.
+ */
+ memset (&req, 0, sizeof (struct addrinfo));
+ req.ai_flags = AI_PASSIVE;
+ req.ai_family = AF_UNSPEC;
+ req.ai_socktype = SOCK_STREAM;
+ snprintf(port_str, sizeof(port_str), "%d", port);
+
+ ret = getaddrinfo (hostname, port_str, &req, &ainfo);
+
+ if (ret != 0)
+ {
+ fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (ret));
+ exit (1);
+ }
+
+ /* Open up sockets on all AF_INET and AF_INET6 addresses */
+ ainfo_save = ainfo;
+
+ n = 0 ;
+ do
+ {
+ if ((ainfo->ai_family != AF_INET) && (ainfo->ai_family != AF_INET6))
+ continue;
+
+ assert(ainfo->ai_family == ainfo->ai_addr->sa_family) ;
+
+ ret = uty_serv_sock_open(ainfo->ai_family, ainfo->ai_socktype,
+ ainfo->ai_protocol, ainfo->ai_addr, port) ;
+ if (ret >= 0)
+ ++n ;
+ }
+ while ((ainfo = ainfo->ai_next) != NULL);
+
+ freeaddrinfo (ainfo_save);
+
+ return n ;
+
+#else
+ zabort("uty_serv_sock_addrinfo not implemented") ;
+#endif /* VTY_USE_ADDRINFO */
+}
+
+/*------------------------------------------------------------------------------
+ * Open listener(s) for VTY_TERM -- not using getaddrinfo() !
+ *
+ * Returns: number of listeners successfully opened.
+ */
+static int
+uty_serv_sock(const char* addr, unsigned short port)
+{
+ int ret;
+ int n ;
+ union sockunion su_addr ;
+ struct sockaddr* sa ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* If have an address, see what kind and whether valid */
+ sa = NULL ;
+
+ if (addr != NULL)
+ {
+ ret = str2sockunion (addr, &su_addr) ;
+ if (ret == 0)
+ sa = &su_addr.sa ;
+ else
+ uzlog(NULL, LOG_ERR, "bad address %s, cannot listen for VTY", addr);
+ } ;
+
+ /* Try for AF_INET */
+ ret = uty_serv_sock_open(AF_INET, SOCK_STREAM, 0, sa, port) ;
+ if (ret >= 0)
+ ++n ; /* opened socket */
+ if (ret == 1)
+ sa = NULL ; /* used the address */
+
+#if HAVE_IPV6
+ /* Try for AF_INET6 */
+ ret = uty_serv_sock_open(AF_INET6, SOCK_STREAM, 0, sa, port) ;
+ if (ret >= 0)
+ ++n ; /* opened socket */
+ if (ret == 1)
+ sa = NULL ; /* used the address */
+#endif
+
+ /* If not used the address... something wrong */
+ if (sa != NULL)
+ uzlog(NULL, LOG_ERR, "could not use address %s, to listen for VTY", addr);
+
+ /* Done */
+ return n ;
+}
+
+/*------------------------------------------------------------------------------
+ * Open a VTY_TERM listener socket.
+ *
+ * The sockaddr 'sa' may be NULL or of a different address family, in which
+ * case "any" address is used.
+ *
+ * If the sockaddr 'sa' is used, only the address portion is used.
+ *
+ * Returns: < 0 => failed
+ * == 0 => OK -- did not use the sockaddr 'sa'.
+ * > 1 => OK -- and did use the sockaddr 'sa'
+ */
+static int
+uty_serv_sock_open(sa_family_t family, int type, int protocol,
+ struct sockaddr* sa, unsigned short port)
+{
+ union sockunion su ;
+ int sock ;
+ int ret ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* Is there an address and is it for this family ? */
+ if ((sa != NULL) || (sa->sa_family == family))
+ /* Set up sockunion containing required family and address */
+ sockunion_new_sockaddr(&su, sa) ;
+ else
+ {
+ /* no address or wrong family -- set up empty sockunion of
+ * required family */
+ sockunion_init_new(&su, family) ;
+ sa = NULL ;
+ } ;
+
+ /* Open the socket and set its properties */
+ sock = sockunion_socket(family, type, protocol) ;
+ if (sock < 0)
+ return -1 ;
+
+ ret = sockopt_reuseaddr (sock);
+
+ if (ret >= 0)
+ ret = sockopt_reuseport (sock);
+
+ if (ret >= 0)
+ ret = set_nonblocking(sock);
+
+ if (ret >= 0)
+ ret = sockunion_bind (sock, &su, port, sa) ;
+
+ if (ret >= 0)
+ ret = sockunion_listen (sock, 3);
+
+ if (ret < 0)
+ {
+ close (sock);
+ return -1 ;
+ }
+
+ /* Socket is open -- set VTY Term listener going */
+ uty_serv_start_listener(sock, VTY_TERM) ;
+
+ /* Return OK and signal whether used address or not */
+ return (sa != NULL) ? 1 : 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Open a VTY_SHEL_SERV listener socket (UNIX domain).
+ *
+ * Returns: < 0 => failed
+ * >= 0 => OK
+ */
+static int
+uty_serv_vtysh(const char *path)
+{
+ int ret;
+ int sock, sa_len, path_len ;
+ struct sockaddr_un sa_un ;
+ mode_t old_mask;
+ struct zprivs_ids_t ids;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* worry about the path length */
+ path_len = strlen(path) + 1 ;
+ if (path_len >= (int)sizeof(sa_un.sun_path))
+ {
+ uzlog(NULL, LOG_ERR, "path too long for unix stream socket: '%s'", path);
+ return -1 ;
+ } ;
+
+ /* First of all, unlink existing socket */
+ unlink (path);
+
+ /* Make UNIX domain socket. */
+ sock = socket (AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0)
+ {
+ uzlog(NULL, LOG_ERR, "Cannot create unix stream socket: %s",
+ safe_strerror(errno));
+ umask (old_mask);
+ return -1 ;
+ }
+
+ /* Bind to the required path */
+ memset (&sa_un, 0, sizeof(sa_un));
+ sa_un.sun_family = AF_UNIX;
+ strncpy (sa_un.sun_path, path, sizeof(sa_un.sun_path) - 1);
+
+ sa_len = SUN_LEN(&sa_un) ;
+
+#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
+ sa_un.sun_len = sa_len ;
+#endif
+
+ old_mask = umask (0007);
+
+ ret = bind (sock, (struct sockaddr *) &sa_un, sa_len) ;
+ if (ret < 0)
+ uzlog(NULL, LOG_ERR, "Cannot bind path %s: %s", path, safe_strerror(errno));
+
+ if (ret >= 0)
+ ret = set_nonblocking(sock);
+
+ if (ret >= 0)
+ {
+ ret = listen (sock, 5);
+ if (ret < 0)
+ uzlog(NULL, LOG_ERR, "listen(fd %d) failed: %s", sock,
+ safe_strerror(errno));
+ } ;
+
+ zprivs_get_ids(&ids);
+
+ if (ids.gid_vty > 0)
+ {
+ /* set group of socket */
+ if ( chown (path, -1, ids.gid_vty) )
+ {
+ uzlog (NULL, LOG_ERR, "uty_serv_vtysh: could chown socket, %s",
+ safe_strerror (errno) );
+ }
+ }
+
+ umask (old_mask);
+
+ /* Give up now if failed along the way */
+ if (ret < 0)
+ {
+ close (sock) ;
+ return -1 ;
+ } ;
+
+ /* Socket is open -- set VTY Term listener going */
+ uty_serv_start_listener(sock, VTY_SHELL_SERV) ;
+
+ return 0 ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Socket is open -- set a VTY listener going
+ *
+ * Note that the vyt_listener structure is passed to the accept action function.
+ */
+static void
+uty_serv_start_listener(int fd, enum vty_type type)
+{
+ vty_listener listener ;
+
+ listener = XCALLOC(MTYPE_VTY, sizeof (struct vty_listener));
+
+ ssl_push(vty_listeners_list, listener, next) ;
+ uty_file_init_new(&listener->file, fd, listener) ;
+
+ listener->type = type ;
+
+ if (vty_cli_nexus)
+ listener->file.action.read.qnexus = vty_accept_qnexus ;
+ else
+ listener->file.action.read.thread = vty_accept_thread ;
+
+ uty_file_set_read(&listener->file, on) ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action for the thread world -- create and dispatch VTY
+ */
+static int
+vty_accept_thread(struct thread *thread)
+{
+ vty_listener listener = THREAD_ARG(thread) ;
+ int result ;
+
+ VTY_LOCK() ;
+
+ result = uty_accept(listener, THREAD_FD(thread));
+
+ uty_file_set_read(&listener->file, on) ;
+
+ VTY_UNLOCK() ;
+ return result ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action for the qnexus world -- create and dispatch VTY
+ */
+static void
+vty_accept_qnexus(qps_file qf, void* listener)
+{
+ VTY_LOCK() ;
+
+ uty_accept(listener, qf->fd);
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_TERM or VTY_SHELL_SERV
+ */
+static int
+uty_accept(vty_listener listener, int listen_sock)
+{
+ VTY_ASSERT_LOCKED() ;
+
+ assert(listener->file.fd == listen_sock) ;
+
+ switch (listener->type)
+ {
+ case VTY_TERM:
+ return uty_accept_term(listener) ;
+
+ case VTY_SHELL_SERV:
+ return uty_accept_shell_serv(listener) ;
+
+ default:
+ zabort("unknown vty type") ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_TERM
+ */
+static int
+uty_accept_term(vty_listener listener)
+{
+ int sock;
+ union sockunion su;
+ int ret;
+ unsigned int on;
+ struct prefix *p ;
+ char buf[SU_ADDRSTRLEN] ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ /* We can handle IPv4 or IPv6 socket. */
+ sockunion_init_new(&su, 0) ;
+
+ sock = sockunion_accept (listener->file.fd, &su);
+
+ if (sock < 0)
+ {
+ if (sock == -1)
+ uzlog (NULL, LOG_WARNING, "can't accept vty socket : %s",
+ safe_strerror (errno));
+ return -1;
+ }
+
+ /* Really MUST have non-blocking */
+ ret = set_nonblocking(sock) ; /* issues WARNING if fails */
+ if (ret < 0)
+ {
+ close(sock) ;
+ return -1 ;
+ } ;
+
+ /* New socket is open... worry about access lists */
+ p = sockunion2hostprefix (&su);
+ ret = 0 ; /* so far, so good */
+
+ if ((p->family == AF_INET) && vty_accesslist_name)
+ {
+ /* VTY's accesslist apply. */
+ struct access_list* acl ;
+
+ if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) &&
+ (access_list_apply (acl, p) == FILTER_DENY))
+ ret = -1 ;
+ }
+
+#ifdef HAVE_IPV6
+ if ((p->family == AF_INET6) && vty_ipv6_accesslist_name)
+ {
+ /* VTY's ipv6 accesslist apply. */
+ struct access_list* acl ;
+
+ if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) &&
+ (access_list_apply (acl, p) == FILTER_DENY))
+ ret = -1 ;
+ }
+#endif /* HAVE_IPV6 */
+
+ prefix_free (p);
+
+ if (ret != 0)
+ {
+ uzlog (NULL, LOG_INFO, "Vty connection refused from %s",
+ sockunion2str (&su, buf, sizeof(buf)));
+ close (sock);
+ return 0;
+ } ;
+
+ /* Final options (optional) */
+ on = 1 ;
+ ret = setsockopt (sock, IPPROTO_TCP, TCP_NODELAY,
+ (void*)&on, sizeof (on));
+ if (ret < 0)
+ uzlog (NULL, LOG_INFO, "can't set sockopt to sock %d: %s",
+ (int)sock, safe_strerror (errno));
+
+ /* All set -- create the VTY_TERM */
+ uty_new_term(sock, &su);
+
+ /* Log new VTY */
+ uzlog (NULL, LOG_INFO, "Vty connection from %s (fd %d)",
+ sockunion2str (&su, buf, sizeof(buf)), sock);
+
+ return 0;
+}
+
+/*------------------------------------------------------------------------------
+ * Accept action -- create and dispatch VTY_SHELL_SERV
+ */
+static int
+uty_accept_shell_serv (vty_listener listener)
+{
+ int sock ;
+ int ret ;
+ int client_len ;
+ struct sockaddr_un client ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ client_len = sizeof(client);
+ memset (&client, 0, client_len);
+
+ sock = accept(listener->file.fd, (struct sockaddr *) &client,
+ (socklen_t *) &client_len) ;
+
+ if (sock < 0)
+ {
+ uzlog (NULL, LOG_WARNING, "can't accept vty shell socket : %s",
+ safe_strerror (errno));
+ return -1;
+ }
+
+ /* Really MUST have non-blocking */
+ ret = set_nonblocking(sock) ; /* issues WARNING if fails */
+ if (ret < 0)
+ {
+ close(sock) ;
+ return -1 ;
+ } ;
+
+ /* All set -- create the VTY_SHELL_SERV */
+ if (VTYSH_DEBUG)
+ printf ("VTY shell accept\n");
+
+ uty_new_shell_serv(sock) ;
+
+ /* Log new VTY */
+ uzlog (NULL, LOG_INFO, "Vty shell connection (fd %d)", sock);
+ return 0;
+}
+
+/*==============================================================================
+ * Reading from the VTY_SHELL_SERV type file.
+ *
+ * The select/pselect call-back ends up in utysh_read_ready().
+ */
+
+/*------------------------------------------------------------------------------
+ * Ready to read -> kicking the "SHELL_SERV CLI"
+ *
+ * End up here when there is something ready to be read.
+ *
+ * Will also end up here if an error has occurred, the other end has closed,
+ * this end has half closed, etc. This fact is used to kick the CLI even when
+ * there is no data to be read.
+ *
+ * Note that nothing is actually read here -- reading is done in the CLI itself,
+ * if required.
+ *
+ * The CLI decides whether to re-enable read, or enable write, or both.
+ */
+static void
+utysh_read_ready(vty_io vio)
+{
+ uty_file_set_read(&vio->file, off) ;
+
+ /* TODO: need minimal "CLI" for VTY_SHELL_SERV
+ * NB: when output from command is flushed out, must append the
+ * following four bytes: '\0' '\0' '\0' <ret>
+ * Where <ret> is the command return code.
+ */
+} ;
+
+/*------------------------------------------------------------------------------
+ * Callback -- qnexus: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static void
+vtysh_read_qnexus(qps_file qf, void* file_info)
+{
+ vty_io vio = file_info;
+
+ VTY_LOCK() ;
+
+ assert((vio->file.fd == qf->fd) && (vio == vio->file.info)) ;
+
+ utysh_read_ready(vio) ;
+
+ VTY_UNLOCK() ;
+}
+
+/*------------------------------------------------------------------------------
+ * Callback -- threads: ready to read -> kicking the "SHELL_SERV CLI"
+ */
+static int
+vtysh_read_thread(struct thread *thread)
+{
+ vty_io vio = THREAD_ARG (thread);
+
+ VTY_LOCK() ;
+
+ assert(vio->file.fd == THREAD_FD (thread) && (vio == vio->file.info)) ;
+
+ vio->file.t_read = NULL ; /* implicitly */
+ utysh_read_ready(vio);
+
+ VTY_UNLOCK() ;
+ return 0 ;
+}
+
+/*------------------------------------------------------------------------------
+ * Read a lump of bytes and shovel into the command line buffer
+ *
+ * Lines coming in are terminated by '\0'.
+ *
+ * Assumes that the incoming command line is empty or otherwise incomplete.
+ *
+ * Moves stuff from the "buf" qstring and appends to "cl" qstring, stopping
+ * when get '\0' or empties the "buf".
+ *
+ * When empties "buf", reads a lump from the file.
+ *
+ * Returns: 0 => command line is incomplete
+ * 1 => have a complete command line
+ * -1 => EOF (or not open, or failed)
+ */
+extern int
+utysh_read (vty_io vio, qstring cl, qstring buf)
+{
+ int get ;
+ char* cp ;
+ char* ep ;
+ size_t have ;
+
+ while (1)
+ {
+ /* process what there is in the buffer */
+ if (buf->len > buf->cp)
+ {
+ cp = qs_cp_char(buf) ;
+ ep = qs_ep_char(buf) ;
+ have = ep - cp ;
+
+ ep = memchr(cp, '\0', have) ;
+ if (ep != NULL)
+ have = ep - cp ; /* have upto, but excluding '\0' */
+
+ if (have > 0) /* take what have */
+ {
+ qs_insert(cl, cp, have) ;
+ cl->cp += have ;
+ buf->cp += have ;
+ } ;
+
+ if (ep != NULL) /* if found '\0' */
+ {
+ qs_term(cl) ; /* '\0' terminate */
+ ++buf->cp ; /* step past it */
+ return 1 ; /* have a complete line <<<<<<<<<<<<< */
+ }
+ } ;
+
+ /* buffer is empty -- try and get some more stuff */
+ assert(buf->len == buf->cp) ;
+
+ if (!vio->file.read_open)
+ return -1 ; /* at EOF if not open <<<<<<<<<<<<< */
+
+ qs_need(buf, 500) ; /* need a reasonable lump */
+ qs_set_empty(buf) ; /* set cp = len = 0 */
+
+ get = read_nb(vio->file.fd, buf->body, buf->size) ;
+ if (get > 0)
+ buf->len = get ;
+ else if (get == 0)
+ return 0 ; /* have an incomplete line <<<<<<<<<<<< */
+ else
+ {
+ if (get == -1)
+ uty_io_error(vio, "read") ;
+
+ vio->file.read_open = 0 ;
+
+ return -1 ; /* at EOF or failed <<<<<<<<<<<<< */
+ } ;
+ } ;
+} ;
+
+/*==============================================================================
+ * Output to vty which are set to "monitor".
+ *
+ * If there is something in the command FIFO and command is not in progress,
+ * then throw logging away -- console is busy dealing with the output from
+ * some command.
+ *
+ * Wipes the command line and flushes the output. If the CLI FIFO is now
+ * empty, add the logging line to it and flush. Enable read, so that the
+ * CLI will be reentered, and the command line restored in due course.
+ */
+
+/*------------------------------------------------------------------------------
+ * Output logging information to all vty which are set to "monitor".
+ */
+extern void
+uty_log(struct logline* ll, struct zlog *zl, int priority,
+ const char *format, va_list va)
+{
+ vty_io vio ;
+ vty_io next ;
+
+ VTY_ASSERT_LOCKED() ;
+
+ next = sdl_head(vio_monitors_base) ;
+
+ if (next == NULL)
+ return ; /* go no further if no "monitor" vtys */
+
+ /* Prepare line for output. */
+ uvzlog_line(ll, zl, priority, format, va, 1) ; /* with crlf */
+
+ /* write to all known "monitor" vty
+ *
+ * While writing to a given "monitor" the monitor flag is cleared. This
+ * means that if the write fails, and logs a message, then will recurse
+ * through here -- but won't log to the monitor that has failed.
+ *
+ * If one of the other monitors fails during this process, will recurse
+ * again, now with two monitors with their monitor flags cleared.
+ *
+ * Once the output (and any recursed output) has completed, then the
+ * monitor flag is restored -- but only if the vty is still write_open.
+ *
+ * A monitor that is not write_open at the end of this, is removed from the
+ * monitors list. The current vio *cannot* be the current vio at a higher
+ * level in any recursion stack, because... if anything higher up the stack
+ * will have their monitor flag cleared, and therefore have been stepped
+ * over at the current level.
+ */
+ while (next != NULL)
+ {
+ vio = next ;
+
+ if ( vio->monitor /* may be temporarily not a monitor */
+ && (vio->cmd_in_progress || vio_fifo_empty(&vio->cmd_obuf)) )
+ {
+ vio->monitor = 0 ; /* avoid recursion */
+
+ uty_cli_wipe(vio) ;
+ uty_write(vio) ;
+
+ if (vio_fifo_empty(&vio->cli_obuf) && vio->file.write_open)
+ {
+ vio_fifo_put(&vio->cli_obuf, ll->line, ll->len) ;
+ uty_write(vio) ;
+ } ;
+
+ uty_file_set_read(&vio->file, on) ;
+
+ /* It is possible that something failed, so that is no longer
+ * write_open, and should no longer be a monitor.
+ */
+ vio->monitor = vio->file.write_open ;
+ } ;
+
+ next = sdl_next(vio, mon_list) ;
+
+ /* take self off list if no onger a monitor */
+ if (!vio->monitor)
+ sdl_del(vio_monitors_base, vio, mon_list) ;
+ } ;
+} ;
+
+/*------------------------------------------------------------------------------
+ * Async-signal-safe version of vty_log for fixed strings.
+ *
+ * This is last gasp operation.
+ */
+void
+vty_log_fixed (const char *buf, size_t len)
+{
+ vty_io vio ;
+
+ /* Write to all known "monitor" vty
+ *
+ * Forget all the niceties -- about to die in any case.
+ */
+ vio = sdl_head(vio_monitors_base) ;
+ while (vio != NULL)
+ {
+ write(vio->file.fd, buf, len) ;
+ write(vio->file.fd, "\r\n", 2) ;
+
+ vio = sdl_next(vio, mon_list) ;
+ } ;
+} ;