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