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