]>
git.proxmox.com Git - mirror_frr.git/blob - vtysh/vtysh_main.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /* Virtual terminal interface shell.
3 * Copyright (C) 2000 Kunihiro Ishiguro
14 /* readline carries some ancient definitions around */
15 #pragma GCC diagnostic push
16 #pragma GCC diagnostic ignored "-Wstrict-prototypes"
17 #include <readline/readline.h>
18 #include <readline/history.h>
19 #pragma GCC diagnostic pop
22 * The append_history function only appears in newer versions
23 * of the readline library it appears like. Since we don't
24 * need this just silently ignore the code on these
27 #if !defined HAVE_APPEND_HISTORY
28 #define append_history(A, B)
31 #include <lib/version.h>
38 #include "lib_errors.h"
40 #include "vtysh/vtysh.h"
41 #include "vtysh/vtysh_user.h"
43 /* VTY shell program name. */
47 static uid_t elevuid
, realuid
;
48 static gid_t elevgid
, realgid
;
50 #define VTYSH_CONFIG_NAME "vtysh.conf"
51 #define FRR_CONFIG_NAME "frr.conf"
53 /* Configuration file name and directory. */
54 static char vtysh_config
[MAXPATHLEN
* 3];
55 char frr_config
[MAXPATHLEN
* 3];
56 char vtydir
[MAXPATHLEN
];
57 static char history_file
[MAXPATHLEN
];
59 /* Flag for indicate executing child command. */
62 /* Flag to indicate if in user/unprivileged mode. */
65 /* Master of threads. */
66 struct event_loop
*master
;
71 static void vtysh_rl_callback(char *line_read
)
75 rl_callback_handler_remove();
78 vtysh_loop_exited
= true;
82 /* If the line has any text in it, save it on the history. But only if
83 * last command in history isn't the same one.
87 last
= previous_history();
88 if (!last
|| strcmp(last
->line
, line_read
) != 0) {
89 add_history(line_read
);
90 append_history(1, history_file
);
94 vtysh_execute(line_read
);
96 if (!vtysh_loop_exited
)
97 rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback
);
102 /* SIGTSTP handler. This function care user's ^Z input. */
103 static void sigtstp(int sig
)
105 rl_callback_handler_remove();
107 /* Execute "end" command. */
108 vtysh_execute("end");
110 if (!vtysh_loop_exited
)
111 rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback
);
113 /* Initialize readline. */
116 rl_forced_update_display();
119 /* SIGINT handler. This function care user's ^Z input. */
120 static void sigint(int sig
)
122 /* Check this process is not child process. */
126 rl_forced_update_display();
130 /* Signale wrapper for vtysh. We don't use sigevent because
131 * vtysh doesn't use threads. TODO */
132 static void vtysh_signal_set(int signo
, void (*func
)(int))
134 struct sigaction sig
;
135 struct sigaction osig
;
137 sig
.sa_handler
= func
;
138 sigemptyset(&sig
.sa_mask
);
141 sig
.sa_flags
|= SA_RESTART
;
142 #endif /* SA_RESTART */
144 sigaction(signo
, &sig
, &osig
);
147 /* Initialization of signal handles. */
148 static void vtysh_signal_init(void)
150 vtysh_signal_set(SIGINT
, sigint
);
151 vtysh_signal_set(SIGTSTP
, sigtstp
);
152 vtysh_signal_set(SIGPIPE
, SIG_IGN
);
155 /* Help information display. */
156 static void usage(int status
)
159 fprintf(stderr
, "Try `%s --help' for more information.\n",
162 printf("Usage : %s [OPTION...]\n\n"
163 "Integrated shell for FRR (version " FRR_VERSION
165 "Configured with:\n " FRR_CONFIG_ARGS
167 "-b, --boot Execute boot startup configuration\n"
168 "-c, --command Execute argument as command\n"
169 "-d, --daemon Connect only to the specified daemon\n"
170 "-f, --inputfile Execute commands from specific file and exit\n"
171 "-E, --echo Echo prompt and command in -c mode\n"
172 "-C, --dryrun Check configuration for validity and exit\n"
173 "-m, --markfile Mark input file with context end\n"
174 " --vty_socket Override vty socket path\n"
175 " --config_dir Override config directory path\n"
176 "-N --pathspace Insert prefix into config & socket paths\n"
177 "-u --user Run as an unprivileged user\n"
178 "-w, --writeconfig Write integrated config (frr.conf) and exit\n"
179 "-H, --histfile Override history file\n"
180 "-t, --timestamp Print a timestamp before going to shell or reading the configuration\n"
181 " --no-fork Don't fork clients to handle daemons (slower for large configs)\n"
182 "-h, --help Display this help and exit\n\n"
183 "Note that multiple commands may be executed from the command\n"
184 "line by passing multiple -c args, or by embedding linefeed\n"
185 "characters in one or more of the commands.\n\n"
186 "Report bugs to %s\n",
187 progname
, FRR_BUG_ADDRESS
);
192 /* VTY shell options, we use GNU getopt library. */
193 #define OPTION_VTYSOCK 1000
194 #define OPTION_CONFDIR 1001
195 #define OPTION_NOFORK 1002
196 struct option longopts
[] = {
197 {"boot", no_argument
, NULL
, 'b'},
198 /* For compatibility with older zebra/quagga versions */
199 {"eval", required_argument
, NULL
, 'e'},
200 {"command", required_argument
, NULL
, 'c'},
201 {"daemon", required_argument
, NULL
, 'd'},
202 {"vty_socket", required_argument
, NULL
, OPTION_VTYSOCK
},
203 {"config_dir", required_argument
, NULL
, OPTION_CONFDIR
},
204 {"inputfile", required_argument
, NULL
, 'f'},
205 {"histfile", required_argument
, NULL
, 'H'},
206 {"echo", no_argument
, NULL
, 'E'},
207 {"dryrun", no_argument
, NULL
, 'C'},
208 {"help", no_argument
, NULL
, 'h'},
209 {"noerror", no_argument
, NULL
, 'n'},
210 {"mark", no_argument
, NULL
, 'm'},
211 {"writeconfig", no_argument
, NULL
, 'w'},
212 {"pathspace", required_argument
, NULL
, 'N'},
213 {"user", no_argument
, NULL
, 'u'},
214 {"timestamp", no_argument
, NULL
, 't'},
215 {"no-fork", no_argument
, NULL
, OPTION_NOFORK
},
218 bool vtysh_loop_exited
;
220 static struct event
*vtysh_rl_read_thread
;
222 static void vtysh_rl_read(struct event
*thread
)
224 event_add_read(master
, vtysh_rl_read
, NULL
, STDIN_FILENO
,
225 &vtysh_rl_read_thread
);
226 rl_callback_read_char();
229 /* Read a string, and return a pointer to it. Returns NULL on EOF. */
230 static void vtysh_rl_run(void)
234 master
= event_master_create(NULL
);
236 rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback
);
237 event_add_read(master
, vtysh_rl_read
, NULL
, STDIN_FILENO
,
238 &vtysh_rl_read_thread
);
240 while (!vtysh_loop_exited
&& event_fetch(master
, &thread
))
243 if (!vtysh_loop_exited
)
244 rl_callback_handler_remove();
246 event_master_free(master
);
249 static void log_it(const char *line
)
251 time_t t
= time(NULL
);
253 const char *user
= getenv("USER");
256 localtime_r(&t
, &tmp
);
260 strftime(tod
, sizeof(tod
), "%Y%m%d-%H:%M.%S", &tmp
);
262 fprintf(logfile
, "%s:%s %s\n", tod
, user
, line
);
267 static void vtysh_flock_config(const char *flock_file
)
271 flock_fd
= open(flock_file
, O_RDONLY
, 0644);
273 fprintf(stderr
, "Unable to create lock file: %s, %s\n",
274 flock_file
, safe_strerror(errno
));
278 while (count
< 400 && (flock(flock_fd
, LOCK_EX
| LOCK_NB
) < 0)) {
285 "Flock of %s failed, continuing this may cause issues\n",
289 static void vtysh_unflock_config(void)
291 flock(flock_fd
, LOCK_UN
);
297 if (elevuid
!= realuid
&& seteuid(elevuid
)) {
298 perror("seteuid(on)");
301 if (elevgid
!= realgid
&& setegid(elevgid
)) {
302 perror("setegid(on)");
309 if (elevuid
!= realuid
&& seteuid(realuid
)) {
310 perror("seteuid(off)");
313 if (elevgid
!= realgid
&& setegid(realgid
)) {
314 perror("setegid(off)");
319 /* VTY shell main routine. */
320 int main(int argc
, char **argv
, char **env
)
326 bool ts_flag
= false;
327 bool no_fork
= false;
328 const char *daemon_name
= NULL
;
329 const char *inputfile
= NULL
;
332 struct cmd_rec
*next
;
334 struct cmd_rec
*tail
= NULL
;
335 int echo_command
= 0;
340 char *homedir
= NULL
;
342 char sysconfdir
[MAXPATHLEN
];
343 const char *pathspace_arg
= NULL
;
344 char pathspace
[MAXPATHLEN
] = "";
345 const char *histfile
= NULL
;
346 const char *histfile_env
= getenv("VTYSH_HISTFILE");
348 /* SUID: drop down to calling user & go back up when needed */
355 user_mode
= 0; /* may be set in options processing */
357 /* Preserve name of myself. */
358 progname
= ((p
= strrchr(argv
[0], '/')) ? ++p
: argv
[0]);
360 strlcpy(sysconfdir
, frr_sysconfdir
, sizeof(sysconfdir
));
363 strlcpy(vtydir
, frr_vtydir
, sizeof(vtydir
));
365 /* Option handling. */
367 opt
= getopt_long(argc
, argv
, "be:c:d:nf:H:mEhCwN:ut", longopts
,
382 cr
= XMALLOC(MTYPE_TMP
, sizeof(*cr
));
392 ditch_suid
= 1; /* option disables SUID */
393 strlcpy(vtydir
, optarg
, sizeof(vtydir
));
396 ditch_suid
= 1; /* option disables SUID */
397 snprintf(sysconfdir
, sizeof(sysconfdir
), "%s/", optarg
);
403 if (strchr(optarg
, '/') || strchr(optarg
, '.')) {
405 "slashes or dots are not permitted in the --pathspace option.\n");
408 pathspace_arg
= optarg
;
409 snprintf(pathspace
, sizeof(pathspace
), "%s/", optarg
);
412 daemon_name
= optarg
;
450 /* No need for forks if we're talking to 1 daemon */
459 if (markfile
+ writeconfig
+ dryrun
+ boot_flag
> 1) {
461 "Invalid combination of arguments. Please specify at most one of:\n\t-b, -C, -m, -w\n");
464 if (inputfile
&& (writeconfig
|| boot_flag
)) {
466 "WARNING: Combining the -f option with -b or -w is NOT SUPPORTED since its\nresults are inconsistent!\n");
469 snprintf(vtysh_config
, sizeof(vtysh_config
), "%s%s%s", sysconfdir
,
470 pathspace
, VTYSH_CONFIG_NAME
);
471 snprintf(frr_config
, sizeof(frr_config
), "%s%s%s", sysconfdir
,
472 pathspace
, FRR_CONFIG_NAME
);
475 strlcat(vtydir
, "/", sizeof(vtydir
));
476 strlcat(vtydir
, pathspace_arg
, sizeof(vtydir
));
479 /* Initialize user input buffer. */
482 /* Signal and others. */
485 /* Make vty structure and register commands. */
494 /* Read vtysh configuration file before connecting to daemons.
495 * (file may not be readable to calling user in SUID mode) */
497 vtysh_apply_config(vtysh_config
, dryrun
, false);
500 /* Error code library system */
507 "-f option MUST be specified with -m option\n");
510 return (vtysh_mark_file(inputfile
));
513 /* Start execution only if not in dry-run mode */
514 if (dryrun
&& !cmd
) {
516 ret
= vtysh_apply_config(inputfile
, dryrun
, false);
518 ret
= vtysh_apply_config(frr_config
, dryrun
, false);
524 if (dryrun
&& cmd
&& cmd
->line
) {
526 vtysh_execute("enable");
529 char *cmdnow
= cmd
->line
, *next
;
531 next
= strchr(cmdnow
, '\n');
536 printf("%s%s\n", vtysh_prompt(),
539 ret
= vtysh_execute_no_pager(cmdnow
);
541 && !(ret
== CMD_SUCCESS
542 || ret
== CMD_SUCCESS_DAEMON
543 || ret
== CMD_WARNING
))
545 } while ((cmdnow
= next
) != NULL
);
549 XFREE(MTYPE_TMP
, cr
);
554 /* Ignore error messages */
556 if (freopen("/dev/null", "w", stdout
) == NULL
) {
558 "Exiting: Failed to duplicate stdout with -n option");
563 /* SUID: go back up elevated privs */
566 /* Make sure we pass authentication before proceeding. */
569 /* Do not connect until we have passed authentication. */
570 if (vtysh_connect_all(daemon_name
) <= 0) {
571 fprintf(stderr
, "Exiting: failed to connect to any daemons.\n");
574 "Hint: if this seems wrong, try running me as a privileged user!\n");
581 /* SUID: back down, don't need privs further on */
587 "writeconfig cannot be used when running as an unprivileged user.\n");
593 vtysh_execute("enable");
594 return vtysh_write_config_integrated();
598 inputfile
= frr_config
;
600 if (inputfile
|| boot_flag
) {
601 vtysh_flock_config(inputfile
);
602 ret
= vtysh_apply_config(inputfile
, dryrun
, !no_fork
);
603 vtysh_unflock_config();
612 * Setup history file for use by both -c and regular input
613 * If we can't find the home directory, then don't store
614 * the history information.
615 * VTYSH_HISTFILE is preferred over command line
616 * argument (-H/--histfile).
619 strlcpy(history_file
, histfile_env
, sizeof(history_file
));
620 } else if (histfile
) {
621 strlcpy(history_file
, histfile
, sizeof(history_file
));
623 homedir
= vtysh_get_home();
625 snprintf(history_file
, sizeof(history_file
),
626 "%s/.history_frr", homedir
);
629 if (strlen(history_file
) > 0) {
630 if (read_history(history_file
) != 0) {
633 fp
= open(history_file
, O_CREAT
| O_EXCL
,
638 read_history(history_file
);
642 if (getenv("VTYSH_LOG")) {
643 const char *logpath
= getenv("VTYSH_LOG");
645 logfile
= fopen(logpath
, "a");
647 fprintf(stderr
, "Failed to open logfile (%s): %s\n",
648 logpath
, strerror(errno
));
654 if (cmd
&& cmd
->line
) {
655 /* Enter into enable node. */
657 vtysh_execute("enable");
659 vtysh_add_timestamp
= ts_flag
;
661 while (cmd
!= NULL
) {
664 while ((eol
= strchr(cmd
->line
, '\n')) != NULL
) {
667 add_history(cmd
->line
);
668 append_history(1, history_file
);
671 printf("%s%s\n", vtysh_prompt(),
677 ret
= vtysh_execute_no_pager(cmd
->line
);
679 && !(ret
== CMD_SUCCESS
680 || ret
== CMD_SUCCESS_DAEMON
681 || ret
== CMD_WARNING
))
687 add_history(cmd
->line
);
688 append_history(1, history_file
);
691 printf("%s%s\n", vtysh_prompt(), cmd
->line
);
697 * Parsing logic for regular commands will be different
698 * than for those commands requiring further
699 * processing, such as cli instructions terminating
700 * with question-mark character.
702 if (!vtysh_execute_command_questionmark(cmd
->line
))
705 ret
= vtysh_execute_no_pager(cmd
->line
);
708 && !(ret
== CMD_SUCCESS
|| ret
== CMD_SUCCESS_DAEMON
709 || ret
== CMD_WARNING
))
716 XFREE(MTYPE_TMP
, cr
);
720 history_truncate_file(history_file
, 1000);
724 vtysh_readline_init();
728 /* Enter into enable node. */
730 vtysh_execute("enable");
732 vtysh_add_timestamp
= ts_flag
;
734 /* Main command loop. */
739 history_truncate_file(history_file
, 1000);