mirror of
https://git.proxmox.com/git/mirror_frr
synced 2025-04-29 18:10:23 +00:00

This feature adds an L3 & L2 VPN application that makes use of the VPN and Encap SAFIs. This code is currently used to support IETF NVO3 style operation. In NVO3 terminology it provides the Network Virtualization Authority (NVA) and the ability to import/export IP prefixes and MAC addresses from Network Virtualization Edges (NVEs). The code supports per-NVE tables. The NVE-NVA protocol used to communicate routing and Ethernet / Layer 2 (L2) forwarding information between NVAs and NVEs is referred to as the Remote Forwarder Protocol (RFP). OpenFlow is an example RFP. For general background on NVO3 and RFP concepts see [1]. For information on Openflow see [2]. RFPs are integrated with BGP via the RF API contained in the new "rfapi" BGP sub-directory. Currently, only a simple example RFP is included in Quagga. Developers may use this example as a starting point to integrate Quagga with an RFP of their choosing, e.g., OpenFlow. The RFAPI code also supports the ability import/export of routing information between VNC and customer edge routers (CEs) operating within a virtual network. Import/export may take place between BGP views or to the default zebera VRF. BGP, with IP VPNs and Tunnel Encapsulation, is used to distribute VPN information between NVAs. BGP based IP VPN support is defined in RFC4364, BGP/MPLS IP Virtual Private Networks (VPNs), and RFC4659, BGP-MPLS IP Virtual Private Network (VPN) Extension for IPv6 VPN . Use of both the Encapsulation Subsequent Address Family Identifier (SAFI) and the Tunnel Encapsulation Attribute, RFC5512, The BGP Encapsulation Subsequent Address Family Identifier (SAFI) and the BGP Tunnel Encapsulation Attribute, are supported. MAC address distribution does not follow any standard BGB encoding, although it was inspired by the early IETF EVPN concepts. The feature is conditionally compiled and disabled by default. Use the --enable-bgp-vnc configure option to enable. The majority of this code was authored by G. Paul Ziemba <paulz@labn.net>. [1] http://tools.ietf.org/html/draft-ietf-nvo3-nve-nva-cp-req [2] https://www.opennetworking.org/sdn-resources/technical-library Now includes changes needed to merge with cmaster-next.
3226 lines
71 KiB
C
3226 lines
71 KiB
C
/*
|
|
* Virtual terminal [aka TeletYpe] interface routine.
|
|
* Copyright (C) 1997, 98 Kunihiro Ishiguro
|
|
*
|
|
* 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 "linklist.h"
|
|
#include "thread.h"
|
|
#include "buffer.h"
|
|
#include <lib/version.h>
|
|
#include "command.h"
|
|
#include "sockunion.h"
|
|
#include "memory.h"
|
|
#include "str.h"
|
|
#include "log.h"
|
|
#include "prefix.h"
|
|
#include "filter.h"
|
|
#include "vty.h"
|
|
#include "privs.h"
|
|
#include "network.h"
|
|
|
|
#include <arpa/telnet.h>
|
|
#include <termios.h>
|
|
|
|
DEFINE_MTYPE_STATIC(LIB, VTY, "VTY")
|
|
DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer")
|
|
DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history")
|
|
|
|
/* Vty events */
|
|
enum event
|
|
{
|
|
VTY_SERV,
|
|
VTY_READ,
|
|
VTY_WRITE,
|
|
VTY_TIMEOUT_RESET,
|
|
#ifdef VTYSH
|
|
VTYSH_SERV,
|
|
VTYSH_READ,
|
|
VTYSH_WRITE
|
|
#endif /* VTYSH */
|
|
};
|
|
|
|
static void vty_event (enum event, int, struct vty *);
|
|
|
|
/* Extern host structure from command.c */
|
|
extern struct host host;
|
|
|
|
/* Vector which store each vty structure. */
|
|
static vector vtyvec;
|
|
|
|
/* Vty timeout value. */
|
|
static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT;
|
|
|
|
/* Vty access-class command */
|
|
static char *vty_accesslist_name = NULL;
|
|
|
|
/* Vty access-calss for IPv6. */
|
|
static char *vty_ipv6_accesslist_name = NULL;
|
|
|
|
/* VTY server thread. */
|
|
static vector Vvty_serv_thread;
|
|
|
|
/* Current directory. */
|
|
char *vty_cwd = NULL;
|
|
|
|
/* Configure lock. */
|
|
static int vty_config;
|
|
|
|
/* Login password check. */
|
|
static int no_password_check = 0;
|
|
|
|
/* Restrict unauthenticated logins? */
|
|
static const u_char restricted_mode_default = 0;
|
|
static u_char restricted_mode = 0;
|
|
|
|
/* Integrated configuration file path */
|
|
char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG;
|
|
|
|
static int do_log_commands = 0;
|
|
|
|
/* VTY standard output function. */
|
|
int
|
|
vty_out (struct vty *vty, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int len = 0;
|
|
int size = 1024;
|
|
char buf[1024];
|
|
char *p = NULL;
|
|
|
|
if (vty_shell (vty))
|
|
{
|
|
va_start (args, format);
|
|
vprintf (format, args);
|
|
va_end (args);
|
|
}
|
|
else
|
|
{
|
|
/* Try to write to initial buffer. */
|
|
va_start (args, format);
|
|
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 = XREALLOC (MTYPE_VTY_OUT_BUF, p, size);
|
|
if (! p)
|
|
return -1;
|
|
|
|
va_start (args, format);
|
|
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, (u_char *) p, len);
|
|
|
|
/* If p is not different with buf, it is allocated buffer. */
|
|
if (p != buf)
|
|
XFREE (MTYPE_VTY_OUT_BUF, p);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
vty_log_out (struct vty *vty, const char *level, const char *proto_str,
|
|
const char *format, struct timestamp_control *ctl, va_list va)
|
|
{
|
|
int ret;
|
|
int len;
|
|
char buf[1024];
|
|
|
|
if (!ctl->already_rendered)
|
|
{
|
|
ctl->len = quagga_timestamp(ctl->precision, ctl->buf, sizeof(ctl->buf));
|
|
ctl->already_rendered = 1;
|
|
}
|
|
if (ctl->len+1 >= sizeof(buf))
|
|
return -1;
|
|
memcpy(buf, ctl->buf, len = ctl->len);
|
|
buf[len++] = ' ';
|
|
buf[len] = '\0';
|
|
|
|
if (level)
|
|
ret = snprintf(buf+len, sizeof(buf)-len, "%s: %s: ", level, proto_str);
|
|
else
|
|
ret = snprintf(buf+len, sizeof(buf)-len, "%s: ", proto_str);
|
|
if ((ret < 0) || ((size_t)(len += ret) >= sizeof(buf)))
|
|
return -1;
|
|
|
|
if (((ret = vsnprintf(buf+len, sizeof(buf)-len, format, va)) < 0) ||
|
|
((size_t)((len += ret)+2) > sizeof(buf)))
|
|
return -1;
|
|
|
|
buf[len++] = '\r';
|
|
buf[len++] = '\n';
|
|
|
|
if (write(vty->wfd, buf, len) < 0)
|
|
{
|
|
if (ERRNO_IO_RETRY(errno))
|
|
/* Kernel buffer is full, probably too much debugging output, so just
|
|
drop the data and ignore. */
|
|
return -1;
|
|
/* Fatal I/O error. */
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
zlog_warn("%s: write failed to vty client fd %d, closing: %s",
|
|
__func__, vty->fd, safe_strerror(errno));
|
|
buffer_reset(vty->obuf);
|
|
/* cannot call vty_close, because a parent routine may still try
|
|
to access the vty struct */
|
|
vty->status = VTY_CLOSE;
|
|
shutdown(vty->fd, SHUT_RDWR);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Output current time to the vty. */
|
|
void
|
|
vty_time_print (struct vty *vty, int cr)
|
|
{
|
|
char buf[QUAGGA_TIMESTAMP_LEN];
|
|
|
|
if (quagga_timestamp(0, buf, sizeof(buf)) == 0)
|
|
{
|
|
zlog (NULL, LOG_INFO, "quagga_timestamp error");
|
|
return;
|
|
}
|
|
if (cr)
|
|
vty_out (vty, "%s\n", buf);
|
|
else
|
|
vty_out (vty, "%s ", buf);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Say hello to vty interface. */
|
|
void
|
|
vty_hello (struct vty *vty)
|
|
{
|
|
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 ((int)*(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.name;
|
|
if (!hostname)
|
|
{
|
|
uname (&names);
|
|
hostname = names.nodename;
|
|
}
|
|
vty_out (vty, cmd_prompt (vty->node), hostname);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
#if 0 /* Currently not used. */
|
|
/* Make don't use lflow vty interface. */
|
|
static void
|
|
vty_dont_lflow_ahead (struct vty *vty)
|
|
{
|
|
unsigned char cmd[] = { IAC, DONT, TELOPT_LFLOW, '\0' };
|
|
vty_out (vty, "%s", cmd);
|
|
}
|
|
#endif /* 0 */
|
|
|
|
/* Allocate new vty struct. */
|
|
struct vty *
|
|
vty_new ()
|
|
{
|
|
struct vty *new = XCALLOC (MTYPE_VTY, sizeof (struct vty));
|
|
|
|
new->obuf = buffer_new(0); /* Use default buffer size. */
|
|
new->buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ);
|
|
new->error_buf = XCALLOC (MTYPE_VTY, VTY_BUFSIZ);
|
|
new->max = VTY_BUFSIZ;
|
|
|
|
return new;
|
|
}
|
|
|
|
/* Authentication of vty */
|
|
static void
|
|
vty_auth (struct vty *vty, char *buf)
|
|
{
|
|
char *passwd = NULL;
|
|
enum node_type next_node = 0;
|
|
int fail;
|
|
char *crypt (const char *, const char *);
|
|
|
|
switch (vty->node)
|
|
{
|
|
case AUTH_NODE:
|
|
if (host.encrypt)
|
|
passwd = host.password_encrypt;
|
|
else
|
|
passwd = host.password;
|
|
if (host.advanced)
|
|
next_node = host.enable ? VIEW_NODE : ENABLE_NODE;
|
|
else
|
|
next_node = VIEW_NODE;
|
|
break;
|
|
case AUTH_ENABLE_NODE:
|
|
if (host.encrypt)
|
|
passwd = host.enable_encrypt;
|
|
else
|
|
passwd = host.enable;
|
|
next_node = ENABLE_NODE;
|
|
break;
|
|
}
|
|
|
|
if (passwd)
|
|
{
|
|
if (host.encrypt)
|
|
fail = strcmp (crypt(buf, passwd), passwd);
|
|
else
|
|
fail = strcmp (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 = restricted_mode ? RESTRICTED_NODE : VIEW_NODE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Command execution over the vty interface. */
|
|
static int
|
|
vty_command (struct vty *vty, char *buf)
|
|
{
|
|
int ret;
|
|
vector vline;
|
|
const char *protocolname;
|
|
char *cp = NULL;
|
|
|
|
/*
|
|
* Log non empty command lines
|
|
*/
|
|
if (do_log_commands)
|
|
cp = buf;
|
|
if (cp != NULL)
|
|
{
|
|
/* Skip white spaces. */
|
|
while (isspace ((int) *cp) && *cp != '\0')
|
|
cp++;
|
|
}
|
|
if (cp != NULL && *cp != '\0')
|
|
{
|
|
unsigned i;
|
|
char vty_str[VTY_BUFSIZ];
|
|
char prompt_str[VTY_BUFSIZ];
|
|
|
|
/* format the base vty info */
|
|
snprintf(vty_str, sizeof(vty_str), "vty[??]@%s", vty->address);
|
|
if (vty)
|
|
for (i = 0; i < vector_active (vtyvec); i++)
|
|
if (vty == vector_slot (vtyvec, i))
|
|
{
|
|
snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s",
|
|
i, vty->address);
|
|
break;
|
|
}
|
|
|
|
/* format the prompt */
|
|
snprintf(prompt_str, sizeof(prompt_str), cmd_prompt (vty->node), vty_str);
|
|
|
|
/* now log the command */
|
|
zlog(NULL, LOG_ERR, "%s%s", prompt_str, buf);
|
|
}
|
|
/* Split readline string up into the vector */
|
|
vline = cmd_make_strvec (buf);
|
|
|
|
if (vline == NULL)
|
|
return CMD_SUCCESS;
|
|
|
|
#ifdef CONSUMED_TIME_CHECK
|
|
{
|
|
RUSAGE_T before;
|
|
RUSAGE_T after;
|
|
unsigned long realtime, cputime;
|
|
|
|
GETRUSAGE(&before);
|
|
#endif /* CONSUMED_TIME_CHECK */
|
|
|
|
ret = cmd_execute_command (vline, vty, NULL, 0);
|
|
|
|
/* Get the name of the protocol if any */
|
|
if (zlog_default)
|
|
protocolname = zlog_proto_names[zlog_default->protocol];
|
|
else
|
|
protocolname = zlog_proto_names[ZLOG_NONE];
|
|
|
|
#ifdef CONSUMED_TIME_CHECK
|
|
GETRUSAGE(&after);
|
|
if ((realtime = thread_consumed_time(&after, &before, &cputime)) >
|
|
CONSUMED_TIME_CHECK)
|
|
/* Warn about CPU hog that must be fixed. */
|
|
zlog_warn("SLOW COMMAND: command took %lums (cpu time %lums): %s",
|
|
realtime/1000, cputime/1000, buf);
|
|
}
|
|
#endif /* CONSUMED_TIME_CHECK */
|
|
|
|
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, "%% [%s] Unknown command: %s%s", protocolname, buf, 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 = XREALLOC (MTYPE_VTY, vty->buf, vty->max);
|
|
vty->error_buf = XREALLOC (MTYPE_VTY, vty->error_buf, vty->max);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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);
|
|
(*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);
|
|
|
|
switch (vty->node)
|
|
{
|
|
case VIEW_NODE:
|
|
case ENABLE_NODE:
|
|
case RESTRICTED_NODE:
|
|
/* Nothing to do. */
|
|
break;
|
|
case CONFIG_NODE:
|
|
case INTERFACE_NODE:
|
|
case ZEBRA_NODE:
|
|
case RIP_NODE:
|
|
case RIPNG_NODE:
|
|
case BGP_NODE:
|
|
case BGP_VPNV4_NODE:
|
|
case BGP_VPNV6_NODE:
|
|
case BGP_ENCAP_NODE:
|
|
case BGP_ENCAPV6_NODE:
|
|
case BGP_VNC_DEFAULTS_NODE:
|
|
case BGP_VNC_NVE_GROUP_NODE:
|
|
case BGP_VNC_L2_GROUP_NODE:
|
|
case BGP_IPV4_NODE:
|
|
case BGP_IPV4M_NODE:
|
|
case BGP_IPV6_NODE:
|
|
case BGP_IPV6M_NODE:
|
|
case RMAP_NODE:
|
|
case OSPF_NODE:
|
|
case OSPF6_NODE:
|
|
case LDP_NODE:
|
|
case LDP_IPV4_NODE:
|
|
case LDP_IPV6_NODE:
|
|
case LDP_IPV4_IFACE_NODE:
|
|
case LDP_IPV6_IFACE_NODE:
|
|
case LDP_L2VPN_NODE:
|
|
case LDP_PSEUDOWIRE_NODE:
|
|
case ISIS_NODE:
|
|
case KEYCHAIN_NODE:
|
|
case KEYCHAIN_KEY_NODE:
|
|
case MASC_NODE:
|
|
case PIM_NODE:
|
|
case VTY_NODE:
|
|
vty_config_unlock (vty);
|
|
vty->node = ENABLE_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->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';
|
|
|
|
if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE)
|
|
return;
|
|
|
|
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_lib (vline, vty, &ret, 1);
|
|
|
|
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, ' ');
|
|
XFREE (MTYPE_TMP, 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]);
|
|
XFREE (MTYPE_TMP, matched[0]);
|
|
vector_only_index_free (matched);
|
|
return;
|
|
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]);
|
|
XFREE (MTYPE_TMP, 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 cmd_token *token)
|
|
{
|
|
char *buf;
|
|
const char *cmd, *p;
|
|
int pos;
|
|
|
|
cmd = token->cmd[0] == '.' ? token->cmd + 1 : token->cmd;
|
|
|
|
if (desc_width <= 0)
|
|
{
|
|
vty_out (vty, " %-*s %s%s", cmd_width, cmd, token->desc, VTY_NEWLINE);
|
|
return;
|
|
}
|
|
|
|
buf = XCALLOC (MTYPE_TMP, strlen (token->desc) + 1);
|
|
|
|
for (p = token->desc; 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);
|
|
|
|
XFREE (MTYPE_TMP, 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 cmd_token *token, *token_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:
|
|
vty_out (vty, "%% Ambiguous command.%s", VTY_NEWLINE);
|
|
goto out;
|
|
break;
|
|
case CMD_ERR_NO_MATCH:
|
|
vty_out (vty, "%% There is no matched command.%s", VTY_NEWLINE);
|
|
goto out;
|
|
break;
|
|
}
|
|
|
|
/* Get width of command string. */
|
|
width = 0;
|
|
for (i = 0; i < vector_active (describe); i++)
|
|
if ((token = vector_slot (describe, i)) != NULL)
|
|
{
|
|
unsigned int len;
|
|
|
|
if (token->cmd[0] == '\0')
|
|
continue;
|
|
|
|
len = strlen (token->cmd);
|
|
if (token->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 ((token = vector_slot (describe, i)) != NULL)
|
|
{
|
|
if (token->cmd[0] == '\0')
|
|
continue;
|
|
|
|
if (strcmp (token->cmd, command_cr) == 0)
|
|
{
|
|
token_cr = token;
|
|
continue;
|
|
}
|
|
|
|
if (!token->desc)
|
|
vty_out (vty, " %-s%s",
|
|
token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
|
|
VTY_NEWLINE);
|
|
else if (desc_width >= strlen (token->desc))
|
|
vty_out (vty, " %-*s %s%s", width,
|
|
token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
|
|
token->desc, VTY_NEWLINE);
|
|
else
|
|
vty_describe_fold (vty, width, desc_width, token);
|
|
|
|
#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 ((token = token_cr))
|
|
{
|
|
if (!token->desc)
|
|
vty_out (vty, " %-s%s",
|
|
token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
|
|
VTY_NEWLINE);
|
|
else if (desc_width >= strlen (token->desc))
|
|
vty_out (vty, " %-*s %s%s", width,
|
|
token->cmd[0] == '.' ? token->cmd + 1 : token->cmd,
|
|
token->desc, VTY_NEWLINE);
|
|
else
|
|
vty_describe_fold (vty, width, desc_width, token);
|
|
}
|
|
|
|
out:
|
|
cmd_free_strvec (vline);
|
|
if (describe)
|
|
vector_free (describe);
|
|
|
|
vty_prompt (vty);
|
|
vty_redraw_line (vty);
|
|
}
|
|
|
|
static void
|
|
vty_clear_buf (struct vty *vty)
|
|
{
|
|
memset (vty->buf, 0, vty->max);
|
|
}
|
|
|
|
/* ^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);
|
|
|
|
switch (vty->node)
|
|
{
|
|
case VIEW_NODE:
|
|
case ENABLE_NODE:
|
|
case RESTRICTED_NODE:
|
|
/* Nothing to do. */
|
|
break;
|
|
case CONFIG_NODE:
|
|
case INTERFACE_NODE:
|
|
case ZEBRA_NODE:
|
|
case RIP_NODE:
|
|
case RIPNG_NODE:
|
|
case BGP_NODE:
|
|
case RMAP_NODE:
|
|
case OSPF_NODE:
|
|
case OSPF6_NODE:
|
|
case LDP_NODE:
|
|
case LDP_IPV4_NODE:
|
|
case LDP_IPV6_NODE:
|
|
case LDP_IPV4_IFACE_NODE:
|
|
case LDP_IPV6_IFACE_NODE:
|
|
case LDP_L2VPN_NODE:
|
|
case LDP_PSEUDOWIRE_NODE:
|
|
case ISIS_NODE:
|
|
case KEYCHAIN_NODE:
|
|
case KEYCHAIN_KEY_NODE:
|
|
case MASC_NODE:
|
|
case PIM_NODE:
|
|
case VTY_NODE:
|
|
vty_config_unlock (vty);
|
|
vty->node = ENABLE_NODE;
|
|
break;
|
|
default:
|
|
/* Unknown node, we have to ignore it. */
|
|
break;
|
|
}
|
|
vty_prompt (vty);
|
|
|
|
/* Set history pointer to the latest one. */
|
|
vty->hp = vty->hindex;
|
|
}
|
|
|
|
/* 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])
|
|
XFREE (MTYPE_VTY_HIST, vty->hist[vty->hindex]);
|
|
vty->hist[vty->hindex] = XSTRDUP (MTYPE_VTY_HIST, vty->buf);
|
|
|
|
/* History index rotation. */
|
|
vty->hindex++;
|
|
if (vty->hindex == VTY_MAXHIST)
|
|
vty->hindex = 0;
|
|
|
|
vty->hp = vty->hindex;
|
|
}
|
|
|
|
/* #define TELNET_OPTION_DEBUG */
|
|
|
|
/* 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)
|
|
zlog_warn("RFC 1073 violation detected: telnet NAWS option "
|
|
"should send %d characters, but we received %lu",
|
|
TELNET_NAWS_SB_LEN, (u_long)vty->sb_len);
|
|
else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN)
|
|
zlog_err("Bug detected: sizeof(vty->sb_buf) %lu < %d, "
|
|
"too small to handle the telnet NAWS option",
|
|
(u_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, vty->buf);
|
|
break;
|
|
default:
|
|
ret = vty_command (vty, vty->buf);
|
|
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;
|
|
}
|
|
|
|
#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. */
|
|
static int
|
|
vty_read (struct thread *thread)
|
|
{
|
|
int i;
|
|
int nbytes;
|
|
unsigned char buf[VTY_READ_BUFSIZ];
|
|
|
|
int vty_sock = THREAD_FD (thread);
|
|
struct vty *vty = THREAD_ARG (thread);
|
|
vty->t_read = NULL;
|
|
|
|
/* 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;
|
|
}
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
zlog_warn("%s: read error on vty client fd %d, closing: %s",
|
|
__func__, vty->fd, safe_strerror(errno));
|
|
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_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);
|
|
else
|
|
{
|
|
vty_event (VTY_WRITE, vty->wfd, vty);
|
|
vty_event (VTY_READ, vty_sock, vty);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Flush buffer to the vty. */
|
|
static int
|
|
vty_flush (struct thread *thread)
|
|
{
|
|
int erase;
|
|
buffer_status_t flushrc;
|
|
int vty_sock = THREAD_FD (thread);
|
|
struct vty *vty = THREAD_ARG (thread);
|
|
|
|
vty->t_write = NULL;
|
|
|
|
/* Tempolary disable read thread. */
|
|
if ((vty->lines == 0) && vty->t_read)
|
|
{
|
|
thread_cancel (vty->t_read);
|
|
vty->t_read = NULL;
|
|
}
|
|
|
|
/* Function execution continue. */
|
|
erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE));
|
|
|
|
/* N.B. if width is 0, that means we don't know the window size. */
|
|
if ((vty->lines == 0) || (vty->width == 0) || (vty->height == 0))
|
|
flushrc = buffer_flush_available(vty->obuf, vty_sock);
|
|
else if (vty->status == VTY_MORELINE)
|
|
flushrc = buffer_flush_window(vty->obuf, vty_sock, vty->width,
|
|
1, erase, 0);
|
|
else
|
|
flushrc = buffer_flush_window(vty->obuf, vty_sock, vty->width,
|
|
vty->lines >= 0 ? vty->lines :
|
|
vty->height,
|
|
erase, 0);
|
|
switch (flushrc)
|
|
{
|
|
case BUFFER_ERROR:
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
zlog_warn("buffer_flush failed on vty client fd %d, closing",
|
|
vty->fd);
|
|
buffer_reset(vty->obuf);
|
|
vty_close(vty);
|
|
return 0;
|
|
case BUFFER_EMPTY:
|
|
if (vty->status == VTY_CLOSE)
|
|
vty_close (vty);
|
|
else
|
|
{
|
|
vty->status = VTY_NORMAL;
|
|
if (vty->lines == 0)
|
|
vty_event (VTY_READ, vty_sock, vty);
|
|
}
|
|
break;
|
|
case BUFFER_PENDING:
|
|
/* There is more data waiting to be written. */
|
|
vty->status = VTY_MORE;
|
|
if (vty->lines == 0)
|
|
vty_event (VTY_WRITE, vty_sock, vty);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* allocate and initialise vty */
|
|
static struct vty *
|
|
vty_new_init (int vty_sock)
|
|
{
|
|
struct vty *vty;
|
|
|
|
vty = vty_new ();
|
|
vty->fd = vty_sock;
|
|
vty->wfd = vty_sock;
|
|
vty->type = VTY_TERM;
|
|
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;
|
|
vty->lines = -1;
|
|
vty->iac = 0;
|
|
vty->iac_sb_in_progress = 0;
|
|
vty->sb_len = 0;
|
|
|
|
return vty;
|
|
}
|
|
|
|
/* Create new vty structure. */
|
|
static struct vty *
|
|
vty_create (int vty_sock, union sockunion *su)
|
|
{
|
|
char buf[SU_ADDRSTRLEN];
|
|
struct vty *vty;
|
|
|
|
sockunion2str(su, buf, SU_ADDRSTRLEN);
|
|
|
|
/* Allocate new vty structure and set up default values. */
|
|
vty = vty_new_init (vty_sock);
|
|
|
|
/* configurable parameters not part of basic init */
|
|
vty->v_timeout = vty_timeout_val;
|
|
strcpy (vty->address, buf);
|
|
if (no_password_check)
|
|
{
|
|
if (restricted_mode)
|
|
vty->node = RESTRICTED_NODE;
|
|
else if (host.advanced)
|
|
vty->node = ENABLE_NODE;
|
|
else
|
|
vty->node = VIEW_NODE;
|
|
}
|
|
if (host.lines >= 0)
|
|
vty->lines = host.lines;
|
|
|
|
if (! no_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 (! no_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;
|
|
}
|
|
|
|
/* create vty for stdio */
|
|
static struct termios stdio_orig_termios;
|
|
static struct vty *stdio_vty = NULL;
|
|
static void (*stdio_vty_atclose)(void);
|
|
|
|
static void
|
|
vty_stdio_reset (void)
|
|
{
|
|
if (stdio_vty)
|
|
{
|
|
tcsetattr (0, TCSANOW, &stdio_orig_termios);
|
|
stdio_vty = NULL;
|
|
|
|
if (stdio_vty_atclose)
|
|
stdio_vty_atclose ();
|
|
stdio_vty_atclose = NULL;
|
|
}
|
|
}
|
|
|
|
struct vty *
|
|
vty_stdio (void (*atclose)())
|
|
{
|
|
struct vty *vty;
|
|
struct termios termios;
|
|
|
|
/* refuse creating two vtys on stdio */
|
|
if (stdio_vty)
|
|
return NULL;
|
|
|
|
vty = stdio_vty = vty_new_init (0);
|
|
stdio_vty_atclose = atclose;
|
|
vty->wfd = 1;
|
|
|
|
/* always have stdio vty in a known _unchangeable_ state, don't want config
|
|
* to have any effect here to make sure scripting this works as intended */
|
|
vty->node = ENABLE_NODE;
|
|
vty->v_timeout = 0;
|
|
strcpy (vty->address, "console");
|
|
|
|
if (!tcgetattr (0, &stdio_orig_termios))
|
|
{
|
|
termios = stdio_orig_termios;
|
|
termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
|
|
| INLCR | IGNCR | ICRNL | IXON);
|
|
termios.c_oflag &= ~OPOST;
|
|
termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
|
termios.c_cflag &= ~(CSIZE | PARENB);
|
|
termios.c_cflag |= CS8;
|
|
tcsetattr (0, TCSANOW, &termios);
|
|
}
|
|
|
|
vty_prompt (vty);
|
|
|
|
/* Add read/write thread. */
|
|
vty_event (VTY_WRITE, 1, vty);
|
|
vty_event (VTY_READ, 0, vty);
|
|
|
|
return vty;
|
|
}
|
|
|
|
/* Accept connection from the network. */
|
|
static int
|
|
vty_accept (struct thread *thread)
|
|
{
|
|
int vty_sock;
|
|
union sockunion su;
|
|
int ret;
|
|
unsigned int on;
|
|
int accept_sock;
|
|
struct prefix p;
|
|
struct access_list *acl = NULL;
|
|
char buf[SU_ADDRSTRLEN];
|
|
|
|
accept_sock = THREAD_FD (thread);
|
|
|
|
/* We continue hearing vty socket. */
|
|
vty_event (VTY_SERV, accept_sock, NULL);
|
|
|
|
memset (&su, 0, sizeof (union sockunion));
|
|
|
|
/* We can handle IPv4 or IPv6 socket. */
|
|
vty_sock = sockunion_accept (accept_sock, &su);
|
|
if (vty_sock < 0)
|
|
{
|
|
zlog_warn ("can't accept vty socket : %s", safe_strerror (errno));
|
|
return -1;
|
|
}
|
|
set_nonblocking(vty_sock);
|
|
|
|
sockunion2hostprefix (&su, &p);
|
|
|
|
/* VTY's accesslist apply. */
|
|
if (p.family == AF_INET && vty_accesslist_name)
|
|
{
|
|
if ((acl = access_list_lookup (AFI_IP, vty_accesslist_name)) &&
|
|
(access_list_apply (acl, &p) == FILTER_DENY))
|
|
{
|
|
zlog (NULL, LOG_INFO, "Vty connection refused from %s",
|
|
sockunion2str (&su, buf, SU_ADDRSTRLEN));
|
|
close (vty_sock);
|
|
|
|
/* continue accepting connections */
|
|
vty_event (VTY_SERV, accept_sock, NULL);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_IPV6
|
|
/* VTY's ipv6 accesslist apply. */
|
|
if (p.family == AF_INET6 && vty_ipv6_accesslist_name)
|
|
{
|
|
if ((acl = access_list_lookup (AFI_IP6, vty_ipv6_accesslist_name)) &&
|
|
(access_list_apply (acl, &p) == FILTER_DENY))
|
|
{
|
|
zlog (NULL, LOG_INFO, "Vty connection refused from %s",
|
|
sockunion2str (&su, buf, SU_ADDRSTRLEN));
|
|
close (vty_sock);
|
|
|
|
/* continue accepting connections */
|
|
vty_event (VTY_SERV, accept_sock, NULL);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* HAVE_IPV6 */
|
|
|
|
on = 1;
|
|
ret = setsockopt (vty_sock, IPPROTO_TCP, TCP_NODELAY,
|
|
(char *) &on, sizeof (on));
|
|
if (ret < 0)
|
|
zlog (NULL, LOG_INFO, "can't set sockopt to vty_sock : %s",
|
|
safe_strerror (errno));
|
|
|
|
zlog (NULL, LOG_INFO, "Vty connection from %s",
|
|
sockunion2str (&su, buf, SU_ADDRSTRLEN));
|
|
|
|
vty_create (vty_sock, &su);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_IPV6
|
|
static void
|
|
vty_serv_sock_addrinfo (const char *hostname, unsigned short port)
|
|
{
|
|
int ret;
|
|
struct addrinfo req;
|
|
struct addrinfo *ainfo;
|
|
struct addrinfo *ainfo_save;
|
|
int sock;
|
|
char port_str[BUFSIZ];
|
|
|
|
memset (&req, 0, sizeof (struct addrinfo));
|
|
req.ai_flags = AI_PASSIVE;
|
|
req.ai_family = AF_UNSPEC;
|
|
req.ai_socktype = SOCK_STREAM;
|
|
sprintf (port_str, "%d", port);
|
|
port_str[sizeof (port_str) - 1] = '\0';
|
|
|
|
ret = getaddrinfo (hostname, port_str, &req, &ainfo);
|
|
|
|
if (ret != 0)
|
|
{
|
|
fprintf (stderr, "getaddrinfo failed: %s\n", gai_strerror (ret));
|
|
exit (1);
|
|
}
|
|
|
|
ainfo_save = ainfo;
|
|
|
|
do
|
|
{
|
|
if (ainfo->ai_family != AF_INET
|
|
#ifdef HAVE_IPV6
|
|
&& ainfo->ai_family != AF_INET6
|
|
#endif /* HAVE_IPV6 */
|
|
)
|
|
continue;
|
|
|
|
sock = socket (ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
|
|
if (sock < 0)
|
|
continue;
|
|
|
|
sockopt_v6only (ainfo->ai_family, sock);
|
|
sockopt_reuseaddr (sock);
|
|
sockopt_reuseport (sock);
|
|
|
|
ret = bind (sock, ainfo->ai_addr, ainfo->ai_addrlen);
|
|
if (ret < 0)
|
|
{
|
|
close (sock); /* Avoid sd leak. */
|
|
continue;
|
|
}
|
|
|
|
ret = listen (sock, 3);
|
|
if (ret < 0)
|
|
{
|
|
close (sock); /* Avoid sd leak. */
|
|
continue;
|
|
}
|
|
|
|
vty_event (VTY_SERV, sock, NULL);
|
|
}
|
|
while ((ainfo = ainfo->ai_next) != NULL);
|
|
|
|
freeaddrinfo (ainfo_save);
|
|
}
|
|
#else /* HAVE_IPV6 */
|
|
|
|
/* Make vty server socket. */
|
|
static void
|
|
vty_serv_sock_family (const char* addr, unsigned short port, int family)
|
|
{
|
|
int ret;
|
|
union sockunion su;
|
|
int accept_sock;
|
|
void* naddr=NULL;
|
|
|
|
memset (&su, 0, sizeof (union sockunion));
|
|
su.sa.sa_family = family;
|
|
if(addr)
|
|
switch(family)
|
|
{
|
|
case AF_INET:
|
|
naddr=&su.sin.sin_addr;
|
|
break;
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
naddr=&su.sin6.sin6_addr;
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if(naddr)
|
|
switch(inet_pton(family,addr,naddr))
|
|
{
|
|
case -1:
|
|
zlog_err("bad address %s",addr);
|
|
naddr=NULL;
|
|
break;
|
|
case 0:
|
|
zlog_err("error translating address %s: %s",addr,safe_strerror(errno));
|
|
naddr=NULL;
|
|
}
|
|
|
|
/* Make new socket. */
|
|
accept_sock = sockunion_stream_socket (&su);
|
|
if (accept_sock < 0)
|
|
return;
|
|
|
|
/* This is server, so reuse address. */
|
|
sockopt_reuseaddr (accept_sock);
|
|
sockopt_reuseport (accept_sock);
|
|
|
|
/* Bind socket to universal address and given port. */
|
|
ret = sockunion_bind (accept_sock, &su, port, naddr);
|
|
if (ret < 0)
|
|
{
|
|
zlog_warn("can't bind socket");
|
|
close (accept_sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
/* Listen socket under queue 3. */
|
|
ret = listen (accept_sock, 3);
|
|
if (ret < 0)
|
|
{
|
|
zlog (NULL, LOG_WARNING, "can't listen socket");
|
|
close (accept_sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
/* Add vty server event. */
|
|
vty_event (VTY_SERV, accept_sock, NULL);
|
|
}
|
|
#endif /* HAVE_IPV6 */
|
|
|
|
#ifdef VTYSH
|
|
/* For sockaddr_un. */
|
|
#include <sys/un.h>
|
|
|
|
/* VTY shell UNIX domain socket. */
|
|
static void
|
|
vty_serv_un (const char *path)
|
|
{
|
|
int ret;
|
|
int sock, len;
|
|
struct sockaddr_un serv;
|
|
mode_t old_mask;
|
|
struct zprivs_ids_t ids;
|
|
|
|
/* First of all, unlink existing socket */
|
|
unlink (path);
|
|
|
|
/* Set umask */
|
|
old_mask = umask (0007);
|
|
|
|
/* Make UNIX domain socket. */
|
|
sock = socket (AF_UNIX, SOCK_STREAM, 0);
|
|
if (sock < 0)
|
|
{
|
|
zlog_err("Cannot create unix stream socket: %s", safe_strerror(errno));
|
|
return;
|
|
}
|
|
|
|
/* Make server socket. */
|
|
memset (&serv, 0, sizeof (struct sockaddr_un));
|
|
serv.sun_family = AF_UNIX;
|
|
strncpy (serv.sun_path, path, strlen (path));
|
|
#ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
|
|
len = serv.sun_len = SUN_LEN(&serv);
|
|
#else
|
|
len = sizeof (serv.sun_family) + strlen (serv.sun_path);
|
|
#endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */
|
|
|
|
ret = bind (sock, (struct sockaddr *) &serv, len);
|
|
if (ret < 0)
|
|
{
|
|
zlog_err("Cannot bind path %s: %s", path, safe_strerror(errno));
|
|
close (sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
ret = listen (sock, 5);
|
|
if (ret < 0)
|
|
{
|
|
zlog_err("listen(fd %d) failed: %s", sock, safe_strerror(errno));
|
|
close (sock); /* Avoid sd leak. */
|
|
return;
|
|
}
|
|
|
|
umask (old_mask);
|
|
|
|
zprivs_get_ids(&ids);
|
|
|
|
if (ids.gid_vty > 0)
|
|
{
|
|
/* set group of socket */
|
|
if ( chown (path, -1, ids.gid_vty) )
|
|
{
|
|
zlog_err ("vty_serv_un: could chown socket, %s",
|
|
safe_strerror (errno) );
|
|
}
|
|
}
|
|
|
|
vty_event (VTYSH_SERV, sock, NULL);
|
|
}
|
|
|
|
/* #define VTYSH_DEBUG 1 */
|
|
|
|
static int
|
|
vtysh_accept (struct thread *thread)
|
|
{
|
|
int accept_sock;
|
|
int sock;
|
|
int client_len;
|
|
struct sockaddr_un client;
|
|
struct vty *vty;
|
|
|
|
accept_sock = THREAD_FD (thread);
|
|
|
|
vty_event (VTYSH_SERV, accept_sock, NULL);
|
|
|
|
memset (&client, 0, sizeof (struct sockaddr_un));
|
|
client_len = sizeof (struct sockaddr_un);
|
|
|
|
sock = accept (accept_sock, (struct sockaddr *) &client,
|
|
(socklen_t *) &client_len);
|
|
|
|
if (sock < 0)
|
|
{
|
|
zlog_warn ("can't accept vty socket : %s", safe_strerror (errno));
|
|
return -1;
|
|
}
|
|
|
|
if (set_nonblocking(sock) < 0)
|
|
{
|
|
zlog_warn ("vtysh_accept: could not set vty socket %d to non-blocking,"
|
|
" %s, closing", sock, safe_strerror (errno));
|
|
close (sock);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef VTYSH_DEBUG
|
|
printf ("VTY shell accept\n");
|
|
#endif /* VTYSH_DEBUG */
|
|
|
|
vty = vty_new ();
|
|
vty->fd = sock;
|
|
vty->wfd = sock;
|
|
vty->type = VTY_SHELL_SERV;
|
|
vty->node = VIEW_NODE;
|
|
|
|
vty_event (VTYSH_READ, sock, vty);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
vtysh_flush(struct vty *vty)
|
|
{
|
|
switch (buffer_flush_available(vty->obuf, vty->wfd))
|
|
{
|
|
case BUFFER_PENDING:
|
|
vty_event(VTYSH_WRITE, vty->wfd, vty);
|
|
break;
|
|
case BUFFER_ERROR:
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
zlog_warn("%s: write error to fd %d, closing", __func__, vty->fd);
|
|
buffer_reset(vty->obuf);
|
|
vty_close(vty);
|
|
return -1;
|
|
break;
|
|
case BUFFER_EMPTY:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
vtysh_read (struct thread *thread)
|
|
{
|
|
int ret;
|
|
int sock;
|
|
int nbytes;
|
|
struct vty *vty;
|
|
unsigned char buf[VTY_READ_BUFSIZ];
|
|
unsigned char *p;
|
|
u_char header[4] = {0, 0, 0, 0};
|
|
|
|
sock = THREAD_FD (thread);
|
|
vty = THREAD_ARG (thread);
|
|
vty->t_read = NULL;
|
|
|
|
if ((nbytes = read (sock, buf, VTY_READ_BUFSIZ)) <= 0)
|
|
{
|
|
if (nbytes < 0)
|
|
{
|
|
if (ERRNO_IO_RETRY(errno))
|
|
{
|
|
vty_event (VTYSH_READ, sock, vty);
|
|
return 0;
|
|
}
|
|
vty->monitor = 0; /* disable monitoring to avoid infinite recursion */
|
|
zlog_warn("%s: read failed on vtysh client fd %d, closing: %s",
|
|
__func__, sock, safe_strerror(errno));
|
|
}
|
|
buffer_reset(vty->obuf);
|
|
vty_close (vty);
|
|
#ifdef VTYSH_DEBUG
|
|
printf ("close vtysh\n");
|
|
#endif /* VTYSH_DEBUG */
|
|
return 0;
|
|
}
|
|
|
|
#ifdef VTYSH_DEBUG
|
|
printf ("line: %.*s\n", nbytes, buf);
|
|
#endif /* VTYSH_DEBUG */
|
|
|
|
for (p = buf; p < buf+nbytes; p++)
|
|
{
|
|
vty_ensure(vty, vty->length+1);
|
|
vty->buf[vty->length++] = *p;
|
|
if (*p == '\0')
|
|
{
|
|
/* Pass this line to parser. */
|
|
ret = vty_execute (vty);
|
|
/* Note that vty_execute clears the command buffer and resets
|
|
vty->length to 0. */
|
|
|
|
/* Return result. */
|
|
#ifdef VTYSH_DEBUG
|
|
printf ("result: %d\n", ret);
|
|
printf ("vtysh node: %d\n", vty->node);
|
|
#endif /* VTYSH_DEBUG */
|
|
|
|
header[3] = ret;
|
|
buffer_put(vty->obuf, header, 4);
|
|
|
|
if (!vty->t_write && (vtysh_flush(vty) < 0))
|
|
/* Try to flush results; exit if a write error occurs. */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
vty_event (VTYSH_READ, sock, vty);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
vtysh_write (struct thread *thread)
|
|
{
|
|
struct vty *vty = THREAD_ARG (thread);
|
|
|
|
vty->t_write = NULL;
|
|
vtysh_flush(vty);
|
|
return 0;
|
|
}
|
|
|
|
#endif /* VTYSH */
|
|
|
|
/* Determine address family to bind. */
|
|
void
|
|
vty_serv_sock (const char *addr, unsigned short port, const char *path)
|
|
{
|
|
/* If port is set to 0, do not listen on TCP/IP at all! */
|
|
if (port)
|
|
{
|
|
|
|
#ifdef HAVE_IPV6
|
|
vty_serv_sock_addrinfo (addr, port);
|
|
#else /* ! HAVE_IPV6 */
|
|
vty_serv_sock_family (addr,port, AF_INET);
|
|
#endif /* HAVE_IPV6 */
|
|
}
|
|
|
|
#ifdef VTYSH
|
|
vty_serv_un (path);
|
|
#endif /* VTYSH */
|
|
}
|
|
|
|
/* Close vty interface. Warning: call this only from functions that
|
|
will be careful not to access the vty afterwards (since it has
|
|
now been freed). This is safest from top-level functions (called
|
|
directly by the thread dispatcher). */
|
|
void
|
|
vty_close (struct vty *vty)
|
|
{
|
|
int i;
|
|
|
|
/* Cancel threads.*/
|
|
if (vty->t_read)
|
|
thread_cancel (vty->t_read);
|
|
if (vty->t_write)
|
|
thread_cancel (vty->t_write);
|
|
if (vty->t_timeout)
|
|
thread_cancel (vty->t_timeout);
|
|
|
|
/* Flush buffer. */
|
|
buffer_flush_all (vty->obuf, vty->wfd);
|
|
|
|
/* Free input buffer. */
|
|
buffer_free (vty->obuf);
|
|
|
|
/* Free command history. */
|
|
for (i = 0; i < VTY_MAXHIST; i++)
|
|
if (vty->hist[i])
|
|
XFREE (MTYPE_VTY_HIST, vty->hist[i]);
|
|
|
|
/* Unset vector. */
|
|
vector_unset (vtyvec, vty->fd);
|
|
|
|
/* Close socket. */
|
|
if (vty->fd > 0)
|
|
close (vty->fd);
|
|
else
|
|
vty_stdio_reset ();
|
|
|
|
if (vty->buf)
|
|
XFREE (MTYPE_VTY, vty->buf);
|
|
|
|
if (vty->error_buf)
|
|
XFREE (MTYPE_VTY, vty->error_buf);
|
|
|
|
/* Check configure. */
|
|
vty_config_unlock (vty);
|
|
|
|
/* OK free vty. */
|
|
XFREE (MTYPE_VTY, vty);
|
|
}
|
|
|
|
/* When time out occur output message then close connection. */
|
|
static int
|
|
vty_timeout (struct thread *thread)
|
|
{
|
|
struct vty *vty;
|
|
|
|
vty = THREAD_ARG (thread);
|
|
vty->t_timeout = NULL;
|
|
vty->v_timeout = 0;
|
|
|
|
/* Clear buffer*/
|
|
buffer_reset (vty->obuf);
|
|
vty_out (vty, "%sVty connection is timed out.%s", VTY_NEWLINE, VTY_NEWLINE);
|
|
|
|
/* Close connection. */
|
|
vty->status = VTY_CLOSE;
|
|
vty_close (vty);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read up configuration file from file_name. */
|
|
static void
|
|
vty_read_file (FILE *confp)
|
|
{
|
|
int ret;
|
|
struct vty *vty;
|
|
unsigned int line_num = 0;
|
|
|
|
vty = vty_new ();
|
|
vty->wfd = dup(STDERR_FILENO); /* vty_close() will close this */
|
|
if (vty->wfd < 0)
|
|
{
|
|
/* Fine, we couldn't make a new fd. vty_close doesn't close stdout. */
|
|
vty->wfd = STDOUT_FILENO;
|
|
}
|
|
vty->fd = STDIN_FILENO;
|
|
vty->type = VTY_FILE;
|
|
vty->node = CONFIG_NODE;
|
|
|
|
/* Execute configuration file */
|
|
ret = config_from_file (vty, confp, &line_num);
|
|
|
|
/* Flush any previous errors before printing messages below */
|
|
buffer_flush_all (vty->obuf, vty->fd);
|
|
|
|
if ( !((ret == CMD_SUCCESS) || (ret == CMD_ERR_NOTHING_TODO)) )
|
|
{
|
|
switch (ret)
|
|
{
|
|
case CMD_ERR_AMBIGUOUS:
|
|
fprintf (stderr, "*** Error reading config: Ambiguous command.\n");
|
|
break;
|
|
case CMD_ERR_NO_MATCH:
|
|
fprintf (stderr, "*** Error reading config: There is no such command.\n");
|
|
break;
|
|
}
|
|
fprintf (stderr, "*** Error occured processing line %u, below:\n%s\n",
|
|
line_num, vty->error_buf);
|
|
}
|
|
|
|
vty_close (vty);
|
|
}
|
|
|
|
static FILE *
|
|
vty_use_backup_config (char *fullpath)
|
|
{
|
|
char *fullpath_sav, *fullpath_tmp;
|
|
FILE *ret = NULL;
|
|
struct stat buf;
|
|
int tmp, sav;
|
|
int c;
|
|
char buffer[512];
|
|
|
|
fullpath_sav = malloc (strlen (fullpath) + strlen (CONF_BACKUP_EXT) + 1);
|
|
strcpy (fullpath_sav, fullpath);
|
|
strcat (fullpath_sav, CONF_BACKUP_EXT);
|
|
if (stat (fullpath_sav, &buf) == -1)
|
|
{
|
|
free (fullpath_sav);
|
|
return NULL;
|
|
}
|
|
|
|
fullpath_tmp = malloc (strlen (fullpath) + 8);
|
|
sprintf (fullpath_tmp, "%s.XXXXXX", fullpath);
|
|
|
|
/* Open file to configuration write. */
|
|
tmp = mkstemp (fullpath_tmp);
|
|
if (tmp < 0)
|
|
{
|
|
free (fullpath_sav);
|
|
free (fullpath_tmp);
|
|
return NULL;
|
|
}
|
|
|
|
sav = open (fullpath_sav, O_RDONLY);
|
|
if (sav < 0)
|
|
{
|
|
unlink (fullpath_tmp);
|
|
free (fullpath_sav);
|
|
free (fullpath_tmp);
|
|
return NULL;
|
|
}
|
|
|
|
while((c = read (sav, buffer, 512)) > 0)
|
|
{
|
|
if (write (tmp, buffer, c) <= 0)
|
|
{
|
|
free (fullpath_sav);
|
|
free (fullpath_tmp);
|
|
close (sav);
|
|
close (tmp);
|
|
return NULL;
|
|
}
|
|
}
|
|
close (sav);
|
|
close (tmp);
|
|
|
|
if (chmod(fullpath_tmp, CONFIGFILE_MASK) != 0)
|
|
{
|
|
unlink (fullpath_tmp);
|
|
free (fullpath_sav);
|
|
free (fullpath_tmp);
|
|
return NULL;
|
|
}
|
|
|
|
if (link (fullpath_tmp, fullpath) == 0)
|
|
ret = fopen (fullpath, "r");
|
|
|
|
unlink (fullpath_tmp);
|
|
|
|
free (fullpath_sav);
|
|
free (fullpath_tmp);
|
|
return ret;
|
|
}
|
|
|
|
/* Read up configuration file from file_name. */
|
|
void
|
|
vty_read_config (char *config_file,
|
|
char *config_default_dir)
|
|
{
|
|
char cwd[MAXPATHLEN];
|
|
FILE *confp = NULL;
|
|
char *fullpath;
|
|
char *tmp = NULL;
|
|
|
|
/* If -f flag specified. */
|
|
if (config_file != NULL)
|
|
{
|
|
if (! IS_DIRECTORY_SEP (config_file[0]))
|
|
{
|
|
if (getcwd (cwd, MAXPATHLEN) == NULL)
|
|
{
|
|
fprintf (stderr, "Failure to determine Current Working Directory %d!\n", errno);
|
|
exit (1);
|
|
}
|
|
tmp = XMALLOC (MTYPE_TMP,
|
|
strlen (cwd) + strlen (config_file) + 2);
|
|
sprintf (tmp, "%s/%s", cwd, config_file);
|
|
fullpath = tmp;
|
|
}
|
|
else
|
|
fullpath = config_file;
|
|
|
|
confp = fopen (fullpath, "r");
|
|
|
|
if (confp == NULL)
|
|
{
|
|
fprintf (stderr, "%s: failed to open configuration file %s: %s\n",
|
|
__func__, fullpath, safe_strerror (errno));
|
|
|
|
confp = vty_use_backup_config (fullpath);
|
|
if (confp)
|
|
fprintf (stderr, "WARNING: using backup configuration file!\n");
|
|
else
|
|
{
|
|
fprintf (stderr, "can't open configuration file [%s]\n",
|
|
config_file);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
host_config_set (config_default_dir);
|
|
|
|
#ifdef VTYSH
|
|
int ret;
|
|
struct stat conf_stat;
|
|
|
|
/* !!!!PLEASE LEAVE!!!!
|
|
* This is NEEDED for use with vtysh -b, or else you can get
|
|
* a real configuration food fight with a lot garbage in the
|
|
* merged configuration file it creates coming from the per
|
|
* daemon configuration files. This also allows the daemons
|
|
* to start if there default configuration file is not
|
|
* present or ignore them, as needed when using vtysh -b to
|
|
* configure the daemons at boot - MAG
|
|
*/
|
|
|
|
/* Stat for vtysh Zebra.conf, if found startup and wait for
|
|
* boot configuration
|
|
*/
|
|
|
|
if ( strstr(config_default_dir, "vtysh") == NULL)
|
|
{
|
|
ret = stat (integrate_default, &conf_stat);
|
|
if (ret >= 0)
|
|
goto tmp_free_and_out;
|
|
}
|
|
#endif /* VTYSH */
|
|
confp = fopen (config_default_dir, "r");
|
|
if (confp == NULL)
|
|
{
|
|
fprintf (stderr, "%s: failed to open configuration file %s: %s\n",
|
|
__func__, config_default_dir, safe_strerror (errno));
|
|
|
|
confp = vty_use_backup_config (config_default_dir);
|
|
if (confp)
|
|
{
|
|
fprintf (stderr, "WARNING: using backup configuration file!\n");
|
|
fullpath = config_default_dir;
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "can't open configuration file [%s]\n",
|
|
config_default_dir);
|
|
goto tmp_free_and_out;
|
|
}
|
|
}
|
|
else
|
|
fullpath = config_default_dir;
|
|
}
|
|
|
|
vty_read_file (confp);
|
|
|
|
fclose (confp);
|
|
|
|
host_config_set (fullpath);
|
|
|
|
tmp_free_and_out:
|
|
if (tmp)
|
|
XFREE (MTYPE_TMP, fullpath);
|
|
}
|
|
|
|
/* Small utility function which output log to the VTY. */
|
|
void
|
|
vty_log (const char *level, const char *proto_str,
|
|
const char *format, struct timestamp_control *ctl, va_list va)
|
|
{
|
|
unsigned int i;
|
|
struct vty *vty;
|
|
|
|
if (!vtyvec)
|
|
return;
|
|
|
|
for (i = 0; i < vector_active (vtyvec); i++)
|
|
if ((vty = vector_slot (vtyvec, i)) != NULL)
|
|
if (vty->monitor)
|
|
{
|
|
va_list ac;
|
|
va_copy(ac, va);
|
|
vty_log_out (vty, level, proto_str, format, ctl, ac);
|
|
va_end(ac);
|
|
}
|
|
}
|
|
|
|
/* Async-signal-safe version of vty_log for fixed strings. */
|
|
void
|
|
vty_log_fixed (char *buf, size_t len)
|
|
{
|
|
unsigned int i;
|
|
struct iovec iov[2];
|
|
char crlf[4] = "\r\n";
|
|
|
|
/* vty may not have been initialised */
|
|
if (!vtyvec)
|
|
return;
|
|
|
|
iov[0].iov_base = buf;
|
|
iov[0].iov_len = len;
|
|
iov[1].iov_base = crlf;
|
|
iov[1].iov_len = 2;
|
|
|
|
for (i = 0; i < vector_active (vtyvec); i++)
|
|
{
|
|
struct vty *vty;
|
|
if (((vty = vector_slot (vtyvec, i)) != NULL) && vty->monitor)
|
|
/* N.B. We don't care about the return code, since process is
|
|
most likely just about to die anyway. */
|
|
if (writev(vty->wfd, iov, 2) == -1)
|
|
{
|
|
fprintf(stderr, "Failure to writev: %d\n", errno);
|
|
exit(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
vty_config_lock (struct vty *vty)
|
|
{
|
|
if (vty_config == 0)
|
|
{
|
|
vty->config = 1;
|
|
vty_config = 1;
|
|
}
|
|
return vty->config;
|
|
}
|
|
|
|
int
|
|
vty_config_unlock (struct vty *vty)
|
|
{
|
|
if (vty_config == 1 && vty->config == 1)
|
|
{
|
|
vty->config = 0;
|
|
vty_config = 0;
|
|
}
|
|
return vty->config;
|
|
}
|
|
|
|
/* Master of the threads. */
|
|
static struct thread_master *vty_master;
|
|
|
|
static void
|
|
vty_event (enum event event, int sock, struct vty *vty)
|
|
{
|
|
struct thread *vty_serv_thread;
|
|
|
|
switch (event)
|
|
{
|
|
case VTY_SERV:
|
|
vty_serv_thread = thread_add_read (vty_master, vty_accept, vty, sock);
|
|
vector_set_index (Vvty_serv_thread, sock, vty_serv_thread);
|
|
break;
|
|
#ifdef VTYSH
|
|
case VTYSH_SERV:
|
|
vty_serv_thread = thread_add_read (vty_master, vtysh_accept, vty, sock);
|
|
vector_set_index (Vvty_serv_thread, sock, vty_serv_thread);
|
|
break;
|
|
case VTYSH_READ:
|
|
vty->t_read = thread_add_read (vty_master, vtysh_read, vty, sock);
|
|
break;
|
|
case VTYSH_WRITE:
|
|
vty->t_write = thread_add_write (vty_master, vtysh_write, vty, sock);
|
|
break;
|
|
#endif /* VTYSH */
|
|
case VTY_READ:
|
|
vty->t_read = thread_add_read (vty_master, vty_read, vty, sock);
|
|
|
|
/* Time out treatment. */
|
|
if (vty->v_timeout)
|
|
{
|
|
if (vty->t_timeout)
|
|
thread_cancel (vty->t_timeout);
|
|
vty->t_timeout =
|
|
thread_add_timer (vty_master, vty_timeout, vty, vty->v_timeout);
|
|
}
|
|
break;
|
|
case VTY_WRITE:
|
|
if (! vty->t_write)
|
|
vty->t_write = thread_add_write (vty_master, vty_flush, vty, sock);
|
|
break;
|
|
case VTY_TIMEOUT_RESET:
|
|
if (vty->t_timeout)
|
|
{
|
|
thread_cancel (vty->t_timeout);
|
|
vty->t_timeout = NULL;
|
|
}
|
|
if (vty->v_timeout)
|
|
{
|
|
vty->t_timeout =
|
|
thread_add_timer (vty_master, vty_timeout, vty, vty->v_timeout);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
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] connected from %s.%s",
|
|
v->config ? "*" : " ",
|
|
i, v->address, 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;
|
|
}
|
|
|
|
/* Set time out value. */
|
|
static int
|
|
exec_timeout (struct vty *vty, const char *min_str, const char *sec_str)
|
|
{
|
|
unsigned long timeout = 0;
|
|
|
|
/* min_str and sec_str are already checked by parser. So it must be
|
|
all digit string. */
|
|
if (min_str)
|
|
{
|
|
timeout = strtol (min_str, NULL, 10);
|
|
timeout *= 60;
|
|
}
|
|
if (sec_str)
|
|
timeout += strtol (sec_str, NULL, 10);
|
|
|
|
vty_timeout_val = timeout;
|
|
vty->v_timeout = timeout;
|
|
vty_event (VTY_TIMEOUT_RESET, 0, vty);
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
DEFUN (exec_timeout_min,
|
|
exec_timeout_min_cmd,
|
|
"exec-timeout <0-35791>",
|
|
"Set timeout value\n"
|
|
"Timeout value in minutes\n")
|
|
{
|
|
return exec_timeout (vty, argv[0], NULL);
|
|
}
|
|
|
|
DEFUN (exec_timeout_sec,
|
|
exec_timeout_sec_cmd,
|
|
"exec-timeout <0-35791> <0-2147483>",
|
|
"Set the EXEC timeout\n"
|
|
"Timeout in minutes\n"
|
|
"Timeout in seconds\n")
|
|
{
|
|
return exec_timeout (vty, argv[0], argv[1]);
|
|
}
|
|
|
|
DEFUN (no_exec_timeout,
|
|
no_exec_timeout_cmd,
|
|
"no exec-timeout",
|
|
NO_STR
|
|
"Set the EXEC timeout\n")
|
|
{
|
|
return exec_timeout (vty, NULL, NULL);
|
|
}
|
|
|
|
/* Set vty access class. */
|
|
DEFUN (vty_access_class,
|
|
vty_access_class_cmd,
|
|
"access-class WORD",
|
|
"Filter connections based on an IP access list\n"
|
|
"IP access list\n")
|
|
{
|
|
if (vty_accesslist_name)
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
|
|
vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]);
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
/* Clear vty access class. */
|
|
DEFUN (no_vty_access_class,
|
|
no_vty_access_class_cmd,
|
|
"no access-class [WORD]",
|
|
NO_STR
|
|
"Filter connections based on an IP access list\n"
|
|
"IP access list\n")
|
|
{
|
|
if (! vty_accesslist_name || (argc && strcmp(vty_accesslist_name, argv[0])))
|
|
{
|
|
vty_out (vty, "Access-class is not currently applied to vty%s",
|
|
VTY_NEWLINE);
|
|
return CMD_WARNING;
|
|
}
|
|
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
|
|
vty_accesslist_name = NULL;
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
#ifdef HAVE_IPV6
|
|
/* Set vty access class. */
|
|
DEFUN (vty_ipv6_access_class,
|
|
vty_ipv6_access_class_cmd,
|
|
"ipv6 access-class WORD",
|
|
IPV6_STR
|
|
"Filter connections based on an IP access list\n"
|
|
"IPv6 access list\n")
|
|
{
|
|
if (vty_ipv6_accesslist_name)
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
|
|
vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[0]);
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
/* Clear vty access class. */
|
|
DEFUN (no_vty_ipv6_access_class,
|
|
no_vty_ipv6_access_class_cmd,
|
|
"no ipv6 access-class [WORD]",
|
|
NO_STR
|
|
IPV6_STR
|
|
"Filter connections based on an IP access list\n"
|
|
"IPv6 access list\n")
|
|
{
|
|
if (! vty_ipv6_accesslist_name ||
|
|
(argc && strcmp(vty_ipv6_accesslist_name, argv[0])))
|
|
{
|
|
vty_out (vty, "IPv6 access-class is not currently applied to vty%s",
|
|
VTY_NEWLINE);
|
|
return CMD_WARNING;
|
|
}
|
|
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
|
|
vty_ipv6_accesslist_name = NULL;
|
|
|
|
return CMD_SUCCESS;
|
|
}
|
|
#endif /* HAVE_IPV6 */
|
|
|
|
/* vty login. */
|
|
DEFUN (vty_login,
|
|
vty_login_cmd,
|
|
"login",
|
|
"Enable password checking\n")
|
|
{
|
|
no_password_check = 0;
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
DEFUN (no_vty_login,
|
|
no_vty_login_cmd,
|
|
"no login",
|
|
NO_STR
|
|
"Enable password checking\n")
|
|
{
|
|
no_password_check = 1;
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
/* initial mode. */
|
|
DEFUN (vty_restricted_mode,
|
|
vty_restricted_mode_cmd,
|
|
"anonymous restricted",
|
|
"Restrict view commands available in anonymous, unauthenticated vty\n")
|
|
{
|
|
restricted_mode = 1;
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
DEFUN (vty_no_restricted_mode,
|
|
vty_no_restricted_mode_cmd,
|
|
"no anonymous restricted",
|
|
NO_STR
|
|
"Enable password checking\n")
|
|
{
|
|
restricted_mode = 0;
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
ALIAS (terminal_no_monitor,
|
|
no_terminal_monitor_cmd,
|
|
"no terminal monitor",
|
|
NO_STR
|
|
"Set terminal line parameters\n"
|
|
"Copy debug output to the current terminal line\n")
|
|
|
|
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;
|
|
}
|
|
|
|
/* vty login. */
|
|
DEFUN (log_commands,
|
|
log_commands_cmd,
|
|
"log commands",
|
|
"Logging control\n"
|
|
"Log all commands (can't be unset without restart)\n")
|
|
{
|
|
do_log_commands = 1;
|
|
return CMD_SUCCESS;
|
|
}
|
|
|
|
/* Display current configuration. */
|
|
static int
|
|
vty_config_write (struct vty *vty)
|
|
{
|
|
vty_out (vty, "line vty%s", VTY_NEWLINE);
|
|
|
|
if (vty_accesslist_name)
|
|
vty_out (vty, " access-class %s%s",
|
|
vty_accesslist_name, VTY_NEWLINE);
|
|
|
|
if (vty_ipv6_accesslist_name)
|
|
vty_out (vty, " ipv6 access-class %s%s",
|
|
vty_ipv6_accesslist_name, VTY_NEWLINE);
|
|
|
|
/* exec-timeout */
|
|
if (vty_timeout_val != VTY_TIMEOUT_DEFAULT)
|
|
vty_out (vty, " exec-timeout %ld %ld%s",
|
|
vty_timeout_val / 60,
|
|
vty_timeout_val % 60, VTY_NEWLINE);
|
|
|
|
/* login */
|
|
if (no_password_check)
|
|
vty_out (vty, " no login%s", VTY_NEWLINE);
|
|
|
|
if (restricted_mode != restricted_mode_default)
|
|
{
|
|
if (restricted_mode_default)
|
|
vty_out (vty, " no anonymous restricted%s", VTY_NEWLINE);
|
|
else
|
|
vty_out (vty, " anonymous restricted%s", VTY_NEWLINE);
|
|
}
|
|
|
|
if (do_log_commands)
|
|
vty_out (vty, "log commands%s", 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 ()
|
|
{
|
|
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);
|
|
}
|
|
|
|
vty_timeout_val = VTY_TIMEOUT_DEFAULT;
|
|
|
|
if (vty_accesslist_name)
|
|
{
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
vty_accesslist_name = NULL;
|
|
}
|
|
|
|
if (vty_ipv6_accesslist_name)
|
|
{
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
vty_ipv6_accesslist_name = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
vty_save_cwd (void)
|
|
{
|
|
char cwd[MAXPATHLEN];
|
|
char *c;
|
|
|
|
c = getcwd (cwd, MAXPATHLEN);
|
|
|
|
if (!c)
|
|
{
|
|
/*
|
|
* At this point if these go wrong, more than likely
|
|
* the whole world is coming down around us
|
|
* Hence not worrying about it too much.
|
|
*/
|
|
if (!chdir (SYSCONFDIR))
|
|
{
|
|
fprintf(stderr, "Failure to chdir to %s, errno: %d\n", SYSCONFDIR, errno);
|
|
exit(-1);
|
|
}
|
|
if (getcwd (cwd, MAXPATHLEN) == NULL)
|
|
{
|
|
fprintf(stderr, "Failure to getcwd, errno: %d\n", errno);
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
vty_cwd = XMALLOC (MTYPE_TMP, strlen (cwd) + 1);
|
|
strcpy (vty_cwd, cwd);
|
|
}
|
|
|
|
char *
|
|
vty_get_cwd ()
|
|
{
|
|
return vty_cwd;
|
|
}
|
|
|
|
int
|
|
vty_shell (struct vty *vty)
|
|
{
|
|
return vty->type == VTY_SHELL ? 1 : 0;
|
|
}
|
|
|
|
int
|
|
vty_shell_serv (struct vty *vty)
|
|
{
|
|
return vty->type == VTY_SHELL_SERV ? 1 : 0;
|
|
}
|
|
|
|
void
|
|
vty_init_vtysh ()
|
|
{
|
|
vtyvec = vector_init (VECTOR_MIN_SIZE);
|
|
}
|
|
|
|
/* Install vty's own commands like `who' command. */
|
|
void
|
|
vty_init (struct thread_master *master_thread)
|
|
{
|
|
/* For further configuration read, preserve current directory. */
|
|
vty_save_cwd ();
|
|
|
|
vtyvec = vector_init (VECTOR_MIN_SIZE);
|
|
|
|
vty_master = master_thread;
|
|
|
|
atexit (vty_stdio_reset);
|
|
|
|
/* Initilize server thread vector. */
|
|
Vvty_serv_thread = vector_init (VECTOR_MIN_SIZE);
|
|
|
|
/* Install bgp top node. */
|
|
install_node (&vty_node, vty_config_write);
|
|
|
|
install_element (RESTRICTED_NODE, &config_who_cmd);
|
|
install_element (RESTRICTED_NODE, &show_history_cmd);
|
|
install_element (VIEW_NODE, &config_who_cmd);
|
|
install_element (VIEW_NODE, &show_history_cmd);
|
|
install_element (ENABLE_NODE, &config_who_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 (CONFIG_NODE, &log_commands_cmd);
|
|
install_element (ENABLE_NODE, &terminal_monitor_cmd);
|
|
install_element (ENABLE_NODE, &terminal_no_monitor_cmd);
|
|
install_element (ENABLE_NODE, &no_terminal_monitor_cmd);
|
|
install_element (ENABLE_NODE, &show_history_cmd);
|
|
|
|
install_default (VTY_NODE);
|
|
install_element (VTY_NODE, &exec_timeout_min_cmd);
|
|
install_element (VTY_NODE, &exec_timeout_sec_cmd);
|
|
install_element (VTY_NODE, &no_exec_timeout_cmd);
|
|
install_element (VTY_NODE, &vty_access_class_cmd);
|
|
install_element (VTY_NODE, &no_vty_access_class_cmd);
|
|
install_element (VTY_NODE, &vty_login_cmd);
|
|
install_element (VTY_NODE, &no_vty_login_cmd);
|
|
install_element (VTY_NODE, &vty_restricted_mode_cmd);
|
|
install_element (VTY_NODE, &vty_no_restricted_mode_cmd);
|
|
#ifdef HAVE_IPV6
|
|
install_element (VTY_NODE, &vty_ipv6_access_class_cmd);
|
|
install_element (VTY_NODE, &no_vty_ipv6_access_class_cmd);
|
|
#endif /* HAVE_IPV6 */
|
|
}
|
|
|
|
void
|
|
vty_terminate (void)
|
|
{
|
|
if (vty_cwd)
|
|
XFREE (MTYPE_TMP, vty_cwd);
|
|
|
|
if (vtyvec && Vvty_serv_thread)
|
|
{
|
|
vty_reset ();
|
|
vector_free (vtyvec);
|
|
vector_free (Vvty_serv_thread);
|
|
}
|
|
}
|
|
|
|
/* Utility functions to get arguments from commands generated
|
|
by the xml2cli.pl script. */
|
|
const char *
|
|
vty_get_arg_value (struct vty_arg *args[], const char *arg)
|
|
{
|
|
while (*args)
|
|
{
|
|
if (strcmp ((*args)->name, arg) == 0)
|
|
return (*args)->value;
|
|
args++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct vty_arg *
|
|
vty_get_arg (struct vty_arg *args[], const char *arg)
|
|
{
|
|
while (*args)
|
|
{
|
|
if (strcmp ((*args)->name, arg) == 0)
|
|
return *args;
|
|
args++;
|
|
}
|
|
return NULL;
|
|
}
|