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