diff options
author | Chris Hall <GMCH@hestia.halldom.com> | 2010-03-16 01:35:19 +0000 |
---|---|---|
committer | Chris Hall <GMCH@hestia.halldom.com> | 2010-03-16 01:35:19 +0000 |
commit | d87a9d74eab06082ea49313083ffa0aa41f666f9 (patch) | |
tree | 7c6f7ae0be39683b7c90ea298454ec28d49406cb /lib/vty_io.c | |
parent | 05fb7fd0421b395c089bb08dd0e0d78d3746b8cf (diff) | |
download | quagga-d87a9d74eab06082ea49313083ffa0aa41f666f9.tar.bz2 quagga-d87a9d74eab06082ea49313083ffa0aa41f666f9.tar.xz |
Major update
bgpd/bgp_advertise.c
bgpd/bgp_advertise.h
The adj_in and adj_out objects are now put on a list based
on the peer to whom the route belongs.
The adj_in and adj_out objects also now point to the bgp_node
which they are routes for.
This substantially reduces the work needed to shut down a peer.
bgpd/bgp_damp.c
Changes to adj_in and adj_out forced small change to macros
used in bgp_damp.c to manage its lists.
bgpd/bgp_debug.c
Replaced direct access to vty->node by the required vty_get_node().
bgpd/bgp_dump.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_engine.h
Modified the debug and trace functions.
bgpd/bgp_fsm.c
Make use of sockunion2str() consistent with common usage.
Improved some documentation.
bgpd/bgp_main.c
Use the newly extended qpn_add_hook_function() facility.
bgpd/bgp_mplsvpn.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_msg_read.c
Bug fix: correct handling of capability code length.
Improvement: better casting in calculation of message length.
bgpd/bgp_msg_write.c
Bug fix: correct byte ordering of bgp_id in open message.
bgpd/bgp_network.c
Bug fix: correct handling of incoming connections.
Takes advantage of improvements in sockunion.c.
bgpd/bgp_nexthop.c
Changes to the names of fields in bgp_info structures.
bgpd/bgp_open_state.c
Remove mistaken #include of memtypes.h
bgpd/bgp_packet.c
Improvements to handling of withdrawing routes for peers.
bgpd/bgp_peer.c
Tidying up the state of peers as they are enabled and disabled.
Improvements to handling of withdrawing routes for peers.
bgpd/bgp_peer.h
Adding list bases for lists of routes originated by the peer.
bgpd/bgp_peer_index.c
Bug fix: correct freeing of peer indexes.
bgpd/bgp_route.c
Implement lists of bgp_info based in the owning peer. Adjust
for name changes to bgp_info fields.
Reimplemented all the clearing functions to use the lists of
items that belong to the peer -- rather than searching route
tables for stuff to withdraw.
Changed work queue handling for added/changed routes, so
that queues run through existing items, rather than having
queues of auxiliary items -- lower memory overhead.
bgpd/bgp_route.h
Added fields to bgp_info to allow all bgp_info originated by
each peer to live on lists based in the peer. And changed the
name of existing fields to avoid confusion.
bgpd/bgp_routemap.c
Removing redundant code and fixing a memory leak.
bgpd/bgp_table.h
Based work queue for added/changed routes directly in the
table, rather than having auxiliary structures.
bgpd/bgp_vty.c
Use vty_get_node() and vty_set_node() rather than direct
access to the vty field.
bgpd/bgpd.c
Implement changes to route clearing.
bgpd/bgpd.h
Changes to work queue handling.
lib/buffer.c
Changes to allow embedded buffer structures.
lib/buffer.h
Moved struct buffer here so that could have embedded buffer
structurs.
lib/command.c
Substantial tidy up and document exercise.
Restructured the top level command processing and finding
of descriptions and command completion.
Removal of unpleasant messing around with the insides of
vector structures.
Movement of some command actions to vty.c.
Uses uty.h to pick up the "private" functions from vty.c
et al.
lib/command.h
Moved the "node" values to node_type.h, so that can use an
enum node_type in places where cannot include command.h.
lib/command_queue.c
Updated to cope with the called command changing the node
value.
Improved handling of revoked commands, so the the command
line handler does not get stuck waiting for a command to
complete which has been revoked !
lib/command_queue.h
Improved message format.
lib/if.c
Use vty_set_node().
lib/keychain.c
Use vty_set_node().
new lib/keystroke.c
new lib/keystroke.h
New code to implement a keystroke FIFO.
This moves some complexity out of the command handler.
The handling of mixtures of escapes and Telnet IACs is
tightened up.
It would be possible to extend this to, say, UTF-8.
Regularises the "stealing" of keystrokes for the "--more--"
output handling... which was a bit hit and miss.
new lib/list_util.c
new lib/list_util.h
New code to implement various forms of linked list,
where the list pointers are embedded in structures.
lib/log.c
Changed the handling of log messages, so that all types
of log output (except syslog) use the same message buffer
scheme, and the message is constructed once and once
only.
Changes to the handling of VTY_LOCK() etc.
Uses uty.h to pick up the "private" functions from vty.c
et al.
lib/log.h
Changes to the buffering of log messages.
new lib/mem_tracker.c
New code to track memory allocation/deallocation, for
debug purposes.
lib/memory.c
lib/memory.h
Updated to allow the use of the mem_tracker.
lib/memtypes.awk
Made the memtypes into a named enum MTYPE.
lib/memtypes.c
Various new memory types.
lib/mqueue.c
lib/mqueue.h
Add mqueue_finish function for close-down.
lib/network.c
lib/network.h
Added non-blocking read_nb() and write_nb().
new lib/node_type.h
As above.
lib/plist.c
Remove vty_puts() which wasn't a good idea.
lib/qlib_init.c
Added qps_init() to first stage and mqueue_finish to
finish.
lib/qpnexus.c
lib/qpnexus.h
More flexible hooks for in_thread_init and in_thread_final.
lib/qpselect.c
lib/qpselect.h
Added qps_start_up() to build the required maps once and
for all.
Added qdebug to control the debug checks and validation.
Improved validation and test functions.
new lib/qstring.c
new lib/qstring.h
New code for limited flexible string handling.
lib/qtimers.c
Added qdebug to control the debug checks and validation.
lib/routemap.c
Use vty_set_node().
lib/sockunion.c
lib/sockunion.h
Tidied up and regularised the handling of sin_len and
sin6_len.
Created common function for setting port into socket.
Created common function for initialisation/allocation of
new sockunion.
Reduced various functions by using common sub-functions.
Rationalised some code.
Added sockunion_listen() and sockunion_new_sockaddr().
Renamed sockunion_new() to sockunion_new_prefix().
Improved some logging messages.
Added documentation.
new lib/uty.h
Functions etc. used only by vty/command/log/vty_io and
vty_cli.
lib/vector.c
lib/vector.h
Added vector_t type.
Removed VECTOR_INDEX, vector_only_wrapper_free() and
vector_only_index_free() -- following improvement of
code in command.c.
Added vector_set_min_length(), vector_set_new_min_length()
and vector_length() functions.
new lib/vio_fifo.c
new lib/vio_fifo.h
New code to manage simple FIFO of indefinite length.
lib/vty.c
lib/vty.h
Reworked. Broken into vty.c, vty_io.c and vty_cli.c.
new lib/vty_cli.c
new lib/vty_cli.h
CLI handling parts of the vty family.
new lib/vty_io.c
new lib/vty_io.h
I/O parts of the vty family.
lib/workqueue.h
Introduced tyedefs for the various call-back entries.
new tests/test-list_util.c
Tests for the list-util stuff.
vtysh/vtysh.c
Small change to interface for cmd_execute_command()
Diffstat (limited to 'lib/vty_io.c')
-rw-r--r-- | lib/vty_io.c | 2202 |
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) ; + } ; +} ; |