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