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