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