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