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