2025-02-05 10:03:58 +01:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
/*
|
|
|
|
* Virtual terminal [aka TeletYpe] interface routine.
|
|
|
|
* Copyright (C) 1997, 98 Kunihiro Ishiguro
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <zebra.h>
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <lib/version.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#ifdef HAVE_LIBPCRE2_POSIX
|
|
|
|
#ifndef _FRR_PCRE2_POSIX
|
|
|
|
#define _FRR_PCRE2_POSIX
|
|
|
|
#include <pcre2posix.h>
|
|
|
|
#endif /* _FRR_PCRE2_POSIX */
|
|
|
|
#elif defined(HAVE_LIBPCREPOSIX)
|
|
|
|
#include <pcreposix.h>
|
|
|
|
#else
|
|
|
|
#include <regex.h>
|
|
|
|
#endif /* HAVE_LIBPCRE2_POSIX */
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#include "debug.h"
|
|
|
|
#include "linklist.h"
|
|
|
|
#include "frrevent.h"
|
|
|
|
#include "buffer.h"
|
|
|
|
#include "command.h"
|
|
|
|
#include "sockunion.h"
|
|
|
|
#include "memory.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "prefix.h"
|
|
|
|
#include "filter.h"
|
|
|
|
#include "vty.h"
|
|
|
|
#include "privs.h"
|
|
|
|
#include "network.h"
|
|
|
|
#include "libfrr.h"
|
|
|
|
#include "frrstr.h"
|
|
|
|
#include "lib_errors.h"
|
|
|
|
#include <libyang/version.h>
|
|
|
|
#include "northbound_cli.h"
|
|
|
|
#include "printfrr.h"
|
|
|
|
#include "json.h"
|
|
|
|
|
|
|
|
#include <arpa/telnet.h>
|
|
|
|
#include <termios.h>
|
|
|
|
|
|
|
|
#include "lib/config_paths.h"
|
|
|
|
|
|
|
|
#include "lib/vty_clippy.c"
|
|
|
|
|
|
|
|
DEFINE_MTYPE_STATIC(LIB, VTY, "VTY");
|
|
|
|
DEFINE_MTYPE_STATIC(LIB, VTY_SERV, "VTY server");
|
|
|
|
DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer");
|
|
|
|
DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history");
|
|
|
|
|
|
|
|
DECLARE_DLIST(vtys, struct vty, itm);
|
|
|
|
|
|
|
|
/* Vty events */
|
|
|
|
enum vty_event {
|
|
|
|
VTY_SERV,
|
|
|
|
VTY_READ,
|
|
|
|
VTY_WRITE,
|
|
|
|
VTY_TIMEOUT_RESET,
|
|
|
|
#ifdef VTYSH
|
|
|
|
VTYSH_SERV,
|
|
|
|
VTYSH_READ,
|
|
|
|
VTYSH_WRITE
|
|
|
|
#endif /* VTYSH */
|
|
|
|
};
|
|
|
|
|
|
|
|
struct nb_config *vty_mgmt_candidate_config;
|
|
|
|
|
|
|
|
static struct mgmt_fe_client *mgmt_fe_client;
|
|
|
|
static bool mgmt_fe_connected;
|
|
|
|
static uint64_t mgmt_client_id_next;
|
|
|
|
static uint64_t mgmt_last_req_id = UINT64_MAX;
|
|
|
|
|
|
|
|
PREDECL_DLIST(vtyservs);
|
|
|
|
|
|
|
|
struct vty_serv {
|
|
|
|
struct vtyservs_item itm;
|
|
|
|
|
|
|
|
int sock;
|
|
|
|
bool vtysh;
|
|
|
|
|
|
|
|
struct event *t_accept;
|
|
|
|
};
|
|
|
|
|
|
|
|
DECLARE_DLIST(vtyservs, struct vty_serv, itm);
|
|
|
|
|
|
|
|
static void vty_event_serv(enum vty_event event, struct vty_serv *);
|
|
|
|
static void vty_event(enum vty_event, struct vty *);
|
|
|
|
static int vtysh_flush(struct vty *vty);
|
|
|
|
|
|
|
|
/* Extern host structure from command.c */
|
|
|
|
extern struct host host;
|
|
|
|
|
|
|
|
/* active listeners */
|
|
|
|
static struct vtyservs_head vty_servs[1] = {INIT_DLIST(vty_servs[0])};
|
|
|
|
|
|
|
|
/* active connections */
|
|
|
|
static struct vtys_head vty_sessions[1] = {INIT_DLIST(vty_sessions[0])};
|
|
|
|
static struct vtys_head vtysh_sessions[1] = {INIT_DLIST(vtysh_sessions[0])};
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
|
|
|
/* Current directory. */
|
|
|
|
static char vty_cwd[MAXPATHLEN];
|
|
|
|
|
|
|
|
/* Login password check. */
|
|
|
|
static int no_password_check = 0;
|
|
|
|
|
|
|
|
/* Integrated configuration file path */
|
|
|
|
static char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG;
|
|
|
|
|
|
|
|
bool vty_log_commands;
|
|
|
|
static bool vty_log_commands_perm;
|
|
|
|
|
|
|
|
char const *const mgmt_daemons[] = {
|
|
|
|
"zebra",
|
|
|
|
#ifdef HAVE_RIPD
|
|
|
|
"ripd",
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_RIPNGD
|
|
|
|
"ripngd",
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_STATICD
|
|
|
|
"staticd",
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
uint mgmt_daemons_count = array_size(mgmt_daemons);
|
|
|
|
|
|
|
|
|
|
|
|
static int vty_mgmt_lock_candidate_inline(struct vty *vty)
|
|
|
|
{
|
|
|
|
assert(!vty->mgmt_locked_candidate_ds);
|
|
|
|
(void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true);
|
|
|
|
return vty->mgmt_locked_candidate_ds ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_unlock_candidate_inline(struct vty *vty)
|
|
|
|
{
|
|
|
|
assert(vty->mgmt_locked_candidate_ds);
|
|
|
|
(void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true);
|
|
|
|
return vty->mgmt_locked_candidate_ds ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_lock_running_inline(struct vty *vty)
|
|
|
|
{
|
|
|
|
assert(!vty->mgmt_locked_running_ds);
|
|
|
|
(void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true);
|
|
|
|
return vty->mgmt_locked_running_ds ? 0 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_unlock_running_inline(struct vty *vty)
|
|
|
|
{
|
|
|
|
assert(vty->mgmt_locked_running_ds);
|
|
|
|
(void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true);
|
|
|
|
return vty->mgmt_locked_running_ds ? -1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_mgmt_resume_response(struct vty *vty, int ret)
|
|
|
|
{
|
|
|
|
uint8_t header[4] = {0, 0, 0, 0};
|
|
|
|
|
|
|
|
if (!vty->mgmt_req_pending_cmd) {
|
|
|
|
zlog_err(
|
|
|
|
"vty resume response called without mgmt_req_pending_cmd");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_fe_client("resuming CLI cmd after %s on vty session-id: %" PRIu64
|
|
|
|
" with '%s'",
|
|
|
|
vty->mgmt_req_pending_cmd, vty->mgmt_session_id,
|
|
|
|
ret == CMD_SUCCESS ? "success" : "failed");
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = NULL;
|
|
|
|
|
|
|
|
if (vty->type != VTY_FILE) {
|
|
|
|
header[3] = ret;
|
|
|
|
buffer_put(vty->obuf, header, 4);
|
|
|
|
if (!vty->t_write && (vtysh_flush(vty) < 0)) {
|
|
|
|
zlog_err("failed to vtysh_flush");
|
|
|
|
/* Try to flush results; exit if a write error occurs */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vty->status == VTY_CLOSE)
|
|
|
|
vty_close(vty);
|
|
|
|
else if (vty->type != VTY_FILE)
|
|
|
|
vty_event(VTYSH_READ, vty);
|
|
|
|
else
|
|
|
|
/* should we assert here? */
|
|
|
|
zlog_err("mgmtd: unexpected resume while reading config file");
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_frame(struct vty *vty, const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
|
|
|
|
va_start(args, format);
|
|
|
|
vsnprintfrr(vty->frame + vty->frame_pos,
|
|
|
|
sizeof(vty->frame) - vty->frame_pos, format, args);
|
|
|
|
vty->frame_pos = strlen(vty->frame);
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_endframe(struct vty *vty, const char *endtext)
|
|
|
|
{
|
|
|
|
if (vty->frame_pos == 0 && endtext)
|
|
|
|
vty_out(vty, "%s", endtext);
|
|
|
|
vty->frame_pos = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool vty_set_include(struct vty *vty, const char *regexp)
|
|
|
|
{
|
|
|
|
int errcode;
|
|
|
|
bool ret = true;
|
|
|
|
char errbuf[256];
|
|
|
|
|
|
|
|
if (!regexp) {
|
|
|
|
if (vty->filter) {
|
|
|
|
regfree(&vty->include);
|
|
|
|
vty->filter = false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
errcode = regcomp(&vty->include, regexp,
|
|
|
|
REG_EXTENDED | REG_NEWLINE | REG_NOSUB);
|
|
|
|
if (errcode) {
|
|
|
|
ret = false;
|
|
|
|
regerror(errcode, &vty->include, errbuf, sizeof(errbuf));
|
|
|
|
vty_out(vty, "%% Regex compilation error: %s\n", errbuf);
|
|
|
|
} else {
|
|
|
|
vty->filter = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VTY standard output function. */
|
|
|
|
int vty_out(struct vty *vty, const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
ssize_t len;
|
|
|
|
char buf[1024];
|
|
|
|
char *p = NULL;
|
|
|
|
char *filtered;
|
|
|
|
/* format string may contain %m, keep errno intact for printfrr */
|
|
|
|
int saved_errno = errno;
|
|
|
|
|
|
|
|
if (vty->frame_pos) {
|
|
|
|
vty->frame_pos = 0;
|
|
|
|
vty_out(vty, "%s", vty->frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(args, format);
|
|
|
|
errno = saved_errno;
|
|
|
|
p = vasnprintfrr(MTYPE_VTY_OUT_BUF, buf, sizeof(buf), format, args);
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
len = strlen(p);
|
|
|
|
|
|
|
|
/* filter buffer */
|
|
|
|
if (vty->filter) {
|
|
|
|
vector lines = frrstr_split_vec(p, "\n");
|
|
|
|
|
|
|
|
/* Place first value in the cache */
|
|
|
|
char *firstline = vector_slot(lines, 0);
|
|
|
|
buffer_put(vty->lbuf, (uint8_t *) firstline, strlen(firstline));
|
|
|
|
|
|
|
|
/* If our split returned more than one entry, time to filter */
|
|
|
|
if (vector_active(lines) > 1) {
|
|
|
|
/*
|
|
|
|
* returned string is MTYPE_TMP so it matches the MTYPE
|
|
|
|
* of everything else in the vector
|
|
|
|
*/
|
|
|
|
char *bstr = buffer_getstr(vty->lbuf);
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
XFREE(MTYPE_TMP, lines->index[0]);
|
|
|
|
vector_set_index(lines, 0, bstr);
|
|
|
|
frrstr_filter_vec(lines, &vty->include);
|
|
|
|
vector_compact(lines);
|
|
|
|
/*
|
|
|
|
* Consider the string "foo\n". If the regex is an empty string
|
|
|
|
* and the line ended with a newline, then the vector will look
|
|
|
|
* like:
|
|
|
|
*
|
|
|
|
* [0]: 'foo'
|
|
|
|
* [1]: ''
|
|
|
|
*
|
|
|
|
* If the regex isn't empty, the vector will look like:
|
|
|
|
*
|
|
|
|
* [0]: 'foo'
|
|
|
|
*
|
|
|
|
* In this case we'd like to preserve the newline, so we add
|
|
|
|
* the empty string [1] as in the first example.
|
|
|
|
*/
|
|
|
|
if (p[strlen(p) - 1] == '\n' && vector_active(lines) > 0
|
|
|
|
&& strlen(vector_slot(lines, vector_active(lines) - 1)))
|
|
|
|
vector_set(lines, XSTRDUP(MTYPE_TMP, ""));
|
|
|
|
|
|
|
|
filtered = frrstr_join_vec(lines, "\n");
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
filtered = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
frrstr_strvec_free(lines);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
filtered = p;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!filtered)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
switch (vty->type) {
|
|
|
|
case VTY_TERM:
|
|
|
|
/* print with crlf replacement */
|
|
|
|
buffer_put_crlf(vty->obuf, (uint8_t *)filtered,
|
|
|
|
strlen(filtered));
|
|
|
|
break;
|
|
|
|
case VTY_SHELL:
|
|
|
|
if (vty->of) {
|
|
|
|
fprintf(vty->of, "%s", filtered);
|
|
|
|
fflush(vty->of);
|
|
|
|
} else if (vty->of_saved) {
|
|
|
|
fprintf(vty->of_saved, "%s", filtered);
|
|
|
|
fflush(vty->of_saved);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VTY_SHELL_SERV:
|
|
|
|
case VTY_FILE:
|
|
|
|
default:
|
2025-02-05 10:17:20 +01:00
|
|
|
vty->vty_buf_size_accumulated += strlen(filtered);
|
2025-02-05 10:03:58 +01:00
|
|
|
/* print without crlf replacement */
|
|
|
|
buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered));
|
2025-02-05 10:17:20 +01:00
|
|
|
/* For every chunk of memory, we invoke vtysh_flush where we
|
|
|
|
* put the data of collective vty->obuf Linked List items on the
|
|
|
|
* socket and free the vty->obuf data.
|
|
|
|
*/
|
|
|
|
if (vty->vty_buf_size_accumulated >= VTY_MAX_INTERMEDIATE_FLUSH) {
|
|
|
|
vty->vty_buf_size_accumulated = 0;
|
|
|
|
vtysh_flush(vty);
|
|
|
|
}
|
2025-02-05 10:03:58 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
|
|
|
|
if (vty->filter && filtered)
|
|
|
|
XFREE(MTYPE_TMP, filtered);
|
|
|
|
|
|
|
|
/* 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_json_helper(struct vty *vty, struct json_object *json,
|
|
|
|
uint32_t options)
|
|
|
|
{
|
|
|
|
const char *text;
|
|
|
|
|
|
|
|
if (!json)
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
|
|
|
|
text = json_object_to_json_string_ext(
|
|
|
|
json, options);
|
|
|
|
vty_out(vty, "%s\n", text);
|
|
|
|
json_object_free(json);
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_json(struct vty *vty, struct json_object *json)
|
|
|
|
{
|
|
|
|
return vty_json_helper(vty, json,
|
|
|
|
JSON_C_TO_STRING_PRETTY |
|
|
|
|
JSON_C_TO_STRING_NOSLASHESCAPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_json_no_pretty(struct vty *vty, struct json_object *json)
|
|
|
|
{
|
|
|
|
return vty_json_helper(vty, json, JSON_C_TO_STRING_NOSLASHESCAPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void vty_json_key(struct vty *vty, const char *key, bool *first_key)
|
|
|
|
{
|
|
|
|
vty_out(vty, "%s\"%s\":", *first_key ? "{" : ",", key);
|
|
|
|
*first_key = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_json_close(struct vty *vty, bool first_key)
|
|
|
|
{
|
|
|
|
if (first_key)
|
|
|
|
/* JSON was not opened */
|
|
|
|
vty_out(vty, "{");
|
|
|
|
vty_out(vty, "}\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_json_empty(struct vty *vty, struct json_object *json)
|
|
|
|
{
|
|
|
|
json_object *jsonobj = json;
|
|
|
|
|
|
|
|
if (!json)
|
|
|
|
jsonobj = json_object_new_object();
|
|
|
|
|
|
|
|
vty_json(vty, jsonobj);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Output current time to the vty. */
|
|
|
|
void vty_time_print(struct vty *vty, int cr)
|
|
|
|
{
|
|
|
|
char buf[FRR_TIMESTAMP_LEN];
|
|
|
|
|
|
|
|
if (frr_timestamp(0, buf, sizeof(buf)) == 0) {
|
|
|
|
zlog_info("frr_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((unsigned char)s[-1]);
|
|
|
|
s--)
|
|
|
|
;
|
|
|
|
*s = '\0';
|
|
|
|
vty_out(vty, "%s\n", buf);
|
|
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
} else
|
|
|
|
vty_out(vty, "MOTD file not found\n");
|
|
|
|
} else if (host.motd)
|
|
|
|
vty_out(vty, "%s", host.motd);
|
|
|
|
}
|
|
|
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
/* prompt formatting has a %s in the cmd_node prompt string.
|
|
|
|
*
|
|
|
|
* Also for some reason GCC emits the warning on the end of the function
|
|
|
|
* (optimization maybe?) rather than on the vty_out line, so this pragma
|
|
|
|
* wraps the entire function rather than just the vty_out line.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Put out prompt and wait input from user. */
|
|
|
|
static void vty_prompt(struct vty *vty)
|
|
|
|
{
|
|
|
|
if (vty->type == VTY_TERM) {
|
|
|
|
vty_out(vty, cmd_prompt(vty->node), cmd_hostname_get());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Authentication of vty */
|
|
|
|
static void vty_auth(struct vty *vty, char *buf)
|
|
|
|
{
|
|
|
|
char *passwd = NULL;
|
|
|
|
enum node_type next_node = 0;
|
|
|
|
int fail;
|
|
|
|
|
|
|
|
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!\n");
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
} else {
|
|
|
|
/* AUTH_ENABLE_NODE */
|
|
|
|
vty->fail = 0;
|
|
|
|
vty_out(vty,
|
|
|
|
"%% Bad enable passwords, too many failures!\n");
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Command execution over the vty interface. */
|
|
|
|
static int vty_command(struct vty *vty, char *buf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
const char *protocolname;
|
|
|
|
char *cp = NULL;
|
|
|
|
|
|
|
|
assert(vty);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log non empty command lines
|
|
|
|
*/
|
|
|
|
if (vty_log_commands &&
|
|
|
|
strncmp(buf, "echo PING", strlen("echo PING")) != 0)
|
|
|
|
cp = buf;
|
|
|
|
if (cp != NULL) {
|
|
|
|
/* Skip white spaces. */
|
|
|
|
while (isspace((unsigned char)*cp) && *cp != '\0')
|
|
|
|
cp++;
|
|
|
|
}
|
|
|
|
if (cp != NULL && *cp != '\0') {
|
|
|
|
char vty_str[VTY_BUFSIZ];
|
|
|
|
char prompt_str[VTY_BUFSIZ];
|
|
|
|
|
|
|
|
/* format the base vty info */
|
|
|
|
snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s", vty->fd,
|
|
|
|
vty->address);
|
|
|
|
|
|
|
|
/* format the prompt */
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
|
|
|
/* prompt formatting has a %s in the cmd_node prompt string */
|
|
|
|
snprintf(prompt_str, sizeof(prompt_str), cmd_prompt(vty->node),
|
|
|
|
vty_str);
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
|
|
|
/* now log the command */
|
|
|
|
zlog_notice("%s%s", prompt_str, buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
RUSAGE_T before;
|
|
|
|
RUSAGE_T after;
|
|
|
|
unsigned long walltime, cputime;
|
|
|
|
|
|
|
|
/* cmd_execute() may change cputime_enabled if we're executing the
|
|
|
|
* "service cputime-stats" command, which can result in nonsensical
|
|
|
|
* and very confusing warnings
|
|
|
|
*/
|
|
|
|
bool cputime_enabled_here = cputime_enabled;
|
|
|
|
|
|
|
|
GETRUSAGE(&before);
|
|
|
|
|
|
|
|
ret = cmd_execute(vty, buf, NULL, 0);
|
|
|
|
|
|
|
|
GETRUSAGE(&after);
|
|
|
|
|
|
|
|
walltime = event_consumed_time(&after, &before, &cputime);
|
|
|
|
|
|
|
|
if (cputime_enabled_here && cputime_enabled && cputime_threshold
|
|
|
|
&& cputime > cputime_threshold)
|
|
|
|
/* Warn about CPU hog that must be fixed. */
|
|
|
|
flog_warn(EC_LIB_SLOW_THREAD_CPU,
|
|
|
|
"CPU HOG: command took %lums (cpu time %lums): %s",
|
|
|
|
walltime / 1000, cputime / 1000, buf);
|
|
|
|
else if (walltime_threshold && walltime > walltime_threshold)
|
|
|
|
flog_warn(EC_LIB_SLOW_THREAD_WALL,
|
|
|
|
"STARVATION: command took %lums (cpu time %lums): %s",
|
|
|
|
walltime / 1000, cputime / 1000, buf);
|
|
|
|
|
|
|
|
/* Get the name of the protocol if any */
|
|
|
|
protocolname = frr_protoname;
|
|
|
|
|
|
|
|
if (ret != CMD_SUCCESS)
|
|
|
|
switch (ret) {
|
|
|
|
case CMD_WARNING:
|
|
|
|
if (vty->type == VTY_FILE)
|
|
|
|
vty_out(vty, "Warning...\n");
|
|
|
|
break;
|
|
|
|
case CMD_ERR_AMBIGUOUS:
|
|
|
|
vty_out(vty, "%% Ambiguous command.\n");
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NO_MATCH:
|
|
|
|
vty_out(vty, "%% [%s] Unknown command: %s\n",
|
|
|
|
protocolname, buf);
|
|
|
|
break;
|
|
|
|
case CMD_ERR_INCOMPLETE:
|
|
|
|
vty_out(vty, "%% Command incomplete.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Basic function to insert character into vty. */
|
|
|
|
static void vty_self_insert(struct vty *vty, char c)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int length;
|
|
|
|
|
|
|
|
if (vty->length + 1 >= VTY_BUFSIZ)
|
|
|
|
return;
|
|
|
|
|
|
|
|
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++;
|
|
|
|
|
|
|
|
vty->buf[vty->length] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Self insert character 'c' in overwrite mode. */
|
|
|
|
static void vty_self_insert_overwrite(struct vty *vty, char c)
|
|
|
|
{
|
|
|
|
if (vty->cp == vty->length) {
|
|
|
|
vty_self_insert(vty, c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->buf[vty->cp++] = c;
|
|
|
|
vty_write(vty, &c, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert a string into vty->buf at the current cursor position.
|
|
|
|
*
|
|
|
|
* If the resultant string would be larger than VTY_BUFSIZ it is
|
|
|
|
* truncated to fit.
|
|
|
|
*/
|
|
|
|
static void vty_insert_word_overwrite(struct vty *vty, char *str)
|
|
|
|
{
|
|
|
|
if (vty->cp == VTY_BUFSIZ)
|
|
|
|
return;
|
|
|
|
|
|
|
|
size_t nwrite = MIN((int)strlen(str), VTY_BUFSIZ - vty->cp - 1);
|
|
|
|
memcpy(&vty->buf[vty->cp], str, nwrite);
|
|
|
|
vty->cp += nwrite;
|
|
|
|
vty->length = MAX(vty->cp, vty->length);
|
|
|
|
vty->buf[vty->length] = '\0';
|
|
|
|
vty_write(vty, str, nwrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
vty->buf[vty->length] = '\0';
|
|
|
|
|
|
|
|
/* 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, "\n");
|
|
|
|
cmd_exit(vty);
|
|
|
|
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, "\n");
|
|
|
|
|
|
|
|
if (vty->config) {
|
|
|
|
vty_config_exit(vty);
|
|
|
|
vty->node = ENABLE_NODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty->cp = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Delete a character 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((unsigned char)vty->buf[vty->length - 1]))
|
|
|
|
vector_set(vline, NULL);
|
|
|
|
|
|
|
|
matched = cmd_complete_command(vline, vty, &ret);
|
|
|
|
|
|
|
|
cmd_free_strvec(vline);
|
|
|
|
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
switch (ret) {
|
|
|
|
case CMD_ERR_AMBIGUOUS:
|
|
|
|
vty_out(vty, "%% Ambiguous command.\n");
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NO_MATCH:
|
|
|
|
/* vty_out (vty, "%% There is no matched command.\n"); */
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
break;
|
|
|
|
case CMD_COMPLETE_FULL_MATCH:
|
|
|
|
if (!matched[0]) {
|
|
|
|
/* 2016-11-28 equinox -- need to debug, SEGV here */
|
|
|
|
vty_out(vty, "%% CLI BUG: FULL_MATCH with NULL str\n");
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
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_COMPLETION, 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_COMPLETION, matched[0]);
|
|
|
|
break;
|
|
|
|
case CMD_COMPLETE_LIST_MATCH:
|
|
|
|
for (i = 0; matched[i] != NULL; i++) {
|
|
|
|
if (i != 0 && ((i % 6) == 0))
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
vty_out(vty, "%-10s ", matched[i]);
|
|
|
|
XFREE(MTYPE_COMPLETION, matched[i]);
|
|
|
|
}
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NOTHING_TODO:
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
XFREE(MTYPE_TMP, 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->text;
|
|
|
|
|
|
|
|
if (desc_width <= 0) {
|
|
|
|
vty_out(vty, " %-*s %s\n", cmd_width, cmd, token->desc);
|
|
|
|
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;
|
|
|
|
|
|
|
|
memcpy(buf, p, pos);
|
|
|
|
buf[pos] = '\0';
|
|
|
|
vty_out(vty, " %-*s %s\n", cmd_width, cmd, buf);
|
|
|
|
|
|
|
|
cmd = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_out(vty, " %-*s %s\n", cmd_width, cmd, p);
|
|
|
|
|
|
|
|
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((unsigned char)vty->buf[vty->length - 1]))
|
|
|
|
vector_set(vline, NULL);
|
|
|
|
|
|
|
|
describe = cmd_describe_command(vline, vty, &ret);
|
|
|
|
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
|
|
|
|
/* Ambiguous error. */
|
|
|
|
switch (ret) {
|
|
|
|
case CMD_ERR_AMBIGUOUS:
|
|
|
|
vty_out(vty, "%% Ambiguous command.\n");
|
|
|
|
goto out;
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NO_MATCH:
|
|
|
|
vty_out(vty, "%% There is no matched command.\n");
|
|
|
|
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->text[0] == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
len = strlen(token->text);
|
|
|
|
|
|
|
|
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->text[0] == '\0')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (strcmp(token->text, CMD_CR_TEXT) == 0) {
|
|
|
|
token_cr = token;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!token->desc)
|
|
|
|
vty_out(vty, " %-s\n", token->text);
|
|
|
|
else if (desc_width >= strlen(token->desc))
|
|
|
|
vty_out(vty, " %-*s %s\n", width, token->text,
|
|
|
|
token->desc);
|
|
|
|
else
|
|
|
|
vty_describe_fold(vty, width, desc_width,
|
|
|
|
token);
|
|
|
|
|
|
|
|
if (IS_VARYING_TOKEN(token->type)) {
|
|
|
|
const char *ref = vector_slot(
|
|
|
|
vline, vector_active(vline) - 1);
|
|
|
|
|
|
|
|
vector varcomps = vector_init(VECTOR_MIN_SIZE);
|
|
|
|
cmd_variable_complete(token, ref, varcomps);
|
|
|
|
|
|
|
|
if (vector_active(varcomps) > 0) {
|
|
|
|
char *ac = cmd_variable_comp2str(
|
|
|
|
varcomps, vty->width);
|
|
|
|
vty_out(vty, "%s\n", ac);
|
|
|
|
XFREE(MTYPE_TMP, ac);
|
|
|
|
}
|
|
|
|
|
|
|
|
vector_free(varcomps);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((token = token_cr)) {
|
|
|
|
if (!token->desc)
|
|
|
|
vty_out(vty, " %-s\n", token->text);
|
|
|
|
else if (desc_width >= strlen(token->desc))
|
|
|
|
vty_out(vty, " %-*s %s\n", width, token->text,
|
|
|
|
token->desc);
|
|
|
|
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, "\n");
|
|
|
|
|
|
|
|
if (vty->config) {
|
|
|
|
vty_config_exit(vty);
|
|
|
|
vty->node = ENABLE_NODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
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. */
|
|
|
|
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 \n");
|
|
|
|
break;
|
|
|
|
case TELOPT_SGA:
|
|
|
|
vty_out(vty, "TELOPT_SGA \n");
|
|
|
|
break;
|
|
|
|
case TELOPT_NAWS:
|
|
|
|
vty_out(vty, "TELOPT_NAWS \n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
vty_out(vty, "%x ", buf[i]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
|
|
|
|
#endif /* TELNET_OPTION_DEBUG */
|
|
|
|
|
|
|
|
switch (buf[0]) {
|
|
|
|
case SB:
|
|
|
|
vty->sb_len = 0;
|
|
|
|
vty->iac_sb_in_progress = 1;
|
|
|
|
return 0;
|
|
|
|
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)
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_SYSTEM_CALL,
|
|
|
|
"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)
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_DEVELOPMENT,
|
|
|
|
"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\n",
|
|
|
|
vty->width, vty->height);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vty->iac_sb_in_progress = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
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
|
|
|
|
#define VTY_CR 3
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
vty_prompt(vty);
|
|
|
|
vty_redraw_line(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read data via vty socket. */
|
|
|
|
static void vty_read(struct event *thread)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int nbytes;
|
|
|
|
unsigned char buf[VTY_READ_BUFSIZ];
|
|
|
|
|
|
|
|
struct vty *vty = EVENT_ARG(thread);
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_SOCKET,
|
|
|
|
"%s: read error on vty client fd %d, closing: %s",
|
|
|
|
__func__, vty->fd, safe_strerror(errno));
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vty->escape == VTY_CR) {
|
|
|
|
/* if we get CR+NL, the NL results in an extra empty
|
|
|
|
* prompt line being printed without this; just drop
|
|
|
|
* the NL if it immediately follows CR.
|
|
|
|
*/
|
|
|
|
vty->escape = VTY_NORMAL;
|
|
|
|
|
|
|
|
if (buf[i] == '\n')
|
|
|
|
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 '\r':
|
|
|
|
vty->escape = VTY_CR;
|
|
|
|
fallthrough;
|
|
|
|
case '\n':
|
|
|
|
vty_out(vty, "\n");
|
|
|
|
buffer_flush_available(vty->obuf, vty->wfd);
|
|
|
|
vty_execute(vty);
|
|
|
|
|
|
|
|
if (vty->pass_fd != -1) {
|
|
|
|
close(vty->pass_fd);
|
|
|
|
vty->pass_fd = -1;
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
vty_event(VTY_READ, vty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Flush buffer to the vty. */
|
|
|
|
static void vty_flush(struct event *thread)
|
|
|
|
{
|
|
|
|
int erase;
|
|
|
|
buffer_status_t flushrc;
|
|
|
|
struct vty *vty = EVENT_ARG(thread);
|
|
|
|
|
|
|
|
/* Tempolary disable read thread. */
|
|
|
|
if (vty->lines == 0)
|
|
|
|
EVENT_OFF(vty->t_read);
|
|
|
|
|
|
|
|
/* 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->wfd);
|
|
|
|
else if (vty->status == VTY_MORELINE)
|
|
|
|
flushrc = buffer_flush_window(vty->obuf, vty->wfd, vty->width,
|
|
|
|
1, erase, 0);
|
|
|
|
else
|
|
|
|
flushrc = buffer_flush_window(
|
|
|
|
vty->obuf, vty->wfd, vty->width,
|
|
|
|
vty->lines >= 0 ? vty->lines : vty->height, erase, 0);
|
|
|
|
switch (flushrc) {
|
|
|
|
case BUFFER_ERROR:
|
|
|
|
zlog_info("buffer_flush failed on vty client fd %d/%d, closing",
|
|
|
|
vty->fd, vty->wfd);
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty_close(vty);
|
|
|
|
return;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Allocate new vty struct. */
|
|
|
|
struct vty *vty_new(void)
|
|
|
|
{
|
|
|
|
struct vty *new = XCALLOC(MTYPE_VTY, sizeof(struct vty));
|
|
|
|
|
|
|
|
new->fd = new->wfd = -1;
|
|
|
|
new->of = stdout;
|
|
|
|
new->lbuf = buffer_new(0);
|
|
|
|
new->obuf = buffer_new(0); /* Use default buffer size. */
|
|
|
|
new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ);
|
|
|
|
new->max = VTY_BUFSIZ;
|
|
|
|
new->pass_fd = -1;
|
|
|
|
|
|
|
|
if (mgmt_fe_client) {
|
|
|
|
if (!mgmt_client_id_next)
|
|
|
|
mgmt_client_id_next++;
|
|
|
|
new->mgmt_client_id = mgmt_client_id_next++;
|
|
|
|
new->mgmt_session_id = 0;
|
|
|
|
mgmt_fe_create_client_session(
|
|
|
|
mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new);
|
|
|
|
/* we short-circuit create the session so it must be set now */
|
|
|
|
assertf(new->mgmt_session_id != 0,
|
|
|
|
"Failed to create client session for VTY");
|
|
|
|
}
|
|
|
|
|
|
|
|
return new;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
vty->xpath_index = 0;
|
|
|
|
memset(vty->xpath, 0, sizeof(vty->xpath));
|
|
|
|
vty->private_config = false;
|
|
|
|
vty->candidate_config = vty_shared_candidate_config;
|
|
|
|
vty->status = VTY_NORMAL;
|
|
|
|
vty->lines = -1;
|
|
|
|
vty->iac = 0;
|
|
|
|
vty->iac_sb_in_progress = 0;
|
|
|
|
vty->sb_len = 0;
|
|
|
|
|
|
|
|
vtys_add_tail(vty_sessions, vty);
|
|
|
|
|
|
|
|
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;
|
|
|
|
strlcpy(vty->address, buf, sizeof(vty->address));
|
|
|
|
if (no_password_check) {
|
|
|
|
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.\n");
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
vty_close(vty);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Say hello to the world. */
|
|
|
|
vty_hello(vty);
|
|
|
|
if (!no_password_check)
|
|
|
|
vty_out(vty, "\nUser Access Verification\n\n");
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
vty_event(VTY_READ, vty);
|
|
|
|
|
|
|
|
return vty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create vty for stdio */
|
|
|
|
static struct termios stdio_orig_termios;
|
|
|
|
static struct vty *stdio_vty = NULL;
|
|
|
|
static bool stdio_termios = false;
|
|
|
|
static void (*stdio_vty_atclose)(int isexit);
|
|
|
|
|
|
|
|
static void vty_stdio_reset(int isexit)
|
|
|
|
{
|
|
|
|
if (stdio_vty) {
|
|
|
|
if (stdio_termios)
|
|
|
|
tcsetattr(0, TCSANOW, &stdio_orig_termios);
|
|
|
|
stdio_termios = false;
|
|
|
|
|
|
|
|
stdio_vty = NULL;
|
|
|
|
|
|
|
|
if (stdio_vty_atclose)
|
|
|
|
stdio_vty_atclose(isexit);
|
|
|
|
stdio_vty_atclose = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_stdio_atexit(void)
|
|
|
|
{
|
|
|
|
vty_stdio_reset(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_stdio_suspend(void)
|
|
|
|
{
|
|
|
|
if (!stdio_vty)
|
|
|
|
return;
|
|
|
|
|
|
|
|
EVENT_OFF(stdio_vty->t_write);
|
|
|
|
EVENT_OFF(stdio_vty->t_read);
|
|
|
|
EVENT_OFF(stdio_vty->t_timeout);
|
|
|
|
|
|
|
|
if (stdio_termios)
|
|
|
|
tcsetattr(0, TCSANOW, &stdio_orig_termios);
|
|
|
|
stdio_termios = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_stdio_resume(void)
|
|
|
|
{
|
|
|
|
if (!stdio_vty)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!tcgetattr(0, &stdio_orig_termios)) {
|
|
|
|
struct termios termios;
|
|
|
|
|
|
|
|
termios = stdio_orig_termios;
|
|
|
|
termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
|
|
|
|
| IGNCR | ICRNL | IXON);
|
|
|
|
termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
|
|
|
|
termios.c_cflag &= ~(CSIZE | PARENB);
|
|
|
|
termios.c_cflag |= CS8;
|
|
|
|
tcsetattr(0, TCSANOW, &termios);
|
|
|
|
stdio_termios = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_prompt(stdio_vty);
|
|
|
|
|
|
|
|
/* Add read/write thread. */
|
|
|
|
vty_event(VTY_WRITE, stdio_vty);
|
|
|
|
vty_event(VTY_READ, stdio_vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_stdio_close(void)
|
|
|
|
{
|
|
|
|
if (!stdio_vty)
|
|
|
|
return;
|
|
|
|
vty_close(stdio_vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct vty *vty_stdio(void (*atclose)(int isexit))
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
strlcpy(vty->address, "console", sizeof(vty->address));
|
|
|
|
|
|
|
|
vty_stdio_resume();
|
|
|
|
return vty;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Accept connection from the network. */
|
|
|
|
static void vty_accept(struct event *thread)
|
|
|
|
{
|
|
|
|
struct vty_serv *vtyserv = EVENT_ARG(thread);
|
|
|
|
int vty_sock;
|
|
|
|
union sockunion su;
|
|
|
|
int ret;
|
|
|
|
unsigned int on;
|
|
|
|
int accept_sock = vtyserv->sock;
|
|
|
|
struct prefix p;
|
|
|
|
struct access_list *acl = NULL;
|
|
|
|
|
|
|
|
/* We continue hearing vty socket. */
|
|
|
|
vty_event_serv(VTY_SERV, vtyserv);
|
|
|
|
|
|
|
|
memset(&su, 0, sizeof(union sockunion));
|
|
|
|
|
|
|
|
/* We can handle IPv4 or IPv6 socket. */
|
|
|
|
vty_sock = sockunion_accept(accept_sock, &su);
|
|
|
|
if (vty_sock < 0) {
|
|
|
|
flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
|
|
|
|
safe_strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
set_nonblocking(vty_sock);
|
|
|
|
set_cloexec(vty_sock);
|
|
|
|
|
|
|
|
if (!sockunion2hostprefix(&su, &p)) {
|
|
|
|
close(vty_sock);
|
|
|
|
zlog_info("Vty unable to convert prefix from sockunion %pSU",
|
|
|
|
&su);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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_info("Vty connection refused from %pSU", &su);
|
|
|
|
close(vty_sock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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_info("Vty connection refused from %pSU", &su);
|
|
|
|
close(vty_sock);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
on = 1;
|
|
|
|
ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on,
|
|
|
|
sizeof(on));
|
|
|
|
if (ret < 0)
|
|
|
|
zlog_info("can't set sockopt to vty_sock : %s",
|
|
|
|
safe_strerror(errno));
|
|
|
|
|
|
|
|
zlog_info("Vty connection from %pSU", &su);
|
|
|
|
|
|
|
|
vty_create(vty_sock, &su);
|
|
|
|
}
|
|
|
|
|
|
|
|
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(req));
|
|
|
|
req.ai_flags = AI_PASSIVE;
|
|
|
|
req.ai_family = AF_UNSPEC;
|
|
|
|
req.ai_socktype = SOCK_STREAM;
|
|
|
|
snprintf(port_str, sizeof(port_str), "%d", port);
|
|
|
|
port_str[sizeof(port_str) - 1] = '\0';
|
|
|
|
|
|
|
|
ret = getaddrinfo(hostname, port_str, &req, &ainfo);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
|
|
|
flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s",
|
|
|
|
gai_strerror(ret));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
ainfo_save = ainfo;
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct vty_serv *vtyserv;
|
|
|
|
|
|
|
|
if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6)
|
|
|
|
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);
|
|
|
|
set_cloexec(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv));
|
|
|
|
vtyserv->sock = sock;
|
|
|
|
vtyservs_add_tail(vty_servs, vtyserv);
|
|
|
|
|
|
|
|
vty_event_serv(VTY_SERV, vtyserv);
|
|
|
|
} while ((ainfo = ainfo->ai_next) != NULL);
|
|
|
|
|
|
|
|
freeaddrinfo(ainfo_save);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef VTYSH
|
|
|
|
/* For sockaddr_un. */
|
|
|
|
#include <sys/un.h>
|
|
|
|
|
|
|
|
/* VTY shell UNIX domain socket. */
|
|
|
|
static void vty_serv_un(const char *path)
|
|
|
|
{
|
|
|
|
struct vty_serv *vtyserv;
|
|
|
|
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) {
|
|
|
|
flog_err_sys(EC_LIB_SOCKET,
|
|
|
|
"Cannot create unix stream socket: %s",
|
|
|
|
safe_strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Make server socket. */
|
|
|
|
memset(&serv, 0, sizeof(serv));
|
|
|
|
serv.sun_family = AF_UNIX;
|
|
|
|
strlcpy(serv.sun_path, path, sizeof(serv.sun_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 */
|
|
|
|
|
|
|
|
set_cloexec(sock);
|
|
|
|
|
|
|
|
ret = bind(sock, (struct sockaddr *)&serv, len);
|
|
|
|
if (ret < 0) {
|
|
|
|
flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path,
|
|
|
|
safe_strerror(errno));
|
|
|
|
close(sock); /* Avoid sd leak. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = listen(sock, 5);
|
|
|
|
if (ret < 0) {
|
|
|
|
flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock,
|
|
|
|
safe_strerror(errno));
|
|
|
|
close(sock); /* Avoid sd leak. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
umask(old_mask);
|
|
|
|
|
|
|
|
zprivs_get_ids(&ids);
|
|
|
|
|
|
|
|
/* Hack: ids.gid_vty is actually a uint, but we stored -1 in it
|
|
|
|
earlier for the case when we don't need to chown the file
|
|
|
|
type casting it here to make a compare */
|
|
|
|
if ((int)ids.gid_vty > 0) {
|
|
|
|
/* set group of socket */
|
|
|
|
if (chown(path, -1, ids.gid_vty)) {
|
|
|
|
flog_err_sys(EC_LIB_SYSTEM_CALL,
|
|
|
|
"vty_serv_un: could chown socket, %s",
|
|
|
|
safe_strerror(errno));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv));
|
|
|
|
vtyserv->sock = sock;
|
|
|
|
vtyserv->vtysh = true;
|
|
|
|
vtyservs_add_tail(vty_servs, vtyserv);
|
|
|
|
|
|
|
|
vty_event_serv(VTYSH_SERV, vtyserv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* #define VTYSH_DEBUG 1 */
|
|
|
|
|
|
|
|
static void vtysh_accept(struct event *thread)
|
|
|
|
{
|
|
|
|
struct vty_serv *vtyserv = EVENT_ARG(thread);
|
|
|
|
int accept_sock = vtyserv->sock;
|
|
|
|
int sock;
|
|
|
|
int client_len;
|
|
|
|
struct sockaddr_un client;
|
|
|
|
struct vty *vty;
|
2025-02-05 10:17:20 +01:00
|
|
|
int ret = 0;
|
|
|
|
uint32_t sndbufsize = VTY_SEND_BUF_MAX;
|
2025-02-05 10:03:58 +01:00
|
|
|
|
|
|
|
vty_event_serv(VTYSH_SERV, vtyserv);
|
|
|
|
|
|
|
|
memset(&client, 0, sizeof(client));
|
|
|
|
client_len = sizeof(struct sockaddr_un);
|
|
|
|
|
|
|
|
sock = accept(accept_sock, (struct sockaddr *)&client,
|
|
|
|
(socklen_t *)&client_len);
|
|
|
|
|
|
|
|
if (sock < 0) {
|
|
|
|
flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s",
|
|
|
|
safe_strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (set_nonblocking(sock) < 0) {
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_SOCKET,
|
|
|
|
"vtysh_accept: could not set vty socket %d to non-blocking, %s, closing",
|
|
|
|
sock, safe_strerror(errno));
|
|
|
|
close(sock);
|
|
|
|
return;
|
|
|
|
}
|
2025-02-05 10:17:20 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Increasing the SEND socket buffer size so that the socket can hold
|
|
|
|
* before sending it to VTY shell.
|
|
|
|
*/
|
|
|
|
ret = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sndbufsize,
|
|
|
|
sizeof(sndbufsize));
|
|
|
|
if (ret < 0) {
|
|
|
|
flog_err(EC_LIB_SOCKET,
|
|
|
|
"Cannot set socket %d send buffer size, %s", sock,
|
|
|
|
safe_strerror(errno));
|
|
|
|
close(sock);
|
|
|
|
return;
|
|
|
|
}
|
2025-02-05 10:03:58 +01:00
|
|
|
set_cloexec(sock);
|
|
|
|
|
|
|
|
#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;
|
|
|
|
vtys_add_tail(vtysh_sessions, vty);
|
|
|
|
|
|
|
|
vty_event(VTYSH_READ, vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vtysh_do_pass_fd(struct vty *vty)
|
|
|
|
{
|
|
|
|
struct iovec iov[1] = {
|
|
|
|
{
|
|
|
|
.iov_base = vty->pass_fd_status,
|
|
|
|
.iov_len = sizeof(vty->pass_fd_status),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
union {
|
|
|
|
uint8_t buf[CMSG_SPACE(sizeof(int))];
|
|
|
|
struct cmsghdr align;
|
|
|
|
} u;
|
|
|
|
struct msghdr mh = {
|
|
|
|
.msg_iov = iov,
|
|
|
|
.msg_iovlen = array_size(iov),
|
|
|
|
.msg_control = u.buf,
|
|
|
|
.msg_controllen = sizeof(u.buf),
|
|
|
|
};
|
|
|
|
struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh);
|
|
|
|
ssize_t ret;
|
|
|
|
|
|
|
|
memset(&u.buf, 0, sizeof(u.buf));
|
|
|
|
cmh->cmsg_level = SOL_SOCKET;
|
|
|
|
cmh->cmsg_type = SCM_RIGHTS;
|
|
|
|
cmh->cmsg_len = CMSG_LEN(sizeof(int));
|
|
|
|
memcpy(CMSG_DATA(cmh), &vty->pass_fd, sizeof(int));
|
|
|
|
|
|
|
|
ret = sendmsg(vty->wfd, &mh, 0);
|
|
|
|
if (ret < 0 && ERRNO_IO_RETRY(errno))
|
|
|
|
return BUFFER_PENDING;
|
|
|
|
|
|
|
|
close(vty->pass_fd);
|
|
|
|
vty->pass_fd = -1;
|
|
|
|
vty->status = VTY_NORMAL;
|
|
|
|
|
|
|
|
if (ret <= 0)
|
|
|
|
return BUFFER_ERROR;
|
|
|
|
|
|
|
|
/* resume accepting commands (suspended in vtysh_read) */
|
|
|
|
vty_event(VTYSH_READ, vty);
|
|
|
|
|
|
|
|
if ((size_t)ret < sizeof(vty->pass_fd_status)) {
|
|
|
|
size_t remains = sizeof(vty->pass_fd_status) - ret;
|
|
|
|
|
|
|
|
buffer_put(vty->obuf, vty->pass_fd_status + ret, remains);
|
|
|
|
return BUFFER_PENDING;
|
|
|
|
}
|
|
|
|
return BUFFER_EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vtysh_flush(struct vty *vty)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = buffer_flush_available(vty->obuf, vty->wfd);
|
|
|
|
if (ret == BUFFER_EMPTY && vty->status == VTY_PASSFD)
|
|
|
|
ret = vtysh_do_pass_fd(vty);
|
|
|
|
|
|
|
|
switch (ret) {
|
|
|
|
case BUFFER_PENDING:
|
|
|
|
vty_event(VTYSH_WRITE, vty);
|
|
|
|
break;
|
|
|
|
case BUFFER_ERROR:
|
|
|
|
flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing",
|
|
|
|
__func__, vty->fd);
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty_close(vty);
|
|
|
|
return -1;
|
|
|
|
case BUFFER_EMPTY:
|
2025-02-05 10:17:20 +01:00
|
|
|
vty->vty_buf_size_accumulated = 0;
|
2025-02-05 10:03:58 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_pass_fd(struct vty *vty, int fd)
|
|
|
|
{
|
|
|
|
if (vty->pass_fd != -1)
|
|
|
|
close(vty->pass_fd);
|
|
|
|
|
|
|
|
vty->pass_fd = fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mgmt_vty_read_configs(void)
|
|
|
|
{
|
|
|
|
char path[PATH_MAX];
|
|
|
|
struct vty *vty;
|
|
|
|
FILE *confp;
|
|
|
|
uint line_num = 0;
|
|
|
|
uint count = 0;
|
|
|
|
uint index;
|
|
|
|
|
|
|
|
vty = vty_new();
|
|
|
|
vty->wfd = STDERR_FILENO;
|
|
|
|
vty->type = VTY_FILE;
|
|
|
|
vty->node = CONFIG_NODE;
|
|
|
|
vty->config = true;
|
|
|
|
vty->pending_allowed = true;
|
|
|
|
|
|
|
|
vty->candidate_config = vty_shared_candidate_config;
|
|
|
|
|
|
|
|
vty_mgmt_lock_candidate_inline(vty);
|
|
|
|
vty_mgmt_lock_running_inline(vty);
|
|
|
|
|
|
|
|
for (index = 0; index < array_size(mgmt_daemons); index++) {
|
|
|
|
snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir,
|
|
|
|
mgmt_daemons[index]);
|
|
|
|
|
|
|
|
confp = vty_open_config(path, config_default);
|
|
|
|
if (!confp)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
zlog_info("mgmtd: reading config file: %s", path);
|
|
|
|
|
|
|
|
/* Execute configuration file */
|
|
|
|
line_num = 0;
|
|
|
|
(void)config_from_file(vty, confp, &line_num);
|
|
|
|
count++;
|
|
|
|
|
|
|
|
fclose(confp);
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir);
|
|
|
|
confp = vty_open_config(path, config_default);
|
|
|
|
if (confp) {
|
|
|
|
zlog_info("mgmtd: reading config file: %s", path);
|
|
|
|
|
|
|
|
line_num = 0;
|
|
|
|
(void)config_from_file(vty, confp, &line_num);
|
|
|
|
count++;
|
|
|
|
|
|
|
|
fclose(confp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Conditionally unlock as the config file may have "exit"d early which
|
|
|
|
* would then have unlocked things.
|
|
|
|
*/
|
|
|
|
if (vty->mgmt_locked_running_ds)
|
|
|
|
vty_mgmt_unlock_running_inline(vty);
|
|
|
|
if (vty->mgmt_locked_candidate_ds)
|
|
|
|
vty_mgmt_unlock_candidate_inline(vty);
|
|
|
|
|
|
|
|
vty->pending_allowed = false;
|
|
|
|
|
|
|
|
if (!count)
|
|
|
|
vty_close(vty);
|
|
|
|
else
|
|
|
|
vty_read_file_finish(vty, NULL);
|
|
|
|
|
|
|
|
zlog_info("mgmtd: finished reading config files");
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vtysh_read(struct event *thread)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
int sock;
|
|
|
|
int nbytes;
|
|
|
|
struct vty *vty;
|
|
|
|
unsigned char buf[VTY_READ_BUFSIZ];
|
|
|
|
unsigned char *p;
|
|
|
|
uint8_t header[4] = {0, 0, 0, 0};
|
|
|
|
|
|
|
|
sock = EVENT_FD(thread);
|
|
|
|
vty = EVENT_ARG(thread);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This code looks like it can read multiple commands from the `buf`
|
|
|
|
* value returned by read(); however, it cannot in some cases.
|
|
|
|
*
|
|
|
|
* There are multiple paths out of the "copying to vty->buf" loop, which
|
|
|
|
* lose any content not yet copied from the stack `buf`, `passfd`,
|
|
|
|
* `CMD_SUSPEND` and finally if a front-end for mgmtd (generally this
|
|
|
|
* would be mgmtd itself). So these code paths are counting on vtysh not
|
|
|
|
* sending us more than 1 command line before waiting on the reply to
|
|
|
|
* that command.
|
|
|
|
*/
|
|
|
|
assert(vty->type == VTY_SHELL_SERV);
|
|
|
|
|
|
|
|
if ((nbytes = read(sock, buf, VTY_READ_BUFSIZ)) <= 0) {
|
|
|
|
if (nbytes < 0) {
|
|
|
|
if (ERRNO_IO_RETRY(errno)) {
|
|
|
|
vty_event(VTYSH_READ, vty);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_SOCKET,
|
|
|
|
"%s: read failed on vtysh client fd %d, closing: %s",
|
|
|
|
__func__, sock, safe_strerror(errno));
|
|
|
|
}
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty_close(vty);
|
|
|
|
#ifdef VTYSH_DEBUG
|
|
|
|
printf("close vtysh\n");
|
|
|
|
#endif /* VTYSH_DEBUG */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef VTYSH_DEBUG
|
|
|
|
printf("line: %.*s\n", nbytes, buf);
|
|
|
|
#endif /* VTYSH_DEBUG */
|
|
|
|
|
|
|
|
if (vty->length + nbytes >= VTY_BUFSIZ) {
|
|
|
|
/* Clear command line buffer. */
|
|
|
|
vty->cp = vty->length = 0;
|
|
|
|
vty_clear_buf(vty);
|
|
|
|
vty_out(vty, "%% Command is too long.\n");
|
|
|
|
} else {
|
|
|
|
for (p = buf; p < buf + nbytes; p++) {
|
|
|
|
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 */
|
|
|
|
if (vty->pass_fd >= 0) {
|
|
|
|
memset(vty->pass_fd_status, 0, 4);
|
|
|
|
vty->pass_fd_status[3] = ret;
|
|
|
|
vty->status = VTY_PASSFD;
|
|
|
|
|
|
|
|
if (!vty->t_write)
|
|
|
|
vty_event(VTYSH_WRITE, vty);
|
|
|
|
|
|
|
|
/* this introduces a "sequence point"
|
|
|
|
* command output is written normally,
|
|
|
|
* read processing is suspended until
|
|
|
|
* buffer is empty
|
|
|
|
* then retcode + FD is written
|
|
|
|
* then normal processing resumes
|
|
|
|
*
|
|
|
|
* => skip vty_event(VTYSH_READ, vty)!
|
|
|
|
*/
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
assertf(vty->status != VTY_PASSFD,
|
|
|
|
"%p address=%s passfd=%d", vty,
|
|
|
|
vty->address, vty->pass_fd);
|
|
|
|
|
|
|
|
/* normalize other invalid values */
|
|
|
|
vty->pass_fd = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* hack for asynchronous "write integrated"
|
|
|
|
* - other commands in "buf" will be ditched
|
|
|
|
* - input during pending config-write is
|
|
|
|
* "unsupported" */
|
|
|
|
if (ret == CMD_SUSPEND)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* with new infra we need to stop response till
|
|
|
|
* we get response through callback.
|
|
|
|
*/
|
|
|
|
if (vty->mgmt_req_pending_cmd) {
|
|
|
|
debug_fe_client("postpone CLI response pending mgmtd %s on vty session-id %" PRIu64,
|
|
|
|
vty->mgmt_req_pending_cmd,
|
|
|
|
vty->mgmt_session_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* warning: watchfrr hardcodes this result write
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vty->status == VTY_CLOSE)
|
|
|
|
vty_close(vty);
|
|
|
|
else
|
|
|
|
vty_event(VTYSH_READ, vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vtysh_write(struct event *thread)
|
|
|
|
{
|
|
|
|
struct vty *vty = EVENT_ARG(thread);
|
|
|
|
|
|
|
|
vtysh_flush(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* VTYSH */
|
|
|
|
|
|
|
|
/* Determine address family to bind. */
|
|
|
|
void vty_serv_start(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)
|
|
|
|
vty_serv_sock_addrinfo(addr, port);
|
|
|
|
|
|
|
|
#ifdef VTYSH
|
|
|
|
vty_serv_un(path);
|
|
|
|
#endif /* VTYSH */
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_serv_stop(void)
|
|
|
|
{
|
|
|
|
struct vty_serv *vtyserv;
|
|
|
|
|
|
|
|
while ((vtyserv = vtyservs_pop(vty_servs))) {
|
|
|
|
EVENT_OFF(vtyserv->t_accept);
|
|
|
|
close(vtyserv->sock);
|
|
|
|
XFREE(MTYPE_VTY_SERV, vtyserv);
|
|
|
|
}
|
|
|
|
|
|
|
|
vtyservs_fini(vty_servs);
|
|
|
|
vtyservs_init(vty_servs);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_error_delete(void *arg)
|
|
|
|
{
|
|
|
|
struct vty_error *ve = arg;
|
|
|
|
|
|
|
|
XFREE(MTYPE_TMP, ve);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
bool was_stdio = false;
|
|
|
|
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we reach here with pending config to commit we will be losing it
|
|
|
|
* so warn the user.
|
|
|
|
*/
|
|
|
|
if (vty->mgmt_num_pending_setcfg)
|
|
|
|
log_err_fe_client(
|
|
|
|
"vty closed, uncommitted config will be lost.");
|
|
|
|
|
|
|
|
/* Drop out of configure / transaction if needed. */
|
|
|
|
vty_config_exit(vty);
|
|
|
|
|
|
|
|
if (mgmt_fe_client && vty->mgmt_session_id) {
|
|
|
|
debug_fe_client("closing vty session");
|
|
|
|
mgmt_fe_destroy_client_session(mgmt_fe_client,
|
|
|
|
vty->mgmt_client_id);
|
|
|
|
vty->mgmt_session_id = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cancel threads.*/
|
|
|
|
EVENT_OFF(vty->t_read);
|
|
|
|
EVENT_OFF(vty->t_write);
|
|
|
|
EVENT_OFF(vty->t_timeout);
|
|
|
|
|
|
|
|
if (vty->pass_fd != -1) {
|
|
|
|
close(vty->pass_fd);
|
|
|
|
vty->pass_fd = -1;
|
|
|
|
}
|
|
|
|
zlog_live_close(&vty->live_log);
|
|
|
|
|
|
|
|
/* Flush buffer. */
|
|
|
|
buffer_flush_all(vty->obuf, vty->wfd);
|
|
|
|
|
|
|
|
/* Free input buffer. */
|
|
|
|
buffer_free(vty->obuf);
|
|
|
|
buffer_free(vty->lbuf);
|
|
|
|
|
|
|
|
/* Free command history. */
|
|
|
|
for (i = 0; i < VTY_MAXHIST; i++) {
|
|
|
|
XFREE(MTYPE_VTY_HIST, vty->hist[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unset vector. */
|
|
|
|
if (vty->fd != -1) {
|
|
|
|
if (vty->type == VTY_SHELL_SERV)
|
|
|
|
vtys_del(vtysh_sessions, vty);
|
|
|
|
else if (vty->type == VTY_TERM)
|
|
|
|
vtys_del(vty_sessions, vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vty->wfd > 0 && vty->type == VTY_FILE)
|
|
|
|
fsync(vty->wfd);
|
|
|
|
|
|
|
|
/* Close socket.
|
|
|
|
* note check is for fd > STDERR_FILENO, not fd != -1.
|
|
|
|
* We never close stdin/stdout/stderr here, because we may be
|
|
|
|
* running in foreground mode with logging to stdout. Also,
|
|
|
|
* additionally, we'd need to replace these fds with /dev/null. */
|
|
|
|
if (vty->wfd > STDERR_FILENO && vty->wfd != vty->fd)
|
|
|
|
close(vty->wfd);
|
|
|
|
if (vty->fd > STDERR_FILENO)
|
|
|
|
close(vty->fd);
|
|
|
|
if (vty->fd == STDIN_FILENO)
|
|
|
|
was_stdio = true;
|
|
|
|
|
|
|
|
XFREE(MTYPE_TMP, vty->pending_cmds_buf);
|
|
|
|
XFREE(MTYPE_VTY, vty->buf);
|
|
|
|
|
|
|
|
if (vty->error) {
|
|
|
|
vty->error->del = vty_error_delete;
|
|
|
|
list_delete(&vty->error);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OK free vty. */
|
|
|
|
XFREE(MTYPE_VTY, vty);
|
|
|
|
|
|
|
|
if (was_stdio)
|
|
|
|
vty_stdio_reset(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When time out occur output message then close connection. */
|
|
|
|
static void vty_timeout(struct event *thread)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
vty = EVENT_ARG(thread);
|
|
|
|
vty->v_timeout = 0;
|
|
|
|
|
|
|
|
/* Clear buffer*/
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty_out(vty, "\nVty connection is timed out.\n");
|
|
|
|
|
|
|
|
/* Close connection. */
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
vty_close(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read up configuration file from file_name. */
|
|
|
|
void vty_read_file(struct nb_config *config, FILE *confp)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
unsigned int line_num = 0;
|
|
|
|
|
|
|
|
vty = vty_new();
|
|
|
|
/* vty_close won't close stderr; if some config command prints
|
|
|
|
* something it'll end up there. (not ideal; it'd be better if output
|
|
|
|
* from a file-load went to logging instead. Also note that if this
|
|
|
|
* function is called after daemonizing, stderr will be /dev/null.)
|
|
|
|
*
|
|
|
|
* vty->fd will be -1 from vty_new()
|
|
|
|
*/
|
|
|
|
vty->wfd = STDERR_FILENO;
|
|
|
|
vty->type = VTY_FILE;
|
|
|
|
vty->node = CONFIG_NODE;
|
|
|
|
vty->config = true;
|
|
|
|
if (config)
|
|
|
|
vty->candidate_config = config;
|
|
|
|
else {
|
|
|
|
vty->private_config = true;
|
|
|
|
vty->candidate_config = nb_config_new(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Execute configuration file */
|
|
|
|
(void)config_from_file(vty, confp, &line_num);
|
|
|
|
|
|
|
|
vty_read_file_finish(vty, config);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_read_file_finish(struct vty *vty, struct nb_config *config)
|
|
|
|
{
|
|
|
|
struct vty_error *ve;
|
|
|
|
struct listnode *node;
|
|
|
|
|
|
|
|
/* Flush any previous errors before printing messages below */
|
|
|
|
buffer_flush_all(vty->obuf, vty->wfd);
|
|
|
|
|
|
|
|
for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) {
|
|
|
|
const char *message = NULL;
|
|
|
|
char *nl;
|
|
|
|
|
|
|
|
switch (ve->cmd_ret) {
|
|
|
|
case CMD_SUCCESS:
|
|
|
|
message = "Command succeeded";
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NOTHING_TODO:
|
|
|
|
message = "Nothing to do";
|
|
|
|
break;
|
|
|
|
case CMD_ERR_AMBIGUOUS:
|
|
|
|
message = "Ambiguous command";
|
|
|
|
break;
|
|
|
|
case CMD_ERR_NO_MATCH:
|
|
|
|
message = "No such command";
|
|
|
|
break;
|
|
|
|
case CMD_WARNING:
|
|
|
|
message = "Command returned Warning";
|
|
|
|
break;
|
|
|
|
case CMD_WARNING_CONFIG_FAILED:
|
|
|
|
message = "Command returned Warning Config Failed";
|
|
|
|
break;
|
|
|
|
case CMD_ERR_INCOMPLETE:
|
|
|
|
message = "Command returned Incomplete";
|
|
|
|
break;
|
|
|
|
case CMD_ERR_EXEED_ARGC_MAX:
|
|
|
|
message =
|
|
|
|
"Command exceeded maximum number of Arguments";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
message = "Command returned unhandled error message";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
nl = strchr(ve->error_buf, '\n');
|
|
|
|
if (nl)
|
|
|
|
*nl = '\0';
|
|
|
|
flog_err(EC_LIB_VTY, "%s on config line %u: %s", message,
|
|
|
|
ve->line_num, ve->error_buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Automatically commit the candidate configuration after
|
|
|
|
* reading the configuration file.
|
|
|
|
*/
|
|
|
|
if (config == NULL) {
|
|
|
|
struct nb_context context = {};
|
|
|
|
char errmsg[BUFSIZ] = {0};
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
context.client = NB_CLIENT_CLI;
|
|
|
|
context.user = vty;
|
|
|
|
ret = nb_candidate_commit(context, vty->candidate_config, true,
|
|
|
|
"Read configuration file", NULL,
|
|
|
|
errmsg, sizeof(errmsg));
|
|
|
|
if (ret != NB_OK && ret != NB_ERR_NO_CHANGES)
|
|
|
|
zlog_err(
|
|
|
|
"%s: failed to read configuration file: %s (%s)",
|
|
|
|
__func__, nb_err_name(ret), errmsg);
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_close(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
static FILE *vty_use_backup_config(const char *fullpath)
|
|
|
|
{
|
|
|
|
char *fullpath_sav, *fullpath_tmp;
|
|
|
|
FILE *ret = NULL;
|
|
|
|
int tmp, sav;
|
|
|
|
int c;
|
|
|
|
char buffer[512];
|
|
|
|
|
|
|
|
size_t fullpath_sav_sz = strlen(fullpath) + strlen(CONF_BACKUP_EXT) + 1;
|
|
|
|
fullpath_sav = malloc(fullpath_sav_sz);
|
|
|
|
strlcpy(fullpath_sav, fullpath, fullpath_sav_sz);
|
|
|
|
strlcat(fullpath_sav, CONF_BACKUP_EXT, fullpath_sav_sz);
|
|
|
|
|
|
|
|
sav = open(fullpath_sav, O_RDONLY);
|
|
|
|
if (sav < 0) {
|
|
|
|
free(fullpath_sav);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
fullpath_tmp = malloc(strlen(fullpath) + 8);
|
|
|
|
snprintf(fullpath_tmp, strlen(fullpath) + 8, "%s.XXXXXX", fullpath);
|
|
|
|
|
|
|
|
/* Open file to configuration write. */
|
|
|
|
tmp = mkstemp(fullpath_tmp);
|
|
|
|
if (tmp < 0)
|
|
|
|
goto out_close_sav;
|
|
|
|
|
|
|
|
if (fchmod(tmp, CONFIGFILE_MASK) != 0)
|
|
|
|
goto out_close;
|
|
|
|
|
|
|
|
while ((c = read(sav, buffer, 512)) > 0) {
|
|
|
|
if (write(tmp, buffer, c) <= 0)
|
|
|
|
goto out_close;
|
|
|
|
}
|
|
|
|
close(sav);
|
|
|
|
close(tmp);
|
|
|
|
|
|
|
|
if (rename(fullpath_tmp, fullpath) == 0)
|
|
|
|
ret = fopen(fullpath, "r");
|
|
|
|
else
|
|
|
|
unlink(fullpath_tmp);
|
|
|
|
|
|
|
|
if (0) {
|
|
|
|
out_close:
|
|
|
|
close(tmp);
|
|
|
|
unlink(fullpath_tmp);
|
|
|
|
out_close_sav:
|
|
|
|
close(sav);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(fullpath_sav);
|
|
|
|
free(fullpath_tmp);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE *vty_open_config(const char *config_file, char *config_default_dir)
|
|
|
|
{
|
|
|
|
char cwd[MAXPATHLEN];
|
|
|
|
FILE *confp = NULL;
|
|
|
|
const 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) {
|
|
|
|
flog_err_sys(
|
|
|
|
EC_LIB_SYSTEM_CALL,
|
|
|
|
"%s: failure to determine Current Working Directory %d!",
|
|
|
|
__func__, errno);
|
|
|
|
goto tmp_free_and_out;
|
|
|
|
}
|
|
|
|
size_t tmp_len = strlen(cwd) + strlen(config_file) + 2;
|
|
|
|
tmp = XMALLOC(MTYPE_TMP, tmp_len);
|
|
|
|
snprintf(tmp, tmp_len, "%s/%s", cwd, config_file);
|
|
|
|
fullpath = tmp;
|
|
|
|
} else
|
|
|
|
fullpath = config_file;
|
|
|
|
|
|
|
|
confp = fopen(fullpath, "r");
|
|
|
|
|
|
|
|
if (confp == NULL) {
|
|
|
|
flog_warn(
|
|
|
|
EC_LIB_BACKUP_CONFIG,
|
|
|
|
"%s: failed to open configuration file %s: %s, checking backup",
|
|
|
|
__func__, fullpath, safe_strerror(errno));
|
|
|
|
|
|
|
|
confp = vty_use_backup_config(fullpath);
|
|
|
|
if (confp)
|
|
|
|
flog_warn(EC_LIB_BACKUP_CONFIG,
|
|
|
|
"using backup configuration file!");
|
|
|
|
else {
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_VTY,
|
|
|
|
"%s: can't open configuration file [%s]",
|
|
|
|
__func__, config_file);
|
|
|
|
goto tmp_free_and_out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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) {
|
|
|
|
flog_err(
|
|
|
|
EC_LIB_SYSTEM_CALL,
|
|
|
|
"%s: failed to open configuration file %s: %s, checking backup",
|
|
|
|
__func__, config_default_dir,
|
|
|
|
safe_strerror(errno));
|
|
|
|
|
|
|
|
confp = vty_use_backup_config(config_default_dir);
|
|
|
|
if (confp) {
|
|
|
|
flog_warn(EC_LIB_BACKUP_CONFIG,
|
|
|
|
"using backup configuration file!");
|
|
|
|
fullpath = config_default_dir;
|
|
|
|
} else {
|
|
|
|
flog_err(EC_LIB_VTY,
|
|
|
|
"can't open configuration file [%s]",
|
|
|
|
config_default_dir);
|
|
|
|
goto tmp_free_and_out;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
fullpath = config_default_dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
host_config_set(fullpath);
|
|
|
|
|
|
|
|
tmp_free_and_out:
|
|
|
|
XFREE(MTYPE_TMP, tmp);
|
|
|
|
|
|
|
|
return confp;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool vty_read_config(struct nb_config *config, const char *config_file,
|
|
|
|
char *config_default_dir)
|
|
|
|
{
|
|
|
|
FILE *confp;
|
|
|
|
|
|
|
|
confp = vty_open_config(config_file, config_default_dir);
|
|
|
|
if (!confp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
vty_read_file(config, confp);
|
|
|
|
|
|
|
|
fclose(confp);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_config_enter(struct vty *vty, bool private_config, bool exclusive,
|
|
|
|
bool file_lock)
|
|
|
|
{
|
|
|
|
if (exclusive && !vty_mgmt_fe_enabled() &&
|
|
|
|
nb_running_lock(NB_CLIENT_CLI, vty)) {
|
|
|
|
vty_out(vty, "%% Configuration is locked by other client\n");
|
|
|
|
return CMD_WARNING;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only need to do a lock when reading a config file as we will be
|
|
|
|
* sending a batch of setcfg changes followed by a single commit
|
|
|
|
* message. For user interactive mode we are doing implicit commits
|
|
|
|
* those will obtain the lock (or not) when they try and commit.
|
|
|
|
*/
|
|
|
|
if (file_lock && vty_mgmt_fe_enabled() && !private_config) {
|
|
|
|
if (vty_mgmt_lock_candidate_inline(vty)) {
|
|
|
|
vty_out(vty,
|
|
|
|
"%% Can't enter config; candidate datastore locked by another session\n");
|
|
|
|
return CMD_WARNING_CONFIG_FAILED;
|
|
|
|
}
|
|
|
|
if (vty_mgmt_lock_running_inline(vty)) {
|
|
|
|
vty_out(vty,
|
|
|
|
"%% Can't enter config; running datastore locked by another session\n");
|
|
|
|
vty_mgmt_unlock_candidate_inline(vty);
|
|
|
|
return CMD_WARNING_CONFIG_FAILED;
|
|
|
|
}
|
|
|
|
assert(vty->mgmt_locked_candidate_ds);
|
|
|
|
assert(vty->mgmt_locked_running_ds);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* As datastores are locked explicitly, we don't need implicit
|
|
|
|
* commits and should allow pending changes.
|
|
|
|
*/
|
|
|
|
vty->pending_allowed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->node = CONFIG_NODE;
|
|
|
|
vty->config = true;
|
|
|
|
vty->private_config = private_config;
|
|
|
|
vty->xpath_index = 0;
|
|
|
|
|
|
|
|
if (private_config) {
|
|
|
|
vty->candidate_config = nb_config_dup(running_config);
|
|
|
|
vty->candidate_config_base = nb_config_dup(running_config);
|
|
|
|
vty_out(vty,
|
|
|
|
"Warning: uncommitted changes will be discarded on exit.\n\n");
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE: On the MGMTD daemon we point the VTY candidate DS to
|
|
|
|
* the global MGMTD candidate DS. Else we point to the VTY
|
|
|
|
* Shared Candidate Config.
|
|
|
|
*/
|
|
|
|
vty->candidate_config = vty_mgmt_candidate_config
|
|
|
|
? vty_mgmt_candidate_config
|
|
|
|
: vty_shared_candidate_config;
|
|
|
|
if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL)
|
|
|
|
vty->candidate_config_base = nb_config_dup(running_config);
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void vty_config_exit(struct vty *vty)
|
|
|
|
{
|
|
|
|
enum node_type node = vty->node;
|
|
|
|
struct cmd_node *cnode;
|
|
|
|
|
|
|
|
/* unlock and jump up to ENABLE_NODE if -and only if- we're
|
|
|
|
* somewhere below CONFIG_NODE */
|
|
|
|
while (node && node != CONFIG_NODE) {
|
|
|
|
cnode = vector_lookup(cmdvec, node);
|
|
|
|
node = cnode->parent_node;
|
|
|
|
}
|
|
|
|
if (node != CONFIG_NODE)
|
|
|
|
/* called outside config, e.g. vty_close() in ENABLE_NODE */
|
|
|
|
return;
|
|
|
|
|
|
|
|
while (vty->node != ENABLE_NODE)
|
|
|
|
/* will call vty_config_node_exit() below */
|
|
|
|
cmd_exit(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_config_node_exit(struct vty *vty)
|
|
|
|
{
|
|
|
|
vty->xpath_index = 0;
|
|
|
|
|
|
|
|
/* TODO: could we check for un-commited changes here? */
|
|
|
|
|
|
|
|
vty->pending_allowed = false;
|
|
|
|
|
|
|
|
if (vty->mgmt_locked_running_ds)
|
|
|
|
vty_mgmt_unlock_running_inline(vty);
|
|
|
|
|
|
|
|
if (vty->mgmt_locked_candidate_ds)
|
|
|
|
vty_mgmt_unlock_candidate_inline(vty);
|
|
|
|
|
|
|
|
/* Perform any pending commits. */
|
|
|
|
(void)nb_cli_pending_commit_check(vty);
|
|
|
|
|
|
|
|
/* Check if there's a pending confirmed commit. */
|
|
|
|
if (vty->t_confirmed_commit_timeout) {
|
|
|
|
vty_out(vty,
|
|
|
|
"exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n");
|
|
|
|
nb_cli_confirmed_commit_rollback(vty);
|
|
|
|
nb_cli_confirmed_commit_clean(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
(void)nb_running_unlock(NB_CLIENT_CLI, vty);
|
|
|
|
|
|
|
|
if (vty->candidate_config) {
|
|
|
|
if (vty->private_config)
|
|
|
|
nb_config_free(vty->candidate_config);
|
|
|
|
vty->candidate_config = NULL;
|
|
|
|
}
|
|
|
|
if (vty->candidate_config_base) {
|
|
|
|
nb_config_free(vty->candidate_config_base);
|
|
|
|
vty->candidate_config_base = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->config = false;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is a config file and we are dropping out of config end
|
|
|
|
* parsing.
|
|
|
|
*/
|
|
|
|
if (vty->type == VTY_FILE && vty->status != VTY_CLOSE) {
|
|
|
|
vty_out(vty, "exit from config node while reading config file");
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Master of the threads. */
|
|
|
|
static struct event_loop *vty_master;
|
|
|
|
|
|
|
|
static void vty_event_serv(enum vty_event event, struct vty_serv *vty_serv)
|
|
|
|
{
|
|
|
|
switch (event) {
|
|
|
|
case VTY_SERV:
|
|
|
|
event_add_read(vty_master, vty_accept, vty_serv, vty_serv->sock,
|
|
|
|
&vty_serv->t_accept);
|
|
|
|
break;
|
|
|
|
#ifdef VTYSH
|
|
|
|
case VTYSH_SERV:
|
|
|
|
event_add_read(vty_master, vtysh_accept, vty_serv,
|
|
|
|
vty_serv->sock, &vty_serv->t_accept);
|
|
|
|
break;
|
|
|
|
#endif /* VTYSH */
|
|
|
|
case VTY_READ:
|
|
|
|
case VTY_WRITE:
|
|
|
|
case VTY_TIMEOUT_RESET:
|
|
|
|
case VTYSH_READ:
|
|
|
|
case VTYSH_WRITE:
|
|
|
|
assert(!"vty_event_serv() called incorrectly");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_event(enum vty_event event, struct vty *vty)
|
|
|
|
{
|
|
|
|
switch (event) {
|
|
|
|
#ifdef VTYSH
|
|
|
|
case VTYSH_READ:
|
|
|
|
event_add_read(vty_master, vtysh_read, vty, vty->fd,
|
|
|
|
&vty->t_read);
|
|
|
|
break;
|
|
|
|
case VTYSH_WRITE:
|
|
|
|
event_add_write(vty_master, vtysh_write, vty, vty->wfd,
|
|
|
|
&vty->t_write);
|
|
|
|
break;
|
|
|
|
#endif /* VTYSH */
|
|
|
|
case VTY_READ:
|
|
|
|
event_add_read(vty_master, vty_read, vty, vty->fd,
|
|
|
|
&vty->t_read);
|
|
|
|
|
|
|
|
/* Time out treatment. */
|
|
|
|
if (vty->v_timeout) {
|
|
|
|
EVENT_OFF(vty->t_timeout);
|
|
|
|
event_add_timer(vty_master, vty_timeout, vty,
|
|
|
|
vty->v_timeout, &vty->t_timeout);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VTY_WRITE:
|
|
|
|
event_add_write(vty_master, vty_flush, vty, vty->wfd,
|
|
|
|
&vty->t_write);
|
|
|
|
break;
|
|
|
|
case VTY_TIMEOUT_RESET:
|
|
|
|
EVENT_OFF(vty->t_timeout);
|
|
|
|
if (vty->v_timeout)
|
|
|
|
event_add_timer(vty_master, vty_timeout, vty,
|
|
|
|
vty->v_timeout, &vty->t_timeout);
|
|
|
|
break;
|
|
|
|
case VTY_SERV:
|
|
|
|
case VTYSH_SERV:
|
|
|
|
assert(!"vty_event() called incorrectly");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFUN_NOSH (config_who,
|
|
|
|
config_who_cmd,
|
|
|
|
"who",
|
|
|
|
"Display who is on vty\n")
|
|
|
|
{
|
|
|
|
struct vty *v;
|
|
|
|
|
|
|
|
frr_each (vtys, vty_sessions, v)
|
|
|
|
vty_out(vty, "%svty[%d] connected from %s%s.\n",
|
|
|
|
v->config ? "*" : " ", v->fd, v->address,
|
|
|
|
zlog_live_is_null(&v->live_log) ? "" : ", live log");
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Move to vty configuration mode. */
|
|
|
|
DEFUN_NOSH (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, 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")
|
|
|
|
{
|
|
|
|
int idx_number = 1;
|
|
|
|
return exec_timeout(vty, argv[idx_number]->arg, 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")
|
|
|
|
{
|
|
|
|
int idx_number = 1;
|
|
|
|
int idx_number_2 = 2;
|
|
|
|
return exec_timeout(vty, argv[idx_number]->arg,
|
|
|
|
argv[idx_number_2]->arg);
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
int idx_word = 1;
|
|
|
|
if (vty_accesslist_name)
|
|
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
|
|
|
|
|
|
vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg);
|
|
|
|
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
int idx_word = 2;
|
|
|
|
const char *accesslist = (argc == 3) ? argv[idx_word]->arg : NULL;
|
|
|
|
if (!vty_accesslist_name
|
|
|
|
|| (argc == 3 && strcmp(vty_accesslist_name, accesslist))) {
|
|
|
|
vty_out(vty, "Access-class is not currently applied to vty\n");
|
|
|
|
return CMD_WARNING_CONFIG_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
|
|
|
|
|
|
vty_accesslist_name = NULL;
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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")
|
|
|
|
{
|
|
|
|
int idx_word = 2;
|
|
|
|
if (vty_ipv6_accesslist_name)
|
|
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
|
|
|
|
|
|
vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg);
|
|
|
|
|
|
|
|
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")
|
|
|
|
{
|
|
|
|
int idx_word = 3;
|
|
|
|
const char *accesslist = (argc == 4) ? argv[idx_word]->arg : NULL;
|
|
|
|
|
|
|
|
if (!vty_ipv6_accesslist_name
|
|
|
|
|| (argc == 4 && strcmp(vty_ipv6_accesslist_name, accesslist))) {
|
|
|
|
vty_out(vty,
|
|
|
|
"IPv6 access-class is not currently applied to vty\n");
|
|
|
|
return CMD_WARNING_CONFIG_FAILED;
|
|
|
|
}
|
|
|
|
|
|
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
|
|
|
|
|
|
vty_ipv6_accesslist_name = NULL;
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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_NOSH(terminal_monitor,
|
|
|
|
terminal_monitor_cmd,
|
|
|
|
"terminal monitor [detach]",
|
|
|
|
"Set terminal line parameters\n"
|
|
|
|
"Copy debug output to the current terminal line\n"
|
|
|
|
"Keep logging feed open independent of VTY session\n")
|
|
|
|
{
|
|
|
|
int fd_ret = -1;
|
|
|
|
|
|
|
|
if (vty->type != VTY_SHELL_SERV) {
|
|
|
|
vty_out(vty, "%% not supported\n");
|
|
|
|
return CMD_WARNING;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argc == 3) {
|
|
|
|
struct zlog_live_cfg detach_log = {};
|
|
|
|
|
|
|
|
zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret);
|
|
|
|
zlog_live_disown(&detach_log);
|
|
|
|
} else
|
|
|
|
zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret);
|
|
|
|
|
|
|
|
if (fd_ret == -1) {
|
|
|
|
vty_out(vty, "%% error opening live log: %m\n");
|
|
|
|
return CMD_WARNING;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_pass_fd(vty, fd_ret);
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFUN_NOSH(no_terminal_monitor,
|
|
|
|
no_terminal_monitor_cmd,
|
|
|
|
"no terminal monitor",
|
|
|
|
NO_STR
|
|
|
|
"Set terminal line parameters\n"
|
|
|
|
"Copy debug output to the current terminal line\n")
|
|
|
|
{
|
|
|
|
zlog_live_close(&vty->live_log);
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFUN_NOSH(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")
|
|
|
|
{
|
|
|
|
return no_terminal_monitor(self, vty, argc, argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DEFUN_NOSH (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\n", vty->hist[index]);
|
|
|
|
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vty login. */
|
|
|
|
DEFPY (log_commands,
|
|
|
|
log_commands_cmd,
|
|
|
|
"[no] log commands",
|
|
|
|
NO_STR
|
|
|
|
"Logging control\n"
|
|
|
|
"Log all commands\n")
|
|
|
|
{
|
|
|
|
if (no) {
|
|
|
|
if (vty_log_commands_perm) {
|
|
|
|
vty_out(vty,
|
|
|
|
"Daemon started with permanent logging turned on for commands, ignoring\n");
|
|
|
|
return CMD_WARNING;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_log_commands = false;
|
|
|
|
} else
|
|
|
|
vty_log_commands = true;
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Display current configuration. */
|
|
|
|
static int vty_config_write(struct vty *vty)
|
|
|
|
{
|
|
|
|
vty_frame(vty, "line vty\n");
|
|
|
|
|
|
|
|
if (vty_accesslist_name)
|
|
|
|
vty_out(vty, " access-class %s\n", vty_accesslist_name);
|
|
|
|
|
|
|
|
if (vty_ipv6_accesslist_name)
|
|
|
|
vty_out(vty, " ipv6 access-class %s\n",
|
|
|
|
vty_ipv6_accesslist_name);
|
|
|
|
|
|
|
|
/* exec-timeout */
|
|
|
|
if (vty_timeout_val != VTY_TIMEOUT_DEFAULT)
|
|
|
|
vty_out(vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60,
|
|
|
|
vty_timeout_val % 60);
|
|
|
|
|
|
|
|
/* login */
|
|
|
|
if (no_password_check)
|
|
|
|
vty_out(vty, " no login\n");
|
|
|
|
|
|
|
|
vty_endframe(vty, "exit\n");
|
|
|
|
|
|
|
|
if (vty_log_commands)
|
|
|
|
vty_out(vty, "log commands\n");
|
|
|
|
|
|
|
|
vty_out(vty, "!\n");
|
|
|
|
|
|
|
|
return CMD_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_config_write(struct vty *vty);
|
|
|
|
struct cmd_node vty_node = {
|
|
|
|
.name = "vty",
|
|
|
|
.node = VTY_NODE,
|
|
|
|
.parent_node = CONFIG_NODE,
|
|
|
|
.prompt = "%s(config-line)# ",
|
|
|
|
.config_write = vty_config_write,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Reset all VTY status. */
|
|
|
|
void vty_reset(void)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
frr_each_safe (vtys, vty_sessions, vty) {
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
vty_close(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_timeout_val = VTY_TIMEOUT_DEFAULT;
|
|
|
|
|
|
|
|
XFREE(MTYPE_VTY, vty_accesslist_name);
|
|
|
|
XFREE(MTYPE_VTY, vty_ipv6_accesslist_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_save_cwd(void)
|
|
|
|
{
|
|
|
|
char *c;
|
|
|
|
|
|
|
|
c = getcwd(vty_cwd, sizeof(vty_cwd));
|
|
|
|
|
|
|
|
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)) {
|
|
|
|
flog_err_sys(EC_LIB_SYSTEM_CALL,
|
|
|
|
"Failure to chdir to %s, errno: %d",
|
|
|
|
SYSCONFDIR, errno);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
if (getcwd(vty_cwd, sizeof(vty_cwd)) == NULL) {
|
|
|
|
flog_err_sys(EC_LIB_SYSTEM_CALL,
|
|
|
|
"Failure to getcwd, errno: %d", errno);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
char *vty_get_cwd(void)
|
|
|
|
{
|
|
|
|
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(void)
|
|
|
|
{
|
|
|
|
/* currently nothing to do, but likely to have future use */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* These functions allow for CLI handling to be placed inside daemons; however,
|
|
|
|
* currently they are only used by mgmtd, with mgmtd having each daemons CLI
|
|
|
|
* functionality linked into it. This design choice was taken for efficiency.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void vty_mgmt_server_connected(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t usr_data, bool connected)
|
|
|
|
{
|
|
|
|
debug_fe_client("Got %sconnected %s MGMTD Frontend Server",
|
|
|
|
!connected ? "dis: " : "", !connected ? "from" : "to");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We should not have any sessions for connecting or disconnecting case.
|
|
|
|
* The fe client library will delete all session on disconnect before
|
|
|
|
* calling us.
|
|
|
|
*/
|
|
|
|
assert(mgmt_fe_client_session_count(client) == 0);
|
|
|
|
|
|
|
|
mgmt_fe_connected = connected;
|
|
|
|
|
|
|
|
/* Start or stop listening for vty connections */
|
|
|
|
if (connected)
|
|
|
|
frr_vty_serv_start(true);
|
|
|
|
else
|
|
|
|
frr_vty_serv_stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A session has successfully been created for a vty.
|
|
|
|
*/
|
|
|
|
static void vty_mgmt_session_notify(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t usr_data, uint64_t client_id,
|
|
|
|
bool create, bool success,
|
|
|
|
uintptr_t session_id, uintptr_t session_ctx)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
zlog_err("%s session for client %" PRIu64 " failed!",
|
|
|
|
create ? "Creating" : "Destroying", client_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_fe_client("%s session for client %" PRIu64 " successfully",
|
|
|
|
create ? "Created" : "Destroyed", client_id);
|
|
|
|
|
|
|
|
if (create) {
|
|
|
|
assert(session_id != 0);
|
|
|
|
vty->mgmt_session_id = session_id;
|
|
|
|
} else {
|
|
|
|
vty->mgmt_session_id = 0;
|
|
|
|
/* We may come here by way of vty_close() and short-circuits */
|
|
|
|
if (vty->status != VTY_CLOSE)
|
|
|
|
vty_close(vty);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t usr_data, uint64_t client_id,
|
|
|
|
uintptr_t session_id,
|
|
|
|
uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
bool lock_ds, bool success,
|
|
|
|
Mgmtd__DatastoreId ds_id,
|
|
|
|
char *errmsg_if_any)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client);
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING);
|
|
|
|
if (!success)
|
|
|
|
zlog_err("%socking for DS %u failed, Err: '%s' vty %p",
|
|
|
|
lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty);
|
|
|
|
else {
|
|
|
|
debug_fe_client("%socked DS %u successfully",
|
|
|
|
lock_ds ? "L" : "Unl", ds_id);
|
|
|
|
if (ds_id == MGMTD_DS_CANDIDATE)
|
|
|
|
vty->mgmt_locked_candidate_ds = lock_ds;
|
|
|
|
else
|
|
|
|
vty->mgmt_locked_running_ds = lock_ds;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_short_circuit && vty->mgmt_req_pending_cmd) {
|
|
|
|
assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ"));
|
|
|
|
vty_mgmt_resume_response(vty,
|
|
|
|
success ? CMD_SUCCESS : CMD_WARNING);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_mgmt_set_config_result_notified(
|
|
|
|
struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id,
|
|
|
|
uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
bool success, Mgmtd__DatastoreId ds_id, bool implicit_commit,
|
|
|
|
char *errmsg_if_any)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
zlog_err("SET_CONFIG request for client 0x%" PRIx64
|
|
|
|
" failed, Error: '%s'",
|
|
|
|
client_id, errmsg_if_any ? errmsg_if_any : "Unknown");
|
|
|
|
vty_out(vty, "%% Configuration failed.\n\n");
|
|
|
|
if (errmsg_if_any)
|
|
|
|
vty_out(vty, "%s\n", errmsg_if_any);
|
|
|
|
} else {
|
|
|
|
debug_fe_client("SET_CONFIG request for client 0x%" PRIx64
|
|
|
|
" req-id %" PRIu64 " was successfull%s%s",
|
|
|
|
client_id, req_id, errmsg_if_any ? ": " : "",
|
|
|
|
errmsg_if_any ?: "");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (implicit_commit) {
|
|
|
|
/* In this case the changes have been applied, we are done */
|
|
|
|
vty_mgmt_unlock_candidate_inline(vty);
|
|
|
|
vty_mgmt_unlock_running_inline(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, success ? CMD_SUCCESS
|
|
|
|
: CMD_WARNING_CONFIG_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_mgmt_commit_config_result_notified(
|
|
|
|
struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id,
|
|
|
|
uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
bool success, Mgmtd__DatastoreId src_ds_id,
|
|
|
|
Mgmtd__DatastoreId dst_ds_id, bool validate_only, char *errmsg_if_any)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64
|
|
|
|
" failed, Error: '%s'",
|
|
|
|
client_id, errmsg_if_any ? errmsg_if_any : "Unknown");
|
|
|
|
vty_out(vty, "%% Configuration failed.\n\n");
|
|
|
|
if (errmsg_if_any)
|
|
|
|
vty_out(vty, "%s\n", errmsg_if_any);
|
|
|
|
} else {
|
|
|
|
debug_fe_client("COMMIT_CONFIG request for client 0x%" PRIx64
|
|
|
|
" req-id %" PRIu64 " was successfull%s%s",
|
|
|
|
client_id, req_id, errmsg_if_any ? ": " : "",
|
|
|
|
errmsg_if_any ?: "");
|
|
|
|
if (errmsg_if_any)
|
|
|
|
vty_out(vty, "MGMTD: %s\n", errmsg_if_any);
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, success ? CMD_SUCCESS
|
|
|
|
: CMD_WARNING_CONFIG_FAILED);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_get_data_result_notified(
|
|
|
|
struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id,
|
|
|
|
uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
bool success, Mgmtd__DatastoreId ds_id, Mgmtd__YangData **yang_data,
|
|
|
|
size_t num_data, int next_key, char *errmsg_if_any)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
size_t indx;
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
zlog_err("GET_DATA request for client 0x%" PRIx64
|
|
|
|
" failed, Error: '%s'",
|
|
|
|
client_id, errmsg_if_any ? errmsg_if_any : "Unknown");
|
|
|
|
vty_out(vty, "ERROR: GET_DATA request failed, Error: %s\n",
|
|
|
|
errmsg_if_any ? errmsg_if_any : "Unknown");
|
|
|
|
vty_mgmt_resume_response(vty, CMD_WARNING);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_fe_client("GET_DATA request succeeded, client 0x%" PRIx64
|
|
|
|
" req-id %" PRIu64 "%s%s",
|
|
|
|
client_id, req_id, errmsg_if_any ? ": " : "",
|
|
|
|
errmsg_if_any ?: "");
|
|
|
|
|
|
|
|
if (req_id != mgmt_last_req_id) {
|
|
|
|
mgmt_last_req_id = req_id;
|
|
|
|
vty_out(vty, "[\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
for (indx = 0; indx < num_data; indx++) {
|
|
|
|
vty_out(vty, " \"%s\": \"%s\"\n", yang_data[indx]->xpath,
|
|
|
|
yang_data[indx]->value->encoded_str_val);
|
|
|
|
}
|
|
|
|
if (next_key < 0) {
|
|
|
|
vty_out(vty, "]\n");
|
|
|
|
vty_mgmt_resume_response(vty, CMD_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t vty_mgmt_libyang_print(void *user_data, const void *buf,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct vty *vty = user_data;
|
|
|
|
|
|
|
|
vty_out(vty, "%.*s", (int)count, (const char *)buf);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vty_out_yang_error(struct vty *vty, LYD_FORMAT format,
|
|
|
|
const struct ly_err_item *ei)
|
|
|
|
{
|
|
|
|
#if (LY_VERSION_MAJOR < 3)
|
|
|
|
#define data_path path
|
|
|
|
#else
|
|
|
|
#define data_path data_path
|
|
|
|
#endif
|
|
|
|
bool have_apptag = ei->apptag && ei->apptag[0] != 0;
|
|
|
|
bool have_path = ei->data_path && ei->data_path[0] != 0;
|
|
|
|
bool have_msg = ei->msg && ei->msg[0] != 0;
|
|
|
|
const char *severity = NULL;
|
|
|
|
const char *evalid = NULL;
|
|
|
|
const char *ecode = NULL;
|
|
|
|
#if (LY_VERSION_MAJOR < 3)
|
|
|
|
LY_ERR err = ei->no;
|
|
|
|
#else
|
|
|
|
LY_ERR err = ei->err;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (ei->level == LY_LLERR)
|
|
|
|
severity = "error";
|
|
|
|
else if (ei->level == LY_LLWRN)
|
|
|
|
severity = "warning";
|
|
|
|
|
|
|
|
ecode = yang_ly_strerrcode(err);
|
|
|
|
if (err == LY_EVALID && ei->vecode != LYVE_SUCCESS)
|
|
|
|
evalid = yang_ly_strvecode(ei->vecode);
|
|
|
|
|
|
|
|
switch (format) {
|
|
|
|
case LYD_XML:
|
|
|
|
vty_out(vty,
|
|
|
|
"<rpc-error xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
|
|
|
|
vty_out(vty, "<error-type>application</error-type>");
|
|
|
|
if (severity)
|
|
|
|
vty_out(vty, "<error-severity>%s</error-severity>",
|
|
|
|
severity);
|
|
|
|
if (ecode)
|
|
|
|
vty_out(vty, "<error-code>%s</error-code>", ecode);
|
|
|
|
if (evalid)
|
|
|
|
vty_out(vty, "<error-validation>%s</error-validation>\n",
|
|
|
|
evalid);
|
|
|
|
if (have_path)
|
|
|
|
vty_out(vty, "<error-path>%s</error-path>\n",
|
|
|
|
ei->data_path);
|
|
|
|
if (have_apptag)
|
|
|
|
vty_out(vty, "<error-app-tag>%s</error-app-tag>\n",
|
|
|
|
ei->apptag);
|
|
|
|
if (have_msg)
|
|
|
|
vty_out(vty, "<error-message>%s</error-message>\n",
|
|
|
|
ei->msg);
|
|
|
|
|
|
|
|
vty_out(vty, "</rpc-error>");
|
|
|
|
break;
|
|
|
|
case LYD_JSON:
|
|
|
|
vty_out(vty, "{ \"error-type\": \"application\"");
|
|
|
|
if (severity)
|
|
|
|
vty_out(vty, ", \"error-severity\": \"%s\"", severity);
|
|
|
|
if (ecode)
|
|
|
|
vty_out(vty, ", \"error-code\": \"%s\"", ecode);
|
|
|
|
if (evalid)
|
|
|
|
vty_out(vty, ", \"error-validation\": \"%s\"", evalid);
|
|
|
|
if (have_path)
|
|
|
|
vty_out(vty, ", \"error-path\": \"%s\"", ei->data_path);
|
|
|
|
if (have_apptag)
|
|
|
|
vty_out(vty, ", \"error-app-tag\": \"%s\"", ei->apptag);
|
|
|
|
if (have_msg)
|
|
|
|
vty_out(vty, ", \"error-message\": \"%s\"", ei->msg);
|
|
|
|
|
|
|
|
vty_out(vty, "}");
|
|
|
|
break;
|
|
|
|
case LYD_UNKNOWN:
|
|
|
|
case LYD_LYB:
|
|
|
|
default:
|
|
|
|
vty_out(vty, "%% error");
|
|
|
|
if (severity)
|
|
|
|
vty_out(vty, " severity: %s", severity);
|
|
|
|
if (evalid)
|
|
|
|
vty_out(vty, " invalid: %s", evalid);
|
|
|
|
if (have_path)
|
|
|
|
vty_out(vty, " path: %s", ei->data_path);
|
|
|
|
if (have_apptag)
|
|
|
|
vty_out(vty, " app-tag: %s", ei->apptag);
|
|
|
|
if (have_msg)
|
|
|
|
vty_out(vty, " msg: %s", ei->msg);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#undef data_path
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint vty_out_yang_errors(struct vty *vty, LYD_FORMAT format)
|
|
|
|
{
|
|
|
|
const struct ly_err_item *ei = ly_err_first(ly_native_ctx);
|
|
|
|
uint count;
|
|
|
|
|
|
|
|
if (!ei)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (format == LYD_JSON)
|
|
|
|
vty_out(vty, "\"ietf-restconf:errors\": [ ");
|
|
|
|
|
|
|
|
for (count = 0; ei; count++, ei = ei->next) {
|
|
|
|
if (count)
|
|
|
|
vty_out(vty, ", ");
|
|
|
|
vty_out_yang_error(vty, format, ei);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (format == LYD_JSON)
|
|
|
|
vty_out(vty, " ]");
|
|
|
|
|
|
|
|
ly_err_clean(ly_native_ctx, NULL);
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int vty_mgmt_get_tree_result_notified(
|
|
|
|
struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id,
|
|
|
|
uint64_t session_id, uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
Mgmtd__DatastoreId ds_id, LYD_FORMAT result_type, void *result,
|
|
|
|
size_t len, int partial_error)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
struct lyd_node *dnode;
|
|
|
|
int ret = CMD_SUCCESS;
|
|
|
|
LY_ERR err;
|
|
|
|
|
|
|
|
vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
debug_fe_client("GET_TREE request %ssucceeded, client 0x%" PRIx64
|
|
|
|
" req-id %" PRIu64,
|
|
|
|
partial_error ? "partially " : "", client_id, req_id);
|
|
|
|
|
|
|
|
assert(result_type == LYD_LYB ||
|
|
|
|
result_type == vty->mgmt_req_pending_data);
|
|
|
|
|
|
|
|
if (vty->mgmt_req_pending_data == LYD_XML && partial_error)
|
|
|
|
vty_out(vty,
|
|
|
|
"<!-- some errors occurred gathering results -->\n");
|
|
|
|
|
|
|
|
if (result_type == LYD_LYB) {
|
|
|
|
/*
|
|
|
|
* parse binary into tree and print in the specified format
|
|
|
|
*/
|
|
|
|
result_type = vty->mgmt_req_pending_data;
|
|
|
|
|
|
|
|
err = lyd_parse_data_mem(ly_native_ctx, result, LYD_LYB, 0, 0,
|
|
|
|
&dnode);
|
|
|
|
if (!err)
|
|
|
|
err = lyd_print_clb(vty_mgmt_libyang_print, vty, dnode,
|
|
|
|
result_type, LYD_PRINT_WITHSIBLINGS);
|
|
|
|
lyd_free_all(dnode);
|
|
|
|
|
|
|
|
if (vty_out_yang_errors(vty, result_type) || err)
|
|
|
|
ret = CMD_WARNING;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Print the in-format result
|
|
|
|
*/
|
|
|
|
assert(result_type == LYD_XML || result_type == LYD_JSON);
|
|
|
|
vty_out(vty, "%.*s\n", (int)len - 1, (const char *)result);
|
|
|
|
}
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, ret);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t user_data,
|
|
|
|
uint64_t client_id, uint64_t session_id,
|
|
|
|
uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
const char *xpath)
|
|
|
|
{
|
|
|
|
struct vty *vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64
|
|
|
|
" was successful, xpath: %s",
|
|
|
|
client_id, req_id, xpath);
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, CMD_SUCCESS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_rpc_result_notified(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t user_data, uint64_t client_id,
|
|
|
|
uint64_t session_id,
|
|
|
|
uintptr_t session_ctx, uint64_t req_id,
|
|
|
|
const char *result)
|
|
|
|
{
|
|
|
|
struct vty *vty = (struct vty *)session_ctx;
|
|
|
|
|
|
|
|
debug_fe_client("RPC request for client 0x%" PRIx64 " req-id %" PRIu64
|
|
|
|
" was successful",
|
|
|
|
client_id, req_id);
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
vty_out(vty, "%s\n", result);
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, CMD_SUCCESS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vty_mgmt_error_notified(struct mgmt_fe_client *client,
|
|
|
|
uintptr_t user_data, uint64_t client_id,
|
|
|
|
uint64_t session_id, uintptr_t session_ctx,
|
|
|
|
uint64_t req_id, int error,
|
|
|
|
const char *errstr)
|
|
|
|
{
|
|
|
|
struct vty *vty = (struct vty *)session_ctx;
|
|
|
|
const char *cname = mgmt_fe_client_name(client);
|
|
|
|
|
|
|
|
if (!vty->mgmt_req_pending_cmd) {
|
2025-02-05 10:17:20 +01:00
|
|
|
debug_fe_client("Error with no pending command: %d returned for client %s 0x%" PRIx64
|
2025-02-05 10:03:58 +01:00
|
|
|
" session-id %" PRIu64 " req-id %" PRIu64
|
|
|
|
"error-str %s",
|
|
|
|
error, cname, client_id, session_id, req_id,
|
|
|
|
errstr);
|
|
|
|
vty_out(vty,
|
|
|
|
"%% Error %d from MGMTD for %s with no pending command: %s\n",
|
|
|
|
error, cname, errstr);
|
|
|
|
return CMD_WARNING;
|
|
|
|
}
|
|
|
|
|
2025-02-05 10:17:20 +01:00
|
|
|
debug_fe_client("Error %d returned for client %s 0x%" PRIx64
|
2025-02-05 10:03:58 +01:00
|
|
|
" session-id %" PRIu64 " req-id %" PRIu64 "error-str %s",
|
|
|
|
error, cname, client_id, session_id, req_id, errstr);
|
|
|
|
|
|
|
|
vty_out(vty, "%% %s (for %s, client %s)\n", errstr,
|
|
|
|
vty->mgmt_req_pending_cmd, cname);
|
|
|
|
|
|
|
|
vty_mgmt_resume_response(vty, error ? CMD_WARNING : CMD_SUCCESS);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct mgmt_fe_client_cbs mgmt_cbs = {
|
|
|
|
.client_connect_notify = vty_mgmt_server_connected,
|
|
|
|
.client_session_notify = vty_mgmt_session_notify,
|
|
|
|
.lock_ds_notify = vty_mgmt_ds_lock_notified,
|
|
|
|
.set_config_notify = vty_mgmt_set_config_result_notified,
|
|
|
|
.commit_config_notify = vty_mgmt_commit_config_result_notified,
|
|
|
|
.get_data_notify = vty_mgmt_get_data_result_notified,
|
|
|
|
.get_tree_notify = vty_mgmt_get_tree_result_notified,
|
|
|
|
.edit_notify = vty_mgmt_edit_result_notified,
|
|
|
|
.rpc_notify = vty_mgmt_rpc_result_notified,
|
|
|
|
.error_notify = vty_mgmt_error_notified,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
void vty_init_mgmt_fe(void)
|
|
|
|
{
|
|
|
|
char name[40];
|
|
|
|
|
|
|
|
assert(vty_master);
|
|
|
|
assert(!mgmt_fe_client);
|
|
|
|
snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(),
|
|
|
|
(long)getpid());
|
|
|
|
mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master);
|
|
|
|
assert(mgmt_fe_client);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool vty_mgmt_fe_enabled(void)
|
|
|
|
{
|
|
|
|
return mgmt_fe_client && mgmt_fe_connected;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty)
|
|
|
|
{
|
|
|
|
return vty->type != VTY_FILE && vty_mgmt_fe_enabled();
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id,
|
|
|
|
bool lock, bool scok)
|
|
|
|
{
|
|
|
|
assert(mgmt_fe_client);
|
|
|
|
assert(vty->mgmt_session_id);
|
|
|
|
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, ds_id, lock, scok)) {
|
|
|
|
zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64,
|
|
|
|
lock ? "" : "UN", vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n",
|
|
|
|
lock ? "" : "UN");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!scok)
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ";
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_config_data(struct vty *vty, const char *xpath_base,
|
|
|
|
bool implicit_commit)
|
|
|
|
{
|
|
|
|
Mgmtd__YangDataValue value[VTY_MAXCFGCHANGES];
|
|
|
|
Mgmtd__YangData cfg_data[VTY_MAXCFGCHANGES];
|
|
|
|
Mgmtd__YangCfgDataReq cfg_req[VTY_MAXCFGCHANGES];
|
|
|
|
Mgmtd__YangCfgDataReq *cfgreq[VTY_MAXCFGCHANGES] = {0};
|
|
|
|
char xpath[VTY_MAXCFGCHANGES][XPATH_MAXLEN];
|
|
|
|
char *change_xpath;
|
|
|
|
size_t indx;
|
|
|
|
|
|
|
|
if (vty->type == VTY_FILE) {
|
|
|
|
/*
|
|
|
|
* if this is a config file read we will not send any of the
|
|
|
|
* changes until we are done reading the file and have modified
|
|
|
|
* the local candidate DS.
|
|
|
|
*/
|
|
|
|
/* no-one else should be sending data right now */
|
|
|
|
assert(!vty->mgmt_num_pending_setcfg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If we are FE client and we have a vty then we have a session */
|
|
|
|
assert(mgmt_fe_client && vty->mgmt_client_id && vty->mgmt_session_id);
|
|
|
|
|
|
|
|
if (!vty->num_cfg_changes)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* grab the candidate and running lock prior to sending implicit commit
|
|
|
|
* command
|
|
|
|
*/
|
|
|
|
if (implicit_commit) {
|
|
|
|
if (vty_mgmt_lock_candidate_inline(vty)) {
|
|
|
|
vty_out(vty,
|
|
|
|
"%% command failed, could not lock candidate DS\n");
|
|
|
|
return -1;
|
|
|
|
} else if (vty_mgmt_lock_running_inline(vty)) {
|
|
|
|
vty_out(vty,
|
|
|
|
"%% command failed, could not lock running DS\n");
|
|
|
|
vty_mgmt_unlock_candidate_inline(vty);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (xpath_base == NULL)
|
|
|
|
xpath_base = "";
|
|
|
|
|
|
|
|
for (indx = 0; indx < vty->num_cfg_changes; indx++) {
|
|
|
|
mgmt_yang_data_init(&cfg_data[indx]);
|
|
|
|
|
|
|
|
if (vty->cfg_changes[indx].value) {
|
|
|
|
mgmt_yang_data_value_init(&value[indx]);
|
|
|
|
value[indx].encoded_str_val =
|
|
|
|
(char *)vty->cfg_changes[indx].value;
|
|
|
|
value[indx].value_case =
|
|
|
|
MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL;
|
|
|
|
cfg_data[indx].value = &value[indx];
|
|
|
|
}
|
|
|
|
|
|
|
|
change_xpath = vty->cfg_changes[indx].xpath;
|
|
|
|
|
|
|
|
memset(xpath[indx], 0, sizeof(xpath[indx]));
|
|
|
|
/* If change xpath is relative, prepend base xpath. */
|
|
|
|
if (change_xpath[0] == '.') {
|
|
|
|
strlcpy(xpath[indx], xpath_base, sizeof(xpath[indx]));
|
|
|
|
change_xpath++; /* skip '.' */
|
|
|
|
}
|
|
|
|
strlcat(xpath[indx], change_xpath, sizeof(xpath[indx]));
|
|
|
|
|
|
|
|
cfg_data[indx].xpath = xpath[indx];
|
|
|
|
|
|
|
|
mgmt_yang_cfg_data_req_init(&cfg_req[indx]);
|
|
|
|
cfg_req[indx].data = &cfg_data[indx];
|
|
|
|
switch (vty->cfg_changes[indx].operation) {
|
|
|
|
case NB_OP_DELETE:
|
|
|
|
cfg_req[indx].req_type =
|
|
|
|
MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NB_OP_DESTROY:
|
|
|
|
cfg_req[indx].req_type =
|
|
|
|
MGMTD__CFG_DATA_REQ_TYPE__REMOVE_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NB_OP_CREATE_EXCL:
|
|
|
|
cfg_req[indx].req_type =
|
|
|
|
MGMTD__CFG_DATA_REQ_TYPE__CREATE_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NB_OP_REPLACE:
|
|
|
|
cfg_req[indx].req_type =
|
|
|
|
MGMTD__CFG_DATA_REQ_TYPE__REPLACE_DATA;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NB_OP_CREATE:
|
|
|
|
case NB_OP_MODIFY:
|
|
|
|
case NB_OP_MOVE:
|
|
|
|
cfg_req[indx].req_type =
|
|
|
|
MGMTD__CFG_DATA_REQ_TYPE__SET_DATA;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assertf(false,
|
|
|
|
"Invalid operation type for send config: %d",
|
|
|
|
vty->cfg_changes[indx].operation);
|
|
|
|
/*NOTREACHED*/
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
cfgreq[indx] = &cfg_req[indx];
|
|
|
|
}
|
|
|
|
if (!indx)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
if (mgmt_fe_send_setcfg_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, MGMTD_DS_CANDIDATE,
|
|
|
|
cfgreq, indx, implicit_commit,
|
|
|
|
MGMTD_DS_RUNNING)) {
|
|
|
|
zlog_err("Failed to send %zu config xpaths to mgmtd", indx);
|
|
|
|
vty_out(vty, "%% Failed to send commands to mgmtd\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_SETCFG_REQ";
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort)
|
|
|
|
{
|
|
|
|
if (mgmt_fe_client && vty->mgmt_session_id) {
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
if (mgmt_fe_send_commitcfg_req(
|
|
|
|
mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, MGMTD_DS_CANDIDATE,
|
|
|
|
MGMTD_DS_RUNNING, validate_only, abort)) {
|
|
|
|
zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64,
|
|
|
|
vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ";
|
|
|
|
vty->mgmt_num_pending_setcfg = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_get_req(struct vty *vty, bool is_config,
|
|
|
|
Mgmtd__DatastoreId datastore, const char **xpath_list,
|
|
|
|
int num_req)
|
|
|
|
{
|
|
|
|
Mgmtd__YangData yang_data[VTY_MAXCFGCHANGES];
|
|
|
|
Mgmtd__YangGetDataReq get_req[VTY_MAXCFGCHANGES];
|
|
|
|
Mgmtd__YangGetDataReq *getreq[VTY_MAXCFGCHANGES];
|
|
|
|
int i;
|
|
|
|
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
|
|
|
|
for (i = 0; i < num_req; i++) {
|
|
|
|
mgmt_yang_get_data_req_init(&get_req[i]);
|
|
|
|
mgmt_yang_data_init(&yang_data[i]);
|
|
|
|
|
|
|
|
yang_data->xpath = (char *)xpath_list[i];
|
|
|
|
|
|
|
|
get_req[i].data = &yang_data[i];
|
|
|
|
getreq[i] = &get_req[i];
|
|
|
|
}
|
|
|
|
if (mgmt_fe_send_get_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, is_config, datastore, getreq,
|
|
|
|
num_req)) {
|
|
|
|
zlog_err("Failed to send GET- to MGMTD for req-id %" PRIu64 ".",
|
|
|
|
vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send GET-CONFIG to MGMTD!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_GETCFG_REQ";
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore,
|
|
|
|
LYD_FORMAT result_type, uint8_t flags,
|
|
|
|
uint8_t defaults, const char *xpath)
|
|
|
|
{
|
|
|
|
LYD_FORMAT intern_format = result_type;
|
|
|
|
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
|
|
|
|
if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, datastore,
|
|
|
|
intern_format, flags, defaults, xpath)) {
|
|
|
|
zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64
|
|
|
|
" req-id %" PRIu64 ".",
|
|
|
|
vty->mgmt_session_id, vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send GET-DATA to MGMTD!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_GET_DATA_REQ";
|
|
|
|
vty->mgmt_req_pending_data = result_type;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore,
|
|
|
|
LYD_FORMAT request_type, uint8_t flags,
|
|
|
|
uint8_t operation, const char *xpath,
|
|
|
|
const char *data)
|
|
|
|
{
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
|
|
|
|
if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, datastore, request_type,
|
|
|
|
flags, operation, xpath, data)) {
|
|
|
|
zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64
|
|
|
|
" req-id %" PRIu64 ".",
|
|
|
|
vty->mgmt_session_id, vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send EDIT to MGMTD!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ";
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vty_mgmt_send_rpc_req(struct vty *vty, LYD_FORMAT request_type,
|
|
|
|
const char *xpath, const char *data)
|
|
|
|
{
|
|
|
|
vty->mgmt_req_id++;
|
|
|
|
|
|
|
|
if (mgmt_fe_send_rpc_req(mgmt_fe_client, vty->mgmt_session_id,
|
|
|
|
vty->mgmt_req_id, request_type, xpath, data)) {
|
|
|
|
zlog_err("Failed to send RPC to MGMTD session-id: %" PRIu64
|
|
|
|
" req-id %" PRIu64 ".",
|
|
|
|
vty->mgmt_session_id, vty->mgmt_req_id);
|
|
|
|
vty_out(vty, "Failed to send RPC to MGMTD!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
vty->mgmt_req_pending_cmd = "MESSAGE_RPC_REQ";
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Install vty's own commands like `who' command. */
|
|
|
|
void vty_init(struct event_loop *master_thread, bool do_command_logging)
|
|
|
|
{
|
|
|
|
/* For further configuration read, preserve current directory. */
|
|
|
|
vty_save_cwd();
|
|
|
|
|
|
|
|
vty_master = master_thread;
|
|
|
|
|
|
|
|
atexit(vty_stdio_atexit);
|
|
|
|
|
|
|
|
/* Install bgp top node. */
|
|
|
|
install_node(&vty_node);
|
|
|
|
|
|
|
|
install_element(VIEW_NODE, &config_who_cmd);
|
|
|
|
install_element(VIEW_NODE, &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(CONFIG_NODE, &log_commands_cmd);
|
|
|
|
|
|
|
|
if (do_command_logging) {
|
|
|
|
vty_log_commands = true;
|
|
|
|
vty_log_commands_perm = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
install_element(ENABLE_NODE, &terminal_monitor_cmd);
|
|
|
|
install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
|
|
|
|
install_element(ENABLE_NODE, &no_terminal_monitor_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_ipv6_access_class_cmd);
|
|
|
|
install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vty_terminate(void)
|
|
|
|
{
|
|
|
|
struct vty *vty;
|
|
|
|
|
|
|
|
if (mgmt_fe_client) {
|
|
|
|
mgmt_fe_client_destroy(mgmt_fe_client);
|
|
|
|
mgmt_fe_client = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(vty_cwd, 0x00, sizeof(vty_cwd));
|
|
|
|
|
|
|
|
vty_reset();
|
|
|
|
|
|
|
|
/* default state of vty_sessions is initialized & empty. */
|
|
|
|
vtys_fini(vty_sessions);
|
|
|
|
vtys_init(vty_sessions);
|
|
|
|
|
|
|
|
/* vty_reset() doesn't close vtysh sessions */
|
|
|
|
frr_each_safe (vtys, vtysh_sessions, vty) {
|
|
|
|
buffer_reset(vty->lbuf);
|
|
|
|
buffer_reset(vty->obuf);
|
|
|
|
vty->status = VTY_CLOSE;
|
|
|
|
vty_close(vty);
|
|
|
|
}
|
|
|
|
|
|
|
|
vtys_fini(vtysh_sessions);
|
|
|
|
vtys_init(vtysh_sessions);
|
|
|
|
|
|
|
|
vty_serv_stop();
|
|
|
|
}
|