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