// SPDX-License-Identifier: GPL-2.0-or-later /* * Virtual terminal [aka TeletYpe] interface routine. * Copyright (C) 1997, 98 Kunihiro Ishiguro */ #include #include #include #include #include #include #ifdef HAVE_LIBPCRE2_POSIX #ifndef _FRR_PCRE2_POSIX #define _FRR_PCRE2_POSIX #include #endif /* _FRR_PCRE2_POSIX */ #elif defined(HAVE_LIBPCREPOSIX) #include #else #include #endif /* HAVE_LIBPCRE2_POSIX */ #include #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 #include "northbound_cli.h" #include "printfrr.h" #include "json.h" #include #include #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: /* print without crlf replacement */ buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered)); 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 /* 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; 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; } 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: 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, ""); vty_out(vty, "application"); if (severity) vty_out(vty, "%s", severity); if (ecode) vty_out(vty, "%s", ecode); if (evalid) vty_out(vty, "%s\n", evalid); if (have_path) vty_out(vty, "%s\n", ei->data_path); if (have_apptag) vty_out(vty, "%s\n", ei->apptag); if (have_msg) vty_out(vty, "%s\n", ei->msg); vty_out(vty, ""); 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, "\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) { debug_fe_client("Erorr with no pending command: %d returned for client %s 0x%" PRIx64 " 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; } debug_fe_client("Erorr %d returned for client %s 0x%" PRIx64 " 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(); }