]> git.proxmox.com Git - mirror_frr.git/blame - vtysh/vtysh_main.c
Merge pull request #5090 from sworleys/Fix-Vrf_ID-Decode
[mirror_frr.git] / vtysh / vtysh_main.c
CommitLineData
718e3744 1/* Virtual terminal interface shell.
2 * Copyright (C) 2000 Kunihiro Ishiguro
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
896014f4
DL
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
718e3744 19 */
20
21#include <zebra.h>
22
23#include <sys/un.h>
24#include <setjmp.h>
25#include <sys/wait.h>
26#include <pwd.h>
e43716f6
DS
27#include <sys/file.h>
28#include <unistd.h>
718e3744 29
30#include <readline/readline.h>
31#include <readline/history.h>
32
8860ffdc
DS
33/*
34 * The append_history function only appears in newer versions
35 * of the readline library it appears like. Since we don't
36 * need this just silently ignore the code on these
37 * ancient platforms.
38 */
39#if !defined HAVE_APPEND_HISTORY
40#define append_history(A, B)
41#endif
42
5e4fa164 43#include <lib/version.h>
718e3744 44#include "getopt.h"
45#include "command.h"
f366ad31 46#include "memory.h"
4201dd11 47#include "linklist.h"
fc7948fa 48#include "memory_vty.h"
eb05883f 49#include "libfrr.h"
1f9128d6
QY
50#include "ferr.h"
51#include "lib_errors.h"
718e3744 52
53#include "vtysh/vtysh.h"
54#include "vtysh/vtysh_user.h"
b094d260 55
718e3744 56/* VTY shell program name. */
57char *progname;
58
32f3268f
DL
59/* SUID mode */
60static uid_t elevuid, realuid;
61static gid_t elevgid, realgid;
62
9b8a8249
DL
63#define VTYSH_CONFIG_NAME "vtysh.conf"
64#define FRR_CONFIG_NAME "frr.conf"
65
67e29abc 66/* Configuration file name and directory. */
ff44f570
DS
67static char vtysh_config[MAXPATHLEN * 3];
68char frr_config[MAXPATHLEN * 3];
9b8a8249
DL
69char vtydir[MAXPATHLEN];
70static char history_file[MAXPATHLEN];
718e3744 71
718e3744 72/* Flag for indicate executing child command. */
73int execute_flag = 0;
74
86b28610 75/* Flag to indicate if in user/unprivileged mode. */
186f6af2 76int user_mode;
86b28610 77
718e3744 78/* For sigsetjmp() & siglongjmp(). */
79static sigjmp_buf jmpbuf;
80
81/* Flag for avoid recursive siglongjmp() call. */
82static int jmpflag = 0;
83
84/* A static variable for holding the line. */
85static char *line_read;
86
87/* Master of threads. */
88struct thread_master *master;
b094d260 89
57fb9748
SH
90/* Command logging */
91FILE *logfile;
92
718e3744 93/* SIGTSTP handler. This function care user's ^Z input. */
d62a17ae 94static void sigtstp(int sig)
718e3744 95{
d62a17ae 96 /* Execute "end" command. */
97 vtysh_execute("end");
718e3744 98
d62a17ae 99 /* Initialize readline. */
100 rl_initialize();
101 printf("\n");
718e3744 102
d62a17ae 103 /* Check jmpflag for duplicate siglongjmp(). */
104 if (!jmpflag)
105 return;
718e3744 106
d62a17ae 107 jmpflag = 0;
108
109 /* Back to main command loop. */
110 siglongjmp(jmpbuf, 1);
718e3744 111}
112
113/* SIGINT handler. This function care user's ^Z input. */
d62a17ae 114static void sigint(int sig)
718e3744 115{
d62a17ae 116 /* Check this process is not child process. */
117 if (!execute_flag) {
118 rl_initialize();
119 printf("\n");
120 rl_forced_update_display();
121 }
718e3744 122}
123
e42f5a37 124/* Signale wrapper for vtysh. We don't use sigevent because
125 * vtysh doesn't use threads. TODO */
d62a17ae 126static void vtysh_signal_set(int signo, void (*func)(int))
718e3744 127{
d62a17ae 128 struct sigaction sig;
129 struct sigaction osig;
718e3744 130
d62a17ae 131 sig.sa_handler = func;
132 sigemptyset(&sig.sa_mask);
133 sig.sa_flags = 0;
718e3744 134#ifdef SA_RESTART
d62a17ae 135 sig.sa_flags |= SA_RESTART;
718e3744 136#endif /* SA_RESTART */
137
d62a17ae 138 sigaction(signo, &sig, &osig);
718e3744 139}
140
141/* Initialization of signal handles. */
d62a17ae 142static void vtysh_signal_init(void)
718e3744 143{
d62a17ae 144 vtysh_signal_set(SIGINT, sigint);
145 vtysh_signal_set(SIGTSTP, sigtstp);
146 vtysh_signal_set(SIGPIPE, SIG_IGN);
718e3744 147}
b094d260 148
718e3744 149/* Help information display. */
d62a17ae 150static void usage(int status)
718e3744 151{
d62a17ae 152 if (status != 0)
153 fprintf(stderr, "Try `%s --help' for more information.\n",
154 progname);
155 else
156 printf("Usage : %s [OPTION...]\n\n"
157 "Integrated shell for FRR. \n\n"
158 "-b, --boot Execute boot startup configuration\n"
159 "-c, --command Execute argument as command\n"
160 "-d, --daemon Connect only to the specified daemon\n"
161 "-f, --inputfile Execute commands from specific file and exit\n"
162 "-E, --echo Echo prompt and command in -c mode\n"
163 "-C, --dryrun Check configuration for validity and exit\n"
164 "-m, --markfile Mark input file with context end\n"
165 " --vty_socket Override vty socket path\n"
166 " --config_dir Override config directory path\n"
8bd33a03 167 "-N --pathspace Insert prefix into config & socket paths\n"
86b28610 168 "-u --user Run as an unprivileged user\n"
d62a17ae 169 "-w, --writeconfig Write integrated config (frr.conf) and exit\n"
170 "-h, --help Display this help and exit\n\n"
171 "Note that multiple commands may be executed from the command\n"
172 "line by passing multiple -c args, or by embedding linefeed\n"
173 "characters in one or more of the commands.\n\n"
174 "Report bugs to %s\n",
175 progname, FRR_BUG_ADDRESS);
176
177 exit(status);
718e3744 178}
179
180/* VTY shell options, we use GNU getopt library. */
87d79a9f 181#define OPTION_VTYSOCK 1000
ce2e9ec3 182#define OPTION_CONFDIR 1001
d62a17ae 183struct option longopts[] = {
184 {"boot", no_argument, NULL, 'b'},
185 /* For compatibility with older zebra/quagga versions */
186 {"eval", required_argument, NULL, 'e'},
187 {"command", required_argument, NULL, 'c'},
188 {"daemon", required_argument, NULL, 'd'},
189 {"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
190 {"config_dir", required_argument, NULL, OPTION_CONFDIR},
191 {"inputfile", required_argument, NULL, 'f'},
192 {"echo", no_argument, NULL, 'E'},
193 {"dryrun", no_argument, NULL, 'C'},
194 {"help", no_argument, NULL, 'h'},
195 {"noerror", no_argument, NULL, 'n'},
196 {"mark", no_argument, NULL, 'm'},
197 {"writeconfig", no_argument, NULL, 'w'},
bd29d463 198 {"pathspace", required_argument, NULL, 'N'},
86b28610 199 {"user", no_argument, NULL, 'u'},
d62a17ae 200 {0}};
b094d260 201
718e3744 202/* Read a string, and return a pointer to it. Returns NULL on EOF. */
d62a17ae 203static char *vtysh_rl_gets(void)
718e3744 204{
d62a17ae 205 HIST_ENTRY *last;
206 /* If the buffer has already been allocated, return the memory
207 * to the free pool. */
208 if (line_read) {
209 free(line_read);
210 line_read = NULL;
211 }
212
213 /* Get a line from the user. Change prompt according to node. XXX. */
214 line_read = readline(vtysh_prompt());
215
216 /* If the line has any text in it, save it on the history. But only if
217 * last command in history isn't the same one. */
218 if (line_read && *line_read) {
219 using_history();
220 last = previous_history();
221 if (!last || strcmp(last->line, line_read) != 0) {
222 add_history(line_read);
223 append_history(1, history_file);
224 }
225 }
226
227 return (line_read);
718e3744 228}
b094d260 229
57fb9748
SH
230static void log_it(const char *line)
231{
d62a17ae 232 time_t t = time(NULL);
233 struct tm *tmp = localtime(&t);
234 const char *user = getenv("USER");
235 char tod[64];
57fb9748 236
d62a17ae 237 if (!user)
238 user = "boot";
f03db93b 239
d62a17ae 240 strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp);
241
242 fprintf(logfile, "%s:%s %s\n", tod, user, line);
57fb9748
SH
243}
244
e43716f6
DS
245static int flock_fd;
246
d62a17ae 247static void vtysh_flock_config(const char *flock_file)
e43716f6 248{
d62a17ae 249 int count = 0;
250
251 flock_fd = open(flock_file, O_RDONLY, 0644);
252 if (flock_fd < 0) {
253 fprintf(stderr, "Unable to create lock file: %s, %s\n",
254 flock_file, safe_strerror(errno));
255 return;
256 }
257
258 while (count < 400 && (flock(flock_fd, LOCK_EX | LOCK_NB) < 0)) {
259 count++;
260 usleep(500000);
261 }
262
263 if (count >= 400)
264 fprintf(stderr,
265 "Flock of %s failed, continuing this may cause issues\n",
266 flock_file);
e43716f6
DS
267}
268
d62a17ae 269static void vtysh_unflock_config(void)
e43716f6 270{
d62a17ae 271 flock(flock_fd, LOCK_UN);
272 close(flock_fd);
e43716f6
DS
273}
274
32f3268f
DL
275void suid_on(void)
276{
277 if (elevuid != realuid && seteuid(elevuid)) {
278 perror("seteuid(on)");
279 exit(1);
280 }
281 if (elevgid != realgid && setegid(elevgid)) {
282 perror("setegid(on)");
283 exit(1);
284 }
285}
286
287void suid_off(void)
288{
289 if (elevuid != realuid && seteuid(realuid)) {
290 perror("seteuid(off)");
291 exit(1);
292 }
293 if (elevgid != realgid && setegid(realgid)) {
294 perror("setegid(off)");
295 exit(1);
296 }
297}
298
718e3744 299/* VTY shell main routine. */
d62a17ae 300int main(int argc, char **argv, char **env)
718e3744 301{
d62a17ae 302 char *p;
303 int opt;
304 int dryrun = 0;
305 int boot_flag = 0;
306 const char *daemon_name = NULL;
307 const char *inputfile = NULL;
d62a17ae 308 struct cmd_rec {
309 char *line;
310 struct cmd_rec *next;
311 } *cmd = NULL;
312 struct cmd_rec *tail = NULL;
313 int echo_command = 0;
314 int no_error = 0;
315 int markfile = 0;
316 int writeconfig = 0;
317 int ret = 0;
318 char *homedir = NULL;
32f3268f 319 int ditch_suid = 0;
9b8a8249 320 char sysconfdir[MAXPATHLEN];
1dede1f8 321 const char *pathspace_arg = NULL;
8bd33a03 322 char pathspace[MAXPATHLEN] = "";
d62a17ae 323
32f3268f
DL
324 /* SUID: drop down to calling user & go back up when needed */
325 elevuid = geteuid();
326 elevgid = getegid();
327 realuid = getuid();
328 realgid = getgid();
329 suid_off();
d62a17ae 330
186f6af2
LB
331 user_mode = 0; /* may be set in options processing */
332
d62a17ae 333 /* Preserve name of myself. */
334 progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
335
9b8a8249 336 strlcpy(sysconfdir, frr_sysconfdir, sizeof(sysconfdir));
43e587c1
DS
337
338 frr_init_vtydir();
9b8a8249
DL
339 strlcpy(vtydir, frr_vtydir, sizeof(vtydir));
340
d62a17ae 341 /* Option handling. */
342 while (1) {
186f6af2
LB
343 opt = getopt_long(argc, argv, "be:c:d:nf:mEhCwN:u",
344 longopts, 0);
d62a17ae 345
346 if (opt == EOF)
347 break;
348
349 switch (opt) {
350 case 0:
351 break;
352 case 'b':
353 boot_flag = 1;
354 break;
355 case 'e':
356 case 'c': {
357 struct cmd_rec *cr;
358 cr = XMALLOC(MTYPE_TMP, sizeof(*cr));
359 cr->line = optarg;
360 cr->next = NULL;
361 if (tail)
362 tail->next = cr;
363 else
364 cmd = cr;
365 tail = cr;
366 } break;
367 case OPTION_VTYSOCK:
32f3268f 368 ditch_suid = 1; /* option disables SUID */
9b8a8249 369 strlcpy(vtydir, optarg, sizeof(vtydir));
d62a17ae 370 break;
371 case OPTION_CONFDIR:
32f3268f 372 ditch_suid = 1; /* option disables SUID */
45d00587 373 snprintf(sysconfdir, sizeof(sysconfdir), "%s/", optarg);
d62a17ae 374 break;
8bd33a03
DL
375 case 'N':
376 if (strchr(optarg, '/') || strchr(optarg, '.')) {
377 fprintf(stderr,
378 "slashes or dots are not permitted in the --pathspace option.\n");
379 exit(1);
380 }
1dede1f8
CF
381 pathspace_arg = optarg;
382 snprintf(pathspace, sizeof(pathspace), "%s/", optarg);
8bd33a03 383 break;
d62a17ae 384 case 'd':
385 daemon_name = optarg;
386 break;
387 case 'f':
388 inputfile = optarg;
389 break;
390 case 'm':
391 markfile = 1;
392 break;
393 case 'n':
394 no_error = 1;
395 break;
396 case 'E':
397 echo_command = 1;
398 break;
399 case 'C':
400 dryrun = 1;
401 break;
86b28610
LB
402 case 'u':
403 user_mode = 1;
404 break;
d62a17ae 405 case 'w':
406 writeconfig = 1;
407 break;
408 case 'h':
409 usage(0);
410 break;
411 default:
412 usage(1);
413 break;
414 }
415 }
416
32f3268f
DL
417 if (ditch_suid) {
418 elevuid = realuid;
419 elevgid = realgid;
420 }
421
d62a17ae 422 if (markfile + writeconfig + dryrun + boot_flag > 1) {
423 fprintf(stderr,
424 "Invalid combination of arguments. Please specify at "
425 "most one of:\n\t-b, -C, -m, -w\n");
426 return 1;
427 }
428 if (inputfile && (writeconfig || boot_flag)) {
429 fprintf(stderr,
430 "WARNING: Combinining the -f option with -b or -w is "
431 "NOT SUPPORTED since its\nresults are inconsistent!\n");
432 }
433
e82314b1 434 snprintf(vtysh_config, sizeof(vtysh_config), "%s%s%s", sysconfdir,
60466a63 435 pathspace, VTYSH_CONFIG_NAME);
e82314b1 436 snprintf(frr_config, sizeof(frr_config), "%s%s%s", sysconfdir,
60466a63 437 pathspace, FRR_CONFIG_NAME);
1dede1f8
CF
438
439 if (pathspace_arg) {
440 strlcat(vtydir, "/", sizeof(vtydir));
441 strlcat(vtydir, pathspace_arg, sizeof(vtydir));
442 }
9b8a8249 443
d62a17ae 444 /* Initialize user input buffer. */
445 line_read = NULL;
446 setlinebuf(stdout);
447
448 /* Signal and others. */
449 vtysh_signal_init();
450
451 /* Make vty structure and register commands. */
452 vtysh_init_vty();
453 vtysh_init_cmd();
454 vtysh_user_init();
455 vtysh_config_init();
456
457 vty_init_vtysh();
458
86b28610
LB
459 if (!user_mode) {
460 /* Read vtysh configuration file before connecting to daemons.
461 * (file may not be readable to calling user in SUID mode) */
462 suid_on();
463 vtysh_read_config(vtysh_config);
464 suid_off();
465 }
1f9128d6
QY
466 /* Error code library system */
467 log_ref_init();
468 lib_error_init();
d62a17ae 469
470 if (markfile) {
471 if (!inputfile) {
472 fprintf(stderr,
473 "-f option MUST be specified with -m option\n");
474 return (1);
475 }
476 return (vtysh_mark_file(inputfile));
477 }
478
479 /* Start execution only if not in dry-run mode */
480 if (dryrun && !cmd) {
481 if (inputfile) {
482 ret = vtysh_read_config(inputfile);
483 } else {
9b8a8249 484 ret = vtysh_read_config(frr_config);
d62a17ae 485 }
486
487 exit(ret);
718e3744 488 }
d62a17ae 489
47402c0f 490 if (dryrun && cmd && cmd->line) {
f205fcdb
LB
491 if (!user_mode)
492 vtysh_execute("enable");
d62a17ae 493 while (cmd) {
494 struct cmd_rec *cr;
495 char *cmdnow = cmd->line, *next;
496 do {
497 next = strchr(cmdnow, '\n');
498 if (next)
499 *next++ = '\0';
500
501 if (echo_command)
502 printf("%s%s\n", vtysh_prompt(),
503 cmdnow);
504
505 ret = vtysh_execute_no_pager(cmdnow);
506 if (!no_error
507 && !(ret == CMD_SUCCESS
508 || ret == CMD_SUCCESS_DAEMON
509 || ret == CMD_WARNING))
510 exit(1);
511 } while ((cmdnow = next) != NULL);
512
513 cr = cmd;
514 cmd = cmd->next;
515 XFREE(MTYPE_TMP, cr);
516 }
517 exit(ret);
0846286b 518 }
d62a17ae 519
520 /* Ignore error messages */
521 if (no_error) {
522 if (freopen("/dev/null", "w", stdout) == NULL) {
523 fprintf(stderr,
524 "Exiting: Failed to duplicate stdout with -n option");
525 exit(1);
526 }
0846286b 527 }
d62a17ae 528
32f3268f
DL
529 /* SUID: go back up elevated privs */
530 suid_on();
531
d62a17ae 532 /* Make sure we pass authentication before proceeding. */
533 vtysh_auth();
534
535 /* Do not connect until we have passed authentication. */
536 if (vtysh_connect_all(daemon_name) <= 0) {
537 fprintf(stderr, "Exiting: failed to connect to any daemons.\n");
538 if (no_error)
539 exit(0);
540 else
541 exit(1);
3221dca8 542 }
cd272640 543
32f3268f
DL
544 /* SUID: back down, don't need privs further on */
545 suid_off();
546
d62a17ae 547 if (writeconfig) {
f205fcdb
LB
548 if (user_mode) {
549 fprintf(stderr,
550 "writeconfig cannot be used when running as an unprivileged user.\n");
551 if (no_error)
552 exit(0);
553 else
554 exit(1);
555 }
d62a17ae 556 vtysh_execute("enable");
557 return vtysh_write_config_integrated();
7a49a5b5 558 }
d62a17ae 559
560 if (inputfile) {
561 vtysh_flock_config(inputfile);
562 ret = vtysh_read_config(inputfile);
563 vtysh_unflock_config();
564 exit(ret);
fba55c8a 565 }
d62a17ae 566
567 /*
568 * Setup history file for use by both -c and regular input
569 * If we can't find the home directory, then don't store
570 * the history information
571 */
572 homedir = vtysh_get_home();
573 if (homedir) {
996c9314
LB
574 snprintf(history_file, sizeof(history_file), "%s/.history_frr",
575 homedir);
d62a17ae 576 if (read_history(history_file) != 0) {
577 int fp;
578
579 fp = open(history_file, O_CREAT | O_EXCL,
580 S_IRUSR | S_IWUSR);
cbb65f5e 581 if (fp != -1)
d62a17ae 582 close(fp);
583
584 read_history(history_file);
585 }
586 }
587
32f3268f
DL
588 if (getenv("VTYSH_LOG")) {
589 const char *logpath = getenv("VTYSH_LOG");
590
591 logfile = fopen(logpath, "a");
592 if (!logfile) {
593 fprintf(stderr, "Failed to open logfile (%s): %s\n",
594 logpath, strerror(errno));
595 exit(1);
596 }
597 }
598
d62a17ae 599 /* If eval mode. */
47402c0f 600 if (cmd && cmd->line) {
d62a17ae 601 /* Enter into enable node. */
f205fcdb
LB
602 if (!user_mode)
603 vtysh_execute("enable");
d62a17ae 604
605 while (cmd != NULL) {
d62a17ae 606 char *eol;
607
608 while ((eol = strchr(cmd->line, '\n')) != NULL) {
609 *eol = '\0';
610
611 add_history(cmd->line);
612 append_history(1, history_file);
613
614 if (echo_command)
615 printf("%s%s\n", vtysh_prompt(),
616 cmd->line);
617
618 if (logfile)
619 log_it(cmd->line);
620
621 ret = vtysh_execute_no_pager(cmd->line);
622 if (!no_error
623 && !(ret == CMD_SUCCESS
624 || ret == CMD_SUCCESS_DAEMON
625 || ret == CMD_WARNING))
626 exit(1);
627
628 cmd->line = eol + 1;
629 }
630
631 add_history(cmd->line);
632 append_history(1, history_file);
633
634 if (echo_command)
635 printf("%s%s\n", vtysh_prompt(), cmd->line);
636
637 if (logfile)
638 log_it(cmd->line);
639
a4364a44 640 /*
76015847
QY
641 * Parsing logic for regular commands will be different
642 * than for those commands requiring further
643 * processing, such as cli instructions terminating
644 * with question-mark character.
a4364a44
RM
645 */
646 if (!vtysh_execute_command_questionmark(cmd->line))
647 ret = CMD_SUCCESS;
648 else
649 ret = vtysh_execute_no_pager(cmd->line);
650
d62a17ae 651 if (!no_error
652 && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON
653 || ret == CMD_WARNING))
654 exit(1);
655
656 {
657 struct cmd_rec *cr;
658 cr = cmd;
659 cmd = cmd->next;
660 XFREE(MTYPE_TMP, cr);
661 }
662 }
663
664 history_truncate_file(history_file, 1000);
665 exit(0);
666 }
667
668 /* Boot startup configuration file. */
669 if (boot_flag) {
9b8a8249 670 vtysh_flock_config(frr_config);
c683bd44 671 ret = vtysh_read_config(frr_config);
d62a17ae 672 vtysh_unflock_config();
673 if (ret) {
674 fprintf(stderr,
675 "Configuration file[%s] processing failure: %d\n",
9b8a8249 676 frr_config, ret);
d62a17ae 677 if (no_error)
678 exit(0);
679 else
680 exit(ret);
681 } else
682 exit(0);
e7168df4 683 }
718e3744 684
d62a17ae 685 vtysh_readline_init();
718e3744 686
d62a17ae 687 vty_hello(vty);
718e3744 688
d62a17ae 689 /* Enter into enable node. */
f205fcdb
LB
690 if (!user_mode)
691 vtysh_execute("enable");
e7168df4 692
d62a17ae 693 /* Preparation for longjmp() in sigtstp(). */
694 sigsetjmp(jmpbuf, 1);
695 jmpflag = 1;
718e3744 696
d62a17ae 697 /* Main command loop. */
698 while (vtysh_rl_gets())
699 vtysh_execute(line_read);
718e3744 700
193a5a95
QY
701 vtysh_uninit();
702
d62a17ae 703 history_truncate_file(history_file, 1000);
704 printf("\n");
718e3744 705
d62a17ae 706 /* Rest in peace. */
707 exit(0);
718e3744 708}