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