libosmocore/src/vty/vty.c

1848 lines
40 KiB
C
Raw Normal View History

/*! \mainpage libosmovty Documentation
*
* \section sec_intro Introduction
* This library is a collection of common code used in various
* GSM related sub-projects inside the Osmocom family of projects. It
* has been imported/derived from the GNU Zebra project.
* \n\n
* libosmovty implements the interactive command-line on the VTY
* (Virtual TTY) as well as configuration file parsing.
* \n\n
* Please note that C language projects inside Osmocom are typically
* single-threaded event-loop state machine designs. As such,
* routines in libosmovty are not thread-safe. If you must use them in
* a multi-threaded context, you have to add your own locking.
*
* libosmovty is developed as part of the Osmocom (Open Source Mobile
* Communications) project, a community-based, collaborative development
* project to create Free and Open Source implementations of mobile
* communications systems. For more information about Osmocom, please
* see https://osmocom.org/
*
* \section sec_copyright Copyright and License
* Copyright © 1997-2007 - Kuninhiro Ishiguro\n
* Copyright © 2008-2012 - Harald Welte, Holger Freyther and contributors\n
* All rights reserved. \n\n
* The source code of libosmovty is licensed under the terms of the GNU
* General Public License as published by the Free Software Foundation;
* either version 2 of the License, or (at your option) any later
* version.\n
* See <http://www.gnu.org/licenses/> or COPYING included in the source
* code package istelf.\n
* The information detailed here is provided AS IS with NO WARRANTY OF
* ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE.
* \n\n
*
* \section sec_tracker Homepage + Issue Tracker
* libosmovty is distributed as part of libosmocore and shares its
* project page at http://osmocom.org/projects/libosmocore
*
* \section sec_contact Contact and Support
* Community-based support is available at the OpenBSC mailing list
* <http://lists.osmocom.org/mailman/listinfo/openbsc>\n
* Commercial support options available upon request from
* <http://sysmocom.de/>
*/
/* SPDX-License-Identifier: GPL-2.0+ */
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <termios.h>
#include <sys/utsname.h>
#include <sys/param.h>
#include <arpa/telnet.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/core/talloc.h>
#ifndef MAXPATHLEN
#define MAXPATHLEN 4096
#endif
/* \addtogroup vty
* @{
* \file vty.c */
#define SYSCONFDIR "/usr/local/etc"
/* our callback, located in telnet_interface.c */
void vty_event(enum event event, int sock, struct vty *vty);
extern struct host host;
/* Vector which store each vty structure. */
static vector vtyvec;
vector Vvty_serv_thread;
char *vty_cwd = NULL;
/* IP address passed to the 'line vty'/'bind' command.
* Setting the default as vty_bind_addr = "127.0.0.1" doesn't allow freeing, so
* use NULL and VTY_BIND_ADDR_DEFAULT instead. */
static const char *vty_bind_addr = NULL;
#define VTY_BIND_ADDR_DEFAULT "127.0.0.1"
/* Port the VTY should bind to. -1 means not configured */
static int vty_bind_port = -1;
/* Configure lock. */
static int vty_config;
static int password_check;
void *tall_vty_ctx;
static void vty_clear_buf(struct vty *vty)
{
memset(vty->buf, 0, vty->max);
}
/*! Allocate a new vty interface structure */
struct vty *vty_new(void)
{
struct vty *new = talloc_zero(tall_vty_ctx, struct vty);
if (!new)
goto out;
VTY: implicit node exit by de-indenting, not parent lookup Note: This will break users' config files if they do not use consistent indenting. (see below for a definition of "consistent".) When reading VTY commands from a file, use indenting as means to implicitly exit child nodes. Do not look for commands in the parent node implicitly. The VTY so far implies 'exit' commands if a VTY line cannot be parsed on the current node, but succeeds on the parent node. That is the mechanism by which our VTY config files do not need 'exit' at the end of each child node. We've hit problems with this in the following scenarios, which will show improved user experience after this patch: *) When both a parent and its child node have commands with identical names: cs7 instace 0 point-code 1.2.3 sccp-address osmo-msc point-code 0.0.1 If I put the parent's command below the child, it is still interpreted in the context of the child node: cs7 instace 0 sccp-address osmo-msc point-code 0.0.1 point-code 1.2.3 Though the indenting lets me assume I am setting the cs7 instance's global PC to 1.2.3, I'm actually overwriting osmo-msc's PC with 1.2.3 and discarding the 0.0.1. *) When a software change moves a VTY command from a child to a parent. Say 'timezone' moved from 'bts' to 'network' level: network timezone 1 2 Say a user still has an old config file with 'timezone' on the child level: network bts 0 timezone 1 2 trx 0 The user would expect an error message that 'timezone' is invalid on the 'bts' level. Instead, the VTY finds the parent node's 'timezone', steps out of 'bts' to the 'network' level, and instead says that the 'trx' command does not exist. Format: Consistent means that two adjacent indenting lines have the exact same indenting characters for the common length: Weird mix if you ask me, but correct and consistent: ROOT <space>PARENT <space><tab><space>CHILD <space><tab><space><tab><tab>GRANDCHILD <space><tab><space><tab><tab>GRANDCHILD2 <space>SIBLING Inconsistent: ROOT <space>PARENT <tab><space>CHILD <space><space><tab>GRANDCHILD <space><tab><tab>GRANDCHILD2 <tab>SIBLING Also, when going back to a parent level, the exact same indenting must be used as before in that node: Incorrect: ROOT <tab>PARENT <tab><tab><tab>CHILD <tab><tab>SIBLING As not really intended side effect, it is also permitted to indent the entire file starting from the root level. We could guard against it but there's no harm: Correct and consistent: <tab>ROOT <tab><tab>PARENT <tab><tab><tab><tab>CHILD <tab><tab>SIBLING Implementation: Track parent nodes state: whenever a command enters a child node, push a parent node onto an llist to remember the exact indentation characters used for that level. As soon as the first line on a child node is parsed, remember this new indentation (which must have a longer strlen() than its parent level) to apply to all remaining child siblings and grandchildren. If the amount of spaces that indent a following VTY command are less than this expected indentation, call vty_go_parent() until it matches up. At any level, if the common length of indentation characters mismatch, abort parsing in error. Transitions to child node are spread across VTY implementations and are hard to change. But transitions to the parent node are all handled by vty_go_parent(). By popping a parent from the list of parents in vty_go_parent(), we can also detect that a command has changed the node without changing the parent, hence it must have stepped into a child node, and we can push a parent frame. The behavior on the interactive telnet VTY remains unchanged. Change-Id: I24cbb3f6de111f2d31110c3c484c066f1153aac9
2017-09-07 01:08:06 +00:00
INIT_LLIST_HEAD(&new->parent_nodes);
new->obuf = buffer_new(new, 0); /* Use default buffer size. */
if (!new->obuf)
goto out_new;
new->buf = _talloc_zero(new, VTY_BUFSIZ, "vty_new->buf");
if (!new->buf)
goto out_obuf;
new->max = VTY_BUFSIZ;
return new;
out_obuf:
buffer_free(new->obuf);
out_new:
talloc_free(new);
new = NULL;
out:
return new;
}
/* Authentication of vty */
static void vty_auth(struct vty *vty)
{
char *passwd = NULL;
enum node_type next_node = 0;
int fail;
char *crypt(const char *, const char *);
switch (vty->node) {
case AUTH_NODE:
#ifdef VTY_CRYPT_PW
if (host.encrypt)
passwd = host.password_encrypt;
else
#endif
passwd = host.password;
if (host.advanced)
next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
else
next_node = VIEW_NODE;
break;
case AUTH_ENABLE_NODE:
#ifdef VTY_CRYPT_PW
if (host.encrypt)
passwd = host.enable_encrypt;
else
#endif
passwd = host.enable;
next_node = ENABLE_NODE;
break;
}
if (passwd) {
#ifdef VTY_CRYPT_PW
if (host.encrypt)
fail = strcmp(crypt(vty->buf, passwd), passwd);
else
#endif
fail = strcmp(vty->buf, passwd);
} else
fail = 1;
if (!fail) {
vty->fail = 0;
vty->node = next_node; /* Success ! */
} else {
vty->fail++;
if (vty->fail >= 3) {
if (vty->node == AUTH_NODE) {
vty_out(vty,
"%% Bad passwords, too many failures!%s",
VTY_NEWLINE);
vty->status = VTY_CLOSE;
} else {
/* AUTH_ENABLE_NODE */
vty->fail = 0;
vty_out(vty,
"%% Bad enable passwords, too many failures!%s",
VTY_NEWLINE);
vty->node = VIEW_NODE;
}
}
}
}
/*! Close a given vty interface. */
void vty_close(struct vty *vty)
{
int i;
/* VTY_CLOSED is handled by the telnet_interface */
vty_event(VTY_CLOSED, vty->fd, vty);
if (vty->obuf) {
/* Flush buffer. */
buffer_flush_all(vty->obuf, vty->fd);
/* Free input buffer. */
buffer_free(vty->obuf);
vty->obuf = NULL;
}
/* Free command history. */
for (i = 0; i < VTY_MAXHIST; i++)
if (vty->hist[i])
talloc_free(vty->hist[i]);
/* Unset vector. */
vector_unset(vtyvec, vty->fd);
/* Close socket. */
if (vty->fd > 0 && vty->fd != fileno(stderr)) {
close(vty->fd);
vty->fd = -1;
}
if (vty->buf) {
talloc_free(vty->buf);
vty->buf = NULL;
}
/* Check configure. */
vty_config_unlock(vty);
/* OK free vty. */
talloc_free(vty);
}
/*! Return if this VTY is a shell or not */
int vty_shell(struct vty *vty)
{
return vty->type == VTY_SHELL ? 1 : 0;
}
int vty_out_va(struct vty *vty, const char *format, va_list ap)
{
int len = 0;
int size = 1024;
char buf[1024];
char *p = NULL;
if (vty_shell(vty)) {
vprintf(format, ap);
} else {
va_list args;
/* Try to write to initial buffer. */
va_copy(args, ap);
len = vsnprintf(buf, sizeof buf, format, args);
va_end(args);
/* Initial buffer is not enough. */
if (len < 0 || len >= size) {
while (1) {
if (len > -1)
size = len + 1;
else
size = size * 2;
p = talloc_realloc_size(vty, p, size);
if (!p)
return -1;
va_copy(args, ap);
len = vsnprintf(p, size, format, args);
va_end(args);
if (len > -1 && len < size)
break;
}
}
/* When initial buffer is enough to store all output. */
if (!p)
p = buf;
/* Pointer p must point out buffer. */
buffer_put(vty->obuf, (unsigned char *) p, len);
/* If p is not different with buf, it is allocated buffer. */
if (p != buf)
talloc_free(p);
}
vty_event(VTY_WRITE, vty->fd, vty);
return len;
}
/*! VTY standard output function
* \param[in] vty VTY to which we should print
* \param[in] format variable-length format string
*/
int vty_out(struct vty *vty, const char *format, ...)
{
va_list args;
int rc;
va_start(args, format);
rc = vty_out_va(vty, format, args);
va_end(args);
return rc;
}
/*! print a newline on the given VTY */
int vty_out_newline(struct vty *vty)
{
const char *p = vty_newline(vty);
buffer_put(vty->obuf, p, strlen(p));
return 0;
}
/*! return the current index of a given VTY */
void *vty_current_index(struct vty *vty)
{
return vty->index;
}
/*! return the current node of a given VTY */
int vty_current_node(struct vty *vty)
{
return vty->node;
}
/*! Lock the configuration to a given VTY
* \param[in] vty VTY to which the config shall be locked
* \returns 1 on success, 0 on error
*
* This shall be used to make sure only one VTY at a given time has
* access to modify the configuration */
int vty_config_lock(struct vty *vty)
{
if (vty_config == 0) {
vty->config = 1;
vty_config = 1;
}
return vty->config;
}
/*! Unlock the configuration from a given VTY
* \param[in] vty VTY from which the configuration shall be unlocked
* \returns 0 in case of success
*/
int vty_config_unlock(struct vty *vty)
{
if (vty_config == 1 && vty->config == 1) {
vty->config = 0;
vty_config = 0;
}
return vty->config;
}
/* Say hello to vty interface. */
void vty_hello(struct vty *vty)
{
const char *app_name = "<unnamed>";
if (host.app_info->name)
app_name = host.app_info->name;
vty_out(vty, "Welcome to the %s VTY interface%s%s",
app_name, VTY_NEWLINE, VTY_NEWLINE);
if (host.app_info->copyright)
vty_out(vty, "%s", host.app_info->copyright);
if (host.motdfile) {
FILE *f;
char buf[4096];
f = fopen(host.motdfile, "r");
if (f) {
while (fgets(buf, sizeof(buf), f)) {
char *s;
/* work backwards to ignore trailling isspace() */
for (s = buf + strlen(buf);
(s > buf) && isspace(*(s - 1)); s--) ;
*s = '\0';
vty_out(vty, "%s%s", buf, VTY_NEWLINE);
}
fclose(f);
} else
vty_out(vty, "MOTD file not found%s", VTY_NEWLINE);
} else if (host.motd)
vty_out(vty, "%s", host.motd);
}
/* Put out prompt and wait input from user. */
static void vty_prompt(struct vty *vty)
{
struct utsname names;
const char *hostname;
if (vty->type == VTY_TERM) {
hostname = host.app_info->name;
if (!hostname) {
uname(&names);
hostname = names.nodename;
}
vty_out(vty, cmd_prompt(vty->node), hostname);
}
}
/* Command execution over the vty interface. */
static int vty_command(struct vty *vty)
{
int ret;
vector vline;
/* Split readline string up into the vector */
vline = cmd_make_strvec(vty->buf);
if (vline == NULL)
return CMD_SUCCESS;
ret = cmd_execute_command(vline, vty, NULL, 0);
if (ret != CMD_SUCCESS)
switch (ret) {
case CMD_WARNING:
if (vty->type == VTY_FILE)
vty_out(vty, "Warning...%s", VTY_NEWLINE);
break;
case CMD_ERR_AMBIGUOUS:
vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
break;
case CMD_ERR_NO_MATCH:
vty_out(vty, "%% Unknown command.%s", VTY_NEWLINE);
break;
case CMD_ERR_INCOMPLETE:
vty_out(vty, "%% Command incomplete.%s", VTY_NEWLINE);
break;
}
cmd_free_strvec(vline);
return ret;
}
static const char telnet_backward_char = 0x08;
static const char telnet_space_char = ' ';
/* Basic function to write buffer to vty. */
static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
{
if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
return;
/* Should we do buffering here ? And make vty_flush (vty) ? */
buffer_put(vty->obuf, buf, nbytes);
}
/* Ensure length of input buffer. Is buffer is short, double it. */
static void vty_ensure(struct vty *vty, int length)
{
if (vty->max <= length) {
vty->max *= 2;
vty->buf = talloc_realloc_size(vty, vty->buf, vty->max);
// FIXME: check return
}
}
/* Basic function to insert character into vty. */
static void vty_self_insert(struct vty *vty, char c)
{
int i;
int length;
vty_ensure(vty, vty->length + 1);
length = vty->length - vty->cp;
memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length);
vty->buf[vty->cp] = c;
vty_write(vty, &vty->buf[vty->cp], length + 1);
for (i = 0; i < length; i++)
vty_write(vty, &telnet_backward_char, 1);
vty->cp++;
vty->length++;
}
/* Self insert character 'c' in overwrite mode. */
static void vty_self_insert_overwrite(struct vty *vty, char c)
{
vty_ensure(vty, vty->length + 1);
vty->buf[vty->cp++] = c;
if (vty->cp > vty->length)
vty->length++;
if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE))
return;
vty_write(vty, &c, 1);
}
/* Insert a word into vty interface with overwrite mode. */
static void vty_insert_word_overwrite(struct vty *vty, char *str)
{
int len = strlen(str);
vty_write(vty, str, len);
strcpy(&vty->buf[vty->cp], str);
vty->cp += len;
vty->length = vty->cp;
}
/* Forward character. */
static void vty_forward_char(struct vty *vty)
{
if (vty->cp < vty->length) {
vty_write(vty, &vty->buf[vty->cp], 1);
vty->cp++;
}
}
/* Backward character. */
static void vty_backward_char(struct vty *vty)
{
if (vty->cp > 0) {
vty->cp--;
vty_write(vty, &telnet_backward_char, 1);
}
}
/* Move to the beginning of the line. */
static void vty_beginning_of_line(struct vty *vty)
{
while (vty->cp)
vty_backward_char(vty);
}
/* Move to the end of the line. */
static void vty_end_of_line(struct vty *vty)
{
while (vty->cp < vty->length)
vty_forward_char(vty);
}
/* Add current command line to the history buffer. */
static void vty_hist_add(struct vty *vty)
{
int index;
if (vty->length == 0)
return;
index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1;
/* Ignore the same string as previous one. */
if (vty->hist[index])
if (strcmp(vty->buf, vty->hist[index]) == 0) {
vty->hp = vty->hindex;
return;
}
/* Insert history entry. */
if (vty->hist[vty->hindex])
talloc_free(vty->hist[vty->hindex]);
vty->hist[vty->hindex] = talloc_strdup(vty, vty->buf);
/* History index rotation. */
vty->hindex++;
if (vty->hindex == VTY_MAXHIST)
vty->hindex = 0;
vty->hp = vty->hindex;
}
/* Get telnet window size. */
static int
vty_telnet_option (struct vty *vty, unsigned char *buf, int nbytes)
{
#ifdef TELNET_OPTION_DEBUG
int i;
for (i = 0; i < nbytes; i++)
{
switch (buf[i])
{
case IAC:
vty_out (vty, "IAC ");
break;
case WILL:
vty_out (vty, "WILL ");
break;
case WONT:
vty_out (vty, "WONT ");
break;
case DO:
vty_out (vty, "DO ");
break;
case DONT:
vty_out (vty, "DONT ");
break;
case SB:
vty_out (vty, "SB ");
break;
case SE:
vty_out (vty, "SE ");
break;
case TELOPT_ECHO:
vty_out (vty, "TELOPT_ECHO %s", VTY_NEWLINE);
break;
case TELOPT_SGA:
vty_out (vty, "TELOPT_SGA %s", VTY_NEWLINE);
break;
case TELOPT_NAWS:
vty_out (vty, "TELOPT_NAWS %s", VTY_NEWLINE);
break;
default:
vty_out (vty, "%x ", buf[i]);
break;
}
}
vty_out (vty, "%s", VTY_NEWLINE);
#endif /* TELNET_OPTION_DEBUG */
switch (buf[0])
{
case SB:
vty->sb_len = 0;
vty->iac_sb_in_progress = 1;
return 0;
break;
case SE:
{
if (!vty->iac_sb_in_progress)
return 0;
if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0'))
{
vty->iac_sb_in_progress = 0;
return 0;
}
switch (vty->sb_buf[0])
{
case TELOPT_NAWS:
if (vty->sb_len != TELNET_NAWS_SB_LEN)
vty_out(vty,"RFC 1073 violation detected: telnet NAWS option "
"should send %d characters, but we received %lu",
TELNET_NAWS_SB_LEN, (unsigned long)vty->sb_len);
else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
vty_out(vty, "Bug detected: sizeof(vty->sb_buf) %lu < %d, "
"too small to handle the telnet NAWS option",
(unsigned long)sizeof(vty->sb_buf), TELNET_NAWS_SB_LEN);
else
{
vty->width = ((vty->sb_buf[1] << 8)|vty->sb_buf[2]);
vty->height = ((vty->sb_buf[3] << 8)|vty->sb_buf[4]);
#ifdef TELNET_OPTION_DEBUG
vty_out(vty, "TELNET NAWS window size negotiation completed: "
"width %d, height %d%s",
vty->width, vty->height, VTY_NEWLINE);
#endif
}
break;
}
vty->iac_sb_in_progress = 0;
return 0;
break;
}
default:
break;
}
return 1;
}
/* Execute current command line. */
static int vty_execute(struct vty *vty)
{
int ret;
ret = CMD_SUCCESS;
switch (vty->node) {
case AUTH_NODE:
case AUTH_ENABLE_NODE:
vty_auth(vty);
break;
default:
ret = vty_command(vty);
if (vty->type == VTY_TERM)
vty_hist_add(vty);
break;
}
/* Clear command line buffer. */
vty->cp = vty->length = 0;
vty_clear_buf(vty);
if (vty->status != VTY_CLOSE)
vty_prompt(vty);
return ret;
}
/* Send WILL TELOPT_ECHO to remote server. */
static void
vty_will_echo (struct vty *vty)
{
unsigned char cmd[] = { IAC, WILL, TELOPT_ECHO, '\0' };
vty_out (vty, "%s", cmd);
}
/* Make suppress Go-Ahead telnet option. */
static void
vty_will_suppress_go_ahead (struct vty *vty)
{
unsigned char cmd[] = { IAC, WILL, TELOPT_SGA, '\0' };
vty_out (vty, "%s", cmd);
}
/* Make don't use linemode over telnet. */
static void
vty_dont_linemode (struct vty *vty)
{
unsigned char cmd[] = { IAC, DONT, TELOPT_LINEMODE, '\0' };
vty_out (vty, "%s", cmd);
}
/* Use window size. */
static void
vty_do_window_size (struct vty *vty)
{
unsigned char cmd[] = { IAC, DO, TELOPT_NAWS, '\0' };
vty_out (vty, "%s", cmd);
}
static void vty_kill_line_from_beginning(struct vty *);
static void vty_redraw_line(struct vty *);
/* Print command line history. This function is called from
vty_next_line and vty_previous_line. */
static void vty_history_print(struct vty *vty)
{
int length;
vty_kill_line_from_beginning(vty);
/* Get previous line from history buffer */
length = strlen(vty->hist[vty->hp]);
memcpy(vty->buf, vty->hist[vty->hp], length);
vty->cp = vty->length = length;
/* Redraw current line */
vty_redraw_line(vty);
}
/* Show next command line history. */
static void vty_next_line(struct vty *vty)
{
int try_index;
if (vty->hp == vty->hindex)
return;
/* Try is there history exist or not. */
try_index = vty->hp;
if (try_index == (VTY_MAXHIST - 1))
try_index = 0;
else
try_index++;
/* If there is not history return. */
if (vty->hist[try_index] == NULL)
return;
else
vty->hp = try_index;
vty_history_print(vty);
}
/* Show previous command line history. */
static void vty_previous_line(struct vty *vty)
{
int try_index;
try_index = vty->hp;
if (try_index == 0)
try_index = VTY_MAXHIST - 1;
else
try_index--;
if (vty->hist[try_index] == NULL)
return;
else
vty->hp = try_index;
vty_history_print(vty);
}
/* This function redraw all of the command line character. */
static void vty_redraw_line(struct vty *vty)
{
vty_write(vty, vty->buf, vty->length);
vty->cp = vty->length;
}
/* Forward word. */
static void vty_forward_word(struct vty *vty)
{
while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
vty_forward_char(vty);
while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
vty_forward_char(vty);
}
/* Backward word without skipping training space. */
static void vty_backward_pure_word(struct vty *vty)
{
while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
vty_backward_char(vty);
}
/* Backward word. */
static void vty_backward_word(struct vty *vty)
{
while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
vty_backward_char(vty);
while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
vty_backward_char(vty);
}
/* When '^D' is typed at the beginning of the line we move to the down
level. */
static void vty_down_level(struct vty *vty)
{
vty_out(vty, "%s", VTY_NEWLINE);
/* call the exit function of the specific node */
if (vty->node > CONFIG_NODE)
vty_go_parent(vty);
else
(*config_exit_cmd.func) (NULL, vty, 0, NULL);
vty_prompt(vty);
vty->cp = 0;
}
/* When '^Z' is received from vty, move down to the enable mode. */
static void vty_end_config(struct vty *vty)
{
vty_out(vty, "%s", VTY_NEWLINE);
/* FIXME: we need to call the exit function of the specific node
* in question, not this generic one that doesn't know all nodes */
switch (vty->node) {
case VIEW_NODE:
case ENABLE_NODE:
/* Nothing to do. */
break;
case CONFIG_NODE:
case VTY_NODE:
vty_config_unlock(vty);
vty->node = ENABLE_NODE;
break;
case CFG_LOG_NODE:
vty->node = CONFIG_NODE;
break;
default:
/* Unknown node, we have to ignore it. */
break;
}
vty_prompt(vty);
vty->cp = 0;
}
/* Delete a charcter at the current point. */
static void vty_delete_char(struct vty *vty)
{
int i;
int size;
if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
return;
if (vty->length == 0) {
vty_down_level(vty);
return;
}
if (vty->cp == vty->length)
return; /* completion need here? */
size = vty->length - vty->cp;
vty->length--;
memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1);
vty->buf[vty->length] = '\0';
vty_write(vty, &vty->buf[vty->cp], size - 1);
vty_write(vty, &telnet_space_char, 1);
for (i = 0; i < size; i++)
vty_write(vty, &telnet_backward_char, 1);
}
/* Delete a character before the point. */
static void vty_delete_backward_char(struct vty *vty)
{
if (vty->cp == 0)
return;
vty_backward_char(vty);
vty_delete_char(vty);
}
/* Kill rest of line from current point. */
static void vty_kill_line(struct vty *vty)
{
int i;
int size;
size = vty->length - vty->cp;
if (size == 0)
return;
for (i = 0; i < size; i++)
vty_write(vty, &telnet_space_char, 1);
for (i = 0; i < size; i++)
vty_write(vty, &telnet_backward_char, 1);
memset(&vty->buf[vty->cp], 0, size);
vty->length = vty->cp;
}
/* Kill line from the beginning. */
static void vty_kill_line_from_beginning(struct vty *vty)
{
vty_beginning_of_line(vty);
vty_kill_line(vty);
}
/* Delete a word before the point. */
static void vty_forward_kill_word(struct vty *vty)
{
while (vty->cp != vty->length && vty->buf[vty->cp] == ' ')
vty_delete_char(vty);
while (vty->cp != vty->length && vty->buf[vty->cp] != ' ')
vty_delete_char(vty);
}
/* Delete a word before the point. */
static void vty_backward_kill_word(struct vty *vty)
{
while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ')
vty_delete_backward_char(vty);
while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ')
vty_delete_backward_char(vty);
}
/* Transpose chars before or at the point. */
static void vty_transpose_chars(struct vty *vty)
{
char c1, c2;
/* If length is short or point is near by the beginning of line then
return. */
if (vty->length < 2 || vty->cp < 1)
return;
/* In case of point is located at the end of the line. */
if (vty->cp == vty->length) {
c1 = vty->buf[vty->cp - 1];
c2 = vty->buf[vty->cp - 2];
vty_backward_char(vty);
vty_backward_char(vty);
vty_self_insert_overwrite(vty, c1);
vty_self_insert_overwrite(vty, c2);
} else {
c1 = vty->buf[vty->cp];
c2 = vty->buf[vty->cp - 1];
vty_backward_char(vty);
vty_self_insert_overwrite(vty, c1);
vty_self_insert_overwrite(vty, c2);
}
}
/* Do completion at vty interface. */
static void vty_complete_command(struct vty *vty)
{
int i;
int ret;
char **matched = NULL;
vector vline;
if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
return;
vline = cmd_make_strvec(vty->buf);
if (vline == NULL)
return;
/* In case of 'help \t'. */
if (isspace((int)vty->buf[vty->length - 1]))
vector_set(vline, NULL);
matched = cmd_complete_command(vline, vty, &ret);
cmd_free_strvec(vline);
vty_out(vty, "%s", VTY_NEWLINE);
switch (ret) {
case CMD_ERR_AMBIGUOUS:
vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
break;
case CMD_ERR_NO_MATCH:
/* vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE); */
vty_prompt(vty);
vty_redraw_line(vty);
break;
case CMD_COMPLETE_FULL_MATCH:
vty_prompt(vty);
vty_redraw_line(vty);
vty_backward_pure_word(vty);
vty_insert_word_overwrite(vty, matched[0]);
vty_self_insert(vty, ' ');
talloc_free(matched[0]);
break;
case CMD_COMPLETE_MATCH:
vty_prompt(vty);
vty_redraw_line(vty);
vty_backward_pure_word(vty);
vty_insert_word_overwrite(vty, matched[0]);
talloc_free(matched[0]);
break;
case CMD_COMPLETE_LIST_MATCH:
for (i = 0; matched[i] != NULL; i++) {
if (i != 0 && ((i % 6) == 0))
vty_out(vty, "%s", VTY_NEWLINE);
vty_out(vty, "%-10s ", matched[i]);
talloc_free(matched[i]);
}
vty_out(vty, "%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
break;
case CMD_ERR_NOTHING_TODO:
vty_prompt(vty);
vty_redraw_line(vty);
break;
default:
break;
}
if (matched)
vector_only_index_free(matched);
}
static void
vty_describe_fold(struct vty *vty, int cmd_width,
unsigned int desc_width, struct desc *desc)
{
char *buf;
const char *cmd, *p;
int pos;
cmd = desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd;
if (desc_width <= 0) {
vty_out(vty, " %-*s %s%s", cmd_width, cmd, desc->str,
VTY_NEWLINE);
return;
}
buf = _talloc_zero(vty, strlen(desc->str) + 1, "describe_fold");
if (!buf)
return;
for (p = desc->str; strlen(p) > desc_width; p += pos + 1) {
for (pos = desc_width; pos > 0; pos--)
if (*(p + pos) == ' ')
break;
if (pos == 0)
break;
strncpy(buf, p, pos);
buf[pos] = '\0';
vty_out(vty, " %-*s %s%s", cmd_width, cmd, buf, VTY_NEWLINE);
cmd = "";
}
vty_out(vty, " %-*s %s%s", cmd_width, cmd, p, VTY_NEWLINE);
talloc_free(buf);
}
/* Describe matched command function. */
static void vty_describe_command(struct vty *vty)
{
int ret;
vector vline;
vector describe;
unsigned int i, width, desc_width;
struct desc *desc, *desc_cr = NULL;
vline = cmd_make_strvec(vty->buf);
/* In case of '> ?'. */
if (vline == NULL) {
vline = vector_init(1);
vector_set(vline, NULL);
} else if (isspace((int)vty->buf[vty->length - 1]))
vector_set(vline, NULL);
describe = cmd_describe_command(vline, vty, &ret);
vty_out(vty, "%s", VTY_NEWLINE);
/* Ambiguous error. */
switch (ret) {
case CMD_ERR_AMBIGUOUS:
cmd_free_strvec(vline);
vty_out(vty, "%% Ambiguous command.%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
return;
break;
case CMD_ERR_NO_MATCH:
cmd_free_strvec(vline);
vty_out(vty, "%% There is no matched command.%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
return;
break;
}
/* Get width of command string. */
width = 0;
for (i = 0; i < vector_active(describe); i++)
if ((desc = vector_slot(describe, i)) != NULL) {
unsigned int len;
if (desc->cmd[0] == '\0')
continue;
len = strlen(desc->cmd);
if (desc->cmd[0] == '.')
len--;
if (width < len)
width = len;
}
/* Get width of description string. */
desc_width = vty->width - (width + 6);
/* Print out description. */
for (i = 0; i < vector_active(describe); i++)
if ((desc = vector_slot(describe, i)) != NULL) {
if (desc->cmd[0] == '\0')
continue;
if (strcmp(desc->cmd, "<cr>") == 0) {
desc_cr = desc;
continue;
}
if (!desc->str)
vty_out(vty, " %-s%s",
desc->cmd[0] ==
'.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
vty_out(vty, " %-*s %s%s", width,
desc->cmd[0] ==
'.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
vty_describe_fold(vty, width, desc_width, desc);
#if 0
vty_out(vty, " %-*s %s%s", width
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str ? desc->str : "", VTY_NEWLINE);
#endif /* 0 */
}
if ((desc = desc_cr)) {
if (!desc->str)
vty_out(vty, " %-s%s",
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
vty_out(vty, " %-*s %s%s", width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
vty_describe_fold(vty, width, desc_width, desc);
}
cmd_free_strvec(vline);
vector_free(describe);
vty_prompt(vty);
vty_redraw_line(vty);
}
/* ^C stop current input and do not add command line to the history. */
static void vty_stop_input(struct vty *vty)
{
vty->cp = vty->length = 0;
vty_clear_buf(vty);
vty_out(vty, "%s", VTY_NEWLINE);
vty_prompt(vty);
/* Set history pointer to the latest one. */
vty->hp = vty->hindex;
}
#define CONTROL(X) ((X) - '@')
#define VTY_NORMAL 0
#define VTY_PRE_ESCAPE 1
#define VTY_ESCAPE 2
/* Escape character command map. */
static void vty_escape_map(unsigned char c, struct vty *vty)
{
switch (c) {
case ('A'):
vty_previous_line(vty);
break;
case ('B'):
vty_next_line(vty);
break;
case ('C'):
vty_forward_char(vty);
break;
case ('D'):
vty_backward_char(vty);
break;
default:
break;
}
/* Go back to normal mode. */
vty->escape = VTY_NORMAL;
}
/* Quit print out to the buffer. */
static void vty_buffer_reset(struct vty *vty)
{
buffer_reset(vty->obuf);
vty_prompt(vty);
vty_redraw_line(vty);
}
/*! Read data via vty socket. */
int vty_read(struct vty *vty)
{
int i;
int nbytes;
unsigned char buf[VTY_READ_BUFSIZ];
int vty_sock = vty->fd;
/* Read raw data from socket */
if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
if (nbytes < 0) {
if (ERRNO_IO_RETRY(errno)) {
vty_event(VTY_READ, vty_sock, vty);
return 0;
}
}
buffer_reset(vty->obuf);
vty->status = VTY_CLOSE;
}
for (i = 0; i < nbytes; i++) {
if (buf[i] == IAC) {
if (!vty->iac) {
vty->iac = 1;
continue;
} else {
vty->iac = 0;
}
}
if (vty->iac_sb_in_progress && !vty->iac) {
if (vty->sb_len < sizeof(vty->sb_buf))
vty->sb_buf[vty->sb_len] = buf[i];
vty->sb_len++;
continue;
}
if (vty->iac) {
/* In case of telnet command */
int ret = 0;
ret = vty_telnet_option(vty, buf + i, nbytes - i);
vty->iac = 0;
i += ret;
continue;
}
if (vty->status == VTY_MORE) {
switch (buf[i]) {
case CONTROL('C'):
case 'q':
case 'Q':
vty_buffer_reset(vty);
break;
#if 0 /* More line does not work for "show ip bgp". */
case '\n':
case '\r':
vty->status = VTY_MORELINE;
break;
#endif
default:
break;
}
continue;
}
/* Escape character. */
if (vty->escape == VTY_ESCAPE) {
vty_escape_map(buf[i], vty);
continue;
}
/* Pre-escape status. */
if (vty->escape == VTY_PRE_ESCAPE) {
switch (buf[i]) {
case '[':
vty->escape = VTY_ESCAPE;
break;
case 'b':
vty_backward_word(vty);
vty->escape = VTY_NORMAL;
break;
case 'f':
vty_forward_word(vty);
vty->escape = VTY_NORMAL;
break;
case 'd':
vty_forward_kill_word(vty);
vty->escape = VTY_NORMAL;
break;
case CONTROL('H'):
case 0x7f:
vty_backward_kill_word(vty);
vty->escape = VTY_NORMAL;
break;
default:
vty->escape = VTY_NORMAL;
break;
}
continue;
}
switch (buf[i]) {
case CONTROL('A'):
vty_beginning_of_line(vty);
break;
case CONTROL('B'):
vty_backward_char(vty);
break;
case CONTROL('C'):
vty_stop_input(vty);
break;
case CONTROL('D'):
vty_delete_char(vty);
break;
case CONTROL('E'):
vty_end_of_line(vty);
break;
case CONTROL('F'):
vty_forward_char(vty);
break;
case CONTROL('H'):
case 0x7f:
vty_delete_backward_char(vty);
break;
case CONTROL('K'):
vty_kill_line(vty);
break;
case CONTROL('N'):
vty_next_line(vty);
break;
case CONTROL('P'):
vty_previous_line(vty);
break;
case CONTROL('T'):
vty_transpose_chars(vty);
break;
case CONTROL('U'):
vty_kill_line_from_beginning(vty);
break;
case CONTROL('W'):
vty_backward_kill_word(vty);
break;
case CONTROL('Z'):
vty_end_config(vty);
break;
case '\n':
case '\r':
vty_out(vty, "%s", VTY_NEWLINE);
vty/vty.c: fix vty_read(): prevent further heap-buffer overrun After reading data from the socket, assigned to a given VTY, we need to '\0'-terminate the received string. Otherwise, further access to that string, stored in a heap buffer vty->buf, would lead to a heap overrun. == How to reproduce? $ python -c "print 'A' * 512" | telnet $HOST $PORT ==21264==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6190000211e0 at pc 0x000000435d2f bp 0x7ffc06c7add0 sp 0x7ffc06c7a578 READ of size 1025 at 0x6190000211e0 thread T0 #0 0x435d2e in __interceptor_strlen (/usr/local/bin/osmo-msc+0x435d2e) #1 0x7fb95bfa5624 in talloc_strdup (/usr/lib/x86_64-linux-gnu/libtalloc.so.2+0x6624) #2 0x7fb95c1be2bc in vty_hist_add /opt/osmocom/libosmocore/src/vty/vty.c:578 #3 0x7fb95c1be2bc in vty_execute /opt/osmocom/libosmocore/src/vty/vty.c:703 #4 0x7fb95c1be2bc in vty_read /opt/osmocom/libosmocore/src/vty/vty.c:1425 #5 0x7fb95c1bfd78 in client_data /opt/osmocom/libosmocore/src/vty/telnet_interface.c:157 #6 0x7fb95b90bd33 in osmo_fd_disp_fds /opt/osmocom/libosmocore/src/select.c:223 #7 0x7fb95b90bd33 in osmo_select_main /opt/osmocom/libosmocore/src/select.c:263 #8 0x5006cc in main /opt/osmocom/osmo-msc/src/osmo-msc/msc_main.c:723:3 #9 0x7fb959935f44 in __libc_start_main /build/eglibc-xkFqqE/eglibc-2.19/csu/libc-start.c:287 #10 0x4226fb in _start (/usr/local/bin/osmo-msc+0x4226fb) == Why exactly 512? Because the initial size of the heap buffer is 512 (see VTY_BUFSIZ). Later on it can be realloc()ated, so X > 512 should also work. Found using AddressSanitizer and Radamsa [1] fuzzer. [1] https://gitlab.com/akihe/radamsa Change-Id: I82f774ad18d0e555eb8f3590a519946d9c583c78
2019-07-27 16:57:12 +00:00
/* '\0'-terminate the command buffer */
vty->buf[vty->length] = '\0';
vty_execute(vty);
break;
case '\t':
vty_complete_command(vty);
break;
case '?':
if (vty->node == AUTH_NODE
|| vty->node == AUTH_ENABLE_NODE)
vty_self_insert(vty, buf[i]);
else
vty_describe_command(vty);
break;
case '\033':
if (i + 1 < nbytes && buf[i + 1] == '[') {
vty->escape = VTY_ESCAPE;
i++;
} else
vty->escape = VTY_PRE_ESCAPE;
break;
default:
if (buf[i] > 31 && buf[i] < 127)
vty_self_insert(vty, buf[i]);
break;
}
}
/* Check status. */
if (vty->status == VTY_CLOSE) {
vty_close(vty);
return -EBADF;
} else {
vty_event(VTY_WRITE, vty_sock, vty);
vty_event(VTY_READ, vty_sock, vty);
}
return 0;
}
/* Read up configuration file */
static int
vty_read_file(FILE *confp, void *priv)
{
int ret;
struct vty *vty;
vty = vty_new();
vty->type = VTY_FILE;
vty->node = CONFIG_NODE;
vty->priv = priv;
/* By default, write to stderr. Otherwise, during parsing of the logging
* configuration, all invocations to vty_out() would make the process
* write() to its own stdin (fd=0)! */
vty->fd = fileno(stderr);
ret = config_from_file(vty, confp);
if (ret != CMD_SUCCESS) {
switch (ret) {
case CMD_ERR_AMBIGUOUS:
fprintf(stderr, "Ambiguous command.\n");
break;
case CMD_ERR_NO_MATCH:
fprintf(stderr, "There is no such command.\n");
break;
VTY: implicit node exit by de-indenting, not parent lookup Note: This will break users' config files if they do not use consistent indenting. (see below for a definition of "consistent".) When reading VTY commands from a file, use indenting as means to implicitly exit child nodes. Do not look for commands in the parent node implicitly. The VTY so far implies 'exit' commands if a VTY line cannot be parsed on the current node, but succeeds on the parent node. That is the mechanism by which our VTY config files do not need 'exit' at the end of each child node. We've hit problems with this in the following scenarios, which will show improved user experience after this patch: *) When both a parent and its child node have commands with identical names: cs7 instace 0 point-code 1.2.3 sccp-address osmo-msc point-code 0.0.1 If I put the parent's command below the child, it is still interpreted in the context of the child node: cs7 instace 0 sccp-address osmo-msc point-code 0.0.1 point-code 1.2.3 Though the indenting lets me assume I am setting the cs7 instance's global PC to 1.2.3, I'm actually overwriting osmo-msc's PC with 1.2.3 and discarding the 0.0.1. *) When a software change moves a VTY command from a child to a parent. Say 'timezone' moved from 'bts' to 'network' level: network timezone 1 2 Say a user still has an old config file with 'timezone' on the child level: network bts 0 timezone 1 2 trx 0 The user would expect an error message that 'timezone' is invalid on the 'bts' level. Instead, the VTY finds the parent node's 'timezone', steps out of 'bts' to the 'network' level, and instead says that the 'trx' command does not exist. Format: Consistent means that two adjacent indenting lines have the exact same indenting characters for the common length: Weird mix if you ask me, but correct and consistent: ROOT <space>PARENT <space><tab><space>CHILD <space><tab><space><tab><tab>GRANDCHILD <space><tab><space><tab><tab>GRANDCHILD2 <space>SIBLING Inconsistent: ROOT <space>PARENT <tab><space>CHILD <space><space><tab>GRANDCHILD <space><tab><tab>GRANDCHILD2 <tab>SIBLING Also, when going back to a parent level, the exact same indenting must be used as before in that node: Incorrect: ROOT <tab>PARENT <tab><tab><tab>CHILD <tab><tab>SIBLING As not really intended side effect, it is also permitted to indent the entire file starting from the root level. We could guard against it but there's no harm: Correct and consistent: <tab>ROOT <tab><tab>PARENT <tab><tab><tab><tab>CHILD <tab><tab>SIBLING Implementation: Track parent nodes state: whenever a command enters a child node, push a parent node onto an llist to remember the exact indentation characters used for that level. As soon as the first line on a child node is parsed, remember this new indentation (which must have a longer strlen() than its parent level) to apply to all remaining child siblings and grandchildren. If the amount of spaces that indent a following VTY command are less than this expected indentation, call vty_go_parent() until it matches up. At any level, if the common length of indentation characters mismatch, abort parsing in error. Transitions to child node are spread across VTY implementations and are hard to change. But transitions to the parent node are all handled by vty_go_parent(). By popping a parent from the list of parents in vty_go_parent(), we can also detect that a command has changed the node without changing the parent, hence it must have stepped into a child node, and we can push a parent frame. The behavior on the interactive telnet VTY remains unchanged. Change-Id: I24cbb3f6de111f2d31110c3c484c066f1153aac9
2017-09-07 01:08:06 +00:00
case CMD_ERR_INVALID_INDENT:
fprintf(stderr,
"Inconsistent indentation -- leading whitespace must match adjacent lines, and\n"
"indentation must reflect child node levels. A mix of tabs and spaces is\n"
"allowed, but their sequence must not change within a child block.\n");
break;
}
fprintf(stderr, "Error occurred during reading the below "
"line:\n%s\n", vty->buf);
vty_close(vty);
return -EINVAL;
}
vty_close(vty);
return 0;
}
/*! Create new vty structure. */
struct vty *
vty_create (int vty_sock, void *priv)
{
struct vty *vty;
struct termios t = {};
tcgetattr(vty_sock, &t);
cfmakeraw(&t);
tcsetattr(vty_sock, TCSANOW, &t);
/* Allocate new vty structure and set up default values. */
vty = vty_new ();
vty->fd = vty_sock;
vty->priv = priv;
vty->type = VTY_TERM;
if (!password_check)
{
if (host.advanced)
vty->node = ENABLE_NODE;
else
vty->node = VIEW_NODE;
}
else
vty->node = AUTH_NODE;
vty->fail = 0;
vty->cp = 0;
vty_clear_buf (vty);
vty->length = 0;
memset (vty->hist, 0, sizeof (vty->hist));
vty->hp = 0;
vty->hindex = 0;
vector_set_index (vtyvec, vty_sock, vty);
vty->status = VTY_NORMAL;
if (host.lines >= 0)
vty->lines = host.lines;
else
vty->lines = -1;
if (password_check)
{
/* Vty is not available if password isn't set. */
if (host.password == NULL && host.password_encrypt == NULL)
{
vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
vty->status = VTY_CLOSE;
vty_close (vty);
return NULL;
}
}
/* Say hello to the world. */
vty_hello (vty);
if (password_check)
vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
/* Setting up terminal. */
vty_will_echo (vty);
vty_will_suppress_go_ahead (vty);
vty_dont_linemode (vty);
vty_do_window_size (vty);
/* vty_dont_lflow_ahead (vty); */
vty_prompt (vty);
/* Add read/write thread. */
vty_event (VTY_WRITE, vty_sock, vty);
vty_event (VTY_READ, vty_sock, vty);
return vty;
}
DEFUN(config_who, config_who_cmd, "who", "Display who is on vty\n")
{
unsigned int i;
struct vty *v;
for (i = 0; i < vector_active(vtyvec); i++)
if ((v = vector_slot(vtyvec, i)) != NULL)
vty_out(vty, "%svty[%d] %s",
v->config ? "*" : " ", i, VTY_NEWLINE);
return CMD_SUCCESS;
}
/* Move to vty configuration mode. */
DEFUN(line_vty,
line_vty_cmd,
"line vty", "Configure a terminal line\n" "Virtual terminal\n")
{
vty->node = VTY_NODE;
return CMD_SUCCESS;
}
/* vty login. */
DEFUN(vty_login, vty_login_cmd, "login", "Enable password checking\n")
{
password_check = 1;
return CMD_SUCCESS;
}
DEFUN(no_vty_login,
no_vty_login_cmd, "no login", NO_STR "Enable password checking\n")
{
password_check = 0;
return CMD_SUCCESS;
}
/* vty bind */
DEFUN(vty_bind, vty_bind_cmd, "bind A.B.C.D [<0-65535>]",
"Accept VTY telnet connections on local interface\n"
"Local interface IP address (default: " VTY_BIND_ADDR_DEFAULT ")\n"
"Local TCP port number\n")
{
talloc_free((void*)vty_bind_addr);
vty_bind_addr = talloc_strdup(tall_vty_ctx, argv[0]);
vty_bind_port = argc > 1 ? atoi(argv[1]) : -1;
return CMD_SUCCESS;
}
const char *vty_get_bind_addr(void)
{
if (!vty_bind_addr)
return VTY_BIND_ADDR_DEFAULT;
return vty_bind_addr;
}
int vty_get_bind_port(int default_port)
{
if (vty_bind_port >= 0)
return vty_bind_port;
return default_port;
}
DEFUN(service_advanced_vty,
service_advanced_vty_cmd,
"service advanced-vty",
"Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
{
host.advanced = 1;
return CMD_SUCCESS;
}
DEFUN(no_service_advanced_vty,
no_service_advanced_vty_cmd,
"no service advanced-vty",
NO_STR
"Set up miscellaneous service\n" "Enable advanced mode vty interface\n")
{
host.advanced = 0;
return CMD_SUCCESS;
}
DEFUN(terminal_monitor,
terminal_monitor_cmd,
"terminal monitor",
"Set terminal line parameters\n"
"Copy debug output to the current terminal line\n")
{
vty->monitor = 1;
return CMD_SUCCESS;
}
DEFUN(terminal_no_monitor,
terminal_no_monitor_cmd,
"terminal no monitor",
"Set terminal line parameters\n"
NO_STR "Copy debug output to the current terminal line\n")
{
vty->monitor = 0;
return CMD_SUCCESS;
}
DEFUN(show_history,
show_history_cmd,
"show history", SHOW_STR "Display the session command history\n")
{
int index;
for (index = vty->hindex + 1; index != vty->hindex;) {
if (index == VTY_MAXHIST) {
index = 0;
continue;
}
if (vty->hist[index] != NULL)
vty_out(vty, " %s%s", vty->hist[index], VTY_NEWLINE);
index++;
}
return CMD_SUCCESS;
}
/* Display current configuration. */
static int vty_config_write(struct vty *vty)
{
vty_out(vty, "line vty%s", VTY_NEWLINE);
/* login */
if (!password_check)
vty_out(vty, " no login%s", VTY_NEWLINE);
else
vty_out(vty, " login%s", VTY_NEWLINE);
/* bind */
if (vty_bind_addr && (strcmp(vty_bind_addr, VTY_BIND_ADDR_DEFAULT) != 0 || vty_bind_port >= 0)) {
if (vty_bind_port >= 0) {
vty_out(vty, " bind %s %d%s", vty_bind_addr,
vty_bind_port, VTY_NEWLINE);
} else {
vty_out(vty, " bind %s%s", vty_bind_addr, VTY_NEWLINE);
}
}
vty_out(vty, "!%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
struct cmd_node vty_node = {
VTY_NODE,
"%s(config-line)# ",
1,
};
/*! Reset all VTY status. */
void vty_reset(void)
{
unsigned int i;
struct vty *vty;
struct thread *vty_serv_thread;
for (i = 0; i < vector_active(vtyvec); i++)
if ((vty = vector_slot(vtyvec, i)) != NULL) {
buffer_reset(vty->obuf);
vty->status = VTY_CLOSE;
vty_close(vty);
}
for (i = 0; i < vector_active(Vvty_serv_thread); i++)
if ((vty_serv_thread =
vector_slot(Vvty_serv_thread, i)) != NULL) {
//thread_cancel (vty_serv_thread);
vector_slot(Vvty_serv_thread, i) = NULL;
close(i);
}
}
static void vty_save_cwd(void)
{
char cwd[MAXPATHLEN];
char *c ;
c = getcwd(cwd, MAXPATHLEN);
if (!c) {
if (chdir(SYSCONFDIR) != 0)
perror("chdir failed");
if (getcwd(cwd, MAXPATHLEN) == NULL)
perror("getcwd failed");
}
vty_cwd = _talloc_zero(tall_vty_ctx, strlen(cwd) + 1, "save_cwd");
strcpy(vty_cwd, cwd);
}
char *vty_get_cwd(void)
{
return vty_cwd;
}
int vty_shell_serv(struct vty *vty)
{
return vty->type == VTY_SHELL_SERV ? 1 : 0;
}
void vty_init_vtysh(void)
{
vtyvec = vector_init(VECTOR_MIN_SIZE);
}
/*! Initialize VTY layer
* \param[in] app_info application information
*/
/* Install vty's own commands like `who' command. */
void vty_init(struct vty_app_info *app_info)
{
tall_vty_ctx = talloc_named_const(NULL, 0, "vty");
tall_vty_vec_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_vector");
tall_vty_cmd_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_command");
cmd_init(1);
host.app_info = app_info;
/* For further configuration read, preserve current directory. */
vty_save_cwd();
vtyvec = vector_init(VECTOR_MIN_SIZE);
/* Install bgp top node. */
install_node(&vty_node, vty_config_write);
install_element_ve(&config_who_cmd);
install_element_ve(&show_history_cmd);
install_element(CONFIG_NODE, &line_vty_cmd);
install_element(CONFIG_NODE, &service_advanced_vty_cmd);
install_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
install_element(CONFIG_NODE, &show_history_cmd);
install_element(ENABLE_NODE, &terminal_monitor_cmd);
install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
install_element(VTY_NODE, &vty_login_cmd);
install_element(VTY_NODE, &no_vty_login_cmd);
install_element(VTY_NODE, &vty_bind_cmd);
}
/*! Read the configuration file using the VTY code
* \param[in] file_name file name of the configuration file
* \param[in] priv private data to be passed to \ref vty_read_file
*/
int vty_read_config_file(const char *file_name, void *priv)
{
FILE *cfile;
int rc;
cfile = fopen(file_name, "r");
if (!cfile)
return -ENOENT;
rc = vty_read_file(cfile, priv);
fclose(cfile);
host_config_set(file_name);
return rc;
}
/*! @} */