Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Virtual terminal [aka TeletYpe] interface routine. |
4 | | * Copyright (C) 1997, 98 Kunihiro Ishiguro |
5 | | */ |
6 | | |
7 | | #include <zebra.h> |
8 | | |
9 | | #include <lib/version.h> |
10 | | #include <sys/types.h> |
11 | | #include <sys/types.h> |
12 | | #ifdef HAVE_LIBPCRE2_POSIX |
13 | | #ifndef _FRR_PCRE2_POSIX |
14 | | #define _FRR_PCRE2_POSIX |
15 | | #include <pcre2posix.h> |
16 | | #endif /* _FRR_PCRE2_POSIX */ |
17 | | #elif defined(HAVE_LIBPCREPOSIX) |
18 | | #include <pcreposix.h> |
19 | | #else |
20 | | #include <regex.h> |
21 | | #endif /* HAVE_LIBPCRE2_POSIX */ |
22 | | #include <stdio.h> |
23 | | |
24 | | #include "debug.h" |
25 | | #include "linklist.h" |
26 | | #include "frrevent.h" |
27 | | #include "buffer.h" |
28 | | #include "command.h" |
29 | | #include "sockunion.h" |
30 | | #include "memory.h" |
31 | | #include "log.h" |
32 | | #include "prefix.h" |
33 | | #include "filter.h" |
34 | | #include "vty.h" |
35 | | #include "privs.h" |
36 | | #include "network.h" |
37 | | #include "libfrr.h" |
38 | | #include "frrstr.h" |
39 | | #include "lib_errors.h" |
40 | | #include "northbound_cli.h" |
41 | | #include "printfrr.h" |
42 | | #include "json.h" |
43 | | |
44 | | #include <arpa/telnet.h> |
45 | | #include <termios.h> |
46 | | |
47 | | #include "lib/vty_clippy.c" |
48 | | |
49 | 8 | DEFINE_MTYPE_STATIC(LIB, VTY, "VTY"); |
50 | 8 | DEFINE_MTYPE_STATIC(LIB, VTY_SERV, "VTY server"); |
51 | 8 | DEFINE_MTYPE_STATIC(LIB, VTY_OUT_BUF, "VTY output buffer"); |
52 | 8 | DEFINE_MTYPE_STATIC(LIB, VTY_HIST, "VTY history"); |
53 | 8 | |
54 | 8 | DECLARE_DLIST(vtys, struct vty, itm); Unexecuted instantiation: vty.c:vtys_del Unexecuted instantiation: vty.c:vtys_next_safe |
55 | | |
56 | | /* Vty events */ |
57 | | enum vty_event { |
58 | | VTY_SERV, |
59 | | VTY_READ, |
60 | | VTY_WRITE, |
61 | | VTY_TIMEOUT_RESET, |
62 | | #ifdef VTYSH |
63 | | VTYSH_SERV, |
64 | | VTYSH_READ, |
65 | | VTYSH_WRITE |
66 | | #endif /* VTYSH */ |
67 | | }; |
68 | | |
69 | | struct nb_config *vty_mgmt_candidate_config; |
70 | | |
71 | | static struct mgmt_fe_client *mgmt_fe_client; |
72 | | static bool mgmt_fe_connected; |
73 | | static uint64_t mgmt_client_id_next; |
74 | | static uint64_t mgmt_last_req_id = UINT64_MAX; |
75 | | |
76 | | PREDECL_DLIST(vtyservs); |
77 | | |
78 | | struct vty_serv { |
79 | | struct vtyservs_item itm; |
80 | | |
81 | | int sock; |
82 | | bool vtysh; |
83 | | |
84 | | struct event *t_accept; |
85 | | }; |
86 | | |
87 | | DECLARE_DLIST(vtyservs, struct vty_serv, itm); |
88 | | |
89 | | static void vty_event_serv(enum vty_event event, struct vty_serv *); |
90 | | static void vty_event(enum vty_event, struct vty *); |
91 | | static int vtysh_flush(struct vty *vty); |
92 | | |
93 | | /* Extern host structure from command.c */ |
94 | | extern struct host host; |
95 | | |
96 | | /* active listeners */ |
97 | | static struct vtyservs_head vty_servs[1] = {INIT_DLIST(vty_servs[0])}; |
98 | | |
99 | | /* active connections */ |
100 | | static struct vtys_head vty_sessions[1] = {INIT_DLIST(vty_sessions[0])}; |
101 | | static struct vtys_head vtysh_sessions[1] = {INIT_DLIST(vtysh_sessions[0])}; |
102 | | |
103 | | /* Vty timeout value. */ |
104 | | static unsigned long vty_timeout_val = VTY_TIMEOUT_DEFAULT; |
105 | | |
106 | | /* Vty access-class command */ |
107 | | static char *vty_accesslist_name = NULL; |
108 | | |
109 | | /* Vty access-calss for IPv6. */ |
110 | | static char *vty_ipv6_accesslist_name = NULL; |
111 | | |
112 | | /* Current directory. */ |
113 | | static char vty_cwd[MAXPATHLEN]; |
114 | | |
115 | | /* Login password check. */ |
116 | | static int no_password_check = 0; |
117 | | |
118 | | /* Integrated configuration file path */ |
119 | | static char integrate_default[] = SYSCONFDIR INTEGRATE_DEFAULT_CONFIG; |
120 | | |
121 | | bool vty_log_commands; |
122 | | static bool vty_log_commands_perm; |
123 | | |
124 | | char const *const mgmt_daemons[] = { |
125 | | #ifdef HAVE_STATICD |
126 | | "staticd", |
127 | | #endif |
128 | | }; |
129 | | uint mgmt_daemons_count = array_size(mgmt_daemons); |
130 | | |
131 | | |
132 | | static int vty_mgmt_lock_candidate_inline(struct vty *vty) |
133 | 0 | { |
134 | 0 | assert(!vty->mgmt_locked_candidate_ds); |
135 | 0 | (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, true, true); |
136 | 0 | return vty->mgmt_locked_candidate_ds ? 0 : -1; |
137 | 0 | } |
138 | | |
139 | | static int vty_mgmt_unlock_candidate_inline(struct vty *vty) |
140 | 0 | { |
141 | 0 | assert(vty->mgmt_locked_candidate_ds); |
142 | 0 | (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_CANDIDATE, false, true); |
143 | 0 | return vty->mgmt_locked_candidate_ds ? -1 : 0; |
144 | 0 | } |
145 | | |
146 | | static int vty_mgmt_lock_running_inline(struct vty *vty) |
147 | 0 | { |
148 | 0 | assert(!vty->mgmt_locked_running_ds); |
149 | 0 | (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, true, true); |
150 | 0 | return vty->mgmt_locked_running_ds ? 0 : -1; |
151 | 0 | } |
152 | | |
153 | | static int vty_mgmt_unlock_running_inline(struct vty *vty) |
154 | 0 | { |
155 | 0 | assert(vty->mgmt_locked_running_ds); |
156 | 0 | (void)vty_mgmt_send_lockds_req(vty, MGMTD_DS_RUNNING, false, true); |
157 | 0 | return vty->mgmt_locked_running_ds ? -1 : 0; |
158 | 0 | } |
159 | | |
160 | | void vty_mgmt_resume_response(struct vty *vty, bool success) |
161 | 0 | { |
162 | 0 | uint8_t header[4] = {0, 0, 0, 0}; |
163 | 0 | int ret = CMD_SUCCESS; |
164 | |
|
165 | 0 | if (!vty->mgmt_req_pending_cmd) { |
166 | 0 | zlog_err( |
167 | 0 | "vty resume response called without mgmt_req_pending_cmd"); |
168 | 0 | return; |
169 | 0 | } |
170 | | |
171 | 0 | if (!success) |
172 | 0 | ret = CMD_WARNING_CONFIG_FAILED; |
173 | |
|
174 | 0 | MGMTD_FE_CLIENT_DBG( |
175 | 0 | "resuming CLI cmd after %s on vty session-id: %" PRIu64 |
176 | 0 | " with '%s'", |
177 | 0 | vty->mgmt_req_pending_cmd, vty->mgmt_session_id, |
178 | 0 | success ? "succeeded" : "failed"); |
179 | |
|
180 | 0 | vty->mgmt_req_pending_cmd = NULL; |
181 | |
|
182 | 0 | if (vty->type != VTY_FILE) { |
183 | 0 | header[3] = ret; |
184 | 0 | buffer_put(vty->obuf, header, 4); |
185 | 0 | if (!vty->t_write && (vtysh_flush(vty) < 0)) { |
186 | 0 | zlog_err("failed to vtysh_flush"); |
187 | | /* Try to flush results; exit if a write error occurs */ |
188 | 0 | return; |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | 0 | if (vty->status == VTY_CLOSE) |
193 | 0 | vty_close(vty); |
194 | 0 | else if (vty->type != VTY_FILE) |
195 | 0 | vty_event(VTYSH_READ, vty); |
196 | 0 | else |
197 | | /* should we assert here? */ |
198 | 0 | zlog_err("mgmtd: unexpected resume while reading config file"); |
199 | 0 | } |
200 | | |
201 | | void vty_frame(struct vty *vty, const char *format, ...) |
202 | 0 | { |
203 | 0 | va_list args; |
204 | |
|
205 | 0 | va_start(args, format); |
206 | 0 | vsnprintfrr(vty->frame + vty->frame_pos, |
207 | 0 | sizeof(vty->frame) - vty->frame_pos, format, args); |
208 | 0 | vty->frame_pos = strlen(vty->frame); |
209 | 0 | va_end(args); |
210 | 0 | } |
211 | | |
212 | | void vty_endframe(struct vty *vty, const char *endtext) |
213 | 0 | { |
214 | 0 | if (vty->frame_pos == 0 && endtext) |
215 | 0 | vty_out(vty, "%s", endtext); |
216 | 0 | vty->frame_pos = 0; |
217 | 0 | } |
218 | | |
219 | | bool vty_set_include(struct vty *vty, const char *regexp) |
220 | 0 | { |
221 | 0 | int errcode; |
222 | 0 | bool ret = true; |
223 | 0 | char errbuf[256]; |
224 | |
|
225 | 0 | if (!regexp) { |
226 | 0 | if (vty->filter) { |
227 | 0 | regfree(&vty->include); |
228 | 0 | vty->filter = false; |
229 | 0 | } |
230 | 0 | return true; |
231 | 0 | } |
232 | | |
233 | 0 | errcode = regcomp(&vty->include, regexp, |
234 | 0 | REG_EXTENDED | REG_NEWLINE | REG_NOSUB); |
235 | 0 | if (errcode) { |
236 | 0 | ret = false; |
237 | 0 | regerror(errcode, &vty->include, errbuf, sizeof(errbuf)); |
238 | 0 | vty_out(vty, "%% Regex compilation error: %s\n", errbuf); |
239 | 0 | } else { |
240 | 0 | vty->filter = true; |
241 | 0 | } |
242 | |
|
243 | 0 | return ret; |
244 | 0 | } |
245 | | |
246 | | /* VTY standard output function. */ |
247 | | int vty_out(struct vty *vty, const char *format, ...) |
248 | 0 | { |
249 | 0 | va_list args; |
250 | 0 | ssize_t len; |
251 | 0 | char buf[1024]; |
252 | 0 | char *p = NULL; |
253 | 0 | char *filtered; |
254 | | /* format string may contain %m, keep errno intact for printfrr */ |
255 | 0 | int saved_errno = errno; |
256 | |
|
257 | 0 | if (vty->frame_pos) { |
258 | 0 | vty->frame_pos = 0; |
259 | 0 | vty_out(vty, "%s", vty->frame); |
260 | 0 | } |
261 | |
|
262 | 0 | va_start(args, format); |
263 | 0 | errno = saved_errno; |
264 | 0 | p = vasnprintfrr(MTYPE_VTY_OUT_BUF, buf, sizeof(buf), format, args); |
265 | 0 | va_end(args); |
266 | |
|
267 | 0 | len = strlen(p); |
268 | | |
269 | | /* filter buffer */ |
270 | 0 | if (vty->filter) { |
271 | 0 | vector lines = frrstr_split_vec(p, "\n"); |
272 | | |
273 | | /* Place first value in the cache */ |
274 | 0 | char *firstline = vector_slot(lines, 0); |
275 | 0 | buffer_put(vty->lbuf, (uint8_t *) firstline, strlen(firstline)); |
276 | | |
277 | | /* If our split returned more than one entry, time to filter */ |
278 | 0 | if (vector_active(lines) > 1) { |
279 | | /* |
280 | | * returned string is MTYPE_TMP so it matches the MTYPE |
281 | | * of everything else in the vector |
282 | | */ |
283 | 0 | char *bstr = buffer_getstr(vty->lbuf); |
284 | 0 | buffer_reset(vty->lbuf); |
285 | 0 | XFREE(MTYPE_TMP, lines->index[0]); |
286 | 0 | vector_set_index(lines, 0, bstr); |
287 | 0 | frrstr_filter_vec(lines, &vty->include); |
288 | 0 | vector_compact(lines); |
289 | | /* |
290 | | * Consider the string "foo\n". If the regex is an empty string |
291 | | * and the line ended with a newline, then the vector will look |
292 | | * like: |
293 | | * |
294 | | * [0]: 'foo' |
295 | | * [1]: '' |
296 | | * |
297 | | * If the regex isn't empty, the vector will look like: |
298 | | * |
299 | | * [0]: 'foo' |
300 | | * |
301 | | * In this case we'd like to preserve the newline, so we add |
302 | | * the empty string [1] as in the first example. |
303 | | */ |
304 | 0 | if (p[strlen(p) - 1] == '\n' && vector_active(lines) > 0 |
305 | 0 | && strlen(vector_slot(lines, vector_active(lines) - 1))) |
306 | 0 | vector_set(lines, XSTRDUP(MTYPE_TMP, "")); |
307 | |
|
308 | 0 | filtered = frrstr_join_vec(lines, "\n"); |
309 | 0 | } |
310 | 0 | else { |
311 | 0 | filtered = NULL; |
312 | 0 | } |
313 | |
|
314 | 0 | frrstr_strvec_free(lines); |
315 | |
|
316 | 0 | } else { |
317 | 0 | filtered = p; |
318 | 0 | } |
319 | |
|
320 | 0 | if (!filtered) |
321 | 0 | goto done; |
322 | | |
323 | 0 | switch (vty->type) { |
324 | 0 | case VTY_TERM: |
325 | | /* print with crlf replacement */ |
326 | 0 | buffer_put_crlf(vty->obuf, (uint8_t *)filtered, |
327 | 0 | strlen(filtered)); |
328 | 0 | break; |
329 | 0 | case VTY_SHELL: |
330 | 0 | if (vty->of) { |
331 | 0 | fprintf(vty->of, "%s", filtered); |
332 | 0 | fflush(vty->of); |
333 | 0 | } else if (vty->of_saved) { |
334 | 0 | fprintf(vty->of_saved, "%s", filtered); |
335 | 0 | fflush(vty->of_saved); |
336 | 0 | } |
337 | 0 | break; |
338 | 0 | case VTY_SHELL_SERV: |
339 | 0 | case VTY_FILE: |
340 | 0 | default: |
341 | | /* print without crlf replacement */ |
342 | 0 | buffer_put(vty->obuf, (uint8_t *)filtered, strlen(filtered)); |
343 | 0 | break; |
344 | 0 | } |
345 | | |
346 | 0 | done: |
347 | |
|
348 | 0 | if (vty->filter && filtered) |
349 | 0 | XFREE(MTYPE_TMP, filtered); |
350 | | |
351 | | /* If p is not different with buf, it is allocated buffer. */ |
352 | 0 | if (p != buf) |
353 | 0 | XFREE(MTYPE_VTY_OUT_BUF, p); |
354 | |
|
355 | 0 | return len; |
356 | 0 | } |
357 | | |
358 | | static int vty_json_helper(struct vty *vty, struct json_object *json, |
359 | | uint32_t options) |
360 | 0 | { |
361 | 0 | const char *text; |
362 | |
|
363 | 0 | if (!json) |
364 | 0 | return CMD_SUCCESS; |
365 | | |
366 | 0 | text = json_object_to_json_string_ext( |
367 | 0 | json, options); |
368 | 0 | vty_out(vty, "%s\n", text); |
369 | 0 | json_object_free(json); |
370 | |
|
371 | 0 | return CMD_SUCCESS; |
372 | 0 | } |
373 | | |
374 | | int vty_json(struct vty *vty, struct json_object *json) |
375 | 0 | { |
376 | 0 | return vty_json_helper(vty, json, |
377 | 0 | JSON_C_TO_STRING_PRETTY | |
378 | 0 | JSON_C_TO_STRING_NOSLASHESCAPE); |
379 | 0 | } |
380 | | |
381 | | int vty_json_no_pretty(struct vty *vty, struct json_object *json) |
382 | 0 | { |
383 | 0 | return vty_json_helper(vty, json, JSON_C_TO_STRING_NOSLASHESCAPE); |
384 | 0 | } |
385 | | |
386 | | void vty_json_empty(struct vty *vty) |
387 | 0 | { |
388 | 0 | json_object *json = json_object_new_object(); |
389 | |
|
390 | 0 | vty_json(vty, json); |
391 | 0 | } |
392 | | |
393 | | /* Output current time to the vty. */ |
394 | | void vty_time_print(struct vty *vty, int cr) |
395 | 0 | { |
396 | 0 | char buf[FRR_TIMESTAMP_LEN]; |
397 | |
|
398 | 0 | if (frr_timestamp(0, buf, sizeof(buf)) == 0) { |
399 | 0 | zlog_info("frr_timestamp error"); |
400 | 0 | return; |
401 | 0 | } |
402 | 0 | if (cr) |
403 | 0 | vty_out(vty, "%s\n", buf); |
404 | 0 | else |
405 | 0 | vty_out(vty, "%s ", buf); |
406 | |
|
407 | 0 | return; |
408 | 0 | } |
409 | | |
410 | | /* Say hello to vty interface. */ |
411 | | void vty_hello(struct vty *vty) |
412 | 0 | { |
413 | 0 | if (host.motdfile) { |
414 | 0 | FILE *f; |
415 | 0 | char buf[4096]; |
416 | |
|
417 | 0 | f = fopen(host.motdfile, "r"); |
418 | 0 | if (f) { |
419 | 0 | while (fgets(buf, sizeof(buf), f)) { |
420 | 0 | char *s; |
421 | | /* work backwards to ignore trailling isspace() |
422 | | */ |
423 | 0 | for (s = buf + strlen(buf); |
424 | 0 | (s > buf) && isspace((unsigned char)s[-1]); |
425 | 0 | s--) |
426 | 0 | ; |
427 | 0 | *s = '\0'; |
428 | 0 | vty_out(vty, "%s\n", buf); |
429 | 0 | } |
430 | 0 | fclose(f); |
431 | 0 | } else |
432 | 0 | vty_out(vty, "MOTD file not found\n"); |
433 | 0 | } else if (host.motd) |
434 | 0 | vty_out(vty, "%s", host.motd); |
435 | 0 | } |
436 | | |
437 | | #pragma GCC diagnostic push |
438 | | #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
439 | | /* prompt formatting has a %s in the cmd_node prompt string. |
440 | | * |
441 | | * Also for some reason GCC emits the warning on the end of the function |
442 | | * (optimization maybe?) rather than on the vty_out line, so this pragma |
443 | | * wraps the entire function rather than just the vty_out line. |
444 | | */ |
445 | | |
446 | | /* Put out prompt and wait input from user. */ |
447 | | static void vty_prompt(struct vty *vty) |
448 | 0 | { |
449 | 0 | if (vty->type == VTY_TERM) { |
450 | 0 | vty_out(vty, cmd_prompt(vty->node), cmd_hostname_get()); |
451 | 0 | } |
452 | 0 | } |
453 | | #pragma GCC diagnostic pop |
454 | | |
455 | | /* Send WILL TELOPT_ECHO to remote server. */ |
456 | | static void vty_will_echo(struct vty *vty) |
457 | 0 | { |
458 | 0 | unsigned char cmd[] = {IAC, WILL, TELOPT_ECHO, '\0'}; |
459 | 0 | vty_out(vty, "%s", cmd); |
460 | 0 | } |
461 | | |
462 | | /* Make suppress Go-Ahead telnet option. */ |
463 | | static void vty_will_suppress_go_ahead(struct vty *vty) |
464 | 0 | { |
465 | 0 | unsigned char cmd[] = {IAC, WILL, TELOPT_SGA, '\0'}; |
466 | 0 | vty_out(vty, "%s", cmd); |
467 | 0 | } |
468 | | |
469 | | /* Make don't use linemode over telnet. */ |
470 | | static void vty_dont_linemode(struct vty *vty) |
471 | 0 | { |
472 | 0 | unsigned char cmd[] = {IAC, DONT, TELOPT_LINEMODE, '\0'}; |
473 | 0 | vty_out(vty, "%s", cmd); |
474 | 0 | } |
475 | | |
476 | | /* Use window size. */ |
477 | | static void vty_do_window_size(struct vty *vty) |
478 | 0 | { |
479 | 0 | unsigned char cmd[] = {IAC, DO, TELOPT_NAWS, '\0'}; |
480 | 0 | vty_out(vty, "%s", cmd); |
481 | 0 | } |
482 | | |
483 | | /* Authentication of vty */ |
484 | | static void vty_auth(struct vty *vty, char *buf) |
485 | 0 | { |
486 | 0 | char *passwd = NULL; |
487 | 0 | enum node_type next_node = 0; |
488 | 0 | int fail; |
489 | 0 |
|
490 | 0 | switch (vty->node) { |
491 | 0 | case AUTH_NODE: |
492 | 0 | if (host.encrypt) |
493 | 0 | passwd = host.password_encrypt; |
494 | 0 | else |
495 | 0 | passwd = host.password; |
496 | 0 | if (host.advanced) |
497 | 0 | next_node = host.enable ? VIEW_NODE : ENABLE_NODE; |
498 | 0 | else |
499 | 0 | next_node = VIEW_NODE; |
500 | 0 | break; |
501 | 0 | case AUTH_ENABLE_NODE: |
502 | 0 | if (host.encrypt) |
503 | 0 | passwd = host.enable_encrypt; |
504 | 0 | else |
505 | 0 | passwd = host.enable; |
506 | 0 | next_node = ENABLE_NODE; |
507 | 0 | break; |
508 | 0 | } |
509 | 0 |
|
510 | 0 | if (passwd) { |
511 | 0 | if (host.encrypt) |
512 | 0 | fail = strcmp(crypt(buf, passwd), passwd); |
513 | 0 | else |
514 | 0 | fail = strcmp(buf, passwd); |
515 | 0 | } else |
516 | 0 | fail = 1; |
517 | 0 |
|
518 | 0 | if (!fail) { |
519 | 0 | vty->fail = 0; |
520 | 0 | vty->node = next_node; /* Success ! */ |
521 | 0 | } else { |
522 | 0 | vty->fail++; |
523 | 0 | if (vty->fail >= 3) { |
524 | 0 | if (vty->node == AUTH_NODE) { |
525 | 0 | vty_out(vty, |
526 | 0 | "%% Bad passwords, too many failures!\n"); |
527 | 0 | vty->status = VTY_CLOSE; |
528 | 0 | } else { |
529 | 0 | /* AUTH_ENABLE_NODE */ |
530 | 0 | vty->fail = 0; |
531 | 0 | vty_out(vty, |
532 | 0 | "%% Bad enable passwords, too many failures!\n"); |
533 | 0 | vty->status = VTY_CLOSE; |
534 | 0 | } |
535 | 0 | } |
536 | 0 | } |
537 | 0 | } |
538 | | |
539 | | /* Command execution over the vty interface. */ |
540 | | static int vty_command(struct vty *vty, char *buf) |
541 | 0 | { |
542 | 0 | int ret; |
543 | 0 | const char *protocolname; |
544 | 0 | char *cp = NULL; |
545 | 0 |
|
546 | 0 | assert(vty); |
547 | 0 |
|
548 | 0 | /* |
549 | 0 | * Log non empty command lines |
550 | 0 | */ |
551 | 0 | if (vty_log_commands && |
552 | 0 | strncmp(buf, "echo PING", strlen("echo PING")) != 0) |
553 | 0 | cp = buf; |
554 | 0 | if (cp != NULL) { |
555 | 0 | /* Skip white spaces. */ |
556 | 0 | while (isspace((unsigned char)*cp) && *cp != '\0') |
557 | 0 | cp++; |
558 | 0 | } |
559 | 0 | if (cp != NULL && *cp != '\0') { |
560 | 0 | char vty_str[VTY_BUFSIZ]; |
561 | 0 | char prompt_str[VTY_BUFSIZ]; |
562 | 0 |
|
563 | 0 | /* format the base vty info */ |
564 | 0 | snprintf(vty_str, sizeof(vty_str), "vty[%d]@%s", vty->fd, |
565 | 0 | vty->address); |
566 | 0 |
|
567 | 0 | /* format the prompt */ |
568 | 0 | #pragma GCC diagnostic push |
569 | 0 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" |
570 | 0 | /* prompt formatting has a %s in the cmd_node prompt string */ |
571 | 0 | snprintf(prompt_str, sizeof(prompt_str), cmd_prompt(vty->node), |
572 | 0 | vty_str); |
573 | 0 | #pragma GCC diagnostic pop |
574 | 0 |
|
575 | 0 | /* now log the command */ |
576 | 0 | zlog_notice("%s%s", prompt_str, buf); |
577 | 0 | } |
578 | 0 |
|
579 | 0 | RUSAGE_T before; |
580 | 0 | RUSAGE_T after; |
581 | 0 | unsigned long walltime, cputime; |
582 | 0 |
|
583 | 0 | /* cmd_execute() may change cputime_enabled if we're executing the |
584 | 0 | * "service cputime-stats" command, which can result in nonsensical |
585 | 0 | * and very confusing warnings |
586 | 0 | */ |
587 | 0 | bool cputime_enabled_here = cputime_enabled; |
588 | 0 |
|
589 | 0 | GETRUSAGE(&before); |
590 | 0 |
|
591 | 0 | ret = cmd_execute(vty, buf, NULL, 0); |
592 | 0 |
|
593 | 0 | GETRUSAGE(&after); |
594 | 0 |
|
595 | 0 | walltime = event_consumed_time(&after, &before, &cputime); |
596 | 0 |
|
597 | 0 | if (cputime_enabled_here && cputime_enabled && cputime_threshold |
598 | 0 | && cputime > cputime_threshold) |
599 | 0 | /* Warn about CPU hog that must be fixed. */ |
600 | 0 | flog_warn(EC_LIB_SLOW_THREAD_CPU, |
601 | 0 | "CPU HOG: command took %lums (cpu time %lums): %s", |
602 | 0 | walltime / 1000, cputime / 1000, buf); |
603 | 0 | else if (walltime_threshold && walltime > walltime_threshold) |
604 | 0 | flog_warn(EC_LIB_SLOW_THREAD_WALL, |
605 | 0 | "STARVATION: command took %lums (cpu time %lums): %s", |
606 | 0 | walltime / 1000, cputime / 1000, buf); |
607 | 0 |
|
608 | 0 | /* Get the name of the protocol if any */ |
609 | 0 | protocolname = frr_protoname; |
610 | 0 |
|
611 | 0 | if (ret != CMD_SUCCESS) |
612 | 0 | switch (ret) { |
613 | 0 | case CMD_WARNING: |
614 | 0 | if (vty->type == VTY_FILE) |
615 | 0 | vty_out(vty, "Warning...\n"); |
616 | 0 | break; |
617 | 0 | case CMD_ERR_AMBIGUOUS: |
618 | 0 | vty_out(vty, "%% Ambiguous command.\n"); |
619 | 0 | break; |
620 | 0 | case CMD_ERR_NO_MATCH: |
621 | 0 | vty_out(vty, "%% [%s] Unknown command: %s\n", |
622 | 0 | protocolname, buf); |
623 | 0 | break; |
624 | 0 | case CMD_ERR_INCOMPLETE: |
625 | 0 | vty_out(vty, "%% Command incomplete.\n"); |
626 | 0 | break; |
627 | 0 | } |
628 | 0 |
|
629 | 0 | return ret; |
630 | 0 | } |
631 | | |
632 | | static const char telnet_backward_char = 0x08; |
633 | | static const char telnet_space_char = ' '; |
634 | | |
635 | | /* Basic function to write buffer to vty. */ |
636 | | static void vty_write(struct vty *vty, const char *buf, size_t nbytes) |
637 | 0 | { |
638 | 0 | if ((vty->node == AUTH_NODE) || (vty->node == AUTH_ENABLE_NODE)) |
639 | 0 | return; |
640 | 0 |
|
641 | 0 | /* Should we do buffering here ? And make vty_flush (vty) ? */ |
642 | 0 | buffer_put(vty->obuf, buf, nbytes); |
643 | 0 | } |
644 | | |
645 | | /* Basic function to insert character into vty. */ |
646 | | static void vty_self_insert(struct vty *vty, char c) |
647 | 0 | { |
648 | 0 | int i; |
649 | 0 | int length; |
650 | 0 |
|
651 | 0 | if (vty->length + 1 >= VTY_BUFSIZ) |
652 | 0 | return; |
653 | 0 |
|
654 | 0 | length = vty->length - vty->cp; |
655 | 0 | memmove(&vty->buf[vty->cp + 1], &vty->buf[vty->cp], length); |
656 | 0 | vty->buf[vty->cp] = c; |
657 | 0 |
|
658 | 0 | vty_write(vty, &vty->buf[vty->cp], length + 1); |
659 | 0 | for (i = 0; i < length; i++) |
660 | 0 | vty_write(vty, &telnet_backward_char, 1); |
661 | 0 |
|
662 | 0 | vty->cp++; |
663 | 0 | vty->length++; |
664 | 0 |
|
665 | 0 | vty->buf[vty->length] = '\0'; |
666 | 0 | } |
667 | | |
668 | | /* Self insert character 'c' in overwrite mode. */ |
669 | | static void vty_self_insert_overwrite(struct vty *vty, char c) |
670 | 0 | { |
671 | 0 | if (vty->cp == vty->length) { |
672 | 0 | vty_self_insert(vty, c); |
673 | 0 | return; |
674 | 0 | } |
675 | 0 |
|
676 | 0 | vty->buf[vty->cp++] = c; |
677 | 0 | vty_write(vty, &c, 1); |
678 | 0 | } |
679 | | |
680 | | /** |
681 | | * Insert a string into vty->buf at the current cursor position. |
682 | | * |
683 | | * If the resultant string would be larger than VTY_BUFSIZ it is |
684 | | * truncated to fit. |
685 | | */ |
686 | | static void vty_insert_word_overwrite(struct vty *vty, char *str) |
687 | 0 | { |
688 | 0 | if (vty->cp == VTY_BUFSIZ) |
689 | 0 | return; |
690 | 0 |
|
691 | 0 | size_t nwrite = MIN((int)strlen(str), VTY_BUFSIZ - vty->cp - 1); |
692 | 0 | memcpy(&vty->buf[vty->cp], str, nwrite); |
693 | 0 | vty->cp += nwrite; |
694 | 0 | vty->length = MAX(vty->cp, vty->length); |
695 | 0 | vty->buf[vty->length] = '\0'; |
696 | 0 | vty_write(vty, str, nwrite); |
697 | 0 | } |
698 | | |
699 | | /* Forward character. */ |
700 | | static void vty_forward_char(struct vty *vty) |
701 | 0 | { |
702 | 0 | if (vty->cp < vty->length) { |
703 | 0 | vty_write(vty, &vty->buf[vty->cp], 1); |
704 | 0 | vty->cp++; |
705 | 0 | } |
706 | 0 | } |
707 | | |
708 | | /* Backward character. */ |
709 | | static void vty_backward_char(struct vty *vty) |
710 | 0 | { |
711 | 0 | if (vty->cp > 0) { |
712 | 0 | vty->cp--; |
713 | 0 | vty_write(vty, &telnet_backward_char, 1); |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | | /* Move to the beginning of the line. */ |
718 | | static void vty_beginning_of_line(struct vty *vty) |
719 | 0 | { |
720 | 0 | while (vty->cp) |
721 | 0 | vty_backward_char(vty); |
722 | 0 | } |
723 | | |
724 | | /* Move to the end of the line. */ |
725 | | static void vty_end_of_line(struct vty *vty) |
726 | 0 | { |
727 | 0 | while (vty->cp < vty->length) |
728 | 0 | vty_forward_char(vty); |
729 | 0 | } |
730 | | |
731 | | static void vty_kill_line_from_beginning(struct vty *); |
732 | | static void vty_redraw_line(struct vty *); |
733 | | |
734 | | /* Print command line history. This function is called from |
735 | | vty_next_line and vty_previous_line. */ |
736 | | static void vty_history_print(struct vty *vty) |
737 | 0 | { |
738 | 0 | int length; |
739 | 0 |
|
740 | 0 | vty_kill_line_from_beginning(vty); |
741 | 0 |
|
742 | 0 | /* Get previous line from history buffer */ |
743 | 0 | length = strlen(vty->hist[vty->hp]); |
744 | 0 | memcpy(vty->buf, vty->hist[vty->hp], length); |
745 | 0 | vty->cp = vty->length = length; |
746 | 0 | vty->buf[vty->length] = '\0'; |
747 | 0 |
|
748 | 0 | /* Redraw current line */ |
749 | 0 | vty_redraw_line(vty); |
750 | 0 | } |
751 | | |
752 | | /* Show next command line history. */ |
753 | | static void vty_next_line(struct vty *vty) |
754 | 0 | { |
755 | 0 | int try_index; |
756 | 0 |
|
757 | 0 | if (vty->hp == vty->hindex) |
758 | 0 | return; |
759 | 0 |
|
760 | 0 | /* Try is there history exist or not. */ |
761 | 0 | try_index = vty->hp; |
762 | 0 | if (try_index == (VTY_MAXHIST - 1)) |
763 | 0 | try_index = 0; |
764 | 0 | else |
765 | 0 | try_index++; |
766 | 0 |
|
767 | 0 | /* If there is not history return. */ |
768 | 0 | if (vty->hist[try_index] == NULL) |
769 | 0 | return; |
770 | 0 | else |
771 | 0 | vty->hp = try_index; |
772 | 0 |
|
773 | 0 | vty_history_print(vty); |
774 | 0 | } |
775 | | |
776 | | /* Show previous command line history. */ |
777 | | static void vty_previous_line(struct vty *vty) |
778 | 0 | { |
779 | 0 | int try_index; |
780 | 0 |
|
781 | 0 | try_index = vty->hp; |
782 | 0 | if (try_index == 0) |
783 | 0 | try_index = VTY_MAXHIST - 1; |
784 | 0 | else |
785 | 0 | try_index--; |
786 | 0 |
|
787 | 0 | if (vty->hist[try_index] == NULL) |
788 | 0 | return; |
789 | 0 | else |
790 | 0 | vty->hp = try_index; |
791 | 0 |
|
792 | 0 | vty_history_print(vty); |
793 | 0 | } |
794 | | |
795 | | /* This function redraw all of the command line character. */ |
796 | | static void vty_redraw_line(struct vty *vty) |
797 | 0 | { |
798 | 0 | vty_write(vty, vty->buf, vty->length); |
799 | 0 | vty->cp = vty->length; |
800 | 0 | } |
801 | | |
802 | | /* Forward word. */ |
803 | | static void vty_forward_word(struct vty *vty) |
804 | 0 | { |
805 | 0 | while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') |
806 | 0 | vty_forward_char(vty); |
807 | 0 |
|
808 | 0 | while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') |
809 | 0 | vty_forward_char(vty); |
810 | 0 | } |
811 | | |
812 | | /* Backward word without skipping training space. */ |
813 | | static void vty_backward_pure_word(struct vty *vty) |
814 | 0 | { |
815 | 0 | while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') |
816 | 0 | vty_backward_char(vty); |
817 | 0 | } |
818 | | |
819 | | /* Backward word. */ |
820 | | static void vty_backward_word(struct vty *vty) |
821 | 0 | { |
822 | 0 | while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') |
823 | 0 | vty_backward_char(vty); |
824 | 0 |
|
825 | 0 | while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') |
826 | 0 | vty_backward_char(vty); |
827 | 0 | } |
828 | | |
829 | | /* When '^D' is typed at the beginning of the line we move to the down |
830 | | level. */ |
831 | | static void vty_down_level(struct vty *vty) |
832 | 0 | { |
833 | 0 | vty_out(vty, "\n"); |
834 | 0 | cmd_exit(vty); |
835 | 0 | vty_prompt(vty); |
836 | 0 | vty->cp = 0; |
837 | 0 | } |
838 | | |
839 | | /* When '^Z' is received from vty, move down to the enable mode. */ |
840 | | static void vty_end_config(struct vty *vty) |
841 | 0 | { |
842 | 0 | vty_out(vty, "\n"); |
843 | 0 |
|
844 | 0 | if (vty->config) { |
845 | 0 | vty_config_exit(vty); |
846 | 0 | vty->node = ENABLE_NODE; |
847 | 0 | } |
848 | 0 |
|
849 | 0 | vty_prompt(vty); |
850 | 0 | vty->cp = 0; |
851 | 0 | } |
852 | | |
853 | | /* Delete a character at the current point. */ |
854 | | static void vty_delete_char(struct vty *vty) |
855 | 0 | { |
856 | 0 | int i; |
857 | 0 | int size; |
858 | 0 |
|
859 | 0 | if (vty->length == 0) { |
860 | 0 | vty_down_level(vty); |
861 | 0 | return; |
862 | 0 | } |
863 | 0 |
|
864 | 0 | if (vty->cp == vty->length) |
865 | 0 | return; /* completion need here? */ |
866 | 0 |
|
867 | 0 | size = vty->length - vty->cp; |
868 | 0 |
|
869 | 0 | vty->length--; |
870 | 0 | memmove(&vty->buf[vty->cp], &vty->buf[vty->cp + 1], size - 1); |
871 | 0 | vty->buf[vty->length] = '\0'; |
872 | 0 |
|
873 | 0 | if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) |
874 | 0 | return; |
875 | 0 |
|
876 | 0 | vty_write(vty, &vty->buf[vty->cp], size - 1); |
877 | 0 | vty_write(vty, &telnet_space_char, 1); |
878 | 0 |
|
879 | 0 | for (i = 0; i < size; i++) |
880 | 0 | vty_write(vty, &telnet_backward_char, 1); |
881 | 0 | } |
882 | | |
883 | | /* Delete a character before the point. */ |
884 | | static void vty_delete_backward_char(struct vty *vty) |
885 | 0 | { |
886 | 0 | if (vty->cp == 0) |
887 | 0 | return; |
888 | 0 |
|
889 | 0 | vty_backward_char(vty); |
890 | 0 | vty_delete_char(vty); |
891 | 0 | } |
892 | | |
893 | | /* Kill rest of line from current point. */ |
894 | | static void vty_kill_line(struct vty *vty) |
895 | 0 | { |
896 | 0 | int i; |
897 | 0 | int size; |
898 | 0 |
|
899 | 0 | size = vty->length - vty->cp; |
900 | 0 |
|
901 | 0 | if (size == 0) |
902 | 0 | return; |
903 | 0 |
|
904 | 0 | for (i = 0; i < size; i++) |
905 | 0 | vty_write(vty, &telnet_space_char, 1); |
906 | 0 | for (i = 0; i < size; i++) |
907 | 0 | vty_write(vty, &telnet_backward_char, 1); |
908 | 0 |
|
909 | 0 | memset(&vty->buf[vty->cp], 0, size); |
910 | 0 | vty->length = vty->cp; |
911 | 0 | } |
912 | | |
913 | | /* Kill line from the beginning. */ |
914 | | static void vty_kill_line_from_beginning(struct vty *vty) |
915 | 0 | { |
916 | 0 | vty_beginning_of_line(vty); |
917 | 0 | vty_kill_line(vty); |
918 | 0 | } |
919 | | |
920 | | /* Delete a word before the point. */ |
921 | | static void vty_forward_kill_word(struct vty *vty) |
922 | 0 | { |
923 | 0 | while (vty->cp != vty->length && vty->buf[vty->cp] == ' ') |
924 | 0 | vty_delete_char(vty); |
925 | 0 | while (vty->cp != vty->length && vty->buf[vty->cp] != ' ') |
926 | 0 | vty_delete_char(vty); |
927 | 0 | } |
928 | | |
929 | | /* Delete a word before the point. */ |
930 | | static void vty_backward_kill_word(struct vty *vty) |
931 | 0 | { |
932 | 0 | while (vty->cp > 0 && vty->buf[vty->cp - 1] == ' ') |
933 | 0 | vty_delete_backward_char(vty); |
934 | 0 | while (vty->cp > 0 && vty->buf[vty->cp - 1] != ' ') |
935 | 0 | vty_delete_backward_char(vty); |
936 | 0 | } |
937 | | |
938 | | /* Transpose chars before or at the point. */ |
939 | | static void vty_transpose_chars(struct vty *vty) |
940 | 0 | { |
941 | 0 | char c1, c2; |
942 | 0 |
|
943 | 0 | /* If length is short or point is near by the beginning of line then |
944 | 0 | return. */ |
945 | 0 | if (vty->length < 2 || vty->cp < 1) |
946 | 0 | return; |
947 | 0 |
|
948 | 0 | /* In case of point is located at the end of the line. */ |
949 | 0 | if (vty->cp == vty->length) { |
950 | 0 | c1 = vty->buf[vty->cp - 1]; |
951 | 0 | c2 = vty->buf[vty->cp - 2]; |
952 | 0 |
|
953 | 0 | vty_backward_char(vty); |
954 | 0 | vty_backward_char(vty); |
955 | 0 | vty_self_insert_overwrite(vty, c1); |
956 | 0 | vty_self_insert_overwrite(vty, c2); |
957 | 0 | } else { |
958 | 0 | c1 = vty->buf[vty->cp]; |
959 | 0 | c2 = vty->buf[vty->cp - 1]; |
960 | 0 |
|
961 | 0 | vty_backward_char(vty); |
962 | 0 | vty_self_insert_overwrite(vty, c1); |
963 | 0 | vty_self_insert_overwrite(vty, c2); |
964 | 0 | } |
965 | 0 | } |
966 | | |
967 | | /* Do completion at vty interface. */ |
968 | | static void vty_complete_command(struct vty *vty) |
969 | 0 | { |
970 | 0 | int i; |
971 | 0 | int ret; |
972 | 0 | char **matched = NULL; |
973 | 0 | vector vline; |
974 | 0 |
|
975 | 0 | if (vty->node == AUTH_NODE || vty->node == AUTH_ENABLE_NODE) |
976 | 0 | return; |
977 | 0 |
|
978 | 0 | vline = cmd_make_strvec(vty->buf); |
979 | 0 | if (vline == NULL) |
980 | 0 | return; |
981 | 0 |
|
982 | 0 | /* In case of 'help \t'. */ |
983 | 0 | if (isspace((unsigned char)vty->buf[vty->length - 1])) |
984 | 0 | vector_set(vline, NULL); |
985 | 0 |
|
986 | 0 | matched = cmd_complete_command(vline, vty, &ret); |
987 | 0 |
|
988 | 0 | cmd_free_strvec(vline); |
989 | 0 |
|
990 | 0 | vty_out(vty, "\n"); |
991 | 0 | switch (ret) { |
992 | 0 | case CMD_ERR_AMBIGUOUS: |
993 | 0 | vty_out(vty, "%% Ambiguous command.\n"); |
994 | 0 | vty_prompt(vty); |
995 | 0 | vty_redraw_line(vty); |
996 | 0 | break; |
997 | 0 | case CMD_ERR_NO_MATCH: |
998 | 0 | /* vty_out (vty, "%% There is no matched command.\n"); */ |
999 | 0 | vty_prompt(vty); |
1000 | 0 | vty_redraw_line(vty); |
1001 | 0 | break; |
1002 | 0 | case CMD_COMPLETE_FULL_MATCH: |
1003 | 0 | if (!matched[0]) { |
1004 | 0 | /* 2016-11-28 equinox -- need to debug, SEGV here */ |
1005 | 0 | vty_out(vty, "%% CLI BUG: FULL_MATCH with NULL str\n"); |
1006 | 0 | vty_prompt(vty); |
1007 | 0 | vty_redraw_line(vty); |
1008 | 0 | break; |
1009 | 0 | } |
1010 | 0 | vty_prompt(vty); |
1011 | 0 | vty_redraw_line(vty); |
1012 | 0 | vty_backward_pure_word(vty); |
1013 | 0 | vty_insert_word_overwrite(vty, matched[0]); |
1014 | 0 | vty_self_insert(vty, ' '); |
1015 | 0 | XFREE(MTYPE_COMPLETION, matched[0]); |
1016 | 0 | break; |
1017 | 0 | case CMD_COMPLETE_MATCH: |
1018 | 0 | vty_prompt(vty); |
1019 | 0 | vty_redraw_line(vty); |
1020 | 0 | vty_backward_pure_word(vty); |
1021 | 0 | vty_insert_word_overwrite(vty, matched[0]); |
1022 | 0 | XFREE(MTYPE_COMPLETION, matched[0]); |
1023 | 0 | break; |
1024 | 0 | case CMD_COMPLETE_LIST_MATCH: |
1025 | 0 | for (i = 0; matched[i] != NULL; i++) { |
1026 | 0 | if (i != 0 && ((i % 6) == 0)) |
1027 | 0 | vty_out(vty, "\n"); |
1028 | 0 | vty_out(vty, "%-10s ", matched[i]); |
1029 | 0 | XFREE(MTYPE_COMPLETION, matched[i]); |
1030 | 0 | } |
1031 | 0 | vty_out(vty, "\n"); |
1032 | 0 |
|
1033 | 0 | vty_prompt(vty); |
1034 | 0 | vty_redraw_line(vty); |
1035 | 0 | break; |
1036 | 0 | case CMD_ERR_NOTHING_TODO: |
1037 | 0 | vty_prompt(vty); |
1038 | 0 | vty_redraw_line(vty); |
1039 | 0 | break; |
1040 | 0 | default: |
1041 | 0 | break; |
1042 | 0 | } |
1043 | 0 | XFREE(MTYPE_TMP, matched); |
1044 | 0 | } |
1045 | | |
1046 | | static void vty_describe_fold(struct vty *vty, int cmd_width, |
1047 | | unsigned int desc_width, struct cmd_token *token) |
1048 | 0 | { |
1049 | 0 | char *buf; |
1050 | 0 | const char *cmd, *p; |
1051 | 0 | int pos; |
1052 | 0 |
|
1053 | 0 | cmd = token->text; |
1054 | 0 |
|
1055 | 0 | if (desc_width <= 0) { |
1056 | 0 | vty_out(vty, " %-*s %s\n", cmd_width, cmd, token->desc); |
1057 | 0 | return; |
1058 | 0 | } |
1059 | 0 |
|
1060 | 0 | buf = XCALLOC(MTYPE_TMP, strlen(token->desc) + 1); |
1061 | 0 |
|
1062 | 0 | for (p = token->desc; strlen(p) > desc_width; p += pos + 1) { |
1063 | 0 | for (pos = desc_width; pos > 0; pos--) |
1064 | 0 | if (*(p + pos) == ' ') |
1065 | 0 | break; |
1066 | 0 |
|
1067 | 0 | if (pos == 0) |
1068 | 0 | break; |
1069 | 0 |
|
1070 | 0 | memcpy(buf, p, pos); |
1071 | 0 | buf[pos] = '\0'; |
1072 | 0 | vty_out(vty, " %-*s %s\n", cmd_width, cmd, buf); |
1073 | 0 |
|
1074 | 0 | cmd = ""; |
1075 | 0 | } |
1076 | 0 |
|
1077 | 0 | vty_out(vty, " %-*s %s\n", cmd_width, cmd, p); |
1078 | 0 |
|
1079 | 0 | XFREE(MTYPE_TMP, buf); |
1080 | 0 | } |
1081 | | |
1082 | | /* Describe matched command function. */ |
1083 | | static void vty_describe_command(struct vty *vty) |
1084 | 0 | { |
1085 | 0 | int ret; |
1086 | 0 | vector vline; |
1087 | 0 | vector describe; |
1088 | 0 | unsigned int i, width, desc_width; |
1089 | 0 | struct cmd_token *token, *token_cr = NULL; |
1090 | 0 |
|
1091 | 0 | vline = cmd_make_strvec(vty->buf); |
1092 | 0 |
|
1093 | 0 | /* In case of '> ?'. */ |
1094 | 0 | if (vline == NULL) { |
1095 | 0 | vline = vector_init(1); |
1096 | 0 | vector_set(vline, NULL); |
1097 | 0 | } else if (isspace((unsigned char)vty->buf[vty->length - 1])) |
1098 | 0 | vector_set(vline, NULL); |
1099 | 0 |
|
1100 | 0 | describe = cmd_describe_command(vline, vty, &ret); |
1101 | 0 |
|
1102 | 0 | vty_out(vty, "\n"); |
1103 | 0 |
|
1104 | 0 | /* Ambiguous error. */ |
1105 | 0 | switch (ret) { |
1106 | 0 | case CMD_ERR_AMBIGUOUS: |
1107 | 0 | vty_out(vty, "%% Ambiguous command.\n"); |
1108 | 0 | goto out; |
1109 | 0 | break; |
1110 | 0 | case CMD_ERR_NO_MATCH: |
1111 | 0 | vty_out(vty, "%% There is no matched command.\n"); |
1112 | 0 | goto out; |
1113 | 0 | break; |
1114 | 0 | } |
1115 | 0 |
|
1116 | 0 | /* Get width of command string. */ |
1117 | 0 | width = 0; |
1118 | 0 | for (i = 0; i < vector_active(describe); i++) |
1119 | 0 | if ((token = vector_slot(describe, i)) != NULL) { |
1120 | 0 | unsigned int len; |
1121 | 0 |
|
1122 | 0 | if (token->text[0] == '\0') |
1123 | 0 | continue; |
1124 | 0 |
|
1125 | 0 | len = strlen(token->text); |
1126 | 0 |
|
1127 | 0 | if (width < len) |
1128 | 0 | width = len; |
1129 | 0 | } |
1130 | 0 |
|
1131 | 0 | /* Get width of description string. */ |
1132 | 0 | desc_width = vty->width - (width + 6); |
1133 | 0 |
|
1134 | 0 | /* Print out description. */ |
1135 | 0 | for (i = 0; i < vector_active(describe); i++) |
1136 | 0 | if ((token = vector_slot(describe, i)) != NULL) { |
1137 | 0 | if (token->text[0] == '\0') |
1138 | 0 | continue; |
1139 | 0 |
|
1140 | 0 | if (strcmp(token->text, CMD_CR_TEXT) == 0) { |
1141 | 0 | token_cr = token; |
1142 | 0 | continue; |
1143 | 0 | } |
1144 | 0 |
|
1145 | 0 | if (!token->desc) |
1146 | 0 | vty_out(vty, " %-s\n", token->text); |
1147 | 0 | else if (desc_width >= strlen(token->desc)) |
1148 | 0 | vty_out(vty, " %-*s %s\n", width, token->text, |
1149 | 0 | token->desc); |
1150 | 0 | else |
1151 | 0 | vty_describe_fold(vty, width, desc_width, |
1152 | 0 | token); |
1153 | 0 |
|
1154 | 0 | if (IS_VARYING_TOKEN(token->type)) { |
1155 | 0 | const char *ref = vector_slot( |
1156 | 0 | vline, vector_active(vline) - 1); |
1157 | 0 |
|
1158 | 0 | vector varcomps = vector_init(VECTOR_MIN_SIZE); |
1159 | 0 | cmd_variable_complete(token, ref, varcomps); |
1160 | 0 |
|
1161 | 0 | if (vector_active(varcomps) > 0) { |
1162 | 0 | char *ac = cmd_variable_comp2str( |
1163 | 0 | varcomps, vty->width); |
1164 | 0 | vty_out(vty, "%s\n", ac); |
1165 | 0 | XFREE(MTYPE_TMP, ac); |
1166 | 0 | } |
1167 | 0 |
|
1168 | 0 | vector_free(varcomps); |
1169 | 0 | } |
1170 | 0 | } |
1171 | 0 |
|
1172 | 0 | if ((token = token_cr)) { |
1173 | 0 | if (!token->desc) |
1174 | 0 | vty_out(vty, " %-s\n", token->text); |
1175 | 0 | else if (desc_width >= strlen(token->desc)) |
1176 | 0 | vty_out(vty, " %-*s %s\n", width, token->text, |
1177 | 0 | token->desc); |
1178 | 0 | else |
1179 | 0 | vty_describe_fold(vty, width, desc_width, token); |
1180 | 0 | } |
1181 | 0 |
|
1182 | 0 | out: |
1183 | 0 | cmd_free_strvec(vline); |
1184 | 0 | if (describe) |
1185 | 0 | vector_free(describe); |
1186 | 0 |
|
1187 | 0 | vty_prompt(vty); |
1188 | 0 | vty_redraw_line(vty); |
1189 | 0 | } |
1190 | | |
1191 | | static void vty_clear_buf(struct vty *vty) |
1192 | 0 | { |
1193 | 0 | memset(vty->buf, 0, vty->max); |
1194 | 0 | } |
1195 | | |
1196 | | /* ^C stop current input and do not add command line to the history. */ |
1197 | | static void vty_stop_input(struct vty *vty) |
1198 | 0 | { |
1199 | 0 | vty->cp = vty->length = 0; |
1200 | 0 | vty_clear_buf(vty); |
1201 | 0 | vty_out(vty, "\n"); |
1202 | 0 |
|
1203 | 0 | if (vty->config) { |
1204 | 0 | vty_config_exit(vty); |
1205 | 0 | vty->node = ENABLE_NODE; |
1206 | 0 | } |
1207 | 0 |
|
1208 | 0 | vty_prompt(vty); |
1209 | 0 |
|
1210 | 0 | /* Set history pointer to the latest one. */ |
1211 | 0 | vty->hp = vty->hindex; |
1212 | 0 | } |
1213 | | |
1214 | | /* Add current command line to the history buffer. */ |
1215 | | static void vty_hist_add(struct vty *vty) |
1216 | 0 | { |
1217 | 0 | int index; |
1218 | 0 |
|
1219 | 0 | if (vty->length == 0) |
1220 | 0 | return; |
1221 | 0 |
|
1222 | 0 | index = vty->hindex ? vty->hindex - 1 : VTY_MAXHIST - 1; |
1223 | 0 |
|
1224 | 0 | /* Ignore the same string as previous one. */ |
1225 | 0 | if (vty->hist[index]) |
1226 | 0 | if (strcmp(vty->buf, vty->hist[index]) == 0) { |
1227 | 0 | vty->hp = vty->hindex; |
1228 | 0 | return; |
1229 | 0 | } |
1230 | 0 |
|
1231 | 0 | /* Insert history entry. */ |
1232 | 0 | XFREE(MTYPE_VTY_HIST, vty->hist[vty->hindex]); |
1233 | 0 | vty->hist[vty->hindex] = XSTRDUP(MTYPE_VTY_HIST, vty->buf); |
1234 | 0 |
|
1235 | 0 | /* History index rotation. */ |
1236 | 0 | vty->hindex++; |
1237 | 0 | if (vty->hindex == VTY_MAXHIST) |
1238 | 0 | vty->hindex = 0; |
1239 | 0 |
|
1240 | 0 | vty->hp = vty->hindex; |
1241 | 0 | } |
1242 | | |
1243 | | /* #define TELNET_OPTION_DEBUG */ |
1244 | | |
1245 | | /* Get telnet window size. */ |
1246 | | static int vty_telnet_option(struct vty *vty, unsigned char *buf, int nbytes) |
1247 | 0 | { |
1248 | 0 | #ifdef TELNET_OPTION_DEBUG |
1249 | 0 | int i; |
1250 | 0 |
|
1251 | 0 | for (i = 0; i < nbytes; i++) { |
1252 | 0 | switch (buf[i]) { |
1253 | 0 | case IAC: |
1254 | 0 | vty_out(vty, "IAC "); |
1255 | 0 | break; |
1256 | 0 | case WILL: |
1257 | 0 | vty_out(vty, "WILL "); |
1258 | 0 | break; |
1259 | 0 | case WONT: |
1260 | 0 | vty_out(vty, "WONT "); |
1261 | 0 | break; |
1262 | 0 | case DO: |
1263 | 0 | vty_out(vty, "DO "); |
1264 | 0 | break; |
1265 | 0 | case DONT: |
1266 | 0 | vty_out(vty, "DONT "); |
1267 | 0 | break; |
1268 | 0 | case SB: |
1269 | 0 | vty_out(vty, "SB "); |
1270 | 0 | break; |
1271 | 0 | case SE: |
1272 | 0 | vty_out(vty, "SE "); |
1273 | 0 | break; |
1274 | 0 | case TELOPT_ECHO: |
1275 | 0 | vty_out(vty, "TELOPT_ECHO \n"); |
1276 | 0 | break; |
1277 | 0 | case TELOPT_SGA: |
1278 | 0 | vty_out(vty, "TELOPT_SGA \n"); |
1279 | 0 | break; |
1280 | 0 | case TELOPT_NAWS: |
1281 | 0 | vty_out(vty, "TELOPT_NAWS \n"); |
1282 | 0 | break; |
1283 | 0 | default: |
1284 | 0 | vty_out(vty, "%x ", buf[i]); |
1285 | 0 | break; |
1286 | 0 | } |
1287 | 0 | } |
1288 | 0 | vty_out(vty, "\n"); |
1289 | 0 |
|
1290 | 0 | #endif /* TELNET_OPTION_DEBUG */ |
1291 | 0 |
|
1292 | 0 | switch (buf[0]) { |
1293 | 0 | case SB: |
1294 | 0 | vty->sb_len = 0; |
1295 | 0 | vty->iac_sb_in_progress = 1; |
1296 | 0 | return 0; |
1297 | 0 | case SE: { |
1298 | 0 | if (!vty->iac_sb_in_progress) |
1299 | 0 | return 0; |
1300 | 0 |
|
1301 | 0 | if ((vty->sb_len == 0) || (vty->sb_buf[0] == '\0')) { |
1302 | 0 | vty->iac_sb_in_progress = 0; |
1303 | 0 | return 0; |
1304 | 0 | } |
1305 | 0 | switch (vty->sb_buf[0]) { |
1306 | 0 | case TELOPT_NAWS: |
1307 | 0 | if (vty->sb_len != TELNET_NAWS_SB_LEN) |
1308 | 0 | flog_err( |
1309 | 0 | EC_LIB_SYSTEM_CALL, |
1310 | 0 | "RFC 1073 violation detected: telnet NAWS option should send %d characters, but we received %lu", |
1311 | 0 | TELNET_NAWS_SB_LEN, |
1312 | 0 | (unsigned long)vty->sb_len); |
1313 | 0 | else if (sizeof(vty->sb_buf) < TELNET_NAWS_SB_LEN) |
1314 | 0 | flog_err( |
1315 | 0 | EC_LIB_DEVELOPMENT, |
1316 | 0 | "Bug detected: sizeof(vty->sb_buf) %lu < %d, too small to handle the telnet NAWS option", |
1317 | 0 | (unsigned long)sizeof(vty->sb_buf), |
1318 | 0 | TELNET_NAWS_SB_LEN); |
1319 | 0 | else { |
1320 | 0 | vty->width = ((vty->sb_buf[1] << 8) |
1321 | 0 | | vty->sb_buf[2]); |
1322 | 0 | vty->height = ((vty->sb_buf[3] << 8) |
1323 | 0 | | vty->sb_buf[4]); |
1324 | 0 | #ifdef TELNET_OPTION_DEBUG |
1325 | 0 | vty_out(vty, |
1326 | 0 | "TELNET NAWS window size negotiation completed: width %d, height %d\n", |
1327 | 0 | vty->width, vty->height); |
1328 | 0 | #endif |
1329 | 0 | } |
1330 | 0 | break; |
1331 | 0 | } |
1332 | 0 | vty->iac_sb_in_progress = 0; |
1333 | 0 | return 0; |
1334 | 0 | } |
1335 | 0 | default: |
1336 | 0 | break; |
1337 | 0 | } |
1338 | 0 | return 1; |
1339 | 0 | } |
1340 | | |
1341 | | /* Execute current command line. */ |
1342 | | static int vty_execute(struct vty *vty) |
1343 | 0 | { |
1344 | 0 | int ret; |
1345 | 0 |
|
1346 | 0 | ret = CMD_SUCCESS; |
1347 | 0 |
|
1348 | 0 | switch (vty->node) { |
1349 | 0 | case AUTH_NODE: |
1350 | 0 | case AUTH_ENABLE_NODE: |
1351 | 0 | vty_auth(vty, vty->buf); |
1352 | 0 | break; |
1353 | 0 | default: |
1354 | 0 | ret = vty_command(vty, vty->buf); |
1355 | 0 | if (vty->type == VTY_TERM) |
1356 | 0 | vty_hist_add(vty); |
1357 | 0 | break; |
1358 | 0 | } |
1359 | 0 |
|
1360 | 0 | /* Clear command line buffer. */ |
1361 | 0 | vty->cp = vty->length = 0; |
1362 | 0 | vty_clear_buf(vty); |
1363 | 0 |
|
1364 | 0 | if (vty->status != VTY_CLOSE) |
1365 | 0 | vty_prompt(vty); |
1366 | 0 |
|
1367 | 0 | return ret; |
1368 | 0 | } |
1369 | | |
1370 | | #define CONTROL(X) ((X) - '@') |
1371 | 0 | #define VTY_NORMAL 0 |
1372 | | #define VTY_PRE_ESCAPE 1 |
1373 | | #define VTY_ESCAPE 2 |
1374 | | #define VTY_CR 3 |
1375 | | |
1376 | | /* Escape character command map. */ |
1377 | | static void vty_escape_map(unsigned char c, struct vty *vty) |
1378 | 0 | { |
1379 | 0 | switch (c) { |
1380 | 0 | case ('A'): |
1381 | 0 | vty_previous_line(vty); |
1382 | 0 | break; |
1383 | 0 | case ('B'): |
1384 | 0 | vty_next_line(vty); |
1385 | 0 | break; |
1386 | 0 | case ('C'): |
1387 | 0 | vty_forward_char(vty); |
1388 | 0 | break; |
1389 | 0 | case ('D'): |
1390 | 0 | vty_backward_char(vty); |
1391 | 0 | break; |
1392 | 0 | default: |
1393 | 0 | break; |
1394 | 0 | } |
1395 | 0 |
|
1396 | 0 | /* Go back to normal mode. */ |
1397 | 0 | vty->escape = VTY_NORMAL; |
1398 | 0 | } |
1399 | | |
1400 | | /* Quit print out to the buffer. */ |
1401 | | static void vty_buffer_reset(struct vty *vty) |
1402 | 0 | { |
1403 | 0 | buffer_reset(vty->obuf); |
1404 | 0 | buffer_reset(vty->lbuf); |
1405 | 0 | vty_prompt(vty); |
1406 | 0 | vty_redraw_line(vty); |
1407 | 0 | } |
1408 | | |
1409 | | /* Read data via vty socket. */ |
1410 | | static void vty_read(struct event *thread) |
1411 | 0 | { |
1412 | 0 | int i; |
1413 | 0 | int nbytes; |
1414 | 0 | unsigned char buf[VTY_READ_BUFSIZ]; |
1415 | 0 |
|
1416 | 0 | struct vty *vty = EVENT_ARG(thread); |
1417 | 0 |
|
1418 | 0 | /* Read raw data from socket */ |
1419 | 0 | if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) { |
1420 | 0 | if (nbytes < 0) { |
1421 | 0 | if (ERRNO_IO_RETRY(errno)) { |
1422 | 0 | vty_event(VTY_READ, vty); |
1423 | 0 | return; |
1424 | 0 | } |
1425 | 0 | flog_err( |
1426 | 0 | EC_LIB_SOCKET, |
1427 | 0 | "%s: read error on vty client fd %d, closing: %s", |
1428 | 0 | __func__, vty->fd, safe_strerror(errno)); |
1429 | 0 | buffer_reset(vty->obuf); |
1430 | 0 | buffer_reset(vty->lbuf); |
1431 | 0 | } |
1432 | 0 | vty->status = VTY_CLOSE; |
1433 | 0 | } |
1434 | 0 |
|
1435 | 0 | for (i = 0; i < nbytes; i++) { |
1436 | 0 | if (buf[i] == IAC) { |
1437 | 0 | if (!vty->iac) { |
1438 | 0 | vty->iac = 1; |
1439 | 0 | continue; |
1440 | 0 | } else { |
1441 | 0 | vty->iac = 0; |
1442 | 0 | } |
1443 | 0 | } |
1444 | 0 |
|
1445 | 0 | if (vty->iac_sb_in_progress && !vty->iac) { |
1446 | 0 | if (vty->sb_len < sizeof(vty->sb_buf)) |
1447 | 0 | vty->sb_buf[vty->sb_len] = buf[i]; |
1448 | 0 | vty->sb_len++; |
1449 | 0 | continue; |
1450 | 0 | } |
1451 | 0 |
|
1452 | 0 | if (vty->iac) { |
1453 | 0 | /* In case of telnet command */ |
1454 | 0 | int ret = 0; |
1455 | 0 | ret = vty_telnet_option(vty, buf + i, nbytes - i); |
1456 | 0 | vty->iac = 0; |
1457 | 0 | i += ret; |
1458 | 0 | continue; |
1459 | 0 | } |
1460 | 0 |
|
1461 | 0 |
|
1462 | 0 | if (vty->status == VTY_MORE) { |
1463 | 0 | switch (buf[i]) { |
1464 | 0 | case CONTROL('C'): |
1465 | 0 | case 'q': |
1466 | 0 | case 'Q': |
1467 | 0 | vty_buffer_reset(vty); |
1468 | 0 | break; |
1469 | 0 | default: |
1470 | 0 | break; |
1471 | 0 | } |
1472 | 0 | continue; |
1473 | 0 | } |
1474 | 0 |
|
1475 | 0 | /* Escape character. */ |
1476 | 0 | if (vty->escape == VTY_ESCAPE) { |
1477 | 0 | vty_escape_map(buf[i], vty); |
1478 | 0 | continue; |
1479 | 0 | } |
1480 | 0 |
|
1481 | 0 | /* Pre-escape status. */ |
1482 | 0 | if (vty->escape == VTY_PRE_ESCAPE) { |
1483 | 0 | switch (buf[i]) { |
1484 | 0 | case '[': |
1485 | 0 | vty->escape = VTY_ESCAPE; |
1486 | 0 | break; |
1487 | 0 | case 'b': |
1488 | 0 | vty_backward_word(vty); |
1489 | 0 | vty->escape = VTY_NORMAL; |
1490 | 0 | break; |
1491 | 0 | case 'f': |
1492 | 0 | vty_forward_word(vty); |
1493 | 0 | vty->escape = VTY_NORMAL; |
1494 | 0 | break; |
1495 | 0 | case 'd': |
1496 | 0 | vty_forward_kill_word(vty); |
1497 | 0 | vty->escape = VTY_NORMAL; |
1498 | 0 | break; |
1499 | 0 | case CONTROL('H'): |
1500 | 0 | case 0x7f: |
1501 | 0 | vty_backward_kill_word(vty); |
1502 | 0 | vty->escape = VTY_NORMAL; |
1503 | 0 | break; |
1504 | 0 | default: |
1505 | 0 | vty->escape = VTY_NORMAL; |
1506 | 0 | break; |
1507 | 0 | } |
1508 | 0 | continue; |
1509 | 0 | } |
1510 | 0 |
|
1511 | 0 | if (vty->escape == VTY_CR) { |
1512 | 0 | /* if we get CR+NL, the NL results in an extra empty |
1513 | 0 | * prompt line being printed without this; just drop |
1514 | 0 | * the NL if it immediately follows CR. |
1515 | 0 | */ |
1516 | 0 | vty->escape = VTY_NORMAL; |
1517 | 0 |
|
1518 | 0 | if (buf[i] == '\n') |
1519 | 0 | continue; |
1520 | 0 | } |
1521 | 0 |
|
1522 | 0 | switch (buf[i]) { |
1523 | 0 | case CONTROL('A'): |
1524 | 0 | vty_beginning_of_line(vty); |
1525 | 0 | break; |
1526 | 0 | case CONTROL('B'): |
1527 | 0 | vty_backward_char(vty); |
1528 | 0 | break; |
1529 | 0 | case CONTROL('C'): |
1530 | 0 | vty_stop_input(vty); |
1531 | 0 | break; |
1532 | 0 | case CONTROL('D'): |
1533 | 0 | vty_delete_char(vty); |
1534 | 0 | break; |
1535 | 0 | case CONTROL('E'): |
1536 | 0 | vty_end_of_line(vty); |
1537 | 0 | break; |
1538 | 0 | case CONTROL('F'): |
1539 | 0 | vty_forward_char(vty); |
1540 | 0 | break; |
1541 | 0 | case CONTROL('H'): |
1542 | 0 | case 0x7f: |
1543 | 0 | vty_delete_backward_char(vty); |
1544 | 0 | break; |
1545 | 0 | case CONTROL('K'): |
1546 | 0 | vty_kill_line(vty); |
1547 | 0 | break; |
1548 | 0 | case CONTROL('N'): |
1549 | 0 | vty_next_line(vty); |
1550 | 0 | break; |
1551 | 0 | case CONTROL('P'): |
1552 | 0 | vty_previous_line(vty); |
1553 | 0 | break; |
1554 | 0 | case CONTROL('T'): |
1555 | 0 | vty_transpose_chars(vty); |
1556 | 0 | break; |
1557 | 0 | case CONTROL('U'): |
1558 | 0 | vty_kill_line_from_beginning(vty); |
1559 | 0 | break; |
1560 | 0 | case CONTROL('W'): |
1561 | 0 | vty_backward_kill_word(vty); |
1562 | 0 | break; |
1563 | 0 | case CONTROL('Z'): |
1564 | 0 | vty_end_config(vty); |
1565 | 0 | break; |
1566 | 0 | case '\r': |
1567 | 0 | vty->escape = VTY_CR; |
1568 | 0 | /* fallthru */ |
1569 | 0 | case '\n': |
1570 | 0 | vty_out(vty, "\n"); |
1571 | 0 | buffer_flush_available(vty->obuf, vty->wfd); |
1572 | 0 | vty_execute(vty); |
1573 | 0 |
|
1574 | 0 | if (vty->pass_fd != -1) { |
1575 | 0 | close(vty->pass_fd); |
1576 | 0 | vty->pass_fd = -1; |
1577 | 0 | } |
1578 | 0 | break; |
1579 | 0 | case '\t': |
1580 | 0 | vty_complete_command(vty); |
1581 | 0 | break; |
1582 | 0 | case '?': |
1583 | 0 | if (vty->node == AUTH_NODE |
1584 | 0 | || vty->node == AUTH_ENABLE_NODE) |
1585 | 0 | vty_self_insert(vty, buf[i]); |
1586 | 0 | else |
1587 | 0 | vty_describe_command(vty); |
1588 | 0 | break; |
1589 | 0 | case '\033': |
1590 | 0 | if (i + 1 < nbytes && buf[i + 1] == '[') { |
1591 | 0 | vty->escape = VTY_ESCAPE; |
1592 | 0 | i++; |
1593 | 0 | } else |
1594 | 0 | vty->escape = VTY_PRE_ESCAPE; |
1595 | 0 | break; |
1596 | 0 | default: |
1597 | 0 | if (buf[i] > 31 && buf[i] < 127) |
1598 | 0 | vty_self_insert(vty, buf[i]); |
1599 | 0 | break; |
1600 | 0 | } |
1601 | 0 | } |
1602 | 0 |
|
1603 | 0 | /* Check status. */ |
1604 | 0 | if (vty->status == VTY_CLOSE) |
1605 | 0 | vty_close(vty); |
1606 | 0 | else { |
1607 | 0 | vty_event(VTY_WRITE, vty); |
1608 | 0 | vty_event(VTY_READ, vty); |
1609 | 0 | } |
1610 | 0 | } |
1611 | | |
1612 | | /* Flush buffer to the vty. */ |
1613 | | static void vty_flush(struct event *thread) |
1614 | 0 | { |
1615 | 0 | int erase; |
1616 | 0 | buffer_status_t flushrc; |
1617 | 0 | struct vty *vty = EVENT_ARG(thread); |
1618 | 0 |
|
1619 | 0 | /* Tempolary disable read thread. */ |
1620 | 0 | if (vty->lines == 0) |
1621 | 0 | EVENT_OFF(vty->t_read); |
1622 | 0 |
|
1623 | 0 | /* Function execution continue. */ |
1624 | 0 | erase = ((vty->status == VTY_MORE || vty->status == VTY_MORELINE)); |
1625 | 0 |
|
1626 | 0 | /* N.B. if width is 0, that means we don't know the window size. */ |
1627 | 0 | if ((vty->lines == 0) || (vty->width == 0) || (vty->height == 0)) |
1628 | 0 | flushrc = buffer_flush_available(vty->obuf, vty->wfd); |
1629 | 0 | else if (vty->status == VTY_MORELINE) |
1630 | 0 | flushrc = buffer_flush_window(vty->obuf, vty->wfd, vty->width, |
1631 | 0 | 1, erase, 0); |
1632 | 0 | else |
1633 | 0 | flushrc = buffer_flush_window( |
1634 | 0 | vty->obuf, vty->wfd, vty->width, |
1635 | 0 | vty->lines >= 0 ? vty->lines : vty->height, erase, 0); |
1636 | 0 | switch (flushrc) { |
1637 | 0 | case BUFFER_ERROR: |
1638 | 0 | zlog_info("buffer_flush failed on vty client fd %d/%d, closing", |
1639 | 0 | vty->fd, vty->wfd); |
1640 | 0 | buffer_reset(vty->lbuf); |
1641 | 0 | buffer_reset(vty->obuf); |
1642 | 0 | vty_close(vty); |
1643 | 0 | return; |
1644 | 0 | case BUFFER_EMPTY: |
1645 | 0 | if (vty->status == VTY_CLOSE) |
1646 | 0 | vty_close(vty); |
1647 | 0 | else { |
1648 | 0 | vty->status = VTY_NORMAL; |
1649 | 0 | if (vty->lines == 0) |
1650 | 0 | vty_event(VTY_READ, vty); |
1651 | 0 | } |
1652 | 0 | break; |
1653 | 0 | case BUFFER_PENDING: |
1654 | 0 | /* There is more data waiting to be written. */ |
1655 | 0 | vty->status = VTY_MORE; |
1656 | 0 | if (vty->lines == 0) |
1657 | 0 | vty_event(VTY_WRITE, vty); |
1658 | 0 | break; |
1659 | 0 | } |
1660 | 0 | } |
1661 | | |
1662 | | /* Allocate new vty struct. */ |
1663 | | struct vty *vty_new(void) |
1664 | 0 | { |
1665 | 0 | struct vty *new = XCALLOC(MTYPE_VTY, sizeof(struct vty)); |
1666 | |
|
1667 | 0 | new->fd = new->wfd = -1; |
1668 | 0 | new->of = stdout; |
1669 | 0 | new->lbuf = buffer_new(0); |
1670 | 0 | new->obuf = buffer_new(0); /* Use default buffer size. */ |
1671 | 0 | new->buf = XCALLOC(MTYPE_VTY, VTY_BUFSIZ); |
1672 | 0 | new->max = VTY_BUFSIZ; |
1673 | 0 | new->pass_fd = -1; |
1674 | |
|
1675 | 0 | if (mgmt_fe_client) { |
1676 | 0 | if (!mgmt_client_id_next) |
1677 | 0 | mgmt_client_id_next++; |
1678 | 0 | new->mgmt_client_id = mgmt_client_id_next++; |
1679 | 0 | new->mgmt_session_id = 0; |
1680 | 0 | mgmt_fe_create_client_session( |
1681 | 0 | mgmt_fe_client, new->mgmt_client_id, (uintptr_t) new); |
1682 | | /* we short-circuit create the session so it must be set now */ |
1683 | 0 | assertf(new->mgmt_session_id != 0, |
1684 | 0 | "Failed to create client session for VTY"); |
1685 | 0 | } |
1686 | |
|
1687 | 0 | return new; |
1688 | 0 | } |
1689 | | |
1690 | | |
1691 | | /* allocate and initialise vty */ |
1692 | | static struct vty *vty_new_init(int vty_sock) |
1693 | 0 | { |
1694 | 0 | struct vty *vty; |
1695 | |
|
1696 | 0 | vty = vty_new(); |
1697 | 0 | vty->fd = vty_sock; |
1698 | 0 | vty->wfd = vty_sock; |
1699 | 0 | vty->type = VTY_TERM; |
1700 | 0 | vty->node = AUTH_NODE; |
1701 | 0 | vty->fail = 0; |
1702 | 0 | vty->cp = 0; |
1703 | 0 | vty_clear_buf(vty); |
1704 | 0 | vty->length = 0; |
1705 | 0 | memset(vty->hist, 0, sizeof(vty->hist)); |
1706 | 0 | vty->hp = 0; |
1707 | 0 | vty->hindex = 0; |
1708 | 0 | vty->xpath_index = 0; |
1709 | 0 | memset(vty->xpath, 0, sizeof(vty->xpath)); |
1710 | 0 | vty->private_config = false; |
1711 | 0 | vty->candidate_config = vty_shared_candidate_config; |
1712 | 0 | vty->status = VTY_NORMAL; |
1713 | 0 | vty->lines = -1; |
1714 | 0 | vty->iac = 0; |
1715 | 0 | vty->iac_sb_in_progress = 0; |
1716 | 0 | vty->sb_len = 0; |
1717 | |
|
1718 | 0 | vtys_add_tail(vty_sessions, vty); |
1719 | |
|
1720 | 0 | return vty; |
1721 | 0 | } |
1722 | | |
1723 | | /* Create new vty structure. */ |
1724 | | static struct vty *vty_create(int vty_sock, union sockunion *su) |
1725 | 0 | { |
1726 | 0 | char buf[SU_ADDRSTRLEN]; |
1727 | 0 | struct vty *vty; |
1728 | 0 |
|
1729 | 0 | sockunion2str(su, buf, SU_ADDRSTRLEN); |
1730 | 0 |
|
1731 | 0 | /* Allocate new vty structure and set up default values. */ |
1732 | 0 | vty = vty_new_init(vty_sock); |
1733 | 0 |
|
1734 | 0 | /* configurable parameters not part of basic init */ |
1735 | 0 | vty->v_timeout = vty_timeout_val; |
1736 | 0 | strlcpy(vty->address, buf, sizeof(vty->address)); |
1737 | 0 | if (no_password_check) { |
1738 | 0 | if (host.advanced) |
1739 | 0 | vty->node = ENABLE_NODE; |
1740 | 0 | else |
1741 | 0 | vty->node = VIEW_NODE; |
1742 | 0 | } |
1743 | 0 | if (host.lines >= 0) |
1744 | 0 | vty->lines = host.lines; |
1745 | 0 |
|
1746 | 0 | if (!no_password_check) { |
1747 | 0 | /* Vty is not available if password isn't set. */ |
1748 | 0 | if (host.password == NULL && host.password_encrypt == NULL) { |
1749 | 0 | vty_out(vty, "Vty password is not set.\n"); |
1750 | 0 | vty->status = VTY_CLOSE; |
1751 | 0 | vty_close(vty); |
1752 | 0 | return NULL; |
1753 | 0 | } |
1754 | 0 | } |
1755 | 0 |
|
1756 | 0 | /* Say hello to the world. */ |
1757 | 0 | vty_hello(vty); |
1758 | 0 | if (!no_password_check) |
1759 | 0 | vty_out(vty, "\nUser Access Verification\n\n"); |
1760 | 0 |
|
1761 | 0 | /* Setting up terminal. */ |
1762 | 0 | vty_will_echo(vty); |
1763 | 0 | vty_will_suppress_go_ahead(vty); |
1764 | 0 |
|
1765 | 0 | vty_dont_linemode(vty); |
1766 | 0 | vty_do_window_size(vty); |
1767 | 0 | /* vty_dont_lflow_ahead (vty); */ |
1768 | 0 |
|
1769 | 0 | vty_prompt(vty); |
1770 | 0 |
|
1771 | 0 | /* Add read/write thread. */ |
1772 | 0 | vty_event(VTY_WRITE, vty); |
1773 | 0 | vty_event(VTY_READ, vty); |
1774 | 0 |
|
1775 | 0 | return vty; |
1776 | 0 | } |
1777 | | |
1778 | | /* create vty for stdio */ |
1779 | | static struct termios stdio_orig_termios; |
1780 | | static struct vty *stdio_vty = NULL; |
1781 | | static bool stdio_termios = false; |
1782 | | static void (*stdio_vty_atclose)(int isexit); |
1783 | | |
1784 | | static void vty_stdio_reset(int isexit) |
1785 | 4 | { |
1786 | 4 | if (stdio_vty) { |
1787 | 0 | if (stdio_termios) |
1788 | 0 | tcsetattr(0, TCSANOW, &stdio_orig_termios); |
1789 | 0 | stdio_termios = false; |
1790 | |
|
1791 | 0 | stdio_vty = NULL; |
1792 | |
|
1793 | 0 | if (stdio_vty_atclose) |
1794 | 0 | stdio_vty_atclose(isexit); |
1795 | 0 | stdio_vty_atclose = NULL; |
1796 | 0 | } |
1797 | 4 | } |
1798 | | |
1799 | | static void vty_stdio_atexit(void) |
1800 | 4 | { |
1801 | 4 | vty_stdio_reset(1); |
1802 | 4 | } |
1803 | | |
1804 | | void vty_stdio_suspend(void) |
1805 | 0 | { |
1806 | 0 | if (!stdio_vty) |
1807 | 0 | return; |
1808 | | |
1809 | 0 | EVENT_OFF(stdio_vty->t_write); |
1810 | 0 | EVENT_OFF(stdio_vty->t_read); |
1811 | 0 | EVENT_OFF(stdio_vty->t_timeout); |
1812 | |
|
1813 | 0 | if (stdio_termios) |
1814 | 0 | tcsetattr(0, TCSANOW, &stdio_orig_termios); |
1815 | 0 | stdio_termios = false; |
1816 | 0 | } |
1817 | | |
1818 | | void vty_stdio_resume(void) |
1819 | 0 | { |
1820 | 0 | if (!stdio_vty) |
1821 | 0 | return; |
1822 | | |
1823 | 0 | if (!tcgetattr(0, &stdio_orig_termios)) { |
1824 | 0 | struct termios termios; |
1825 | |
|
1826 | 0 | termios = stdio_orig_termios; |
1827 | 0 | termios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR |
1828 | 0 | | IGNCR | ICRNL | IXON); |
1829 | 0 | termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); |
1830 | 0 | termios.c_cflag &= ~(CSIZE | PARENB); |
1831 | 0 | termios.c_cflag |= CS8; |
1832 | 0 | tcsetattr(0, TCSANOW, &termios); |
1833 | 0 | stdio_termios = true; |
1834 | 0 | } |
1835 | |
|
1836 | 0 | vty_prompt(stdio_vty); |
1837 | | |
1838 | | /* Add read/write thread. */ |
1839 | 0 | vty_event(VTY_WRITE, stdio_vty); |
1840 | 0 | vty_event(VTY_READ, stdio_vty); |
1841 | 0 | } |
1842 | | |
1843 | | void vty_stdio_close(void) |
1844 | 0 | { |
1845 | 0 | if (!stdio_vty) |
1846 | 0 | return; |
1847 | 0 | vty_close(stdio_vty); |
1848 | 0 | } |
1849 | | |
1850 | | struct vty *vty_stdio(void (*atclose)(int isexit)) |
1851 | 0 | { |
1852 | 0 | struct vty *vty; |
1853 | | |
1854 | | /* refuse creating two vtys on stdio */ |
1855 | 0 | if (stdio_vty) |
1856 | 0 | return NULL; |
1857 | | |
1858 | 0 | vty = stdio_vty = vty_new_init(0); |
1859 | 0 | stdio_vty_atclose = atclose; |
1860 | 0 | vty->wfd = 1; |
1861 | | |
1862 | | /* always have stdio vty in a known _unchangeable_ state, don't want |
1863 | | * config |
1864 | | * to have any effect here to make sure scripting this works as intended |
1865 | | */ |
1866 | 0 | vty->node = ENABLE_NODE; |
1867 | 0 | vty->v_timeout = 0; |
1868 | 0 | strlcpy(vty->address, "console", sizeof(vty->address)); |
1869 | |
|
1870 | 0 | vty_stdio_resume(); |
1871 | 0 | return vty; |
1872 | 0 | } |
1873 | | |
1874 | | /* Accept connection from the network. */ |
1875 | | static void vty_accept(struct event *thread) |
1876 | 0 | { |
1877 | 0 | struct vty_serv *vtyserv = EVENT_ARG(thread); |
1878 | 0 | int vty_sock; |
1879 | 0 | union sockunion su; |
1880 | 0 | int ret; |
1881 | 0 | unsigned int on; |
1882 | 0 | int accept_sock = vtyserv->sock; |
1883 | 0 | struct prefix p; |
1884 | 0 | struct access_list *acl = NULL; |
1885 | 0 |
|
1886 | 0 | /* We continue hearing vty socket. */ |
1887 | 0 | vty_event_serv(VTY_SERV, vtyserv); |
1888 | 0 |
|
1889 | 0 | memset(&su, 0, sizeof(union sockunion)); |
1890 | 0 |
|
1891 | 0 | /* We can handle IPv4 or IPv6 socket. */ |
1892 | 0 | vty_sock = sockunion_accept(accept_sock, &su); |
1893 | 0 | if (vty_sock < 0) { |
1894 | 0 | flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", |
1895 | 0 | safe_strerror(errno)); |
1896 | 0 | return; |
1897 | 0 | } |
1898 | 0 | set_nonblocking(vty_sock); |
1899 | 0 | set_cloexec(vty_sock); |
1900 | 0 |
|
1901 | 0 | if (!sockunion2hostprefix(&su, &p)) { |
1902 | 0 | close(vty_sock); |
1903 | 0 | zlog_info("Vty unable to convert prefix from sockunion %pSU", |
1904 | 0 | &su); |
1905 | 0 | return; |
1906 | 0 | } |
1907 | 0 |
|
1908 | 0 | /* VTY's accesslist apply. */ |
1909 | 0 | if (p.family == AF_INET && vty_accesslist_name) { |
1910 | 0 | if ((acl = access_list_lookup(AFI_IP, vty_accesslist_name)) |
1911 | 0 | && (access_list_apply(acl, &p) == FILTER_DENY)) { |
1912 | 0 | zlog_info("Vty connection refused from %pSU", &su); |
1913 | 0 | close(vty_sock); |
1914 | 0 | return; |
1915 | 0 | } |
1916 | 0 | } |
1917 | 0 |
|
1918 | 0 | /* VTY's ipv6 accesslist apply. */ |
1919 | 0 | if (p.family == AF_INET6 && vty_ipv6_accesslist_name) { |
1920 | 0 | if ((acl = access_list_lookup(AFI_IP6, |
1921 | 0 | vty_ipv6_accesslist_name)) |
1922 | 0 | && (access_list_apply(acl, &p) == FILTER_DENY)) { |
1923 | 0 | zlog_info("Vty connection refused from %pSU", &su); |
1924 | 0 | close(vty_sock); |
1925 | 0 | return; |
1926 | 0 | } |
1927 | 0 | } |
1928 | 0 |
|
1929 | 0 | on = 1; |
1930 | 0 | ret = setsockopt(vty_sock, IPPROTO_TCP, TCP_NODELAY, (char *)&on, |
1931 | 0 | sizeof(on)); |
1932 | 0 | if (ret < 0) |
1933 | 0 | zlog_info("can't set sockopt to vty_sock : %s", |
1934 | 0 | safe_strerror(errno)); |
1935 | 0 |
|
1936 | 0 | zlog_info("Vty connection from %pSU", &su); |
1937 | 0 |
|
1938 | 0 | vty_create(vty_sock, &su); |
1939 | 0 | } |
1940 | | |
1941 | | static void vty_serv_sock_addrinfo(const char *hostname, unsigned short port) |
1942 | 0 | { |
1943 | 0 | int ret; |
1944 | 0 | struct addrinfo req; |
1945 | 0 | struct addrinfo *ainfo; |
1946 | 0 | struct addrinfo *ainfo_save; |
1947 | 0 | int sock; |
1948 | 0 | char port_str[BUFSIZ]; |
1949 | |
|
1950 | 0 | memset(&req, 0, sizeof(req)); |
1951 | 0 | req.ai_flags = AI_PASSIVE; |
1952 | 0 | req.ai_family = AF_UNSPEC; |
1953 | 0 | req.ai_socktype = SOCK_STREAM; |
1954 | 0 | snprintf(port_str, sizeof(port_str), "%d", port); |
1955 | 0 | port_str[sizeof(port_str) - 1] = '\0'; |
1956 | |
|
1957 | 0 | ret = getaddrinfo(hostname, port_str, &req, &ainfo); |
1958 | |
|
1959 | 0 | if (ret != 0) { |
1960 | 0 | flog_err_sys(EC_LIB_SYSTEM_CALL, "getaddrinfo failed: %s", |
1961 | 0 | gai_strerror(ret)); |
1962 | 0 | exit(1); |
1963 | 0 | } |
1964 | | |
1965 | 0 | ainfo_save = ainfo; |
1966 | |
|
1967 | 0 | do { |
1968 | 0 | struct vty_serv *vtyserv; |
1969 | |
|
1970 | 0 | if (ainfo->ai_family != AF_INET && ainfo->ai_family != AF_INET6) |
1971 | 0 | continue; |
1972 | | |
1973 | 0 | sock = socket(ainfo->ai_family, ainfo->ai_socktype, |
1974 | 0 | ainfo->ai_protocol); |
1975 | 0 | if (sock < 0) |
1976 | 0 | continue; |
1977 | | |
1978 | 0 | sockopt_v6only(ainfo->ai_family, sock); |
1979 | 0 | sockopt_reuseaddr(sock); |
1980 | 0 | sockopt_reuseport(sock); |
1981 | 0 | set_cloexec(sock); |
1982 | |
|
1983 | 0 | ret = bind(sock, ainfo->ai_addr, ainfo->ai_addrlen); |
1984 | 0 | if (ret < 0) { |
1985 | 0 | close(sock); /* Avoid sd leak. */ |
1986 | 0 | continue; |
1987 | 0 | } |
1988 | | |
1989 | 0 | ret = listen(sock, 3); |
1990 | 0 | if (ret < 0) { |
1991 | 0 | close(sock); /* Avoid sd leak. */ |
1992 | 0 | continue; |
1993 | 0 | } |
1994 | | |
1995 | 0 | vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv)); |
1996 | 0 | vtyserv->sock = sock; |
1997 | 0 | vtyservs_add_tail(vty_servs, vtyserv); |
1998 | |
|
1999 | 0 | vty_event_serv(VTY_SERV, vtyserv); |
2000 | 0 | } while ((ainfo = ainfo->ai_next) != NULL); |
2001 | |
|
2002 | 0 | freeaddrinfo(ainfo_save); |
2003 | 0 | } |
2004 | | |
2005 | | #ifdef VTYSH |
2006 | | /* For sockaddr_un. */ |
2007 | | #include <sys/un.h> |
2008 | | |
2009 | | /* VTY shell UNIX domain socket. */ |
2010 | | static void vty_serv_un(const char *path) |
2011 | 0 | { |
2012 | 0 | struct vty_serv *vtyserv; |
2013 | 0 | int ret; |
2014 | 0 | int sock, len; |
2015 | 0 | struct sockaddr_un serv; |
2016 | 0 | mode_t old_mask; |
2017 | 0 | struct zprivs_ids_t ids; |
2018 | | |
2019 | | /* First of all, unlink existing socket */ |
2020 | 0 | unlink(path); |
2021 | | |
2022 | | /* Set umask */ |
2023 | 0 | old_mask = umask(0007); |
2024 | | |
2025 | | /* Make UNIX domain socket. */ |
2026 | 0 | sock = socket(AF_UNIX, SOCK_STREAM, 0); |
2027 | 0 | if (sock < 0) { |
2028 | 0 | flog_err_sys(EC_LIB_SOCKET, |
2029 | 0 | "Cannot create unix stream socket: %s", |
2030 | 0 | safe_strerror(errno)); |
2031 | 0 | return; |
2032 | 0 | } |
2033 | | |
2034 | | /* Make server socket. */ |
2035 | 0 | memset(&serv, 0, sizeof(serv)); |
2036 | 0 | serv.sun_family = AF_UNIX; |
2037 | 0 | strlcpy(serv.sun_path, path, sizeof(serv.sun_path)); |
2038 | | #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN |
2039 | | len = serv.sun_len = SUN_LEN(&serv); |
2040 | | #else |
2041 | 0 | len = sizeof(serv.sun_family) + strlen(serv.sun_path); |
2042 | 0 | #endif /* HAVE_STRUCT_SOCKADDR_UN_SUN_LEN */ |
2043 | |
|
2044 | 0 | set_cloexec(sock); |
2045 | |
|
2046 | 0 | ret = bind(sock, (struct sockaddr *)&serv, len); |
2047 | 0 | if (ret < 0) { |
2048 | 0 | flog_err_sys(EC_LIB_SOCKET, "Cannot bind path %s: %s", path, |
2049 | 0 | safe_strerror(errno)); |
2050 | 0 | close(sock); /* Avoid sd leak. */ |
2051 | 0 | return; |
2052 | 0 | } |
2053 | | |
2054 | 0 | ret = listen(sock, 5); |
2055 | 0 | if (ret < 0) { |
2056 | 0 | flog_err_sys(EC_LIB_SOCKET, "listen(fd %d) failed: %s", sock, |
2057 | 0 | safe_strerror(errno)); |
2058 | 0 | close(sock); /* Avoid sd leak. */ |
2059 | 0 | return; |
2060 | 0 | } |
2061 | | |
2062 | 0 | umask(old_mask); |
2063 | |
|
2064 | 0 | zprivs_get_ids(&ids); |
2065 | | |
2066 | | /* Hack: ids.gid_vty is actually a uint, but we stored -1 in it |
2067 | | earlier for the case when we don't need to chown the file |
2068 | | type casting it here to make a compare */ |
2069 | 0 | if ((int)ids.gid_vty > 0) { |
2070 | | /* set group of socket */ |
2071 | 0 | if (chown(path, -1, ids.gid_vty)) { |
2072 | 0 | flog_err_sys(EC_LIB_SYSTEM_CALL, |
2073 | 0 | "vty_serv_un: could chown socket, %s", |
2074 | 0 | safe_strerror(errno)); |
2075 | 0 | } |
2076 | 0 | } |
2077 | |
|
2078 | 0 | vtyserv = XCALLOC(MTYPE_VTY_SERV, sizeof(*vtyserv)); |
2079 | 0 | vtyserv->sock = sock; |
2080 | 0 | vtyserv->vtysh = true; |
2081 | 0 | vtyservs_add_tail(vty_servs, vtyserv); |
2082 | |
|
2083 | 0 | vty_event_serv(VTYSH_SERV, vtyserv); |
2084 | 0 | } |
2085 | | |
2086 | | /* #define VTYSH_DEBUG 1 */ |
2087 | | |
2088 | | static void vtysh_accept(struct event *thread) |
2089 | 0 | { |
2090 | 0 | struct vty_serv *vtyserv = EVENT_ARG(thread); |
2091 | 0 | int accept_sock = vtyserv->sock; |
2092 | 0 | int sock; |
2093 | 0 | int client_len; |
2094 | 0 | struct sockaddr_un client; |
2095 | 0 | struct vty *vty; |
2096 | 0 |
|
2097 | 0 | vty_event_serv(VTYSH_SERV, vtyserv); |
2098 | 0 |
|
2099 | 0 | memset(&client, 0, sizeof(client)); |
2100 | 0 | client_len = sizeof(struct sockaddr_un); |
2101 | 0 |
|
2102 | 0 | sock = accept(accept_sock, (struct sockaddr *)&client, |
2103 | 0 | (socklen_t *)&client_len); |
2104 | 0 |
|
2105 | 0 | if (sock < 0) { |
2106 | 0 | flog_err(EC_LIB_SOCKET, "can't accept vty socket : %s", |
2107 | 0 | safe_strerror(errno)); |
2108 | 0 | return; |
2109 | 0 | } |
2110 | 0 |
|
2111 | 0 | if (set_nonblocking(sock) < 0) { |
2112 | 0 | flog_err( |
2113 | 0 | EC_LIB_SOCKET, |
2114 | 0 | "vtysh_accept: could not set vty socket %d to non-blocking, %s, closing", |
2115 | 0 | sock, safe_strerror(errno)); |
2116 | 0 | close(sock); |
2117 | 0 | return; |
2118 | 0 | } |
2119 | 0 | set_cloexec(sock); |
2120 | 0 |
|
2121 | 0 | #ifdef VTYSH_DEBUG |
2122 | 0 | printf("VTY shell accept\n"); |
2123 | 0 | #endif /* VTYSH_DEBUG */ |
2124 | 0 |
|
2125 | 0 | vty = vty_new(); |
2126 | 0 | vty->fd = sock; |
2127 | 0 | vty->wfd = sock; |
2128 | 0 | vty->type = VTY_SHELL_SERV; |
2129 | 0 | vty->node = VIEW_NODE; |
2130 | 0 | vtys_add_tail(vtysh_sessions, vty); |
2131 | 0 |
|
2132 | 0 | vty_event(VTYSH_READ, vty); |
2133 | 0 | } |
2134 | | |
2135 | | static int vtysh_do_pass_fd(struct vty *vty) |
2136 | 0 | { |
2137 | 0 | struct iovec iov[1] = { |
2138 | 0 | { |
2139 | 0 | .iov_base = vty->pass_fd_status, |
2140 | 0 | .iov_len = sizeof(vty->pass_fd_status), |
2141 | 0 | }, |
2142 | 0 | }; |
2143 | 0 | union { |
2144 | 0 | uint8_t buf[CMSG_SPACE(sizeof(int))]; |
2145 | 0 | struct cmsghdr align; |
2146 | 0 | } u; |
2147 | 0 | struct msghdr mh = { |
2148 | 0 | .msg_iov = iov, |
2149 | 0 | .msg_iovlen = array_size(iov), |
2150 | 0 | .msg_control = u.buf, |
2151 | 0 | .msg_controllen = sizeof(u.buf), |
2152 | 0 | }; |
2153 | 0 | struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh); |
2154 | 0 | ssize_t ret; |
2155 | |
|
2156 | 0 | memset(&u.buf, 0, sizeof(u.buf)); |
2157 | 0 | cmh->cmsg_level = SOL_SOCKET; |
2158 | 0 | cmh->cmsg_type = SCM_RIGHTS; |
2159 | 0 | cmh->cmsg_len = CMSG_LEN(sizeof(int)); |
2160 | 0 | memcpy(CMSG_DATA(cmh), &vty->pass_fd, sizeof(int)); |
2161 | |
|
2162 | 0 | ret = sendmsg(vty->wfd, &mh, 0); |
2163 | 0 | if (ret < 0 && ERRNO_IO_RETRY(errno)) |
2164 | 0 | return BUFFER_PENDING; |
2165 | | |
2166 | 0 | close(vty->pass_fd); |
2167 | 0 | vty->pass_fd = -1; |
2168 | 0 | vty->status = VTY_NORMAL; |
2169 | |
|
2170 | 0 | if (ret <= 0) |
2171 | 0 | return BUFFER_ERROR; |
2172 | | |
2173 | | /* resume accepting commands (suspended in vtysh_read) */ |
2174 | 0 | vty_event(VTYSH_READ, vty); |
2175 | |
|
2176 | 0 | if ((size_t)ret < sizeof(vty->pass_fd_status)) { |
2177 | 0 | size_t remains = sizeof(vty->pass_fd_status) - ret; |
2178 | |
|
2179 | 0 | buffer_put(vty->obuf, vty->pass_fd_status + ret, remains); |
2180 | 0 | return BUFFER_PENDING; |
2181 | 0 | } |
2182 | 0 | return BUFFER_EMPTY; |
2183 | 0 | } |
2184 | | |
2185 | | static int vtysh_flush(struct vty *vty) |
2186 | 0 | { |
2187 | 0 | int ret; |
2188 | |
|
2189 | 0 | ret = buffer_flush_available(vty->obuf, vty->wfd); |
2190 | 0 | if (ret == BUFFER_EMPTY && vty->status == VTY_PASSFD) |
2191 | 0 | ret = vtysh_do_pass_fd(vty); |
2192 | |
|
2193 | 0 | switch (ret) { |
2194 | 0 | case BUFFER_PENDING: |
2195 | 0 | vty_event(VTYSH_WRITE, vty); |
2196 | 0 | break; |
2197 | 0 | case BUFFER_ERROR: |
2198 | 0 | flog_err(EC_LIB_SOCKET, "%s: write error to fd %d, closing", |
2199 | 0 | __func__, vty->fd); |
2200 | 0 | buffer_reset(vty->lbuf); |
2201 | 0 | buffer_reset(vty->obuf); |
2202 | 0 | vty_close(vty); |
2203 | 0 | return -1; |
2204 | 0 | case BUFFER_EMPTY: |
2205 | 0 | break; |
2206 | 0 | } |
2207 | 0 | return 0; |
2208 | 0 | } |
2209 | | |
2210 | | void vty_pass_fd(struct vty *vty, int fd) |
2211 | 0 | { |
2212 | 0 | if (vty->pass_fd != -1) |
2213 | 0 | close(vty->pass_fd); |
2214 | |
|
2215 | 0 | vty->pass_fd = fd; |
2216 | 0 | } |
2217 | | |
2218 | | bool mgmt_vty_read_configs(void) |
2219 | 0 | { |
2220 | 0 | char path[PATH_MAX]; |
2221 | 0 | struct vty *vty; |
2222 | 0 | FILE *confp; |
2223 | 0 | uint line_num = 0; |
2224 | 0 | uint count = 0; |
2225 | 0 | uint index; |
2226 | |
|
2227 | 0 | vty = vty_new(); |
2228 | 0 | vty->wfd = STDERR_FILENO; |
2229 | 0 | vty->type = VTY_FILE; |
2230 | 0 | vty->node = CONFIG_NODE; |
2231 | 0 | vty->config = true; |
2232 | 0 | vty->pending_allowed = true; |
2233 | |
|
2234 | 0 | vty->candidate_config = vty_shared_candidate_config; |
2235 | |
|
2236 | 0 | vty_mgmt_lock_candidate_inline(vty); |
2237 | 0 | vty_mgmt_lock_running_inline(vty); |
2238 | |
|
2239 | 0 | for (index = 0; index < array_size(mgmt_daemons); index++) { |
2240 | 0 | snprintf(path, sizeof(path), "%s/%s.conf", frr_sysconfdir, |
2241 | 0 | mgmt_daemons[index]); |
2242 | |
|
2243 | 0 | confp = vty_open_config(path, config_default); |
2244 | 0 | if (!confp) |
2245 | 0 | continue; |
2246 | | |
2247 | 0 | zlog_info("mgmtd: reading config file: %s", path); |
2248 | | |
2249 | | /* Execute configuration file */ |
2250 | 0 | line_num = 0; |
2251 | 0 | (void)config_from_file(vty, confp, &line_num); |
2252 | 0 | count++; |
2253 | |
|
2254 | 0 | fclose(confp); |
2255 | 0 | } |
2256 | |
|
2257 | 0 | snprintf(path, sizeof(path), "%s/mgmtd.conf", frr_sysconfdir); |
2258 | 0 | confp = vty_open_config(path, config_default); |
2259 | 0 | if (!confp) { |
2260 | 0 | char *orig; |
2261 | |
|
2262 | 0 | snprintf(path, sizeof(path), "%s/zebra.conf", frr_sysconfdir); |
2263 | 0 | orig = XSTRDUP(MTYPE_TMP, host_config_get()); |
2264 | |
|
2265 | 0 | zlog_info("mgmtd: trying backup config file: %s", path); |
2266 | 0 | confp = vty_open_config(path, config_default); |
2267 | |
|
2268 | 0 | host_config_set(path); |
2269 | 0 | XFREE(MTYPE_TMP, orig); |
2270 | 0 | } |
2271 | |
|
2272 | 0 | if (confp) { |
2273 | 0 | zlog_info("mgmtd: reading config file: %s", path); |
2274 | |
|
2275 | 0 | line_num = 0; |
2276 | 0 | (void)config_from_file(vty, confp, &line_num); |
2277 | 0 | count++; |
2278 | |
|
2279 | 0 | fclose(confp); |
2280 | 0 | } |
2281 | | |
2282 | | /* Conditionally unlock as the config file may have "exit"d early which |
2283 | | * would then have unlocked things. |
2284 | | */ |
2285 | 0 | if (vty->mgmt_locked_running_ds) |
2286 | 0 | vty_mgmt_unlock_running_inline(vty); |
2287 | 0 | if (vty->mgmt_locked_candidate_ds) |
2288 | 0 | vty_mgmt_unlock_candidate_inline(vty); |
2289 | |
|
2290 | 0 | vty->pending_allowed = false; |
2291 | |
|
2292 | 0 | if (!count) |
2293 | 0 | vty_close(vty); |
2294 | 0 | else |
2295 | 0 | vty_read_file_finish(vty, NULL); |
2296 | |
|
2297 | 0 | zlog_info("mgmtd: finished reading config files"); |
2298 | |
|
2299 | 0 | return true; |
2300 | 0 | } |
2301 | | |
2302 | | static void vtysh_read(struct event *thread) |
2303 | 0 | { |
2304 | 0 | int ret; |
2305 | 0 | int sock; |
2306 | 0 | int nbytes; |
2307 | 0 | struct vty *vty; |
2308 | 0 | unsigned char buf[VTY_READ_BUFSIZ]; |
2309 | 0 | unsigned char *p; |
2310 | 0 | uint8_t header[4] = {0, 0, 0, 0}; |
2311 | 0 |
|
2312 | 0 | sock = EVENT_FD(thread); |
2313 | 0 | vty = EVENT_ARG(thread); |
2314 | 0 |
|
2315 | 0 | /* |
2316 | 0 | * This code looks like it can read multiple commands from the `buf` |
2317 | 0 | * value returned by read(); however, it cannot in some cases. |
2318 | 0 | * |
2319 | 0 | * There are multiple paths out of the "copying to vty->buf" loop, which |
2320 | 0 | * lose any content not yet copied from the stack `buf`, `passfd`, |
2321 | 0 | * `CMD_SUSPEND` and finally if a front-end for mgmtd (generally this |
2322 | 0 | * would be mgmtd itself). So these code paths are counting on vtysh not |
2323 | 0 | * sending us more than 1 command line before waiting on the reply to |
2324 | 0 | * that command. |
2325 | 0 | */ |
2326 | 0 | assert(vty->type == VTY_SHELL_SERV); |
2327 | 0 |
|
2328 | 0 | if ((nbytes = read(sock, buf, VTY_READ_BUFSIZ)) <= 0) { |
2329 | 0 | if (nbytes < 0) { |
2330 | 0 | if (ERRNO_IO_RETRY(errno)) { |
2331 | 0 | vty_event(VTYSH_READ, vty); |
2332 | 0 | return; |
2333 | 0 | } |
2334 | 0 | flog_err( |
2335 | 0 | EC_LIB_SOCKET, |
2336 | 0 | "%s: read failed on vtysh client fd %d, closing: %s", |
2337 | 0 | __func__, sock, safe_strerror(errno)); |
2338 | 0 | } |
2339 | 0 | buffer_reset(vty->lbuf); |
2340 | 0 | buffer_reset(vty->obuf); |
2341 | 0 | vty_close(vty); |
2342 | 0 | #ifdef VTYSH_DEBUG |
2343 | 0 | printf("close vtysh\n"); |
2344 | 0 | #endif /* VTYSH_DEBUG */ |
2345 | 0 | return; |
2346 | 0 | } |
2347 | 0 |
|
2348 | 0 | #ifdef VTYSH_DEBUG |
2349 | 0 | printf("line: %.*s\n", nbytes, buf); |
2350 | 0 | #endif /* VTYSH_DEBUG */ |
2351 | 0 |
|
2352 | 0 | if (vty->length + nbytes >= VTY_BUFSIZ) { |
2353 | 0 | /* Clear command line buffer. */ |
2354 | 0 | vty->cp = vty->length = 0; |
2355 | 0 | vty_clear_buf(vty); |
2356 | 0 | vty_out(vty, "%% Command is too long.\n"); |
2357 | 0 | } else { |
2358 | 0 | for (p = buf; p < buf + nbytes; p++) { |
2359 | 0 | vty->buf[vty->length++] = *p; |
2360 | 0 | if (*p == '\0') { |
2361 | 0 | /* Pass this line to parser. */ |
2362 | 0 | ret = vty_execute(vty); |
2363 | 0 | /* Note that vty_execute clears the command buffer and resets |
2364 | 0 | vty->length to 0. */ |
2365 | 0 |
|
2366 | 0 | /* Return result. */ |
2367 | 0 | #ifdef VTYSH_DEBUG |
2368 | 0 | printf("result: %d\n", ret); |
2369 | 0 | printf("vtysh node: %d\n", vty->node); |
2370 | 0 | #endif /* VTYSH_DEBUG */ |
2371 | 0 |
|
2372 | 0 | if (vty->pass_fd != -1) { |
2373 | 0 | memset(vty->pass_fd_status, 0, 4); |
2374 | 0 | vty->pass_fd_status[3] = ret; |
2375 | 0 | vty->status = VTY_PASSFD; |
2376 | 0 |
|
2377 | 0 | if (!vty->t_write) |
2378 | 0 | vty_event(VTYSH_WRITE, vty); |
2379 | 0 |
|
2380 | 0 | /* this introduces a "sequence point" |
2381 | 0 | * command output is written normally, |
2382 | 0 | * read processing is suspended until |
2383 | 0 | * buffer is empty |
2384 | 0 | * then retcode + FD is written |
2385 | 0 | * then normal processing resumes |
2386 | 0 | * |
2387 | 0 | * => skip vty_event(VTYSH_READ, vty)! |
2388 | 0 | */ |
2389 | 0 | return; |
2390 | 0 | } |
2391 | 0 |
|
2392 | 0 | /* hack for asynchronous "write integrated" |
2393 | 0 | * - other commands in "buf" will be ditched |
2394 | 0 | * - input during pending config-write is |
2395 | 0 | * "unsupported" */ |
2396 | 0 | if (ret == CMD_SUSPEND) |
2397 | 0 | break; |
2398 | 0 |
|
2399 | 0 | /* with new infra we need to stop response till |
2400 | 0 | * we get response through callback. |
2401 | 0 | */ |
2402 | 0 | if (vty->mgmt_req_pending_cmd) { |
2403 | 0 | MGMTD_FE_CLIENT_DBG( |
2404 | 0 | "postpone CLI response pending mgmtd %s on vty session-id %" PRIu64, |
2405 | 0 | vty->mgmt_req_pending_cmd, |
2406 | 0 | vty->mgmt_session_id); |
2407 | 0 | return; |
2408 | 0 | } |
2409 | 0 |
|
2410 | 0 | /* warning: watchfrr hardcodes this result write |
2411 | 0 | */ |
2412 | 0 | header[3] = ret; |
2413 | 0 | buffer_put(vty->obuf, header, 4); |
2414 | 0 |
|
2415 | 0 | if (!vty->t_write && (vtysh_flush(vty) < 0)) |
2416 | 0 | /* Try to flush results; exit if a write |
2417 | 0 | * error occurs. */ |
2418 | 0 | return; |
2419 | 0 | } |
2420 | 0 | } |
2421 | 0 | } |
2422 | 0 |
|
2423 | 0 | if (vty->status == VTY_CLOSE) |
2424 | 0 | vty_close(vty); |
2425 | 0 | else |
2426 | 0 | vty_event(VTYSH_READ, vty); |
2427 | 0 | } |
2428 | | |
2429 | | static void vtysh_write(struct event *thread) |
2430 | 0 | { |
2431 | 0 | struct vty *vty = EVENT_ARG(thread); |
2432 | 0 |
|
2433 | 0 | vtysh_flush(vty); |
2434 | 0 | } |
2435 | | |
2436 | | #endif /* VTYSH */ |
2437 | | |
2438 | | /* Determine address family to bind. */ |
2439 | | void vty_serv_start(const char *addr, unsigned short port, const char *path) |
2440 | 0 | { |
2441 | | /* If port is set to 0, do not listen on TCP/IP at all! */ |
2442 | 0 | if (port) |
2443 | 0 | vty_serv_sock_addrinfo(addr, port); |
2444 | |
|
2445 | 0 | #ifdef VTYSH |
2446 | 0 | vty_serv_un(path); |
2447 | 0 | #endif /* VTYSH */ |
2448 | 0 | } |
2449 | | |
2450 | | void vty_serv_stop(void) |
2451 | 0 | { |
2452 | 0 | struct vty_serv *vtyserv; |
2453 | |
|
2454 | 0 | while ((vtyserv = vtyservs_pop(vty_servs))) { |
2455 | 0 | EVENT_OFF(vtyserv->t_accept); |
2456 | 0 | close(vtyserv->sock); |
2457 | 0 | XFREE(MTYPE_VTY_SERV, vtyserv); |
2458 | 0 | } |
2459 | |
|
2460 | 0 | vtyservs_fini(vty_servs); |
2461 | 0 | vtyservs_init(vty_servs); |
2462 | 0 | } |
2463 | | |
2464 | | static void vty_error_delete(void *arg) |
2465 | 0 | { |
2466 | 0 | struct vty_error *ve = arg; |
2467 | |
|
2468 | 0 | XFREE(MTYPE_TMP, ve); |
2469 | 0 | } |
2470 | | |
2471 | | /* Close vty interface. Warning: call this only from functions that |
2472 | | will be careful not to access the vty afterwards (since it has |
2473 | | now been freed). This is safest from top-level functions (called |
2474 | | directly by the thread dispatcher). */ |
2475 | | void vty_close(struct vty *vty) |
2476 | 0 | { |
2477 | 0 | int i; |
2478 | 0 | bool was_stdio = false; |
2479 | |
|
2480 | 0 | vty->status = VTY_CLOSE; |
2481 | | |
2482 | | /* |
2483 | | * If we reach here with pending config to commit we will be losing it |
2484 | | * so warn the user. |
2485 | | */ |
2486 | 0 | if (vty->mgmt_num_pending_setcfg) |
2487 | 0 | MGMTD_FE_CLIENT_ERR( |
2488 | 0 | "vty closed, uncommitted config will be lost."); |
2489 | | |
2490 | | /* Drop out of configure / transaction if needed. */ |
2491 | 0 | vty_config_exit(vty); |
2492 | |
|
2493 | 0 | if (mgmt_fe_client && vty->mgmt_session_id) { |
2494 | 0 | MGMTD_FE_CLIENT_DBG("closing vty session"); |
2495 | 0 | mgmt_fe_destroy_client_session(mgmt_fe_client, |
2496 | 0 | vty->mgmt_client_id); |
2497 | 0 | vty->mgmt_session_id = 0; |
2498 | 0 | } |
2499 | | |
2500 | | /* Cancel threads.*/ |
2501 | 0 | EVENT_OFF(vty->t_read); |
2502 | 0 | EVENT_OFF(vty->t_write); |
2503 | 0 | EVENT_OFF(vty->t_timeout); |
2504 | |
|
2505 | 0 | if (vty->pass_fd != -1) { |
2506 | 0 | close(vty->pass_fd); |
2507 | 0 | vty->pass_fd = -1; |
2508 | 0 | } |
2509 | 0 | zlog_live_close(&vty->live_log); |
2510 | | |
2511 | | /* Flush buffer. */ |
2512 | 0 | buffer_flush_all(vty->obuf, vty->wfd); |
2513 | | |
2514 | | /* Free input buffer. */ |
2515 | 0 | buffer_free(vty->obuf); |
2516 | 0 | buffer_free(vty->lbuf); |
2517 | | |
2518 | | /* Free command history. */ |
2519 | 0 | for (i = 0; i < VTY_MAXHIST; i++) { |
2520 | 0 | XFREE(MTYPE_VTY_HIST, vty->hist[i]); |
2521 | 0 | } |
2522 | | |
2523 | | /* Unset vector. */ |
2524 | 0 | if (vty->fd != -1) { |
2525 | 0 | if (vty->type == VTY_SHELL_SERV) |
2526 | 0 | vtys_del(vtysh_sessions, vty); |
2527 | 0 | else if (vty->type == VTY_TERM) |
2528 | 0 | vtys_del(vty_sessions, vty); |
2529 | 0 | } |
2530 | |
|
2531 | 0 | if (vty->wfd > 0 && vty->type == VTY_FILE) |
2532 | 0 | fsync(vty->wfd); |
2533 | | |
2534 | | /* Close socket. |
2535 | | * note check is for fd > STDERR_FILENO, not fd != -1. |
2536 | | * We never close stdin/stdout/stderr here, because we may be |
2537 | | * running in foreground mode with logging to stdout. Also, |
2538 | | * additionally, we'd need to replace these fds with /dev/null. */ |
2539 | 0 | if (vty->wfd > STDERR_FILENO && vty->wfd != vty->fd) |
2540 | 0 | close(vty->wfd); |
2541 | 0 | if (vty->fd > STDERR_FILENO) |
2542 | 0 | close(vty->fd); |
2543 | 0 | if (vty->fd == STDIN_FILENO) |
2544 | 0 | was_stdio = true; |
2545 | |
|
2546 | 0 | XFREE(MTYPE_TMP, vty->pending_cmds_buf); |
2547 | 0 | XFREE(MTYPE_VTY, vty->buf); |
2548 | |
|
2549 | 0 | if (vty->error) { |
2550 | 0 | vty->error->del = vty_error_delete; |
2551 | 0 | list_delete(&vty->error); |
2552 | 0 | } |
2553 | | |
2554 | | /* OK free vty. */ |
2555 | 0 | XFREE(MTYPE_VTY, vty); |
2556 | |
|
2557 | 0 | if (was_stdio) |
2558 | 0 | vty_stdio_reset(0); |
2559 | 0 | } |
2560 | | |
2561 | | /* When time out occur output message then close connection. */ |
2562 | | static void vty_timeout(struct event *thread) |
2563 | 0 | { |
2564 | 0 | struct vty *vty; |
2565 | 0 |
|
2566 | 0 | vty = EVENT_ARG(thread); |
2567 | 0 | vty->v_timeout = 0; |
2568 | 0 |
|
2569 | 0 | /* Clear buffer*/ |
2570 | 0 | buffer_reset(vty->lbuf); |
2571 | 0 | buffer_reset(vty->obuf); |
2572 | 0 | vty_out(vty, "\nVty connection is timed out.\n"); |
2573 | 0 |
|
2574 | 0 | /* Close connection. */ |
2575 | 0 | vty->status = VTY_CLOSE; |
2576 | 0 | vty_close(vty); |
2577 | 0 | } |
2578 | | |
2579 | | /* Read up configuration file from file_name. */ |
2580 | | void vty_read_file(struct nb_config *config, FILE *confp) |
2581 | 0 | { |
2582 | 0 | struct vty *vty; |
2583 | 0 | unsigned int line_num = 0; |
2584 | |
|
2585 | 0 | vty = vty_new(); |
2586 | | /* vty_close won't close stderr; if some config command prints |
2587 | | * something it'll end up there. (not ideal; it'd be better if output |
2588 | | * from a file-load went to logging instead. Also note that if this |
2589 | | * function is called after daemonizing, stderr will be /dev/null.) |
2590 | | * |
2591 | | * vty->fd will be -1 from vty_new() |
2592 | | */ |
2593 | 0 | vty->wfd = STDERR_FILENO; |
2594 | 0 | vty->type = VTY_FILE; |
2595 | 0 | vty->node = CONFIG_NODE; |
2596 | 0 | vty->config = true; |
2597 | 0 | if (config) |
2598 | 0 | vty->candidate_config = config; |
2599 | 0 | else { |
2600 | 0 | vty->private_config = true; |
2601 | 0 | vty->candidate_config = nb_config_new(NULL); |
2602 | 0 | } |
2603 | | |
2604 | | /* Execute configuration file */ |
2605 | 0 | (void)config_from_file(vty, confp, &line_num); |
2606 | |
|
2607 | 0 | vty_read_file_finish(vty, config); |
2608 | 0 | } |
2609 | | |
2610 | | void vty_read_file_finish(struct vty *vty, struct nb_config *config) |
2611 | 0 | { |
2612 | 0 | struct vty_error *ve; |
2613 | 0 | struct listnode *node; |
2614 | | |
2615 | | /* Flush any previous errors before printing messages below */ |
2616 | 0 | buffer_flush_all(vty->obuf, vty->wfd); |
2617 | |
|
2618 | 0 | for (ALL_LIST_ELEMENTS_RO(vty->error, node, ve)) { |
2619 | 0 | const char *message = NULL; |
2620 | 0 | char *nl; |
2621 | |
|
2622 | 0 | switch (ve->cmd_ret) { |
2623 | 0 | case CMD_SUCCESS: |
2624 | 0 | message = "Command succeeded"; |
2625 | 0 | break; |
2626 | 0 | case CMD_ERR_NOTHING_TODO: |
2627 | 0 | message = "Nothing to do"; |
2628 | 0 | break; |
2629 | 0 | case CMD_ERR_AMBIGUOUS: |
2630 | 0 | message = "Ambiguous command"; |
2631 | 0 | break; |
2632 | 0 | case CMD_ERR_NO_MATCH: |
2633 | 0 | message = "No such command"; |
2634 | 0 | break; |
2635 | 0 | case CMD_WARNING: |
2636 | 0 | message = "Command returned Warning"; |
2637 | 0 | break; |
2638 | 0 | case CMD_WARNING_CONFIG_FAILED: |
2639 | 0 | message = "Command returned Warning Config Failed"; |
2640 | 0 | break; |
2641 | 0 | case CMD_ERR_INCOMPLETE: |
2642 | 0 | message = "Command returned Incomplete"; |
2643 | 0 | break; |
2644 | 0 | case CMD_ERR_EXEED_ARGC_MAX: |
2645 | 0 | message = |
2646 | 0 | "Command exceeded maximum number of Arguments"; |
2647 | 0 | break; |
2648 | 0 | default: |
2649 | 0 | message = "Command returned unhandled error message"; |
2650 | 0 | break; |
2651 | 0 | } |
2652 | | |
2653 | 0 | nl = strchr(ve->error_buf, '\n'); |
2654 | 0 | if (nl) |
2655 | 0 | *nl = '\0'; |
2656 | 0 | flog_err(EC_LIB_VTY, "%s on config line %u: %s", message, |
2657 | 0 | ve->line_num, ve->error_buf); |
2658 | 0 | } |
2659 | | |
2660 | | /* |
2661 | | * Automatically commit the candidate configuration after |
2662 | | * reading the configuration file. |
2663 | | */ |
2664 | 0 | if (config == NULL) { |
2665 | 0 | struct nb_context context = {}; |
2666 | 0 | char errmsg[BUFSIZ] = {0}; |
2667 | 0 | int ret; |
2668 | |
|
2669 | 0 | context.client = NB_CLIENT_CLI; |
2670 | 0 | context.user = vty; |
2671 | 0 | ret = nb_candidate_commit(context, vty->candidate_config, true, |
2672 | 0 | "Read configuration file", NULL, |
2673 | 0 | errmsg, sizeof(errmsg)); |
2674 | 0 | if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) |
2675 | 0 | zlog_err( |
2676 | 0 | "%s: failed to read configuration file: %s (%s)", |
2677 | 0 | __func__, nb_err_name(ret), errmsg); |
2678 | 0 | } |
2679 | |
|
2680 | 0 | vty_close(vty); |
2681 | 0 | } |
2682 | | |
2683 | | static FILE *vty_use_backup_config(const char *fullpath) |
2684 | 0 | { |
2685 | 0 | char *fullpath_sav, *fullpath_tmp; |
2686 | 0 | FILE *ret = NULL; |
2687 | 0 | int tmp, sav; |
2688 | 0 | int c; |
2689 | 0 | char buffer[512]; |
2690 | |
|
2691 | 0 | size_t fullpath_sav_sz = strlen(fullpath) + strlen(CONF_BACKUP_EXT) + 1; |
2692 | 0 | fullpath_sav = malloc(fullpath_sav_sz); |
2693 | 0 | strlcpy(fullpath_sav, fullpath, fullpath_sav_sz); |
2694 | 0 | strlcat(fullpath_sav, CONF_BACKUP_EXT, fullpath_sav_sz); |
2695 | |
|
2696 | 0 | sav = open(fullpath_sav, O_RDONLY); |
2697 | 0 | if (sav < 0) { |
2698 | 0 | free(fullpath_sav); |
2699 | 0 | return NULL; |
2700 | 0 | } |
2701 | | |
2702 | 0 | fullpath_tmp = malloc(strlen(fullpath) + 8); |
2703 | 0 | snprintf(fullpath_tmp, strlen(fullpath) + 8, "%s.XXXXXX", fullpath); |
2704 | | |
2705 | | /* Open file to configuration write. */ |
2706 | 0 | tmp = mkstemp(fullpath_tmp); |
2707 | 0 | if (tmp < 0) |
2708 | 0 | goto out_close_sav; |
2709 | | |
2710 | 0 | if (fchmod(tmp, CONFIGFILE_MASK) != 0) |
2711 | 0 | goto out_close; |
2712 | | |
2713 | 0 | while ((c = read(sav, buffer, 512)) > 0) { |
2714 | 0 | if (write(tmp, buffer, c) <= 0) |
2715 | 0 | goto out_close; |
2716 | 0 | } |
2717 | 0 | close(sav); |
2718 | 0 | close(tmp); |
2719 | |
|
2720 | 0 | if (rename(fullpath_tmp, fullpath) == 0) |
2721 | 0 | ret = fopen(fullpath, "r"); |
2722 | 0 | else |
2723 | 0 | unlink(fullpath_tmp); |
2724 | |
|
2725 | 0 | if (0) { |
2726 | 0 | out_close: |
2727 | 0 | close(tmp); |
2728 | 0 | unlink(fullpath_tmp); |
2729 | 0 | out_close_sav: |
2730 | 0 | close(sav); |
2731 | 0 | } |
2732 | |
|
2733 | 0 | free(fullpath_sav); |
2734 | 0 | free(fullpath_tmp); |
2735 | 0 | return ret; |
2736 | 0 | } |
2737 | | |
2738 | | FILE *vty_open_config(const char *config_file, char *config_default_dir) |
2739 | 0 | { |
2740 | 0 | char cwd[MAXPATHLEN]; |
2741 | 0 | FILE *confp = NULL; |
2742 | 0 | const char *fullpath; |
2743 | 0 | char *tmp = NULL; |
2744 | | |
2745 | | /* If -f flag specified. */ |
2746 | 0 | if (config_file != NULL) { |
2747 | 0 | if (!IS_DIRECTORY_SEP(config_file[0])) { |
2748 | 0 | if (getcwd(cwd, MAXPATHLEN) == NULL) { |
2749 | 0 | flog_err_sys( |
2750 | 0 | EC_LIB_SYSTEM_CALL, |
2751 | 0 | "%s: failure to determine Current Working Directory %d!", |
2752 | 0 | __func__, errno); |
2753 | 0 | goto tmp_free_and_out; |
2754 | 0 | } |
2755 | 0 | size_t tmp_len = strlen(cwd) + strlen(config_file) + 2; |
2756 | 0 | tmp = XMALLOC(MTYPE_TMP, tmp_len); |
2757 | 0 | snprintf(tmp, tmp_len, "%s/%s", cwd, config_file); |
2758 | 0 | fullpath = tmp; |
2759 | 0 | } else |
2760 | 0 | fullpath = config_file; |
2761 | | |
2762 | 0 | confp = fopen(fullpath, "r"); |
2763 | |
|
2764 | 0 | if (confp == NULL) { |
2765 | 0 | flog_warn( |
2766 | 0 | EC_LIB_BACKUP_CONFIG, |
2767 | 0 | "%s: failed to open configuration file %s: %s, checking backup", |
2768 | 0 | __func__, fullpath, safe_strerror(errno)); |
2769 | |
|
2770 | 0 | confp = vty_use_backup_config(fullpath); |
2771 | 0 | if (confp) |
2772 | 0 | flog_warn(EC_LIB_BACKUP_CONFIG, |
2773 | 0 | "using backup configuration file!"); |
2774 | 0 | else { |
2775 | 0 | flog_err( |
2776 | 0 | EC_LIB_VTY, |
2777 | 0 | "%s: can't open configuration file [%s]", |
2778 | 0 | __func__, config_file); |
2779 | 0 | goto tmp_free_and_out; |
2780 | 0 | } |
2781 | 0 | } |
2782 | 0 | } else { |
2783 | |
|
2784 | 0 | host_config_set(config_default_dir); |
2785 | |
|
2786 | 0 | #ifdef VTYSH |
2787 | 0 | int ret; |
2788 | 0 | struct stat conf_stat; |
2789 | | |
2790 | | /* !!!!PLEASE LEAVE!!!! |
2791 | | * This is NEEDED for use with vtysh -b, or else you can get |
2792 | | * a real configuration food fight with a lot garbage in the |
2793 | | * merged configuration file it creates coming from the per |
2794 | | * daemon configuration files. This also allows the daemons |
2795 | | * to start if there default configuration file is not |
2796 | | * present or ignore them, as needed when using vtysh -b to |
2797 | | * configure the daemons at boot - MAG |
2798 | | */ |
2799 | | |
2800 | | /* Stat for vtysh Zebra.conf, if found startup and wait for |
2801 | | * boot configuration |
2802 | | */ |
2803 | |
|
2804 | 0 | if (strstr(config_default_dir, "vtysh") == NULL) { |
2805 | 0 | ret = stat(integrate_default, &conf_stat); |
2806 | 0 | if (ret >= 0) |
2807 | 0 | goto tmp_free_and_out; |
2808 | 0 | } |
2809 | 0 | #endif /* VTYSH */ |
2810 | 0 | confp = fopen(config_default_dir, "r"); |
2811 | 0 | if (confp == NULL) { |
2812 | 0 | flog_err( |
2813 | 0 | EC_LIB_SYSTEM_CALL, |
2814 | 0 | "%s: failed to open configuration file %s: %s, checking backup", |
2815 | 0 | __func__, config_default_dir, |
2816 | 0 | safe_strerror(errno)); |
2817 | |
|
2818 | 0 | confp = vty_use_backup_config(config_default_dir); |
2819 | 0 | if (confp) { |
2820 | 0 | flog_warn(EC_LIB_BACKUP_CONFIG, |
2821 | 0 | "using backup configuration file!"); |
2822 | 0 | fullpath = config_default_dir; |
2823 | 0 | } else { |
2824 | 0 | flog_err(EC_LIB_VTY, |
2825 | 0 | "can't open configuration file [%s]", |
2826 | 0 | config_default_dir); |
2827 | 0 | goto tmp_free_and_out; |
2828 | 0 | } |
2829 | 0 | } else |
2830 | 0 | fullpath = config_default_dir; |
2831 | 0 | } |
2832 | | |
2833 | 0 | host_config_set(fullpath); |
2834 | |
|
2835 | 0 | tmp_free_and_out: |
2836 | 0 | XFREE(MTYPE_TMP, tmp); |
2837 | |
|
2838 | 0 | return confp; |
2839 | 0 | } |
2840 | | |
2841 | | |
2842 | | bool vty_read_config(struct nb_config *config, const char *config_file, |
2843 | | char *config_default_dir) |
2844 | 0 | { |
2845 | 0 | FILE *confp; |
2846 | |
|
2847 | 0 | confp = vty_open_config(config_file, config_default_dir); |
2848 | 0 | if (!confp) |
2849 | 0 | return false; |
2850 | | |
2851 | 0 | vty_read_file(config, confp); |
2852 | |
|
2853 | 0 | fclose(confp); |
2854 | |
|
2855 | 0 | return true; |
2856 | 0 | } |
2857 | | |
2858 | | int vty_config_enter(struct vty *vty, bool private_config, bool exclusive, |
2859 | | bool file_lock) |
2860 | 0 | { |
2861 | 0 | if (exclusive && !vty_mgmt_fe_enabled() && |
2862 | 0 | nb_running_lock(NB_CLIENT_CLI, vty)) { |
2863 | 0 | vty_out(vty, "%% Configuration is locked by other client\n"); |
2864 | 0 | return CMD_WARNING; |
2865 | 0 | } |
2866 | | |
2867 | | /* |
2868 | | * We only need to do a lock when reading a config file as we will be |
2869 | | * sending a batch of setcfg changes followed by a single commit |
2870 | | * message. For user interactive mode we are doing implicit commits |
2871 | | * those will obtain the lock (or not) when they try and commit. |
2872 | | */ |
2873 | 0 | if (file_lock && vty_mgmt_fe_enabled() && !private_config) { |
2874 | 0 | if (vty_mgmt_lock_candidate_inline(vty)) { |
2875 | 0 | vty_out(vty, |
2876 | 0 | "%% Can't enter config; candidate datastore locked by another session\n"); |
2877 | 0 | return CMD_WARNING_CONFIG_FAILED; |
2878 | 0 | } |
2879 | 0 | if (vty_mgmt_lock_running_inline(vty)) { |
2880 | 0 | vty_out(vty, |
2881 | 0 | "%% Can't enter config; running datastore locked by another session\n"); |
2882 | 0 | vty_mgmt_unlock_candidate_inline(vty); |
2883 | 0 | return CMD_WARNING_CONFIG_FAILED; |
2884 | 0 | } |
2885 | 0 | assert(vty->mgmt_locked_candidate_ds); |
2886 | 0 | assert(vty->mgmt_locked_running_ds); |
2887 | 0 | } |
2888 | | |
2889 | 0 | vty->node = CONFIG_NODE; |
2890 | 0 | vty->config = true; |
2891 | 0 | vty->private_config = private_config; |
2892 | 0 | vty->xpath_index = 0; |
2893 | |
|
2894 | 0 | if (private_config) { |
2895 | 0 | vty->candidate_config = nb_config_dup(running_config); |
2896 | 0 | vty->candidate_config_base = nb_config_dup(running_config); |
2897 | 0 | vty_out(vty, |
2898 | 0 | "Warning: uncommitted changes will be discarded on exit.\n\n"); |
2899 | 0 | return CMD_SUCCESS; |
2900 | 0 | } |
2901 | | |
2902 | | /* |
2903 | | * NOTE: On the MGMTD daemon we point the VTY candidate DS to |
2904 | | * the global MGMTD candidate DS. Else we point to the VTY |
2905 | | * Shared Candidate Config. |
2906 | | */ |
2907 | 0 | vty->candidate_config = vty_mgmt_candidate_config |
2908 | 0 | ? vty_mgmt_candidate_config |
2909 | 0 | : vty_shared_candidate_config; |
2910 | 0 | if (frr_get_cli_mode() == FRR_CLI_TRANSACTIONAL) |
2911 | 0 | vty->candidate_config_base = nb_config_dup(running_config); |
2912 | |
|
2913 | 0 | return CMD_SUCCESS; |
2914 | 0 | } |
2915 | | |
2916 | | |
2917 | | void vty_config_exit(struct vty *vty) |
2918 | 0 | { |
2919 | 0 | enum node_type node = vty->node; |
2920 | 0 | struct cmd_node *cnode; |
2921 | | |
2922 | | /* unlock and jump up to ENABLE_NODE if -and only if- we're |
2923 | | * somewhere below CONFIG_NODE */ |
2924 | 0 | while (node && node != CONFIG_NODE) { |
2925 | 0 | cnode = vector_lookup(cmdvec, node); |
2926 | 0 | node = cnode->parent_node; |
2927 | 0 | } |
2928 | 0 | if (node != CONFIG_NODE) |
2929 | | /* called outside config, e.g. vty_close() in ENABLE_NODE */ |
2930 | 0 | return; |
2931 | | |
2932 | 0 | while (vty->node != ENABLE_NODE) |
2933 | | /* will call vty_config_node_exit() below */ |
2934 | 0 | cmd_exit(vty); |
2935 | 0 | } |
2936 | | |
2937 | | int vty_config_node_exit(struct vty *vty) |
2938 | 0 | { |
2939 | 0 | vty->xpath_index = 0; |
2940 | | |
2941 | | /* TODO: could we check for un-commited changes here? */ |
2942 | |
|
2943 | 0 | if (vty->mgmt_locked_running_ds) |
2944 | 0 | vty_mgmt_unlock_running_inline(vty); |
2945 | |
|
2946 | 0 | if (vty->mgmt_locked_candidate_ds) |
2947 | 0 | vty_mgmt_unlock_candidate_inline(vty); |
2948 | | |
2949 | | /* Perform any pending commits. */ |
2950 | 0 | (void)nb_cli_pending_commit_check(vty); |
2951 | | |
2952 | | /* Check if there's a pending confirmed commit. */ |
2953 | 0 | if (vty->t_confirmed_commit_timeout) { |
2954 | 0 | vty_out(vty, |
2955 | 0 | "exiting with a pending confirmed commit. Rolling back to previous configuration.\n\n"); |
2956 | 0 | nb_cli_confirmed_commit_rollback(vty); |
2957 | 0 | nb_cli_confirmed_commit_clean(vty); |
2958 | 0 | } |
2959 | |
|
2960 | 0 | (void)nb_running_unlock(NB_CLIENT_CLI, vty); |
2961 | |
|
2962 | 0 | if (vty->candidate_config) { |
2963 | 0 | if (vty->private_config) |
2964 | 0 | nb_config_free(vty->candidate_config); |
2965 | 0 | vty->candidate_config = NULL; |
2966 | 0 | } |
2967 | 0 | if (vty->candidate_config_base) { |
2968 | 0 | nb_config_free(vty->candidate_config_base); |
2969 | 0 | vty->candidate_config_base = NULL; |
2970 | 0 | } |
2971 | |
|
2972 | 0 | vty->config = false; |
2973 | | |
2974 | | /* |
2975 | | * If this is a config file and we are dropping out of config end |
2976 | | * parsing. |
2977 | | */ |
2978 | 0 | if (vty->type == VTY_FILE && vty->status != VTY_CLOSE) { |
2979 | 0 | vty_out(vty, "exit from config node while reading config file"); |
2980 | 0 | vty->status = VTY_CLOSE; |
2981 | 0 | } |
2982 | |
|
2983 | 0 | return 1; |
2984 | 0 | } |
2985 | | |
2986 | | /* Master of the threads. */ |
2987 | | static struct event_loop *vty_master; |
2988 | | |
2989 | | static void vty_event_serv(enum vty_event event, struct vty_serv *vty_serv) |
2990 | 0 | { |
2991 | 0 | switch (event) { |
2992 | 0 | case VTY_SERV: |
2993 | 0 | event_add_read(vty_master, vty_accept, vty_serv, vty_serv->sock, |
2994 | 0 | &vty_serv->t_accept); |
2995 | 0 | break; |
2996 | 0 | #ifdef VTYSH |
2997 | 0 | case VTYSH_SERV: |
2998 | 0 | event_add_read(vty_master, vtysh_accept, vty_serv, |
2999 | 0 | vty_serv->sock, &vty_serv->t_accept); |
3000 | 0 | break; |
3001 | 0 | #endif /* VTYSH */ |
3002 | 0 | case VTY_READ: |
3003 | 0 | case VTY_WRITE: |
3004 | 0 | case VTY_TIMEOUT_RESET: |
3005 | 0 | case VTYSH_READ: |
3006 | 0 | case VTYSH_WRITE: |
3007 | 0 | assert(!"vty_event_serv() called incorrectly"); |
3008 | 0 | } |
3009 | 0 | } |
3010 | | |
3011 | | static void vty_event(enum vty_event event, struct vty *vty) |
3012 | 0 | { |
3013 | 0 | switch (event) { |
3014 | 0 | #ifdef VTYSH |
3015 | 0 | case VTYSH_READ: |
3016 | 0 | event_add_read(vty_master, vtysh_read, vty, vty->fd, |
3017 | 0 | &vty->t_read); |
3018 | 0 | break; |
3019 | 0 | case VTYSH_WRITE: |
3020 | 0 | event_add_write(vty_master, vtysh_write, vty, vty->wfd, |
3021 | 0 | &vty->t_write); |
3022 | 0 | break; |
3023 | 0 | #endif /* VTYSH */ |
3024 | 0 | case VTY_READ: |
3025 | 0 | event_add_read(vty_master, vty_read, vty, vty->fd, |
3026 | 0 | &vty->t_read); |
3027 | | |
3028 | | /* Time out treatment. */ |
3029 | 0 | if (vty->v_timeout) { |
3030 | 0 | EVENT_OFF(vty->t_timeout); |
3031 | 0 | event_add_timer(vty_master, vty_timeout, vty, |
3032 | 0 | vty->v_timeout, &vty->t_timeout); |
3033 | 0 | } |
3034 | 0 | break; |
3035 | 0 | case VTY_WRITE: |
3036 | 0 | event_add_write(vty_master, vty_flush, vty, vty->wfd, |
3037 | 0 | &vty->t_write); |
3038 | 0 | break; |
3039 | 0 | case VTY_TIMEOUT_RESET: |
3040 | 0 | EVENT_OFF(vty->t_timeout); |
3041 | 0 | if (vty->v_timeout) |
3042 | 0 | event_add_timer(vty_master, vty_timeout, vty, |
3043 | 0 | vty->v_timeout, &vty->t_timeout); |
3044 | 0 | break; |
3045 | 0 | case VTY_SERV: |
3046 | 0 | case VTYSH_SERV: |
3047 | 0 | assert(!"vty_event() called incorrectly"); |
3048 | 0 | } |
3049 | 0 | } |
3050 | | |
3051 | | DEFUN_NOSH (config_who, |
3052 | | config_who_cmd, |
3053 | | "who", |
3054 | | "Display who is on vty\n") |
3055 | 0 | { |
3056 | 0 | struct vty *v; |
3057 | |
|
3058 | 0 | frr_each (vtys, vty_sessions, v) |
3059 | 0 | vty_out(vty, "%svty[%d] connected from %s%s.\n", |
3060 | 0 | v->config ? "*" : " ", v->fd, v->address, |
3061 | 0 | zlog_live_is_null(&v->live_log) ? "" : ", live log"); |
3062 | 0 | return CMD_SUCCESS; |
3063 | 0 | } |
3064 | | |
3065 | | /* Move to vty configuration mode. */ |
3066 | | DEFUN_NOSH (line_vty, |
3067 | | line_vty_cmd, |
3068 | | "line vty", |
3069 | | "Configure a terminal line\n" |
3070 | | "Virtual terminal\n") |
3071 | 0 | { |
3072 | 0 | vty->node = VTY_NODE; |
3073 | 0 | return CMD_SUCCESS; |
3074 | 0 | } |
3075 | | |
3076 | | /* Set time out value. */ |
3077 | | static int exec_timeout(struct vty *vty, const char *min_str, |
3078 | | const char *sec_str) |
3079 | 0 | { |
3080 | 0 | unsigned long timeout = 0; |
3081 | | |
3082 | | /* min_str and sec_str are already checked by parser. So it must be |
3083 | | all digit string. */ |
3084 | 0 | if (min_str) { |
3085 | 0 | timeout = strtol(min_str, NULL, 10); |
3086 | 0 | timeout *= 60; |
3087 | 0 | } |
3088 | 0 | if (sec_str) |
3089 | 0 | timeout += strtol(sec_str, NULL, 10); |
3090 | |
|
3091 | 0 | vty_timeout_val = timeout; |
3092 | 0 | vty->v_timeout = timeout; |
3093 | 0 | vty_event(VTY_TIMEOUT_RESET, vty); |
3094 | | |
3095 | |
|
3096 | 0 | return CMD_SUCCESS; |
3097 | 0 | } |
3098 | | |
3099 | | DEFUN (exec_timeout_min, |
3100 | | exec_timeout_min_cmd, |
3101 | | "exec-timeout (0-35791)", |
3102 | | "Set timeout value\n" |
3103 | | "Timeout value in minutes\n") |
3104 | 0 | { |
3105 | 0 | int idx_number = 1; |
3106 | 0 | return exec_timeout(vty, argv[idx_number]->arg, NULL); |
3107 | 0 | } |
3108 | | |
3109 | | DEFUN (exec_timeout_sec, |
3110 | | exec_timeout_sec_cmd, |
3111 | | "exec-timeout (0-35791) (0-2147483)", |
3112 | | "Set the EXEC timeout\n" |
3113 | | "Timeout in minutes\n" |
3114 | | "Timeout in seconds\n") |
3115 | 0 | { |
3116 | 0 | int idx_number = 1; |
3117 | 0 | int idx_number_2 = 2; |
3118 | 0 | return exec_timeout(vty, argv[idx_number]->arg, |
3119 | 0 | argv[idx_number_2]->arg); |
3120 | 0 | } |
3121 | | |
3122 | | DEFUN (no_exec_timeout, |
3123 | | no_exec_timeout_cmd, |
3124 | | "no exec-timeout", |
3125 | | NO_STR |
3126 | | "Set the EXEC timeout\n") |
3127 | 0 | { |
3128 | 0 | return exec_timeout(vty, NULL, NULL); |
3129 | 0 | } |
3130 | | |
3131 | | /* Set vty access class. */ |
3132 | | DEFUN (vty_access_class, |
3133 | | vty_access_class_cmd, |
3134 | | "access-class WORD", |
3135 | | "Filter connections based on an IP access list\n" |
3136 | | "IP access list\n") |
3137 | 0 | { |
3138 | 0 | int idx_word = 1; |
3139 | 0 | if (vty_accesslist_name) |
3140 | 0 | XFREE(MTYPE_VTY, vty_accesslist_name); |
3141 | |
|
3142 | 0 | vty_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); |
3143 | |
|
3144 | 0 | return CMD_SUCCESS; |
3145 | 0 | } |
3146 | | |
3147 | | /* Clear vty access class. */ |
3148 | | DEFUN (no_vty_access_class, |
3149 | | no_vty_access_class_cmd, |
3150 | | "no access-class [WORD]", |
3151 | | NO_STR |
3152 | | "Filter connections based on an IP access list\n" |
3153 | | "IP access list\n") |
3154 | 0 | { |
3155 | 0 | int idx_word = 2; |
3156 | 0 | const char *accesslist = (argc == 3) ? argv[idx_word]->arg : NULL; |
3157 | 0 | if (!vty_accesslist_name |
3158 | 0 | || (argc == 3 && strcmp(vty_accesslist_name, accesslist))) { |
3159 | 0 | vty_out(vty, "Access-class is not currently applied to vty\n"); |
3160 | 0 | return CMD_WARNING_CONFIG_FAILED; |
3161 | 0 | } |
3162 | | |
3163 | 0 | XFREE(MTYPE_VTY, vty_accesslist_name); |
3164 | |
|
3165 | 0 | vty_accesslist_name = NULL; |
3166 | |
|
3167 | 0 | return CMD_SUCCESS; |
3168 | 0 | } |
3169 | | |
3170 | | /* Set vty access class. */ |
3171 | | DEFUN (vty_ipv6_access_class, |
3172 | | vty_ipv6_access_class_cmd, |
3173 | | "ipv6 access-class WORD", |
3174 | | IPV6_STR |
3175 | | "Filter connections based on an IP access list\n" |
3176 | | "IPv6 access list\n") |
3177 | 0 | { |
3178 | 0 | int idx_word = 2; |
3179 | 0 | if (vty_ipv6_accesslist_name) |
3180 | 0 | XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); |
3181 | |
|
3182 | 0 | vty_ipv6_accesslist_name = XSTRDUP(MTYPE_VTY, argv[idx_word]->arg); |
3183 | |
|
3184 | 0 | return CMD_SUCCESS; |
3185 | 0 | } |
3186 | | |
3187 | | /* Clear vty access class. */ |
3188 | | DEFUN (no_vty_ipv6_access_class, |
3189 | | no_vty_ipv6_access_class_cmd, |
3190 | | "no ipv6 access-class [WORD]", |
3191 | | NO_STR |
3192 | | IPV6_STR |
3193 | | "Filter connections based on an IP access list\n" |
3194 | | "IPv6 access list\n") |
3195 | 0 | { |
3196 | 0 | int idx_word = 3; |
3197 | 0 | const char *accesslist = (argc == 4) ? argv[idx_word]->arg : NULL; |
3198 | |
|
3199 | 0 | if (!vty_ipv6_accesslist_name |
3200 | 0 | || (argc == 4 && strcmp(vty_ipv6_accesslist_name, accesslist))) { |
3201 | 0 | vty_out(vty, |
3202 | 0 | "IPv6 access-class is not currently applied to vty\n"); |
3203 | 0 | return CMD_WARNING_CONFIG_FAILED; |
3204 | 0 | } |
3205 | | |
3206 | 0 | XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); |
3207 | |
|
3208 | 0 | vty_ipv6_accesslist_name = NULL; |
3209 | |
|
3210 | 0 | return CMD_SUCCESS; |
3211 | 0 | } |
3212 | | |
3213 | | /* vty login. */ |
3214 | | DEFUN (vty_login, |
3215 | | vty_login_cmd, |
3216 | | "login", |
3217 | | "Enable password checking\n") |
3218 | 0 | { |
3219 | 0 | no_password_check = 0; |
3220 | 0 | return CMD_SUCCESS; |
3221 | 0 | } |
3222 | | |
3223 | | DEFUN (no_vty_login, |
3224 | | no_vty_login_cmd, |
3225 | | "no login", |
3226 | | NO_STR |
3227 | | "Enable password checking\n") |
3228 | 0 | { |
3229 | 0 | no_password_check = 1; |
3230 | 0 | return CMD_SUCCESS; |
3231 | 0 | } |
3232 | | |
3233 | | DEFUN (service_advanced_vty, |
3234 | | service_advanced_vty_cmd, |
3235 | | "service advanced-vty", |
3236 | | "Set up miscellaneous service\n" |
3237 | | "Enable advanced mode vty interface\n") |
3238 | 0 | { |
3239 | 0 | host.advanced = 1; |
3240 | 0 | return CMD_SUCCESS; |
3241 | 0 | } |
3242 | | |
3243 | | DEFUN (no_service_advanced_vty, |
3244 | | no_service_advanced_vty_cmd, |
3245 | | "no service advanced-vty", |
3246 | | NO_STR |
3247 | | "Set up miscellaneous service\n" |
3248 | | "Enable advanced mode vty interface\n") |
3249 | 0 | { |
3250 | 0 | host.advanced = 0; |
3251 | 0 | return CMD_SUCCESS; |
3252 | 0 | } |
3253 | | |
3254 | | DEFUN_NOSH(terminal_monitor, |
3255 | | terminal_monitor_cmd, |
3256 | | "terminal monitor [detach]", |
3257 | | "Set terminal line parameters\n" |
3258 | | "Copy debug output to the current terminal line\n" |
3259 | | "Keep logging feed open independent of VTY session\n") |
3260 | 0 | { |
3261 | 0 | int fd_ret = -1; |
3262 | |
|
3263 | 0 | if (vty->type != VTY_SHELL_SERV) { |
3264 | 0 | vty_out(vty, "%% not supported\n"); |
3265 | 0 | return CMD_WARNING; |
3266 | 0 | } |
3267 | | |
3268 | 0 | if (argc == 3) { |
3269 | 0 | struct zlog_live_cfg detach_log = {}; |
3270 | |
|
3271 | 0 | zlog_live_open(&detach_log, LOG_DEBUG, &fd_ret); |
3272 | 0 | zlog_live_disown(&detach_log); |
3273 | 0 | } else |
3274 | 0 | zlog_live_open(&vty->live_log, LOG_DEBUG, &fd_ret); |
3275 | |
|
3276 | 0 | if (fd_ret == -1) { |
3277 | 0 | vty_out(vty, "%% error opening live log: %m\n"); |
3278 | 0 | return CMD_WARNING; |
3279 | 0 | } |
3280 | | |
3281 | 0 | vty_pass_fd(vty, fd_ret); |
3282 | 0 | return CMD_SUCCESS; |
3283 | 0 | } |
3284 | | |
3285 | | DEFUN_NOSH(no_terminal_monitor, |
3286 | | no_terminal_monitor_cmd, |
3287 | | "no terminal monitor", |
3288 | | NO_STR |
3289 | | "Set terminal line parameters\n" |
3290 | | "Copy debug output to the current terminal line\n") |
3291 | 0 | { |
3292 | 0 | zlog_live_close(&vty->live_log); |
3293 | 0 | return CMD_SUCCESS; |
3294 | 0 | } |
3295 | | |
3296 | | DEFUN_NOSH(terminal_no_monitor, |
3297 | | terminal_no_monitor_cmd, |
3298 | | "terminal no monitor", |
3299 | | "Set terminal line parameters\n" |
3300 | | NO_STR |
3301 | | "Copy debug output to the current terminal line\n") |
3302 | 0 | { |
3303 | 0 | return no_terminal_monitor(self, vty, argc, argv); |
3304 | 0 | } |
3305 | | |
3306 | | |
3307 | | DEFUN_NOSH (show_history, |
3308 | | show_history_cmd, |
3309 | | "show history", |
3310 | | SHOW_STR |
3311 | | "Display the session command history\n") |
3312 | 0 | { |
3313 | 0 | int index; |
3314 | |
|
3315 | 0 | for (index = vty->hindex + 1; index != vty->hindex;) { |
3316 | 0 | if (index == VTY_MAXHIST) { |
3317 | 0 | index = 0; |
3318 | 0 | continue; |
3319 | 0 | } |
3320 | | |
3321 | 0 | if (vty->hist[index] != NULL) |
3322 | 0 | vty_out(vty, " %s\n", vty->hist[index]); |
3323 | |
|
3324 | 0 | index++; |
3325 | 0 | } |
3326 | |
|
3327 | 0 | return CMD_SUCCESS; |
3328 | 0 | } |
3329 | | |
3330 | | /* vty login. */ |
3331 | | DEFPY (log_commands, |
3332 | | log_commands_cmd, |
3333 | | "[no] log commands", |
3334 | | NO_STR |
3335 | | "Logging control\n" |
3336 | | "Log all commands\n") |
3337 | 0 | { |
3338 | 0 | if (no) { |
3339 | 0 | if (vty_log_commands_perm) { |
3340 | 0 | vty_out(vty, |
3341 | 0 | "Daemon started with permanent logging turned on for commands, ignoring\n"); |
3342 | 0 | return CMD_WARNING; |
3343 | 0 | } |
3344 | | |
3345 | 0 | vty_log_commands = false; |
3346 | 0 | } else |
3347 | 0 | vty_log_commands = true; |
3348 | | |
3349 | 0 | return CMD_SUCCESS; |
3350 | 0 | } |
3351 | | |
3352 | | /* Display current configuration. */ |
3353 | | static int vty_config_write(struct vty *vty) |
3354 | 0 | { |
3355 | 0 | vty_frame(vty, "line vty\n"); |
3356 | |
|
3357 | 0 | if (vty_accesslist_name) |
3358 | 0 | vty_out(vty, " access-class %s\n", vty_accesslist_name); |
3359 | |
|
3360 | 0 | if (vty_ipv6_accesslist_name) |
3361 | 0 | vty_out(vty, " ipv6 access-class %s\n", |
3362 | 0 | vty_ipv6_accesslist_name); |
3363 | | |
3364 | | /* exec-timeout */ |
3365 | 0 | if (vty_timeout_val != VTY_TIMEOUT_DEFAULT) |
3366 | 0 | vty_out(vty, " exec-timeout %ld %ld\n", vty_timeout_val / 60, |
3367 | 0 | vty_timeout_val % 60); |
3368 | | |
3369 | | /* login */ |
3370 | 0 | if (no_password_check) |
3371 | 0 | vty_out(vty, " no login\n"); |
3372 | |
|
3373 | 0 | vty_endframe(vty, "exit\n"); |
3374 | |
|
3375 | 0 | if (vty_log_commands) |
3376 | 0 | vty_out(vty, "log commands\n"); |
3377 | |
|
3378 | 0 | vty_out(vty, "!\n"); |
3379 | |
|
3380 | 0 | return CMD_SUCCESS; |
3381 | 0 | } |
3382 | | |
3383 | | static int vty_config_write(struct vty *vty); |
3384 | | struct cmd_node vty_node = { |
3385 | | .name = "vty", |
3386 | | .node = VTY_NODE, |
3387 | | .parent_node = CONFIG_NODE, |
3388 | | .prompt = "%s(config-line)# ", |
3389 | | .config_write = vty_config_write, |
3390 | | }; |
3391 | | |
3392 | | /* Reset all VTY status. */ |
3393 | | void vty_reset(void) |
3394 | 0 | { |
3395 | 0 | struct vty *vty; |
3396 | |
|
3397 | 0 | frr_each_safe (vtys, vty_sessions, vty) { |
3398 | 0 | buffer_reset(vty->lbuf); |
3399 | 0 | buffer_reset(vty->obuf); |
3400 | 0 | vty->status = VTY_CLOSE; |
3401 | 0 | vty_close(vty); |
3402 | 0 | } |
3403 | |
|
3404 | 0 | vty_timeout_val = VTY_TIMEOUT_DEFAULT; |
3405 | |
|
3406 | 0 | XFREE(MTYPE_VTY, vty_accesslist_name); |
3407 | 0 | XFREE(MTYPE_VTY, vty_ipv6_accesslist_name); |
3408 | 0 | } |
3409 | | |
3410 | | static void vty_save_cwd(void) |
3411 | 4 | { |
3412 | 4 | char *c; |
3413 | | |
3414 | 4 | c = getcwd(vty_cwd, sizeof(vty_cwd)); |
3415 | | |
3416 | 4 | if (!c) { |
3417 | | /* |
3418 | | * At this point if these go wrong, more than likely |
3419 | | * the whole world is coming down around us |
3420 | | * Hence not worrying about it too much. |
3421 | | */ |
3422 | 0 | if (chdir(SYSCONFDIR)) { |
3423 | 0 | flog_err_sys(EC_LIB_SYSTEM_CALL, |
3424 | 0 | "Failure to chdir to %s, errno: %d", |
3425 | 0 | SYSCONFDIR, errno); |
3426 | 0 | exit(-1); |
3427 | 0 | } |
3428 | 0 | if (getcwd(vty_cwd, sizeof(vty_cwd)) == NULL) { |
3429 | 0 | flog_err_sys(EC_LIB_SYSTEM_CALL, |
3430 | 0 | "Failure to getcwd, errno: %d", errno); |
3431 | 0 | exit(-1); |
3432 | 0 | } |
3433 | 0 | } |
3434 | 4 | } |
3435 | | |
3436 | | char *vty_get_cwd(void) |
3437 | 0 | { |
3438 | 0 | return vty_cwd; |
3439 | 0 | } |
3440 | | |
3441 | | int vty_shell(struct vty *vty) |
3442 | 0 | { |
3443 | 0 | return vty->type == VTY_SHELL ? 1 : 0; |
3444 | 0 | } |
3445 | | |
3446 | | int vty_shell_serv(struct vty *vty) |
3447 | 0 | { |
3448 | 0 | return vty->type == VTY_SHELL_SERV ? 1 : 0; |
3449 | 0 | } |
3450 | | |
3451 | | void vty_init_vtysh(void) |
3452 | 0 | { |
3453 | | /* currently nothing to do, but likely to have future use */ |
3454 | 0 | } |
3455 | | |
3456 | | |
3457 | | /* |
3458 | | * These functions allow for CLI handling to be placed inside daemons; however, |
3459 | | * currently they are only used by mgmtd, with mgmtd having each daemons CLI |
3460 | | * functionality linked into it. This design choice was taken for efficiency. |
3461 | | */ |
3462 | | |
3463 | | static void vty_mgmt_server_connected(struct mgmt_fe_client *client, |
3464 | | uintptr_t usr_data, bool connected) |
3465 | 0 | { |
3466 | 0 | MGMTD_FE_CLIENT_DBG("Got %sconnected %s MGMTD Frontend Server", |
3467 | 0 | !connected ? "dis: " : "", |
3468 | 0 | !connected ? "from" : "to"); |
3469 | | |
3470 | | /* |
3471 | | * We should not have any sessions for connecting or disconnecting case. |
3472 | | * The fe client library will delete all session on disconnect before |
3473 | | * calling us. |
3474 | | */ |
3475 | 0 | assert(mgmt_fe_client_session_count(client) == 0); |
3476 | |
|
3477 | 0 | mgmt_fe_connected = connected; |
3478 | | |
3479 | | /* Start or stop listening for vty connections */ |
3480 | 0 | if (connected) |
3481 | 0 | frr_vty_serv_start(); |
3482 | 0 | else |
3483 | 0 | frr_vty_serv_stop(); |
3484 | 0 | } |
3485 | | |
3486 | | /* |
3487 | | * A session has successfully been created for a vty. |
3488 | | */ |
3489 | | static void vty_mgmt_session_notify(struct mgmt_fe_client *client, |
3490 | | uintptr_t usr_data, uint64_t client_id, |
3491 | | bool create, bool success, |
3492 | | uintptr_t session_id, uintptr_t session_ctx) |
3493 | 0 | { |
3494 | 0 | struct vty *vty; |
3495 | |
|
3496 | 0 | vty = (struct vty *)session_ctx; |
3497 | |
|
3498 | 0 | if (!success) { |
3499 | 0 | zlog_err("%s session for client %" PRIu64 " failed!", |
3500 | 0 | create ? "Creating" : "Destroying", client_id); |
3501 | 0 | return; |
3502 | 0 | } |
3503 | | |
3504 | 0 | MGMTD_FE_CLIENT_DBG("%s session for client %" PRIu64 " successfully", |
3505 | 0 | create ? "Created" : "Destroyed", client_id); |
3506 | |
|
3507 | 0 | if (create) { |
3508 | 0 | assert(session_id != 0); |
3509 | 0 | vty->mgmt_session_id = session_id; |
3510 | 0 | } else { |
3511 | 0 | vty->mgmt_session_id = 0; |
3512 | | /* We may come here by way of vty_close() and short-circuits */ |
3513 | 0 | if (vty->status != VTY_CLOSE) |
3514 | 0 | vty_close(vty); |
3515 | 0 | } |
3516 | 0 | } |
3517 | | |
3518 | | static void vty_mgmt_ds_lock_notified(struct mgmt_fe_client *client, |
3519 | | uintptr_t usr_data, uint64_t client_id, |
3520 | | uintptr_t session_id, |
3521 | | uintptr_t session_ctx, uint64_t req_id, |
3522 | | bool lock_ds, bool success, |
3523 | | Mgmtd__DatastoreId ds_id, |
3524 | | char *errmsg_if_any) |
3525 | 0 | { |
3526 | 0 | struct vty *vty; |
3527 | 0 | bool is_short_circuit = mgmt_fe_client_current_msg_short_circuit(client); |
3528 | |
|
3529 | 0 | vty = (struct vty *)session_ctx; |
3530 | |
|
3531 | 0 | assert(ds_id == MGMTD_DS_CANDIDATE || ds_id == MGMTD_DS_RUNNING); |
3532 | 0 | if (!success) |
3533 | 0 | zlog_err("%socking for DS %u failed, Err: '%s' vty %p", |
3534 | 0 | lock_ds ? "L" : "Unl", ds_id, errmsg_if_any, vty); |
3535 | 0 | else { |
3536 | 0 | MGMTD_FE_CLIENT_DBG("%socked DS %u successfully", |
3537 | 0 | lock_ds ? "L" : "Unl", ds_id); |
3538 | 0 | if (ds_id == MGMTD_DS_CANDIDATE) |
3539 | 0 | vty->mgmt_locked_candidate_ds = lock_ds; |
3540 | 0 | else |
3541 | 0 | vty->mgmt_locked_running_ds = lock_ds; |
3542 | 0 | } |
3543 | |
|
3544 | 0 | if (!is_short_circuit && vty->mgmt_req_pending_cmd) { |
3545 | 0 | assert(!strcmp(vty->mgmt_req_pending_cmd, "MESSAGE_LOCKDS_REQ")); |
3546 | 0 | vty_mgmt_resume_response(vty, success); |
3547 | 0 | } |
3548 | 0 | } |
3549 | | |
3550 | | static void vty_mgmt_set_config_result_notified( |
3551 | | struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, |
3552 | | uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, |
3553 | | bool success, Mgmtd__DatastoreId ds_id, bool implicit_commit, |
3554 | | char *errmsg_if_any) |
3555 | 0 | { |
3556 | 0 | struct vty *vty; |
3557 | |
|
3558 | 0 | vty = (struct vty *)session_ctx; |
3559 | |
|
3560 | 0 | if (!success) { |
3561 | 0 | zlog_err("SET_CONFIG request for client 0x%" PRIx64 |
3562 | 0 | " failed, Error: '%s'", |
3563 | 0 | client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); |
3564 | 0 | vty_out(vty, "ERROR: SET_CONFIG request failed, Error: %s\n", |
3565 | 0 | errmsg_if_any ? errmsg_if_any : "Unknown"); |
3566 | 0 | } else { |
3567 | 0 | MGMTD_FE_CLIENT_DBG("SET_CONFIG request for client 0x%" PRIx64 |
3568 | 0 | " req-id %" PRIu64 " was successfull", |
3569 | 0 | client_id, req_id); |
3570 | 0 | } |
3571 | |
|
3572 | 0 | if (implicit_commit) { |
3573 | | /* In this case the changes have been applied, we are done */ |
3574 | 0 | vty_mgmt_unlock_candidate_inline(vty); |
3575 | 0 | vty_mgmt_unlock_running_inline(vty); |
3576 | 0 | } |
3577 | |
|
3578 | 0 | vty_mgmt_resume_response(vty, success); |
3579 | 0 | } |
3580 | | |
3581 | | static void vty_mgmt_commit_config_result_notified( |
3582 | | struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, |
3583 | | uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, |
3584 | | bool success, Mgmtd__DatastoreId src_ds_id, |
3585 | | Mgmtd__DatastoreId dst_ds_id, bool validate_only, char *errmsg_if_any) |
3586 | 0 | { |
3587 | 0 | struct vty *vty; |
3588 | |
|
3589 | 0 | vty = (struct vty *)session_ctx; |
3590 | |
|
3591 | 0 | if (!success) { |
3592 | 0 | zlog_err("COMMIT_CONFIG request for client 0x%" PRIx64 |
3593 | 0 | " failed, Error: '%s'", |
3594 | 0 | client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); |
3595 | 0 | vty_out(vty, "ERROR: COMMIT_CONFIG request failed, Error: %s\n", |
3596 | 0 | errmsg_if_any ? errmsg_if_any : "Unknown"); |
3597 | 0 | } else { |
3598 | 0 | MGMTD_FE_CLIENT_DBG( |
3599 | 0 | "COMMIT_CONFIG request for client 0x%" PRIx64 |
3600 | 0 | " req-id %" PRIu64 " was successfull", |
3601 | 0 | client_id, req_id); |
3602 | 0 | if (errmsg_if_any) |
3603 | 0 | vty_out(vty, "MGMTD: %s\n", errmsg_if_any); |
3604 | 0 | } |
3605 | |
|
3606 | 0 | vty_mgmt_resume_response(vty, success); |
3607 | 0 | } |
3608 | | |
3609 | | static int vty_mgmt_get_data_result_notified( |
3610 | | struct mgmt_fe_client *client, uintptr_t usr_data, uint64_t client_id, |
3611 | | uintptr_t session_id, uintptr_t session_ctx, uint64_t req_id, |
3612 | | bool success, Mgmtd__DatastoreId ds_id, Mgmtd__YangData **yang_data, |
3613 | | size_t num_data, int next_key, char *errmsg_if_any) |
3614 | 0 | { |
3615 | 0 | struct vty *vty; |
3616 | 0 | size_t indx; |
3617 | |
|
3618 | 0 | vty = (struct vty *)session_ctx; |
3619 | |
|
3620 | 0 | if (!success) { |
3621 | 0 | zlog_err("GET_DATA request for client 0x%" PRIx64 |
3622 | 0 | " failed, Error: '%s'", |
3623 | 0 | client_id, errmsg_if_any ? errmsg_if_any : "Unknown"); |
3624 | 0 | vty_out(vty, "ERROR: GET_DATA request failed, Error: %s\n", |
3625 | 0 | errmsg_if_any ? errmsg_if_any : "Unknown"); |
3626 | 0 | vty_mgmt_resume_response(vty, success); |
3627 | 0 | return -1; |
3628 | 0 | } |
3629 | | |
3630 | 0 | MGMTD_FE_CLIENT_DBG("GET_DATA request succeeded, client 0x%" PRIx64 |
3631 | 0 | " req-id %" PRIu64, |
3632 | 0 | client_id, req_id); |
3633 | |
|
3634 | 0 | if (req_id != mgmt_last_req_id) { |
3635 | 0 | mgmt_last_req_id = req_id; |
3636 | 0 | vty_out(vty, "[\n"); |
3637 | 0 | } |
3638 | |
|
3639 | 0 | for (indx = 0; indx < num_data; indx++) { |
3640 | 0 | vty_out(vty, " \"%s\": \"%s\"\n", yang_data[indx]->xpath, |
3641 | 0 | yang_data[indx]->value->encoded_str_val); |
3642 | 0 | } |
3643 | 0 | if (next_key < 0) { |
3644 | 0 | vty_out(vty, "]\n"); |
3645 | 0 | vty_mgmt_resume_response(vty, success); |
3646 | 0 | } |
3647 | |
|
3648 | 0 | return 0; |
3649 | 0 | } |
3650 | | |
3651 | | static struct mgmt_fe_client_cbs mgmt_cbs = { |
3652 | | .client_connect_notify = vty_mgmt_server_connected, |
3653 | | .client_session_notify = vty_mgmt_session_notify, |
3654 | | .lock_ds_notify = vty_mgmt_ds_lock_notified, |
3655 | | .set_config_notify = vty_mgmt_set_config_result_notified, |
3656 | | .commit_config_notify = vty_mgmt_commit_config_result_notified, |
3657 | | .get_data_notify = vty_mgmt_get_data_result_notified, |
3658 | | }; |
3659 | | |
3660 | | void vty_init_mgmt_fe(void) |
3661 | 0 | { |
3662 | 0 | char name[40]; |
3663 | |
|
3664 | 0 | assert(vty_master); |
3665 | 0 | assert(!mgmt_fe_client); |
3666 | 0 | snprintf(name, sizeof(name), "vty-%s-%ld", frr_get_progname(), |
3667 | 0 | (long)getpid()); |
3668 | 0 | mgmt_fe_client = mgmt_fe_client_create(name, &mgmt_cbs, 0, vty_master); |
3669 | 0 | assert(mgmt_fe_client); |
3670 | 0 | } |
3671 | | |
3672 | | bool vty_mgmt_fe_enabled(void) |
3673 | 0 | { |
3674 | 0 | return mgmt_fe_client && mgmt_fe_connected; |
3675 | 0 | } |
3676 | | |
3677 | | bool vty_mgmt_should_process_cli_apply_changes(struct vty *vty) |
3678 | 0 | { |
3679 | 0 | return vty->type != VTY_FILE && vty_mgmt_fe_enabled(); |
3680 | 0 | } |
3681 | | |
3682 | | int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, |
3683 | | bool lock, bool scok) |
3684 | 0 | { |
3685 | 0 | assert(mgmt_fe_client); |
3686 | 0 | assert(vty->mgmt_session_id); |
3687 | |
|
3688 | 0 | vty->mgmt_req_id++; |
3689 | 0 | if (mgmt_fe_send_lockds_req(mgmt_fe_client, vty->mgmt_session_id, |
3690 | 0 | vty->mgmt_req_id, ds_id, lock, scok)) { |
3691 | 0 | zlog_err("Failed sending %sLOCK-DS-REQ req-id %" PRIu64, |
3692 | 0 | lock ? "" : "UN", vty->mgmt_req_id); |
3693 | 0 | vty_out(vty, "Failed to send %sLOCK-DS-REQ to MGMTD!\n", |
3694 | 0 | lock ? "" : "UN"); |
3695 | 0 | return -1; |
3696 | 0 | } |
3697 | | |
3698 | 0 | if (!scok) |
3699 | 0 | vty->mgmt_req_pending_cmd = "MESSAGE_LOCKDS_REQ"; |
3700 | |
|
3701 | 0 | return 0; |
3702 | 0 | } |
3703 | | |
3704 | | int vty_mgmt_send_config_data(struct vty *vty, bool implicit_commit) |
3705 | 0 | { |
3706 | 0 | Mgmtd__YangDataValue value[VTY_MAXCFGCHANGES]; |
3707 | 0 | Mgmtd__YangData cfg_data[VTY_MAXCFGCHANGES]; |
3708 | 0 | Mgmtd__YangCfgDataReq cfg_req[VTY_MAXCFGCHANGES]; |
3709 | 0 | Mgmtd__YangCfgDataReq *cfgreq[VTY_MAXCFGCHANGES] = {0}; |
3710 | 0 | size_t indx; |
3711 | |
|
3712 | 0 | if (vty->type == VTY_FILE) { |
3713 | | /* |
3714 | | * if this is a config file read we will not send any of the |
3715 | | * changes until we are done reading the file and have modified |
3716 | | * the local candidate DS. |
3717 | | */ |
3718 | | /* no-one else should be sending data right now */ |
3719 | 0 | assert(!vty->mgmt_num_pending_setcfg); |
3720 | 0 | return 0; |
3721 | 0 | } |
3722 | | |
3723 | | /* If we are FE client and we have a vty then we have a session */ |
3724 | 0 | assert(mgmt_fe_client && vty->mgmt_client_id && vty->mgmt_session_id); |
3725 | |
|
3726 | 0 | if (!vty->num_cfg_changes) |
3727 | 0 | return 0; |
3728 | | |
3729 | | /* grab the candidate and running lock prior to sending implicit commit |
3730 | | * command |
3731 | | */ |
3732 | 0 | if (implicit_commit) { |
3733 | 0 | if (vty_mgmt_lock_candidate_inline(vty)) { |
3734 | 0 | vty_out(vty, |
3735 | 0 | "%% command failed, could not lock candidate DS\n"); |
3736 | 0 | return -1; |
3737 | 0 | } else if (vty_mgmt_lock_running_inline(vty)) { |
3738 | 0 | vty_out(vty, |
3739 | 0 | "%% command failed, could not lock running DS\n"); |
3740 | 0 | vty_mgmt_unlock_candidate_inline(vty); |
3741 | 0 | return -1; |
3742 | 0 | } |
3743 | 0 | } |
3744 | | |
3745 | 0 | for (indx = 0; indx < vty->num_cfg_changes; indx++) { |
3746 | 0 | mgmt_yang_data_init(&cfg_data[indx]); |
3747 | |
|
3748 | 0 | if (vty->cfg_changes[indx].value) { |
3749 | 0 | mgmt_yang_data_value_init(&value[indx]); |
3750 | 0 | value[indx].encoded_str_val = |
3751 | 0 | (char *)vty->cfg_changes[indx].value; |
3752 | 0 | value[indx].value_case = |
3753 | 0 | MGMTD__YANG_DATA_VALUE__VALUE_ENCODED_STR_VAL; |
3754 | 0 | cfg_data[indx].value = &value[indx]; |
3755 | 0 | } |
3756 | |
|
3757 | 0 | cfg_data[indx].xpath = vty->cfg_changes[indx].xpath; |
3758 | |
|
3759 | 0 | mgmt_yang_cfg_data_req_init(&cfg_req[indx]); |
3760 | 0 | cfg_req[indx].data = &cfg_data[indx]; |
3761 | 0 | switch (vty->cfg_changes[indx].operation) { |
3762 | 0 | case NB_OP_DESTROY: |
3763 | 0 | cfg_req[indx].req_type = |
3764 | 0 | MGMTD__CFG_DATA_REQ_TYPE__DELETE_DATA; |
3765 | 0 | break; |
3766 | | |
3767 | 0 | case NB_OP_CREATE: |
3768 | 0 | case NB_OP_MODIFY: |
3769 | 0 | case NB_OP_MOVE: |
3770 | 0 | case NB_OP_PRE_VALIDATE: |
3771 | 0 | case NB_OP_APPLY_FINISH: |
3772 | 0 | cfg_req[indx].req_type = |
3773 | 0 | MGMTD__CFG_DATA_REQ_TYPE__SET_DATA; |
3774 | 0 | break; |
3775 | 0 | case NB_OP_GET_ELEM: |
3776 | 0 | case NB_OP_GET_NEXT: |
3777 | 0 | case NB_OP_GET_KEYS: |
3778 | 0 | case NB_OP_LOOKUP_ENTRY: |
3779 | 0 | case NB_OP_RPC: |
3780 | 0 | default: |
3781 | 0 | assertf(false, |
3782 | 0 | "Invalid operation type for send config: %d", |
3783 | 0 | vty->cfg_changes[indx].operation); |
3784 | | /*NOTREACHED*/ |
3785 | 0 | abort(); |
3786 | 0 | } |
3787 | | |
3788 | 0 | cfgreq[indx] = &cfg_req[indx]; |
3789 | 0 | } |
3790 | 0 | if (!indx) |
3791 | 0 | return 0; |
3792 | | |
3793 | 0 | vty->mgmt_req_id++; |
3794 | 0 | if (mgmt_fe_send_setcfg_req(mgmt_fe_client, vty->mgmt_session_id, |
3795 | 0 | vty->mgmt_req_id, MGMTD_DS_CANDIDATE, |
3796 | 0 | cfgreq, indx, implicit_commit, |
3797 | 0 | MGMTD_DS_RUNNING)) { |
3798 | 0 | zlog_err("Failed to send %zu config xpaths to mgmtd", indx); |
3799 | 0 | vty_out(vty, "%% Failed to send commands to mgmtd\n"); |
3800 | 0 | return -1; |
3801 | 0 | } |
3802 | | |
3803 | 0 | vty->mgmt_req_pending_cmd = "MESSAGE_SETCFG_REQ"; |
3804 | |
|
3805 | 0 | return 0; |
3806 | 0 | } |
3807 | | |
3808 | | int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, bool abort) |
3809 | 0 | { |
3810 | 0 | if (mgmt_fe_client && vty->mgmt_session_id) { |
3811 | 0 | vty->mgmt_req_id++; |
3812 | 0 | if (mgmt_fe_send_commitcfg_req( |
3813 | 0 | mgmt_fe_client, vty->mgmt_session_id, |
3814 | 0 | vty->mgmt_req_id, MGMTD_DS_CANDIDATE, |
3815 | 0 | MGMTD_DS_RUNNING, validate_only, abort)) { |
3816 | 0 | zlog_err("Failed sending COMMIT-REQ req-id %" PRIu64, |
3817 | 0 | vty->mgmt_req_id); |
3818 | 0 | vty_out(vty, "Failed to send COMMIT-REQ to MGMTD!\n"); |
3819 | 0 | return -1; |
3820 | 0 | } |
3821 | | |
3822 | 0 | vty->mgmt_req_pending_cmd = "MESSAGE_COMMCFG_REQ"; |
3823 | 0 | vty->mgmt_num_pending_setcfg = 0; |
3824 | 0 | } |
3825 | | |
3826 | 0 | return 0; |
3827 | 0 | } |
3828 | | |
3829 | | int vty_mgmt_send_get_req(struct vty *vty, bool is_config, |
3830 | | Mgmtd__DatastoreId datastore, const char **xpath_list, |
3831 | | int num_req) |
3832 | 0 | { |
3833 | 0 | Mgmtd__YangData yang_data[VTY_MAXCFGCHANGES]; |
3834 | 0 | Mgmtd__YangGetDataReq get_req[VTY_MAXCFGCHANGES]; |
3835 | 0 | Mgmtd__YangGetDataReq *getreq[VTY_MAXCFGCHANGES]; |
3836 | 0 | int i; |
3837 | |
|
3838 | 0 | vty->mgmt_req_id++; |
3839 | |
|
3840 | 0 | for (i = 0; i < num_req; i++) { |
3841 | 0 | mgmt_yang_get_data_req_init(&get_req[i]); |
3842 | 0 | mgmt_yang_data_init(&yang_data[i]); |
3843 | |
|
3844 | 0 | yang_data->xpath = (char *)xpath_list[i]; |
3845 | |
|
3846 | 0 | get_req[i].data = &yang_data[i]; |
3847 | 0 | getreq[i] = &get_req[i]; |
3848 | 0 | } |
3849 | 0 | if (mgmt_fe_send_get_req(mgmt_fe_client, vty->mgmt_session_id, |
3850 | 0 | vty->mgmt_req_id, is_config, datastore, getreq, |
3851 | 0 | num_req)) { |
3852 | 0 | zlog_err("Failed to send GET- to MGMTD for req-id %" PRIu64 ".", |
3853 | 0 | vty->mgmt_req_id); |
3854 | 0 | vty_out(vty, "Failed to send GET-CONFIG to MGMTD!\n"); |
3855 | 0 | return -1; |
3856 | 0 | } |
3857 | | |
3858 | 0 | vty->mgmt_req_pending_cmd = "MESSAGE_GETCFG_REQ"; |
3859 | |
|
3860 | 0 | return 0; |
3861 | 0 | } |
3862 | | |
3863 | | /* Install vty's own commands like `who' command. */ |
3864 | | void vty_init(struct event_loop *master_thread, bool do_command_logging) |
3865 | 4 | { |
3866 | | /* For further configuration read, preserve current directory. */ |
3867 | 4 | vty_save_cwd(); |
3868 | | |
3869 | 4 | vty_master = master_thread; |
3870 | | |
3871 | 4 | atexit(vty_stdio_atexit); |
3872 | | |
3873 | | /* Install bgp top node. */ |
3874 | 4 | install_node(&vty_node); |
3875 | | |
3876 | 4 | install_element(VIEW_NODE, &config_who_cmd); |
3877 | 4 | install_element(VIEW_NODE, &show_history_cmd); |
3878 | 4 | install_element(CONFIG_NODE, &line_vty_cmd); |
3879 | 4 | install_element(CONFIG_NODE, &service_advanced_vty_cmd); |
3880 | 4 | install_element(CONFIG_NODE, &no_service_advanced_vty_cmd); |
3881 | 4 | install_element(CONFIG_NODE, &show_history_cmd); |
3882 | 4 | install_element(CONFIG_NODE, &log_commands_cmd); |
3883 | | |
3884 | 4 | if (do_command_logging) { |
3885 | 0 | vty_log_commands = true; |
3886 | 0 | vty_log_commands_perm = true; |
3887 | 0 | } |
3888 | | |
3889 | 4 | install_element(ENABLE_NODE, &terminal_monitor_cmd); |
3890 | 4 | install_element(ENABLE_NODE, &terminal_no_monitor_cmd); |
3891 | 4 | install_element(ENABLE_NODE, &no_terminal_monitor_cmd); |
3892 | | |
3893 | 4 | install_default(VTY_NODE); |
3894 | 4 | install_element(VTY_NODE, &exec_timeout_min_cmd); |
3895 | 4 | install_element(VTY_NODE, &exec_timeout_sec_cmd); |
3896 | 4 | install_element(VTY_NODE, &no_exec_timeout_cmd); |
3897 | 4 | install_element(VTY_NODE, &vty_access_class_cmd); |
3898 | 4 | install_element(VTY_NODE, &no_vty_access_class_cmd); |
3899 | 4 | install_element(VTY_NODE, &vty_login_cmd); |
3900 | 4 | install_element(VTY_NODE, &no_vty_login_cmd); |
3901 | 4 | install_element(VTY_NODE, &vty_ipv6_access_class_cmd); |
3902 | 4 | install_element(VTY_NODE, &no_vty_ipv6_access_class_cmd); |
3903 | 4 | } |
3904 | | |
3905 | | void vty_terminate(void) |
3906 | 0 | { |
3907 | 0 | struct vty *vty; |
3908 | |
|
3909 | 0 | if (mgmt_fe_client) { |
3910 | 0 | mgmt_fe_client_destroy(mgmt_fe_client); |
3911 | 0 | mgmt_fe_client = 0; |
3912 | 0 | } |
3913 | |
|
3914 | 0 | memset(vty_cwd, 0x00, sizeof(vty_cwd)); |
3915 | |
|
3916 | 0 | vty_reset(); |
3917 | | |
3918 | | /* default state of vty_sessions is initialized & empty. */ |
3919 | 0 | vtys_fini(vty_sessions); |
3920 | 0 | vtys_init(vty_sessions); |
3921 | | |
3922 | | /* vty_reset() doesn't close vtysh sessions */ |
3923 | 0 | frr_each_safe (vtys, vtysh_sessions, vty) { |
3924 | 0 | buffer_reset(vty->lbuf); |
3925 | 0 | buffer_reset(vty->obuf); |
3926 | 0 | vty->status = VTY_CLOSE; |
3927 | 0 | vty_close(vty); |
3928 | 0 | } |
3929 | |
|
3930 | 0 | vtys_fini(vtysh_sessions); |
3931 | 0 | vtys_init(vtysh_sessions); |
3932 | |
|
3933 | 0 | vty_serv_stop(); |
3934 | 0 | } |