summaryrefslogtreecommitdiffstats
path: root/lib/command.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 09:53:30 +0000
commit2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch)
treec05dc0f8e6aa3accc84e3e5cffc933ed94941383 /lib/command.c
parentInitial commit. (diff)
downloadfrr-upstream/8.4.4.tar.xz
frr-upstream/8.4.4.zip
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/command.c')
-rw-r--r--lib/command.c2633
1 files changed, 2633 insertions, 0 deletions
diff --git a/lib/command.c b/lib/command.c
new file mode 100644
index 0000000..ca05cd6
--- /dev/null
+++ b/lib/command.c
@@ -0,0 +1,2633 @@
+/*
+ * CLI backend interface.
+ *
+ * --
+ * Copyright (C) 2016 Cumulus Networks, Inc.
+ * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ * Copyright (C) 2013 by Open Source Routing.
+ * Copyright (C) 2013 by Internet Systems Consortium, Inc. ("ISC")
+ *
+ * This file is part of GNU Zebra.
+ *
+ * GNU Zebra is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * GNU Zebra is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; see the file COPYING; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <zebra.h>
+#include <lib/version.h>
+
+#include "command.h"
+#include "frrstr.h"
+#include "memory.h"
+#include "log.h"
+#include "log_vty.h"
+#include "thread.h"
+#include "vector.h"
+#include "linklist.h"
+#include "vty.h"
+#include "workqueue.h"
+#include "vrf.h"
+#include "command_match.h"
+#include "command_graph.h"
+#include "qobj.h"
+#include "defaults.h"
+#include "libfrr.h"
+#include "jhash.h"
+#include "hook.h"
+#include "lib_errors.h"
+#include "northbound_cli.h"
+#include "network.h"
+
+#include "frrscript.h"
+
+DEFINE_MTYPE_STATIC(LIB, HOST, "Host config");
+DEFINE_MTYPE(LIB, COMPLETION, "Completion item");
+
+#define item(x) \
+ { \
+ x, #x \
+ }
+
+/* clang-format off */
+const struct message tokennames[] = {
+ item(WORD_TKN),
+ item(VARIABLE_TKN),
+ item(RANGE_TKN),
+ item(IPV4_TKN),
+ item(IPV4_PREFIX_TKN),
+ item(IPV6_TKN),
+ item(IPV6_PREFIX_TKN),
+ item(MAC_TKN),
+ item(MAC_PREFIX_TKN),
+ item(FORK_TKN),
+ item(JOIN_TKN),
+ item(START_TKN),
+ item(END_TKN),
+ item(NEG_ONLY_TKN),
+ {0},
+};
+/* clang-format on */
+
+/* Command vector which includes some level of command lists. Normally
+ each daemon maintains each own cmdvec. */
+vector cmdvec = NULL;
+
+/* Host information structure. */
+struct host host;
+
+/* for vtysh, put together CLI trees only when switching into node */
+static bool defer_cli_tree;
+
+/*
+ * Returns host.name if any, otherwise
+ * it returns the system hostname.
+ */
+const char *cmd_hostname_get(void)
+{
+ return host.name;
+}
+
+/*
+ * Returns unix domainname
+ */
+const char *cmd_domainname_get(void)
+{
+ return host.domainname;
+}
+
+const char *cmd_system_get(void)
+{
+ return host.system;
+}
+
+const char *cmd_release_get(void)
+{
+ return host.release;
+}
+
+const char *cmd_version_get(void)
+{
+ return host.version;
+}
+
+bool cmd_allow_reserved_ranges_get(void)
+{
+ return host.allow_reserved_ranges;
+}
+
+static int root_on_exit(struct vty *vty);
+
+/* Standard command node structures. */
+static struct cmd_node auth_node = {
+ .name = "auth",
+ .node = AUTH_NODE,
+ .prompt = "Password: ",
+};
+
+static struct cmd_node view_node = {
+ .name = "view",
+ .node = VIEW_NODE,
+ .prompt = "%s> ",
+ .node_exit = root_on_exit,
+};
+
+static struct cmd_node auth_enable_node = {
+ .name = "auth enable",
+ .node = AUTH_ENABLE_NODE,
+ .prompt = "Password: ",
+};
+
+static struct cmd_node enable_node = {
+ .name = "enable",
+ .node = ENABLE_NODE,
+ .prompt = "%s# ",
+ .node_exit = root_on_exit,
+};
+
+static int config_write_host(struct vty *vty);
+static struct cmd_node config_node = {
+ .name = "config",
+ .node = CONFIG_NODE,
+ .parent_node = ENABLE_NODE,
+ .prompt = "%s(config)# ",
+ .config_write = config_write_host,
+ .node_exit = vty_config_node_exit,
+};
+
+/* This is called from main when a daemon is invoked with -v or --version. */
+void print_version(const char *progname)
+{
+ printf("%s version %s\n", progname, FRR_VERSION);
+ printf("%s\n", FRR_COPYRIGHT);
+#ifdef ENABLE_VERSION_BUILD_CONFIG
+ printf("configured with:\n\t%s\n", FRR_CONFIG_ARGS);
+#endif
+}
+
+char *argv_concat(struct cmd_token **argv, int argc, int shift)
+{
+ int cnt = MAX(argc - shift, 0);
+ const char *argstr[cnt + 1];
+
+ if (!cnt)
+ return NULL;
+
+ for (int i = 0; i < cnt; i++)
+ argstr[i] = argv[i + shift]->arg;
+
+ return frrstr_join(argstr, cnt, " ");
+}
+
+vector cmd_make_strvec(const char *string)
+{
+ if (!string)
+ return NULL;
+
+ const char *copy = string;
+
+ /* skip leading whitespace */
+ while (isspace((unsigned char)*copy) && *copy != '\0')
+ copy++;
+
+ /* if the entire string was whitespace or a comment, return */
+ if (*copy == '\0' || *copy == '!' || *copy == '#')
+ return NULL;
+
+ vector result = frrstr_split_vec(copy, "\n\r\t ");
+
+ for (unsigned int i = 0; i < vector_active(result); i++) {
+ if (strlen(vector_slot(result, i)) == 0) {
+ XFREE(MTYPE_TMP, vector_slot(result, i));
+ vector_unset(result, i);
+ }
+ }
+
+ vector_compact(result);
+
+ return result;
+}
+
+void cmd_free_strvec(vector v)
+{
+ frrstr_strvec_free(v);
+}
+
+/**
+ * Convenience function for accessing argv data.
+ *
+ * @param argc
+ * @param argv
+ * @param text definition snippet of the desired token
+ * @param index the starting index, and where to store the
+ * index of the found token if it exists
+ * @return 1 if found, 0 otherwise
+ */
+int argv_find(struct cmd_token **argv, int argc, const char *text, int *index)
+{
+ int found = 0;
+ for (int i = *index; i < argc && found == 0; i++)
+ if ((found = strmatch(text, argv[i]->text)))
+ *index = i;
+ return found;
+}
+
+static unsigned int cmd_hash_key(const void *p)
+{
+ int size = sizeof(p);
+
+ return jhash(p, size, 0);
+}
+
+static bool cmd_hash_cmp(const void *a, const void *b)
+{
+ return a == b;
+}
+
+/* Install top node of command vector. */
+void install_node(struct cmd_node *node)
+{
+#define CMD_HASH_STR_SIZE 256
+ char hash_name[CMD_HASH_STR_SIZE];
+
+ vector_set_index(cmdvec, node->node, node);
+ node->cmdgraph = graph_new();
+ node->cmd_vector = vector_init(VECTOR_MIN_SIZE);
+ // add start node
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(node->cmdgraph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ snprintf(hash_name, sizeof(hash_name), "Command Hash: %s", node->name);
+ node->cmd_hash =
+ hash_create_size(16, cmd_hash_key, cmd_hash_cmp, hash_name);
+}
+
+/* Return prompt character of specified node. */
+const char *cmd_prompt(enum node_type node)
+{
+ struct cmd_node *cnode;
+
+ cnode = vector_slot(cmdvec, node);
+ return cnode->prompt;
+}
+
+void cmd_defer_tree(bool val)
+{
+ defer_cli_tree = val;
+}
+
+/* Install a command into a node. */
+void _install_element(enum node_type ntype, const struct cmd_element *cmd)
+{
+ struct cmd_node *cnode;
+
+ /* cmd_init hasn't been called */
+ if (!cmdvec) {
+ fprintf(stderr, "%s called before cmd_init, breakage likely\n",
+ __func__);
+ return;
+ }
+
+ cnode = vector_lookup(cmdvec, ntype);
+
+ if (cnode == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d does not exist.\n"
+ "\tplease call install_node() before install_element()\n",
+ cmd->name, cmd->string, ntype);
+ exit(EXIT_FAILURE);
+ }
+
+ if (hash_lookup(cnode->cmd_hash, (void *)cmd) != NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d (%s) already has this command installed.\n"
+ "\tduplicate install_element call?\n",
+ cmd->name, cmd->string, ntype, cnode->name);
+ return;
+ }
+
+ (void)hash_get(cnode->cmd_hash, (void *)cmd, hash_alloc_intern);
+
+ if (cnode->graph_built || !defer_cli_tree) {
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(graph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, +1);
+ graph_delete_graph(graph);
+
+ cnode->graph_built = true;
+ }
+
+ vector_set(cnode->cmd_vector, (void *)cmd);
+
+ if (ntype == VIEW_NODE)
+ _install_element(ENABLE_NODE, cmd);
+}
+
+static void cmd_finalize_iter(struct hash_bucket *hb, void *arg)
+{
+ struct cmd_node *cnode = arg;
+ const struct cmd_element *cmd = hb->data;
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+
+ graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, +1);
+ graph_delete_graph(graph);
+}
+
+void cmd_finalize_node(struct cmd_node *cnode)
+{
+ if (cnode->graph_built)
+ return;
+
+ hash_iterate(cnode->cmd_hash, cmd_finalize_iter, cnode);
+ cnode->graph_built = true;
+}
+
+void uninstall_element(enum node_type ntype, const struct cmd_element *cmd)
+{
+ struct cmd_node *cnode;
+
+ /* cmd_init hasn't been called */
+ if (!cmdvec) {
+ fprintf(stderr, "%s called before cmd_init, breakage likely\n",
+ __func__);
+ return;
+ }
+
+ cnode = vector_lookup(cmdvec, ntype);
+
+ if (cnode == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d does not exist.\n"
+ "\tplease call install_node() before uninstall_element()\n",
+ cmd->name, cmd->string, ntype);
+ exit(EXIT_FAILURE);
+ }
+
+ if (hash_release(cnode->cmd_hash, (void *)cmd) == NULL) {
+ fprintf(stderr,
+ "%s[%s]:\n"
+ "\tnode %d (%s) does not have this command installed.\n"
+ "\tduplicate uninstall_element call?\n",
+ cmd->name, cmd->string, ntype, cnode->name);
+ return;
+ }
+
+ vector_unset_value(cnode->cmd_vector, (void *)cmd);
+
+ if (cnode->graph_built) {
+ struct graph *graph = graph_new();
+ struct cmd_token *token =
+ cmd_token_new(START_TKN, CMD_ATTR_NORMAL, NULL, NULL);
+ graph_new_node(graph, token,
+ (void (*)(void *)) & cmd_token_del);
+
+ cmd_graph_parse(graph, cmd);
+ cmd_graph_names(graph);
+ cmd_graph_merge(cnode->cmdgraph, graph, -1);
+ graph_delete_graph(graph);
+ }
+
+ if (ntype == VIEW_NODE)
+ uninstall_element(ENABLE_NODE, cmd);
+}
+
+
+static const unsigned char itoa64[] =
+ "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+static void to64(char *s, long v, int n)
+{
+ while (--n >= 0) {
+ *s++ = itoa64[v & 0x3f];
+ v >>= 6;
+ }
+}
+
+static char *zencrypt(const char *passwd)
+{
+ char salt[6];
+ struct timeval tv;
+
+ gettimeofday(&tv, 0);
+
+ to64(&salt[0], frr_weak_random(), 3);
+ to64(&salt[3], tv.tv_usec, 3);
+ salt[5] = '\0';
+
+ return crypt(passwd, salt);
+}
+
+static bool full_cli;
+
+/* This function write configuration of this host. */
+static int config_write_host(struct vty *vty)
+{
+ const char *name;
+
+ name = cmd_hostname_get();
+ if (name && name[0] != '\0')
+ vty_out(vty, "hostname %s\n", name);
+
+ name = cmd_domainname_get();
+ if (name && name[0] != '\0')
+ vty_out(vty, "domainname %s\n", name);
+
+ if (cmd_allow_reserved_ranges_get())
+ vty_out(vty, "allow-reserved-ranges\n");
+
+ /* The following are all configuration commands that are not sent to
+ * watchfrr. For instance watchfrr is hardcoded to log to syslog so
+ * we would always display 'log syslog informational' in the config
+ * which would cause other daemons to then switch to syslog when they
+ * parse frr.conf.
+ */
+ if (full_cli) {
+ if (host.encrypt) {
+ if (host.password_encrypt)
+ vty_out(vty, "password 8 %s\n",
+ host.password_encrypt);
+ if (host.enable_encrypt)
+ vty_out(vty, "enable password 8 %s\n",
+ host.enable_encrypt);
+ } else {
+ if (host.password)
+ vty_out(vty, "password %s\n", host.password);
+ if (host.enable)
+ vty_out(vty, "enable password %s\n",
+ host.enable);
+ }
+ log_config_write(vty);
+
+ /* print disable always, but enable only if default is flipped
+ * => prep for future removal of compile-time knob
+ */
+ if (!cputime_enabled)
+ vty_out(vty, "no service cputime-stats\n");
+#ifdef EXCLUDE_CPU_TIME
+ else
+ vty_out(vty, "service cputime-stats\n");
+#endif
+
+ if (!cputime_threshold)
+ vty_out(vty, "no service cputime-warning\n");
+#if defined(CONSUMED_TIME_CHECK) && CONSUMED_TIME_CHECK != 5000000
+ else /* again, always print non-default */
+#else
+ else if (cputime_threshold != 5000000)
+#endif
+ vty_out(vty, "service cputime-warning %lu\n",
+ cputime_threshold);
+
+ if (!walltime_threshold)
+ vty_out(vty, "no service walltime-warning\n");
+#if defined(CONSUMED_TIME_CHECK) && CONSUMED_TIME_CHECK != 5000000
+ else /* again, always print non-default */
+#else
+ else if (walltime_threshold != 5000000)
+#endif
+ vty_out(vty, "service walltime-warning %lu\n",
+ walltime_threshold);
+
+ if (host.advanced)
+ vty_out(vty, "service advanced-vty\n");
+
+ if (host.encrypt)
+ vty_out(vty, "service password-encryption\n");
+
+ if (host.lines >= 0)
+ vty_out(vty, "service terminal-length %d\n",
+ host.lines);
+
+ if (host.motdfile)
+ vty_out(vty, "banner motd file %s\n", host.motdfile);
+ else if (host.motd
+ && strncmp(host.motd, FRR_DEFAULT_MOTD,
+ strlen(host.motd)))
+ vty_out(vty, "banner motd line %s\n", host.motd);
+ else if (!host.motd)
+ vty_out(vty, "no banner motd\n");
+ }
+
+ if (debug_memstats_at_exit)
+ vty_out(vty, "!\ndebug memstats-at-exit\n");
+
+ return 1;
+}
+
+/* Utility function for getting command graph. */
+static struct graph *cmd_node_graph(vector v, enum node_type ntype)
+{
+ struct cmd_node *cnode = vector_slot(v, ntype);
+
+ cmd_finalize_node(cnode);
+ return cnode->cmdgraph;
+}
+
+static int cmd_try_do_shortcut(enum node_type node, char *first_word)
+{
+ if (first_word != NULL && node != AUTH_NODE && node != VIEW_NODE
+ && node != AUTH_ENABLE_NODE && 0 == strcmp("do", first_word))
+ return 1;
+ return 0;
+}
+
+/**
+ * Compare function for cmd_token.
+ * Used with qsort to sort command completions.
+ */
+static int compare_completions(const void *fst, const void *snd)
+{
+ const struct cmd_token *first = *(const struct cmd_token * const *)fst,
+ *secnd = *(const struct cmd_token * const *)snd;
+ return strcmp(first->text, secnd->text);
+}
+
+/**
+ * Takes a list of completions returned by command_complete,
+ * dedeuplicates them based on both text and description,
+ * sorts them, and returns them as a vector.
+ *
+ * @param completions linked list of cmd_token
+ * @return deduplicated and sorted vector with
+ */
+vector completions_to_vec(struct list *completions)
+{
+ vector comps = vector_init(VECTOR_MIN_SIZE);
+
+ struct listnode *ln;
+ struct cmd_token *token, *cr = NULL;
+ unsigned int i, exists;
+ for (ALL_LIST_ELEMENTS_RO(completions, ln, token)) {
+ if (token->type == END_TKN && (cr = token))
+ continue;
+
+ // linear search for token in completions vector
+ exists = 0;
+ for (i = 0; i < vector_active(comps) && !exists; i++) {
+ struct cmd_token *curr = vector_slot(comps, i);
+#ifdef VTYSH_DEBUG
+ exists = !strcmp(curr->text, token->text)
+ && !strcmp(curr->desc, token->desc);
+#else
+ exists = !strcmp(curr->text, token->text);
+#endif /* VTYSH_DEBUG */
+ }
+
+ if (!exists)
+ vector_set(comps, token);
+ }
+
+ // sort completions
+ qsort(comps->index, vector_active(comps), sizeof(void *),
+ &compare_completions);
+
+ // make <cr> the first element, if it is present
+ if (cr) {
+ vector_set_index(comps, vector_active(comps), NULL);
+ memmove(comps->index + 1, comps->index,
+ (comps->alloced - 1) * sizeof(void *));
+ vector_set_index(comps, 0, cr);
+ }
+
+ return comps;
+}
+/**
+ * Generates a vector of cmd_token representing possible completions
+ * on the current input.
+ *
+ * @param vline the vectorized input line
+ * @param vty the vty with the node to match on
+ * @param status pointer to matcher status code
+ * @return vector of struct cmd_token * with possible completions
+ */
+static vector cmd_complete_command_real(vector vline, struct vty *vty,
+ int *status)
+{
+ struct list *completions;
+ struct graph *cmdgraph = cmd_node_graph(cmdvec, vty->node);
+
+ enum matcher_rv rv = command_complete(cmdgraph, vline, &completions);
+
+ if (MATCHER_ERROR(rv)) {
+ *status = CMD_ERR_NO_MATCH;
+ return NULL;
+ }
+
+ vector comps = completions_to_vec(completions);
+ list_delete(&completions);
+
+ // set status code appropriately
+ switch (vector_active(comps)) {
+ case 0:
+ *status = CMD_ERR_NO_MATCH;
+ break;
+ case 1:
+ *status = CMD_COMPLETE_FULL_MATCH;
+ break;
+ default:
+ *status = CMD_COMPLETE_LIST_MATCH;
+ }
+
+ return comps;
+}
+
+vector cmd_describe_command(vector vline, struct vty *vty, int *status)
+{
+ vector ret;
+
+ if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+ enum node_type onode;
+ int orig_xpath_index;
+ vector shifted_vline;
+ unsigned int index;
+
+ onode = vty->node;
+ orig_xpath_index = vty->xpath_index;
+ vty->node = ENABLE_NODE;
+ vty->xpath_index = 0;
+ /* We can try it on enable node, cos' the vty is authenticated
+ */
+
+ shifted_vline = vector_init(vector_count(vline));
+ /* use memcpy? */
+ for (index = 1; index < vector_active(vline); index++) {
+ vector_set_index(shifted_vline, index - 1,
+ vector_lookup(vline, index));
+ }
+
+ ret = cmd_complete_command_real(shifted_vline, vty, status);
+
+ vector_free(shifted_vline);
+ vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
+ return ret;
+ }
+
+ return cmd_complete_command_real(vline, vty, status);
+}
+
+static struct list *varhandlers = NULL;
+
+void cmd_variable_complete(struct cmd_token *token, const char *arg,
+ vector comps)
+{
+ struct listnode *ln;
+ const struct cmd_variable_handler *cvh;
+ size_t i, argsz;
+ vector tmpcomps;
+
+ tmpcomps = arg ? vector_init(VECTOR_MIN_SIZE) : comps;
+
+ for (ALL_LIST_ELEMENTS_RO(varhandlers, ln, cvh)) {
+ if (cvh->tokenname && strcmp(cvh->tokenname, token->text))
+ continue;
+ if (cvh->varname && (!token->varname
+ || strcmp(cvh->varname, token->varname)))
+ continue;
+ cvh->completions(tmpcomps, token);
+ break;
+ }
+
+ if (!arg)
+ return;
+
+ argsz = strlen(arg);
+ for (i = vector_active(tmpcomps); i; i--) {
+ char *item = vector_slot(tmpcomps, i - 1);
+ if (strlen(item) >= argsz && !strncmp(item, arg, argsz))
+ vector_set(comps, item);
+ else
+ XFREE(MTYPE_COMPLETION, item);
+ }
+ vector_free(tmpcomps);
+}
+
+#define AUTOCOMP_INDENT 5
+
+char *cmd_variable_comp2str(vector comps, unsigned short cols)
+{
+ size_t bsz = 16;
+ char *buf = XCALLOC(MTYPE_TMP, bsz);
+ int lc = AUTOCOMP_INDENT;
+ size_t cs = AUTOCOMP_INDENT;
+ size_t itemlen;
+ snprintf(buf, bsz, "%*s", AUTOCOMP_INDENT, "");
+ for (size_t j = 0; j < vector_active(comps); j++) {
+ char *item = vector_slot(comps, j);
+ itemlen = strlen(item);
+
+ size_t next_sz = cs + itemlen + AUTOCOMP_INDENT + 3;
+
+ if (next_sz > bsz) {
+ /* Make sure the buf size is large enough */
+ bsz = next_sz;
+ buf = XREALLOC(MTYPE_TMP, buf, bsz);
+ }
+ if (lc + itemlen + 1 >= cols) {
+ cs += snprintf(&buf[cs], bsz - cs, "\n%*s",
+ AUTOCOMP_INDENT, "");
+ lc = AUTOCOMP_INDENT;
+ }
+
+ size_t written = snprintf(&buf[cs], bsz - cs, "%s ", item);
+ lc += written;
+ cs += written;
+ XFREE(MTYPE_COMPLETION, item);
+ vector_set_index(comps, j, NULL);
+ }
+ return buf;
+}
+
+void cmd_variable_handler_register(const struct cmd_variable_handler *cvh)
+{
+ if (!varhandlers)
+ return;
+
+ for (; cvh->completions; cvh++)
+ listnode_add(varhandlers, (void *)cvh);
+}
+
+DEFUN_HIDDEN (autocomplete,
+ autocomplete_cmd,
+ "autocomplete TYPE TEXT VARNAME",
+ "Autocompletion handler (internal, for vtysh)\n"
+ "cmd_token->type\n"
+ "cmd_token->text\n"
+ "cmd_token->varname\n")
+{
+ struct cmd_token tok;
+ vector comps = vector_init(32);
+ size_t i;
+
+ memset(&tok, 0, sizeof(tok));
+ tok.type = atoi(argv[1]->arg);
+ tok.text = argv[2]->arg;
+ tok.varname = argv[3]->arg;
+ if (!strcmp(tok.varname, "-"))
+ tok.varname = NULL;
+
+ cmd_variable_complete(&tok, NULL, comps);
+
+ for (i = 0; i < vector_active(comps); i++) {
+ char *text = vector_slot(comps, i);
+ vty_out(vty, "%s\n", text);
+ XFREE(MTYPE_COMPLETION, text);
+ }
+
+ vector_free(comps);
+ return CMD_SUCCESS;
+}
+
+/**
+ * Generate possible tab-completions for the given input. This function only
+ * returns results that would result in a valid command if used as Readline
+ * completions (as is the case in vtysh). For instance, if the passed vline ends
+ * with '4.3.2', the strings 'A.B.C.D' and 'A.B.C.D/M' will _not_ be returned.
+ *
+ * @param vline vectorized input line
+ * @param vty the vty
+ * @param status location to store matcher status code in
+ * @return set of valid strings for use with Readline as tab-completions.
+ */
+
+char **cmd_complete_command(vector vline, struct vty *vty, int *status)
+{
+ char **ret = NULL;
+ int original_node = vty->node;
+ vector input_line = vector_init(vector_count(vline));
+
+ // if the first token is 'do' we'll want to execute the command in the
+ // enable node
+ int do_shortcut = cmd_try_do_shortcut(vty->node, vector_slot(vline, 0));
+ vty->node = do_shortcut ? ENABLE_NODE : original_node;
+
+ // construct the input line we'll be matching on
+ unsigned int offset = (do_shortcut) ? 1 : 0;
+ for (unsigned index = 0; index + offset < vector_active(vline); index++)
+ vector_set_index(input_line, index,
+ vector_lookup(vline, index + offset));
+
+ // get token completions -- this is a copying operation
+ vector comps = NULL, initial_comps;
+ initial_comps = cmd_complete_command_real(input_line, vty, status);
+
+ if (!MATCHER_ERROR(*status)) {
+ assert(initial_comps);
+ // filter out everything that is not suitable for a
+ // tab-completion
+ comps = vector_init(VECTOR_MIN_SIZE);
+ for (unsigned int i = 0; i < vector_active(initial_comps);
+ i++) {
+ struct cmd_token *token = vector_slot(initial_comps, i);
+ if (token->type == WORD_TKN)
+ vector_set(comps, XSTRDUP(MTYPE_COMPLETION,
+ token->text));
+ else if (IS_VARYING_TOKEN(token->type)) {
+ const char *ref = vector_lookup(
+ vline, vector_active(vline) - 1);
+ cmd_variable_complete(token, ref, comps);
+ }
+ }
+ vector_free(initial_comps);
+
+ // since we filtered results, we need to re-set status code
+ switch (vector_active(comps)) {
+ case 0:
+ *status = CMD_ERR_NO_MATCH;
+ break;
+ case 1:
+ *status = CMD_COMPLETE_FULL_MATCH;
+ break;
+ default:
+ *status = CMD_COMPLETE_LIST_MATCH;
+ }
+
+ // copy completions text into an array of char*
+ ret = XMALLOC(MTYPE_TMP,
+ (vector_active(comps) + 1) * sizeof(char *));
+ unsigned int i;
+ for (i = 0; i < vector_active(comps); i++) {
+ ret[i] = vector_slot(comps, i);
+ }
+ // set the last element to NULL, because this array is used in
+ // a Readline completion_generator function which expects NULL
+ // as a sentinel value
+ ret[i] = NULL;
+ vector_free(comps);
+ comps = NULL;
+ } else if (initial_comps)
+ vector_free(initial_comps);
+
+ // comps should always be null here
+ assert(!comps);
+
+ // free the adjusted input line
+ vector_free(input_line);
+
+ // reset vty->node to its original value
+ vty->node = original_node;
+
+ return ret;
+}
+
+/* return parent node */
+/* MUST eventually converge on CONFIG_NODE */
+enum node_type node_parent(enum node_type node)
+{
+ struct cmd_node *cnode;
+
+ assert(node > CONFIG_NODE);
+
+ cnode = vector_lookup(cmdvec, node);
+
+ return cnode->parent_node;
+}
+
+/* Execute command by argument vline vector. */
+static int cmd_execute_command_real(vector vline, enum cmd_filter_type filter,
+ struct vty *vty,
+ const struct cmd_element **cmd,
+ unsigned int up_level)
+{
+ struct list *argv_list;
+ enum matcher_rv status;
+ const struct cmd_element *matched_element = NULL;
+ unsigned int i;
+ int xpath_index = vty->xpath_index;
+ int node = vty->node;
+
+ /* only happens for legacy split config file load; need to check for
+ * a match before calling node_exit handlers below
+ */
+ for (i = 0; i < up_level; i++) {
+ struct cmd_node *cnode;
+
+ if (node <= CONFIG_NODE)
+ return CMD_NO_LEVEL_UP;
+
+ cnode = vector_slot(cmdvec, node);
+ node = node_parent(node);
+
+ if (xpath_index > 0 && !cnode->no_xpath)
+ xpath_index--;
+ }
+
+ struct graph *cmdgraph = cmd_node_graph(cmdvec, node);
+ status = command_match(cmdgraph, vline, &argv_list, &matched_element);
+
+ if (cmd)
+ *cmd = matched_element;
+
+ // if matcher error, return corresponding CMD_ERR
+ if (MATCHER_ERROR(status)) {
+ if (argv_list)
+ list_delete(&argv_list);
+ switch (status) {
+ case MATCHER_INCOMPLETE:
+ return CMD_ERR_INCOMPLETE;
+ case MATCHER_AMBIGUOUS:
+ return CMD_ERR_AMBIGUOUS;
+ default:
+ return CMD_ERR_NO_MATCH;
+ }
+ }
+
+ for (i = 0; i < up_level; i++)
+ cmd_exit(vty);
+
+ // build argv array from argv list
+ struct cmd_token **argv = XMALLOC(
+ MTYPE_TMP, argv_list->count * sizeof(struct cmd_token *));
+ struct listnode *ln;
+ struct cmd_token *token;
+
+ i = 0;
+ for (ALL_LIST_ELEMENTS_RO(argv_list, ln, token))
+ argv[i++] = token;
+
+ int argc = argv_list->count;
+
+ int ret;
+ if (matched_element->daemon)
+ ret = CMD_SUCCESS_DAEMON;
+ else {
+ if (vty->config) {
+ /* Clear array of enqueued configuration changes. */
+ vty->num_cfg_changes = 0;
+ memset(&vty->cfg_changes, 0, sizeof(vty->cfg_changes));
+
+ /* Regenerate candidate configuration if necessary. */
+ if (frr_get_cli_mode() == FRR_CLI_CLASSIC
+ && running_config->version
+ > vty->candidate_config->version)
+ nb_config_replace(vty->candidate_config,
+ running_config, true);
+
+ /*
+ * Perform pending commit (if any) before executing
+ * non-YANG command.
+ */
+ if (matched_element->attr != CMD_ATTR_YANG)
+ (void)nb_cli_pending_commit_check(vty);
+ }
+
+ ret = matched_element->func(matched_element, vty, argc, argv);
+ }
+
+ // delete list and cmd_token's in it
+ list_delete(&argv_list);
+ XFREE(MTYPE_TMP, argv);
+
+ return ret;
+}
+
+/**
+ * Execute a given command, handling things like "do ..." and checking
+ * whether the given command might apply at a parent node if doesn't
+ * apply for the current node.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ * one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element of the matched command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @param vtysh If set != 0, don't lookup the command at parent nodes.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int cmd_execute_command(vector vline, struct vty *vty,
+ const struct cmd_element **cmd, int vtysh)
+{
+ int ret, saved_ret = 0;
+ enum node_type onode, try_node;
+ int orig_xpath_index;
+
+ onode = try_node = vty->node;
+ orig_xpath_index = vty->xpath_index;
+
+ if (cmd_try_do_shortcut(vty->node, vector_slot(vline, 0))) {
+ vector shifted_vline;
+ unsigned int index;
+
+ vty->node = ENABLE_NODE;
+ vty->xpath_index = 0;
+ /* We can try it on enable node, cos' the vty is authenticated
+ */
+
+ shifted_vline = vector_init(vector_count(vline));
+ /* use memcpy? */
+ for (index = 1; index < vector_active(vline); index++)
+ vector_set_index(shifted_vline, index - 1,
+ vector_lookup(vline, index));
+
+ ret = cmd_execute_command_real(shifted_vline, FILTER_RELAXED,
+ vty, cmd, 0);
+
+ vector_free(shifted_vline);
+ vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
+ return ret;
+ }
+
+ saved_ret = ret =
+ cmd_execute_command_real(vline, FILTER_RELAXED, vty, cmd, 0);
+
+ if (vtysh)
+ return saved_ret;
+
+ if (ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE
+ && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED) {
+ /* This assumes all nodes above CONFIG_NODE are childs of
+ * CONFIG_NODE */
+ while (vty->node > CONFIG_NODE) {
+ struct cmd_node *cnode = vector_slot(cmdvec, try_node);
+
+ try_node = node_parent(try_node);
+ vty->node = try_node;
+ if (vty->xpath_index > 0 && !cnode->no_xpath)
+ vty->xpath_index--;
+
+ ret = cmd_execute_command_real(vline, FILTER_RELAXED,
+ vty, cmd, 0);
+ if (ret == CMD_SUCCESS || ret == CMD_WARNING
+ || ret == CMD_ERR_AMBIGUOUS || ret == CMD_ERR_INCOMPLETE
+ || ret == CMD_NOT_MY_INSTANCE
+ || ret == CMD_WARNING_CONFIG_FAILED)
+ return ret;
+ }
+ /* no command succeeded, reset the vty to the original node */
+ vty->node = onode;
+ vty->xpath_index = orig_xpath_index;
+ }
+
+ /* return command status for original node */
+ return saved_ret;
+}
+
+/**
+ * Execute a given command, matching it strictly against the current node.
+ * This mode is used when reading config files.
+ *
+ * @param vline Command line input, vector of char* where each element is
+ * one input token.
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element* of the matched command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int cmd_execute_command_strict(vector vline, struct vty *vty,
+ const struct cmd_element **cmd)
+{
+ return cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd, 0);
+}
+
+/*
+ * Hook for preprocessing command string before executing.
+ *
+ * All subscribers are called with the raw command string that is to be
+ * executed. If any changes are to be made, a new string should be allocated
+ * with MTYPE_TMP and *cmd_out updated to point to this new string. The caller
+ * is then responsible for freeing this string.
+ *
+ * All processing functions must be mutually exclusive in their action, i.e. if
+ * one subscriber decides to modify the command, all others must not modify it
+ * when called. Feeding the output of one processing command into a subsequent
+ * one is not supported.
+ *
+ * This hook is intentionally internal to the command processing system.
+ *
+ * cmd_in
+ * The raw command string.
+ *
+ * cmd_out
+ * The result of any processing.
+ */
+DECLARE_HOOK(cmd_execute,
+ (struct vty *vty, const char *cmd_in, char **cmd_out),
+ (vty, cmd_in, cmd_out));
+DEFINE_HOOK(cmd_execute, (struct vty *vty, const char *cmd_in, char **cmd_out),
+ (vty, cmd_in, cmd_out));
+
+/* Hook executed after a CLI command. */
+DECLARE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec),
+ (vty, cmd_exec));
+DEFINE_KOOH(cmd_execute_done, (struct vty *vty, const char *cmd_exec),
+ (vty, cmd_exec));
+
+/*
+ * cmd_execute hook subscriber to handle `|` actions.
+ */
+static int handle_pipe_action(struct vty *vty, const char *cmd_in,
+ char **cmd_out)
+{
+ /* look for `|` */
+ char *orig, *working, *token, *u;
+ char *pipe = strstr(cmd_in, "| ");
+ int ret = 0;
+
+ if (!pipe)
+ return 0;
+
+ /* duplicate string for processing purposes, not including pipe */
+ orig = working = XSTRDUP(MTYPE_TMP, pipe + 2);
+
+ /* retrieve action */
+ token = strsep(&working, " ");
+ assert(token);
+
+ /* match result to known actions */
+ if (strmatch(token, "include")) {
+ /* the remaining text should be a regexp */
+ char *regexp = working;
+
+ if (!regexp) {
+ vty_out(vty, "%% Need a regexp to filter with\n");
+ ret = 1;
+ goto fail;
+ }
+
+ bool succ = vty_set_include(vty, regexp);
+
+ if (!succ) {
+ vty_out(vty, "%% Bad regexp '%s'\n", regexp);
+ ret = 1;
+ goto fail;
+ }
+ *cmd_out = XSTRDUP(MTYPE_TMP, cmd_in);
+ u = *cmd_out;
+ strsep(&u, "|");
+ } else {
+ vty_out(vty, "%% Unknown action '%s'\n", token);
+ ret = 1;
+ goto fail;
+ }
+
+fail:
+ XFREE(MTYPE_TMP, orig);
+ return ret;
+}
+
+static int handle_pipe_action_done(struct vty *vty, const char *cmd_exec)
+{
+ if (vty->filter)
+ vty_set_include(vty, NULL);
+
+ return 0;
+}
+
+int cmd_execute(struct vty *vty, const char *cmd,
+ const struct cmd_element **matched, int vtysh)
+{
+ int ret;
+ char *cmd_out = NULL;
+ const char *cmd_exec = NULL;
+ vector vline;
+
+ ret = hook_call(cmd_execute, vty, cmd, &cmd_out);
+ if (ret) {
+ ret = CMD_WARNING;
+ goto free;
+ }
+
+ cmd_exec = cmd_out ? (const char *)cmd_out : cmd;
+
+ vline = cmd_make_strvec(cmd_exec);
+
+ if (vline) {
+ ret = cmd_execute_command(vline, vty, matched, vtysh);
+ cmd_free_strvec(vline);
+ } else {
+ ret = CMD_SUCCESS;
+ }
+
+free:
+ hook_call(cmd_execute_done, vty, cmd_exec);
+
+ XFREE(MTYPE_TMP, cmd_out);
+
+ return ret;
+}
+
+
+/**
+ * Parse one line of config, walking up the parse tree attempting to find a
+ * match
+ *
+ * @param vty The vty context in which the command should be executed.
+ * @param cmd Pointer where the struct cmd_element* of the match command
+ * will be stored, if any. May be set to NULL if this info is
+ * not needed.
+ * @param use_daemon Boolean to control whether or not we match on
+ * CMD_SUCCESS_DAEMON
+ * or not.
+ * @return The status of the command that has been executed or an error code
+ * as to why no command could be executed.
+ */
+int command_config_read_one_line(struct vty *vty,
+ const struct cmd_element **cmd,
+ uint32_t line_num, int use_daemon)
+{
+ vector vline;
+ int ret;
+ unsigned up_level = 0;
+
+ vline = cmd_make_strvec(vty->buf);
+
+ /* In case of comment line */
+ if (vline == NULL)
+ return CMD_SUCCESS;
+
+ /* Execute configuration command : this is strict match */
+ ret = cmd_execute_command_strict(vline, vty, cmd);
+
+ /* The logic for trying parent nodes is in cmd_execute_command_real()
+ * since calling ->node_exit() correctly is a bit involved. This is
+ * also the only reason CMD_NO_LEVEL_UP exists.
+ */
+ while (!(use_daemon && ret == CMD_SUCCESS_DAEMON)
+ && !(!use_daemon && ret == CMD_ERR_NOTHING_TODO)
+ && ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_AMBIGUOUS && ret != CMD_ERR_INCOMPLETE
+ && ret != CMD_NOT_MY_INSTANCE && ret != CMD_WARNING_CONFIG_FAILED
+ && ret != CMD_NO_LEVEL_UP)
+ ret = cmd_execute_command_real(vline, FILTER_STRICT, vty, cmd,
+ ++up_level);
+
+ if (ret == CMD_NO_LEVEL_UP)
+ ret = CMD_ERR_NO_MATCH;
+
+ if (ret != CMD_SUCCESS &&
+ ret != CMD_WARNING &&
+ ret != CMD_SUCCESS_DAEMON) {
+ struct vty_error *ve = XCALLOC(MTYPE_TMP, sizeof(*ve));
+
+ memcpy(ve->error_buf, vty->buf, VTY_BUFSIZ);
+ ve->line_num = line_num;
+ if (!vty->error)
+ vty->error = list_new();
+
+ listnode_add(vty->error, ve);
+ }
+
+ cmd_free_strvec(vline);
+
+ return ret;
+}
+
+/* Configuration make from file. */
+int config_from_file(struct vty *vty, FILE *fp, unsigned int *line_num)
+{
+ int ret, error_ret = 0;
+ *line_num = 0;
+
+ while (fgets(vty->buf, VTY_BUFSIZ, fp)) {
+ ++(*line_num);
+
+ ret = command_config_read_one_line(vty, NULL, *line_num, 0);
+
+ if (ret != CMD_SUCCESS && ret != CMD_WARNING
+ && ret != CMD_ERR_NOTHING_TODO)
+ error_ret = ret;
+ }
+
+ if (error_ret) {
+ return error_ret;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* Configuration from terminal */
+DEFUN (config_terminal,
+ config_terminal_cmd,
+ "configure [terminal]",
+ "Configuration from vty interface\n"
+ "Configuration terminal\n")
+{
+ return vty_config_enter(vty, false, false);
+}
+
+/* Enable command */
+DEFUN (enable,
+ config_enable_cmd,
+ "enable",
+ "Turn on privileged mode command\n")
+{
+ /* If enable password is NULL, change to ENABLE_NODE */
+ if ((host.enable == NULL && host.enable_encrypt == NULL)
+ || vty->type == VTY_SHELL_SERV)
+ vty->node = ENABLE_NODE;
+ else
+ vty->node = AUTH_ENABLE_NODE;
+
+ return CMD_SUCCESS;
+}
+
+/* Disable command */
+DEFUN (disable,
+ config_disable_cmd,
+ "disable",
+ "Turn off privileged mode command\n")
+{
+ if (vty->node == ENABLE_NODE)
+ vty->node = VIEW_NODE;
+ return CMD_SUCCESS;
+}
+
+/* Down vty node level. */
+DEFUN (config_exit,
+ config_exit_cmd,
+ "exit",
+ "Exit current mode and down to previous mode\n")
+{
+ cmd_exit(vty);
+ return CMD_SUCCESS;
+}
+
+static int root_on_exit(struct vty *vty)
+{
+ if (vty_shell(vty))
+ exit(0);
+ else
+ vty->status = VTY_CLOSE;
+ return 0;
+}
+
+void cmd_exit(struct vty *vty)
+{
+ struct cmd_node *cnode = vector_lookup(cmdvec, vty->node);
+
+ if (cnode->node_exit) {
+ if (!cnode->node_exit(vty))
+ return;
+ }
+ if (cnode->parent_node)
+ vty->node = cnode->parent_node;
+ if (vty->xpath_index > 0 && !cnode->no_xpath)
+ vty->xpath_index--;
+}
+
+/* ALIAS_FIXME */
+DEFUN (config_quit,
+ config_quit_cmd,
+ "quit",
+ "Exit current mode and down to previous mode\n")
+{
+ return config_exit(self, vty, argc, argv);
+}
+
+
+/* End of configuration. */
+DEFUN (config_end,
+ config_end_cmd,
+ "end",
+ "End current mode and change to enable mode.\n")
+{
+ if (vty->config) {
+ vty_config_exit(vty);
+ vty->node = ENABLE_NODE;
+ }
+ return CMD_SUCCESS;
+}
+
+/* Show version. */
+DEFUN (show_version,
+ show_version_cmd,
+ "show version",
+ SHOW_STR
+ "Displays zebra version\n")
+{
+ vty_out(vty, "%s %s (%s) on %s(%s).\n", FRR_FULL_NAME, FRR_VERSION,
+ cmd_hostname_get() ? cmd_hostname_get() : "", cmd_system_get(),
+ cmd_release_get());
+ vty_out(vty, "%s%s\n", FRR_COPYRIGHT, GIT_INFO);
+#ifdef ENABLE_VERSION_BUILD_CONFIG
+ vty_out(vty, "configured with:\n %s\n", FRR_CONFIG_ARGS);
+#endif
+ return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+DEFUN (config_help,
+ config_help_cmd,
+ "help",
+ "Description of the interactive help system\n")
+{
+ vty_out(vty,
+ "Quagga VTY provides advanced help feature. When you need help,\n\
+anytime at the command line please press '?'.\n\
+\n\
+If nothing matches, the help list will be empty and you must backup\n\
+ until entering a '?' shows the available options.\n\
+Two styles of help are provided:\n\
+1. Full help is available when you are ready to enter a\n\
+command argument (e.g. 'show ?') and describes each possible\n\
+argument.\n\
+2. Partial help is provided when an abbreviated argument is entered\n\
+ and you want to know what arguments match the input\n\
+ (e.g. 'show me?'.)\n\n");
+ return CMD_SUCCESS;
+}
+
+static void permute(struct graph_node *start, struct vty *vty)
+{
+ static struct list *position = NULL;
+ if (!position)
+ position = list_new();
+
+ struct cmd_token *stok = start->data;
+ struct graph_node *gnn;
+ struct listnode *ln;
+
+ // recursive dfs
+ listnode_add(position, start);
+ for (unsigned int i = 0; i < vector_active(start->to); i++) {
+ struct graph_node *gn = vector_slot(start->to, i);
+ struct cmd_token *tok = gn->data;
+ if (tok->attr == CMD_ATTR_HIDDEN
+ || tok->attr == CMD_ATTR_DEPRECATED)
+ continue;
+ else if (tok->type == END_TKN || gn == start) {
+ vty_out(vty, " ");
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn)) {
+ struct cmd_token *tt = gnn->data;
+ if (tt->type < SPECIAL_TKN)
+ vty_out(vty, " %s", tt->text);
+ }
+ if (gn == start)
+ vty_out(vty, "...");
+ vty_out(vty, "\n");
+ } else {
+ bool skip = false;
+ if (stok->type == FORK_TKN && tok->type != FORK_TKN)
+ for (ALL_LIST_ELEMENTS_RO(position, ln, gnn))
+ if (gnn == gn) {
+ skip = true;
+ break;
+ }
+ if (!skip)
+ permute(gn, vty);
+ }
+ }
+ list_delete_node(position, listtail(position));
+}
+
+static void print_cmd(struct vty *vty, const char *cmd)
+{
+ int i, j, len = strlen(cmd);
+ char buf[len + 1];
+ bool skip = false;
+
+ j = 0;
+ for (i = 0; i < len; i++) {
+ /* skip varname */
+ if (cmd[i] == '$')
+ skip = true;
+ else if (strchr(" ()<>[]{}|", cmd[i]))
+ skip = false;
+
+ if (skip)
+ continue;
+
+ if (isspace(cmd[i])) {
+ /* skip leading whitespace */
+ if (i == 0)
+ continue;
+ /* skip trailing whitespace */
+ if (i == len - 1)
+ continue;
+ /* skip all whitespace after opening brackets or pipe */
+ if (strchr("(<[{|", cmd[i - 1])) {
+ while (isspace(cmd[i + 1]))
+ i++;
+ continue;
+ }
+ /* skip repeated whitespace */
+ if (isspace(cmd[i + 1]))
+ continue;
+ /* skip whitespace before closing brackets or pipe */
+ if (strchr(")>]}|", cmd[i + 1]))
+ continue;
+ /* convert tabs to spaces */
+ if (cmd[i] == '\t') {
+ buf[j++] = ' ';
+ continue;
+ }
+ }
+
+ buf[j++] = cmd[i];
+ }
+ buf[j] = 0;
+
+ vty_out(vty, "%s\n", buf);
+}
+
+int cmd_list_cmds(struct vty *vty, int do_permute)
+{
+ struct cmd_node *node = vector_slot(cmdvec, vty->node);
+
+ if (do_permute) {
+ cmd_finalize_node(node);
+ permute(vector_slot(node->cmdgraph->nodes, 0), vty);
+ } else {
+ /* loop over all commands at this node */
+ const struct cmd_element *element = NULL;
+ for (unsigned int i = 0; i < vector_active(node->cmd_vector);
+ i++)
+ if ((element = vector_slot(node->cmd_vector, i))
+ && element->attr != CMD_ATTR_DEPRECATED
+ && element->attr != CMD_ATTR_HIDDEN) {
+ vty_out(vty, " ");
+ print_cmd(vty, element->string);
+ }
+ }
+ return CMD_SUCCESS;
+}
+
+/* Help display function for all node. */
+DEFUN (config_list,
+ config_list_cmd,
+ "list [permutations]",
+ "Print command list\n"
+ "Print all possible command permutations\n")
+{
+ return cmd_list_cmds(vty, argc == 2);
+}
+
+DEFUN (show_commandtree,
+ show_commandtree_cmd,
+ "show commandtree [permutations]",
+ SHOW_STR
+ "Show command tree\n"
+ "Permutations that we are interested in\n")
+{
+ return cmd_list_cmds(vty, argc == 3);
+}
+
+DEFUN_HIDDEN(show_cli_graph,
+ show_cli_graph_cmd,
+ "show cli graph",
+ SHOW_STR
+ "CLI reflection\n"
+ "Dump current command space as DOT graph\n")
+{
+ struct cmd_node *cn = vector_slot(cmdvec, vty->node);
+ char *dot;
+
+ cmd_finalize_node(cn);
+ dot = cmd_graph_dump_dot(cn->cmdgraph);
+
+ vty_out(vty, "%s\n", dot);
+ XFREE(MTYPE_TMP, dot);
+ return CMD_SUCCESS;
+}
+
+static int vty_write_config(struct vty *vty)
+{
+ size_t i;
+ struct cmd_node *node;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+
+ nb_cli_show_config_prepare(running_config, false);
+
+ if (vty->type == VTY_TERM) {
+ vty_out(vty, "\nCurrent configuration:\n");
+ vty_out(vty, "!\n");
+ }
+
+ if (strcmp(frr_defaults_version(), FRR_VER_SHORT))
+ vty_out(vty, "! loaded from %s\n", frr_defaults_version());
+ vty_out(vty, "frr version %s\n", FRR_VER_SHORT);
+ vty_out(vty, "frr defaults %s\n", frr_defaults_profile());
+ vty_out(vty, "!\n");
+
+ for (i = 0; i < vector_active(cmdvec); i++)
+ if ((node = vector_slot(cmdvec, i)) && node->config_write) {
+ if ((*node->config_write)(vty))
+ vty_out(vty, "!\n");
+ }
+
+ if (vty->type == VTY_TERM) {
+ vty_out(vty, "end\n");
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int file_write_config(struct vty *vty)
+{
+ int fd, dirfd;
+ char *config_file, *slash;
+ char *config_file_tmp = NULL;
+ char *config_file_sav = NULL;
+ int ret = CMD_WARNING;
+ struct vty *file_vty;
+ struct stat conf_stat;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+
+ /* Check and see if we are operating under vtysh configuration */
+ if (host.config == NULL) {
+ vty_out(vty,
+ "Can't save to configuration file, using vtysh.\n");
+ return CMD_WARNING;
+ }
+
+ /* Get filename. */
+ config_file = host.config;
+
+#ifndef O_DIRECTORY
+#define O_DIRECTORY 0
+#endif
+ slash = strrchr(config_file, '/');
+ if (slash) {
+ char *config_dir = XSTRDUP(MTYPE_TMP, config_file);
+ config_dir[slash - config_file] = '\0';
+ dirfd = open(config_dir, O_DIRECTORY | O_RDONLY);
+ XFREE(MTYPE_TMP, config_dir);
+ } else
+ dirfd = open(".", O_DIRECTORY | O_RDONLY);
+ /* if dirfd is invalid, directory sync fails, but we're still OK */
+
+ size_t config_file_sav_sz = strlen(config_file) + strlen(CONF_BACKUP_EXT) + 1;
+ config_file_sav = XMALLOC(MTYPE_TMP, config_file_sav_sz);
+ strlcpy(config_file_sav, config_file, config_file_sav_sz);
+ strlcat(config_file_sav, CONF_BACKUP_EXT, config_file_sav_sz);
+
+
+ config_file_tmp = XMALLOC(MTYPE_TMP, strlen(config_file) + 8);
+ snprintf(config_file_tmp, strlen(config_file) + 8, "%s.XXXXXX",
+ config_file);
+
+ /* Open file to configuration write. */
+ fd = mkstemp(config_file_tmp);
+ if (fd < 0) {
+ vty_out(vty, "Can't open configuration file %s.\n",
+ config_file_tmp);
+ goto finished;
+ }
+ if (fchmod(fd, CONFIGFILE_MASK) != 0) {
+ vty_out(vty, "Can't chmod configuration file %s: %s (%d).\n",
+ config_file_tmp, safe_strerror(errno), errno);
+ goto finished;
+ }
+
+ /* Make vty for configuration file. */
+ file_vty = vty_new();
+ file_vty->wfd = fd;
+ file_vty->type = VTY_FILE;
+
+ /* Config file header print. */
+ vty_out(file_vty, "!\n! Zebra configuration saved from vty\n! ");
+ vty_time_print(file_vty, 1);
+ vty_out(file_vty, "!\n");
+ vty_write_config(file_vty);
+ vty_close(file_vty);
+
+ if (stat(config_file, &conf_stat) >= 0) {
+ if (unlink(config_file_sav) != 0)
+ if (errno != ENOENT) {
+ vty_out(vty,
+ "Can't unlink backup configuration file %s.\n",
+ config_file_sav);
+ goto finished;
+ }
+ if (link(config_file, config_file_sav) != 0) {
+ vty_out(vty,
+ "Can't backup old configuration file %s.\n",
+ config_file_sav);
+ goto finished;
+ }
+ if (dirfd >= 0)
+ fsync(dirfd);
+ }
+ if (rename(config_file_tmp, config_file) != 0) {
+ vty_out(vty, "Can't save configuration file %s.\n",
+ config_file);
+ goto finished;
+ }
+ if (dirfd >= 0)
+ fsync(dirfd);
+
+ vty_out(vty, "Configuration saved to %s\n", config_file);
+ ret = CMD_SUCCESS;
+
+finished:
+ if (ret != CMD_SUCCESS)
+ unlink(config_file_tmp);
+ if (dirfd >= 0)
+ close(dirfd);
+ XFREE(MTYPE_TMP, config_file_tmp);
+ XFREE(MTYPE_TMP, config_file_sav);
+ return ret;
+}
+
+/* Write current configuration into file. */
+
+DEFUN (config_write,
+ config_write_cmd,
+ "write [<file|memory|terminal>]",
+ "Write running configuration to memory, network, or terminal\n"
+ "Write to configuration file\n"
+ "Write configuration currently in memory\n"
+ "Write configuration to terminal\n")
+{
+ const int idx_type = 1;
+
+ // if command was 'write terminal' or 'write memory'
+ if (argc == 2 && (!strcmp(argv[idx_type]->text, "terminal"))) {
+ return vty_write_config(vty);
+ }
+
+ return file_write_config(vty);
+}
+
+/* ALIAS_FIXME for 'write <terminal|memory>' */
+DEFUN (show_running_config,
+ show_running_config_cmd,
+ "show running-config",
+ SHOW_STR
+ "running configuration (same as write terminal)\n")
+{
+ return vty_write_config(vty);
+}
+
+/* ALIAS_FIXME for 'write file' */
+DEFUN (copy_runningconf_startupconf,
+ copy_runningconf_startupconf_cmd,
+ "copy running-config startup-config",
+ "Copy configuration\n"
+ "Copy running config to... \n"
+ "Copy running config to startup config (same as write file/memory)\n")
+{
+ return file_write_config(vty);
+}
+/** -- **/
+
+/* Write startup configuration into the terminal. */
+DEFUN (show_startup_config,
+ show_startup_config_cmd,
+ "show startup-config",
+ SHOW_STR
+ "Contents of startup configuration\n")
+{
+ char buf[BUFSIZ];
+ FILE *confp;
+
+ if (host.noconfig)
+ return CMD_SUCCESS;
+ if (host.config == NULL)
+ return CMD_WARNING;
+
+ confp = fopen(host.config, "r");
+ if (confp == NULL) {
+ vty_out(vty, "Can't open configuration file [%s] due to '%s'\n",
+ host.config, safe_strerror(errno));
+ return CMD_WARNING;
+ }
+
+ while (fgets(buf, BUFSIZ, confp)) {
+ char *cp = buf;
+
+ while (*cp != '\r' && *cp != '\n' && *cp != '\0')
+ cp++;
+ *cp = '\0';
+
+ vty_out(vty, "%s\n", buf);
+ }
+
+ fclose(confp);
+
+ return CMD_SUCCESS;
+}
+
+int cmd_domainname_set(const char *domainname)
+{
+ XFREE(MTYPE_HOST, host.domainname);
+ host.domainname = domainname ? XSTRDUP(MTYPE_HOST, domainname) : NULL;
+ return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN(config_domainname,
+ domainname_cmd,
+ "domainname WORD",
+ "Set system's domain name\n"
+ "This system's domain name\n")
+{
+ struct cmd_token *word = argv[1];
+
+ if (!isalpha((unsigned char)word->arg[0])) {
+ vty_out(vty, "Please specify string starting with alphabet\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return cmd_domainname_set(word->arg);
+}
+
+DEFUN(config_no_domainname,
+ no_domainname_cmd,
+ "no domainname [DOMAINNAME]",
+ NO_STR
+ "Reset system's domain name\n"
+ "domain name of this router\n")
+{
+ return cmd_domainname_set(NULL);
+}
+
+int cmd_hostname_set(const char *hostname)
+{
+ XFREE(MTYPE_HOST, host.name);
+ host.name = hostname ? XSTRDUP(MTYPE_HOST, hostname) : NULL;
+ return CMD_SUCCESS;
+}
+
+/* Hostname configuration */
+DEFUN (config_hostname,
+ hostname_cmd,
+ "hostname WORD",
+ "Set system's network name\n"
+ "This system's network name\n")
+{
+ struct cmd_token *word = argv[1];
+
+ if (!isalnum((unsigned char)word->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphabet or number\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ /* With reference to RFC 1123 Section 2.1 */
+ if (strlen(word->arg) > HOSTNAME_LEN) {
+ vty_out(vty, "Hostname length should be less than %d chars\n",
+ HOSTNAME_LEN);
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ return cmd_hostname_set(word->arg);
+}
+
+DEFUN (config_no_hostname,
+ no_hostname_cmd,
+ "no hostname [HOSTNAME]",
+ NO_STR
+ "Reset system's network name\n"
+ "Host name of this router\n")
+{
+ return cmd_hostname_set(NULL);
+}
+
+/* VTY interface password set. */
+DEFUN (config_password,
+ password_cmd,
+ "password [(8-8)] WORD",
+ "Modify the terminal connection password\n"
+ "Specifies a HIDDEN password will follow\n"
+ "The password string\n")
+{
+ int idx_8 = 1;
+ int idx_word = 2;
+ if (argc == 3) // '8' was specified
+ {
+ if (host.password)
+ XFREE(MTYPE_HOST, host.password);
+ host.password = NULL;
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, argv[idx_word]->arg);
+ return CMD_SUCCESS;
+ }
+
+ if (!isalnum((unsigned char)argv[idx_8]->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphanumeric\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (host.password)
+ XFREE(MTYPE_HOST, host.password);
+ host.password = NULL;
+
+ if (host.encrypt) {
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg));
+ } else
+ host.password = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* VTY interface password delete. */
+DEFUN (no_config_password,
+ no_password_cmd,
+ "no password",
+ NO_STR
+ "Modify the terminal connection password\n")
+{
+ bool warned = false;
+
+ if (host.password) {
+ if (!vty_shell_serv(vty)) {
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ warned = true;
+ }
+ XFREE(MTYPE_HOST, host.password);
+ }
+ host.password = NULL;
+
+ if (host.password_encrypt) {
+ if (!warned && !vty_shell_serv(vty))
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ }
+ host.password_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+/* VTY enable password set. */
+DEFUN (config_enable_password,
+ enable_password_cmd,
+ "enable password [(8-8)] WORD",
+ "Modify enable password parameters\n"
+ "Assign the privileged level password\n"
+ "Specifies a HIDDEN password will follow\n"
+ "The HIDDEN 'enable' password string\n")
+{
+ int idx_8 = 2;
+ int idx_word = 3;
+
+ /* Crypt type is specified. */
+ if (argc == 4) {
+ if (argv[idx_8]->arg[0] == '8') {
+ if (host.enable)
+ XFREE(MTYPE_HOST, host.enable);
+ host.enable = NULL;
+
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, argv[idx_word]->arg);
+
+ return CMD_SUCCESS;
+ } else {
+ vty_out(vty, "Unknown encryption type.\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+ }
+
+ if (!isalnum((unsigned char)argv[idx_8]->arg[0])) {
+ vty_out(vty,
+ "Please specify string starting with alphanumeric\n");
+ return CMD_WARNING_CONFIG_FAILED;
+ }
+
+ if (host.enable)
+ XFREE(MTYPE_HOST, host.enable);
+ host.enable = NULL;
+
+ /* Plain password input. */
+ if (host.encrypt) {
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(argv[idx_8]->arg));
+ } else
+ host.enable = XSTRDUP(MTYPE_HOST, argv[idx_8]->arg);
+
+ return CMD_SUCCESS;
+}
+
+/* VTY enable password delete. */
+DEFUN (no_config_enable_password,
+ no_enable_password_cmd,
+ "no enable password",
+ NO_STR
+ "Modify enable password parameters\n"
+ "Assign the privileged level password\n")
+{
+ bool warned = false;
+
+ if (host.enable) {
+ if (!vty_shell_serv(vty)) {
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ warned = true;
+ }
+ XFREE(MTYPE_HOST, host.enable);
+ }
+ host.enable = NULL;
+
+ if (host.enable_encrypt) {
+ if (!warned && !vty_shell_serv(vty))
+ vty_out(vty, NO_PASSWD_CMD_WARNING);
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ }
+ host.enable_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (service_password_encrypt,
+ service_password_encrypt_cmd,
+ "service password-encryption",
+ "Set up miscellaneous service\n"
+ "Enable encrypted passwords\n")
+{
+ if (host.encrypt)
+ return CMD_SUCCESS;
+
+ host.encrypt = 1;
+
+ if (host.password) {
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(host.password));
+ }
+ if (host.enable) {
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt =
+ XSTRDUP(MTYPE_HOST, zencrypt(host.enable));
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_service_password_encrypt,
+ no_service_password_encrypt_cmd,
+ "no service password-encryption",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "Enable encrypted passwords\n")
+{
+ if (!host.encrypt)
+ return CMD_SUCCESS;
+
+ host.encrypt = 0;
+
+ if (host.password_encrypt)
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ host.password_encrypt = NULL;
+
+ if (host.enable_encrypt)
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ host.enable_encrypt = NULL;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_terminal_length,
+ config_terminal_length_cmd,
+ "terminal length (0-512)",
+ "Set terminal line parameters\n"
+ "Set number of lines on a screen\n"
+ "Number of lines on screen (0 for no pausing)\n")
+{
+ int idx_number = 2;
+
+ vty->lines = atoi(argv[idx_number]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_terminal_no_length,
+ config_terminal_no_length_cmd,
+ "terminal no length",
+ "Set terminal line parameters\n"
+ NO_STR
+ "Set number of lines on a screen\n")
+{
+ vty->lines = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN (service_terminal_length,
+ service_terminal_length_cmd,
+ "service terminal-length (0-512)",
+ "Set up miscellaneous service\n"
+ "System wide terminal length configuration\n"
+ "Number of lines of VTY (0 means no line control)\n")
+{
+ int idx_number = 2;
+
+ host.lines = atoi(argv[idx_number]->arg);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_service_terminal_length,
+ no_service_terminal_length_cmd,
+ "no service terminal-length [(0-512)]",
+ NO_STR
+ "Set up miscellaneous service\n"
+ "System wide terminal length configuration\n"
+ "Number of lines of VTY (0 means no line control)\n")
+{
+ host.lines = -1;
+ return CMD_SUCCESS;
+}
+
+DEFUN_HIDDEN (do_echo,
+ echo_cmd,
+ "echo MESSAGE...",
+ "Echo a message back to the vty\n"
+ "The message to echo\n")
+{
+ char *message;
+
+ vty_out(vty, "%s\n",
+ ((message = argv_concat(argv, argc, 1)) ? message : ""));
+ if (message)
+ XFREE(MTYPE_TMP, message);
+ return CMD_SUCCESS;
+}
+
+DEFUN (config_logmsg,
+ config_logmsg_cmd,
+ "logmsg <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging> MESSAGE...",
+ "Send a message to enabled logging destinations\n"
+ LOG_LEVEL_DESC
+ "The message to send\n")
+{
+ int idx_log_level = 1;
+ int idx_message = 2;
+ int level;
+ char *message;
+
+ level = log_level_match(argv[idx_log_level]->arg);
+ if (level == ZLOG_DISABLED)
+ return CMD_ERR_NO_MATCH;
+
+ zlog(level, "%s",
+ ((message = argv_concat(argv, argc, idx_message)) ? message : ""));
+ if (message)
+ XFREE(MTYPE_TMP, message);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_memstats,
+ debug_memstats_cmd,
+ "[no] debug memstats-at-exit",
+ NO_STR
+ DEBUG_STR
+ "Print memory type statistics at exit\n")
+{
+ debug_memstats_at_exit = !!strcmp(argv[0]->text, "no");
+ return CMD_SUCCESS;
+}
+
+int cmd_banner_motd_file(const char *file)
+{
+ int success = CMD_SUCCESS;
+ char p[PATH_MAX];
+ char *rpath;
+ char *in;
+
+ rpath = realpath(file, p);
+ if (!rpath)
+ return CMD_ERR_NO_FILE;
+ in = strstr(rpath, SYSCONFDIR);
+ if (in == rpath) {
+ XFREE(MTYPE_HOST, host.motdfile);
+ host.motdfile = XSTRDUP(MTYPE_HOST, file);
+ } else
+ success = CMD_WARNING_CONFIG_FAILED;
+
+ return success;
+}
+
+void cmd_banner_motd_line(const char *line)
+{
+ XFREE(MTYPE_HOST, host.motd);
+ host.motd = XSTRDUP(MTYPE_HOST, line);
+}
+
+DEFUN (banner_motd_file,
+ banner_motd_file_cmd,
+ "banner motd file FILE",
+ "Set banner\n"
+ "Banner for motd\n"
+ "Banner from a file\n"
+ "Filename\n")
+{
+ int idx_file = 3;
+ const char *filename = argv[idx_file]->arg;
+ int cmd = cmd_banner_motd_file(filename);
+
+ if (cmd == CMD_ERR_NO_FILE)
+ vty_out(vty, "%s does not exist\n", filename);
+ else if (cmd == CMD_WARNING_CONFIG_FAILED)
+ vty_out(vty, "%s must be in %s\n", filename, SYSCONFDIR);
+
+ return cmd;
+}
+
+DEFUN (banner_motd_line,
+ banner_motd_line_cmd,
+ "banner motd line LINE...",
+ "Set banner\n"
+ "Banner for motd\n"
+ "Banner from an input\n"
+ "Text\n")
+{
+ int idx = 0;
+ char *motd;
+
+ argv_find(argv, argc, "LINE", &idx);
+ motd = argv_concat(argv, argc, idx);
+
+ cmd_banner_motd_line(motd);
+ XFREE(MTYPE_TMP, motd);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (banner_motd_default,
+ banner_motd_default_cmd,
+ "banner motd default",
+ "Set banner string\n"
+ "Strings for motd\n"
+ "Default string\n")
+{
+ cmd_banner_motd_line(FRR_DEFAULT_MOTD);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_banner_motd,
+ no_banner_motd_cmd,
+ "no banner motd",
+ NO_STR
+ "Set banner string\n"
+ "Strings for motd\n")
+{
+ host.motd = NULL;
+ if (host.motdfile)
+ XFREE(MTYPE_HOST, host.motdfile);
+ host.motdfile = NULL;
+ return CMD_SUCCESS;
+}
+
+DEFUN(allow_reserved_ranges, allow_reserved_ranges_cmd, "allow-reserved-ranges",
+ "Allow using IPv4 (Class E) reserved IP space\n")
+{
+ host.allow_reserved_ranges = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_allow_reserved_ranges, no_allow_reserved_ranges_cmd,
+ "no allow-reserved-ranges",
+ NO_STR "Allow using IPv4 (Class E) reserved IP space\n")
+{
+ host.allow_reserved_ranges = false;
+ return CMD_SUCCESS;
+}
+
+int cmd_find_cmds(struct vty *vty, struct cmd_token **argv, int argc)
+{
+ const struct cmd_node *node;
+ const struct cmd_element *cli;
+ vector clis;
+
+ regex_t exp = {};
+
+ char *pattern = argv_concat(argv, argc, 1);
+ int cr = regcomp(&exp, pattern, REG_NOSUB | REG_EXTENDED);
+ XFREE(MTYPE_TMP, pattern);
+
+ if (cr != 0) {
+ switch (cr) {
+ case REG_BADBR:
+ vty_out(vty, "%% Invalid {...} expression\n");
+ break;
+ case REG_BADRPT:
+ vty_out(vty, "%% Bad repetition operator\n");
+ break;
+ case REG_BADPAT:
+ vty_out(vty, "%% Regex syntax error\n");
+ break;
+ case REG_ECOLLATE:
+ vty_out(vty, "%% Invalid collating element\n");
+ break;
+ case REG_ECTYPE:
+ vty_out(vty, "%% Invalid character class name\n");
+ break;
+ case REG_EESCAPE:
+ vty_out(vty,
+ "%% Regex ended with escape character (\\)\n");
+ break;
+ case REG_ESUBREG:
+ vty_out(vty,
+ "%% Invalid number in \\digit construction\n");
+ break;
+ case REG_EBRACK:
+ vty_out(vty, "%% Unbalanced square brackets\n");
+ break;
+ case REG_EPAREN:
+ vty_out(vty, "%% Unbalanced parentheses\n");
+ break;
+ case REG_EBRACE:
+ vty_out(vty, "%% Unbalanced braces\n");
+ break;
+ case REG_ERANGE:
+ vty_out(vty,
+ "%% Invalid endpoint in range expression\n");
+ break;
+ case REG_ESPACE:
+ vty_out(vty, "%% Failed to compile (out of memory)\n");
+ break;
+ }
+
+ goto done;
+ }
+
+
+ for (unsigned int i = 0; i < vector_active(cmdvec); i++) {
+ node = vector_slot(cmdvec, i);
+ if (!node)
+ continue;
+ clis = node->cmd_vector;
+ for (unsigned int j = 0; j < vector_active(clis); j++) {
+ cli = vector_slot(clis, j);
+
+ if (regexec(&exp, cli->string, 0, NULL, 0) == 0) {
+ vty_out(vty, " (%s) ", node->name);
+ print_cmd(vty, cli->string);
+ }
+ }
+ }
+
+done:
+ regfree(&exp);
+ return CMD_SUCCESS;
+}
+
+DEFUN(find,
+ find_cmd,
+ "find REGEX...",
+ "Find CLI command matching a regular expression\n"
+ "Search pattern (POSIX regex)\n")
+{
+ return cmd_find_cmds(vty, argv, argc);
+}
+
+#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
+DEFUN(script, script_cmd, "script SCRIPT FUNCTION",
+ "Test command - execute a function in a script\n"
+ "Script name (same as filename in /etc/frr/scripts/)\n"
+ "Function name (in the script)\n")
+{
+ struct prefix p;
+
+ (void)str2prefix("1.2.3.4/24", &p);
+ struct frrscript *fs = frrscript_new(argv[1]->arg);
+
+ if (frrscript_load(fs, argv[2]->arg, NULL)) {
+ vty_out(vty,
+ "/etc/frr/scripts/%s.lua or function '%s' not found\n",
+ argv[1]->arg, argv[2]->arg);
+ }
+
+ int ret = frrscript_call(fs, argv[2]->arg, ("p", &p));
+ char buf[40];
+ prefix2str(&p, buf, sizeof(buf));
+ vty_out(vty, "p: %s\n", buf);
+ vty_out(vty, "Script result: %d\n", ret);
+
+ frrscript_delete(fs);
+
+ return CMD_SUCCESS;
+}
+#endif
+
+/* Set config filename. Called from vty.c */
+void host_config_set(const char *filename)
+{
+ XFREE(MTYPE_HOST, host.config);
+ host.config = XSTRDUP(MTYPE_HOST, filename);
+}
+
+const char *host_config_get(void)
+{
+ return host.config;
+}
+
+void install_default(enum node_type node)
+{
+ _install_element(node, &config_exit_cmd);
+ _install_element(node, &config_quit_cmd);
+ _install_element(node, &config_end_cmd);
+ _install_element(node, &config_help_cmd);
+ _install_element(node, &config_list_cmd);
+ _install_element(node, &show_cli_graph_cmd);
+ _install_element(node, &find_cmd);
+
+ _install_element(node, &config_write_cmd);
+ _install_element(node, &show_running_config_cmd);
+
+ _install_element(node, &autocomplete_cmd);
+
+ nb_cli_install_default(node);
+}
+
+/* Initialize command interface. Install basic nodes and commands.
+ *
+ * terminal = 0 -- vtysh / no logging, no config control
+ * terminal = 1 -- normal daemon
+ * terminal = -1 -- watchfrr / no logging, but minimal config control */
+void cmd_init(int terminal)
+{
+ struct utsname names;
+
+ uname(&names);
+ qobj_init();
+
+ /* register command preprocessors */
+ hook_register(cmd_execute, handle_pipe_action);
+ hook_register(cmd_execute_done, handle_pipe_action_done);
+
+ varhandlers = list_new();
+
+ /* Allocate initial top vector of commands. */
+ cmdvec = vector_init(VECTOR_MIN_SIZE);
+
+ /* Default host value settings. */
+ host.name = XSTRDUP(MTYPE_HOST, names.nodename);
+ host.system = XSTRDUP(MTYPE_HOST, names.sysname);
+ host.release = XSTRDUP(MTYPE_HOST, names.release);
+ host.version = XSTRDUP(MTYPE_HOST, names.version);
+
+#ifdef HAVE_STRUCT_UTSNAME_DOMAINNAME
+ if ((strcmp(names.domainname, "(none)") == 0))
+ host.domainname = NULL;
+ else
+ host.domainname = XSTRDUP(MTYPE_HOST, names.domainname);
+#else
+ host.domainname = NULL;
+#endif
+ host.password = NULL;
+ host.enable = NULL;
+ host.config = NULL;
+ host.noconfig = (terminal < 0);
+ host.lines = -1;
+ cmd_banner_motd_line(FRR_DEFAULT_MOTD);
+ host.motdfile = NULL;
+ host.allow_reserved_ranges = false;
+
+ /* Install top nodes. */
+ install_node(&view_node);
+ install_node(&enable_node);
+ install_node(&auth_node);
+ install_node(&auth_enable_node);
+ install_node(&config_node);
+
+ /* Each node's basic commands. */
+ install_element(VIEW_NODE, &show_version_cmd);
+ install_element(ENABLE_NODE, &show_startup_config_cmd);
+
+ if (terminal) {
+ install_element(ENABLE_NODE, &debug_memstats_cmd);
+
+ install_element(VIEW_NODE, &config_list_cmd);
+ install_element(VIEW_NODE, &config_exit_cmd);
+ install_element(VIEW_NODE, &config_quit_cmd);
+ install_element(VIEW_NODE, &config_help_cmd);
+ install_element(VIEW_NODE, &config_enable_cmd);
+ install_element(VIEW_NODE, &config_terminal_length_cmd);
+ install_element(VIEW_NODE, &config_terminal_no_length_cmd);
+ install_element(VIEW_NODE, &show_commandtree_cmd);
+ install_element(VIEW_NODE, &echo_cmd);
+ install_element(VIEW_NODE, &autocomplete_cmd);
+ install_element(VIEW_NODE, &find_cmd);
+#if defined(DEV_BUILD) && defined(HAVE_SCRIPTING)
+ install_element(VIEW_NODE, &script_cmd);
+#endif
+
+
+ install_element(ENABLE_NODE, &config_end_cmd);
+ install_element(ENABLE_NODE, &config_disable_cmd);
+ install_element(ENABLE_NODE, &config_terminal_cmd);
+ install_element(ENABLE_NODE, &copy_runningconf_startupconf_cmd);
+ install_element(ENABLE_NODE, &config_write_cmd);
+ install_element(ENABLE_NODE, &show_running_config_cmd);
+ install_element(ENABLE_NODE, &config_logmsg_cmd);
+
+ install_default(CONFIG_NODE);
+
+ thread_cmd_init();
+ workqueue_cmd_init();
+ hash_cmd_init();
+ }
+
+ install_element(CONFIG_NODE, &hostname_cmd);
+ install_element(CONFIG_NODE, &no_hostname_cmd);
+ install_element(CONFIG_NODE, &domainname_cmd);
+ install_element(CONFIG_NODE, &no_domainname_cmd);
+
+ if (terminal > 0) {
+ full_cli = true;
+
+ install_element(CONFIG_NODE, &debug_memstats_cmd);
+
+ install_element(CONFIG_NODE, &password_cmd);
+ install_element(CONFIG_NODE, &no_password_cmd);
+ install_element(CONFIG_NODE, &enable_password_cmd);
+ install_element(CONFIG_NODE, &no_enable_password_cmd);
+
+ install_element(CONFIG_NODE, &service_password_encrypt_cmd);
+ install_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
+ install_element(CONFIG_NODE, &banner_motd_default_cmd);
+ install_element(CONFIG_NODE, &banner_motd_file_cmd);
+ install_element(CONFIG_NODE, &banner_motd_line_cmd);
+ install_element(CONFIG_NODE, &no_banner_motd_cmd);
+ install_element(CONFIG_NODE, &service_terminal_length_cmd);
+ install_element(CONFIG_NODE, &no_service_terminal_length_cmd);
+ install_element(CONFIG_NODE, &allow_reserved_ranges_cmd);
+ install_element(CONFIG_NODE, &no_allow_reserved_ranges_cmd);
+
+ log_cmd_init();
+ vrf_install_commands();
+ }
+
+#ifdef DEV_BUILD
+ grammar_sandbox_init();
+#endif
+}
+
+void cmd_terminate(void)
+{
+ struct cmd_node *cmd_node;
+
+ hook_unregister(cmd_execute, handle_pipe_action);
+ hook_unregister(cmd_execute_done, handle_pipe_action_done);
+
+ if (cmdvec) {
+ for (unsigned int i = 0; i < vector_active(cmdvec); i++)
+ if ((cmd_node = vector_slot(cmdvec, i)) != NULL) {
+ // deleting the graph delets the cmd_element as
+ // well
+ graph_delete_graph(cmd_node->cmdgraph);
+ vector_free(cmd_node->cmd_vector);
+ hash_clean(cmd_node->cmd_hash, NULL);
+ hash_free(cmd_node->cmd_hash);
+ cmd_node->cmd_hash = NULL;
+ }
+
+ vector_free(cmdvec);
+ cmdvec = NULL;
+ }
+
+ XFREE(MTYPE_HOST, host.name);
+ XFREE(MTYPE_HOST, host.system);
+ XFREE(MTYPE_HOST, host.release);
+ XFREE(MTYPE_HOST, host.version);
+ XFREE(MTYPE_HOST, host.domainname);
+ XFREE(MTYPE_HOST, host.password);
+ XFREE(MTYPE_HOST, host.password_encrypt);
+ XFREE(MTYPE_HOST, host.enable);
+ XFREE(MTYPE_HOST, host.enable_encrypt);
+ XFREE(MTYPE_HOST, host.motdfile);
+ XFREE(MTYPE_HOST, host.config);
+ XFREE(MTYPE_HOST, host.motd);
+
+ list_delete(&varhandlers);
+ qobj_finish();
+}