]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
718e3744 | 2 | /* Virtual terminal interface shell. |
3 | * Copyright (C) 2000 Kunihiro Ishiguro | |
718e3744 | 4 | */ |
5 | ||
6 | #include <zebra.h> | |
7 | ||
8 | #include <sys/un.h> | |
9 | #include <setjmp.h> | |
718e3744 | 10 | #include <pwd.h> |
e43716f6 DS |
11 | #include <sys/file.h> |
12 | #include <unistd.h> | |
718e3744 | 13 | |
9c149084 DL |
14 | /* readline carries some ancient definitions around */ |
15 | #pragma GCC diagnostic push | |
16 | #pragma GCC diagnostic ignored "-Wstrict-prototypes" | |
718e3744 | 17 | #include <readline/readline.h> |
18 | #include <readline/history.h> | |
9c149084 | 19 | #pragma GCC diagnostic pop |
718e3744 | 20 | |
8860ffdc DS |
21 | /* |
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 | |
25 | * ancient platforms. | |
26 | */ | |
27 | #if !defined HAVE_APPEND_HISTORY | |
28 | #define append_history(A, B) | |
29 | #endif | |
30 | ||
5e4fa164 | 31 | #include <lib/version.h> |
718e3744 | 32 | #include "getopt.h" |
33 | #include "command.h" | |
f366ad31 | 34 | #include "memory.h" |
4201dd11 | 35 | #include "linklist.h" |
eb05883f | 36 | #include "libfrr.h" |
1f9128d6 QY |
37 | #include "ferr.h" |
38 | #include "lib_errors.h" | |
718e3744 | 39 | |
40 | #include "vtysh/vtysh.h" | |
41 | #include "vtysh/vtysh_user.h" | |
b094d260 | 42 | |
718e3744 | 43 | /* VTY shell program name. */ |
44 | char *progname; | |
45 | ||
32f3268f DL |
46 | /* SUID mode */ |
47 | static uid_t elevuid, realuid; | |
48 | static gid_t elevgid, realgid; | |
49 | ||
9b8a8249 DL |
50 | #define VTYSH_CONFIG_NAME "vtysh.conf" |
51 | #define FRR_CONFIG_NAME "frr.conf" | |
52 | ||
67e29abc | 53 | /* Configuration file name and directory. */ |
ff44f570 DS |
54 | static char vtysh_config[MAXPATHLEN * 3]; |
55 | char frr_config[MAXPATHLEN * 3]; | |
9b8a8249 DL |
56 | char vtydir[MAXPATHLEN]; |
57 | static char history_file[MAXPATHLEN]; | |
718e3744 | 58 | |
718e3744 | 59 | /* Flag for indicate executing child command. */ |
60 | int execute_flag = 0; | |
61 | ||
86b28610 | 62 | /* Flag to indicate if in user/unprivileged mode. */ |
186f6af2 | 63 | int user_mode; |
86b28610 | 64 | |
718e3744 | 65 | /* Master of threads. */ |
cd9d0537 | 66 | struct event_loop *master; |
b094d260 | 67 | |
57fb9748 SH |
68 | /* Command logging */ |
69 | FILE *logfile; | |
70 | ||
149a3fff DA |
71 | static void vtysh_rl_callback(char *line_read) |
72 | { | |
73 | HIST_ENTRY *last; | |
74 | ||
75 | rl_callback_handler_remove(); | |
76 | ||
77 | if (!line_read) { | |
78 | vtysh_loop_exited = true; | |
79 | return; | |
80 | } | |
81 | ||
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. | |
84 | */ | |
85 | if (*line_read) { | |
86 | using_history(); | |
87 | last = previous_history(); | |
88 | if (!last || strcmp(last->line, line_read) != 0) { | |
89 | add_history(line_read); | |
90 | append_history(1, history_file); | |
91 | } | |
92 | } | |
93 | ||
94 | vtysh_execute(line_read); | |
95 | ||
96 | if (!vtysh_loop_exited) | |
97 | rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback); | |
5098d577 DS |
98 | |
99 | free(line_read); | |
149a3fff DA |
100 | } |
101 | ||
718e3744 | 102 | /* SIGTSTP handler. This function care user's ^Z input. */ |
d62a17ae | 103 | static void sigtstp(int sig) |
718e3744 | 104 | { |
149a3fff DA |
105 | rl_callback_handler_remove(); |
106 | ||
d62a17ae | 107 | /* Execute "end" command. */ |
108 | vtysh_execute("end"); | |
718e3744 | 109 | |
149a3fff DA |
110 | if (!vtysh_loop_exited) |
111 | rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback); | |
112 | ||
d62a17ae | 113 | /* Initialize readline. */ |
114 | rl_initialize(); | |
115 | printf("\n"); | |
149a3fff | 116 | rl_forced_update_display(); |
718e3744 | 117 | } |
118 | ||
119 | /* SIGINT handler. This function care user's ^Z input. */ | |
d62a17ae | 120 | static void sigint(int sig) |
718e3744 | 121 | { |
d62a17ae | 122 | /* Check this process is not child process. */ |
123 | if (!execute_flag) { | |
124 | rl_initialize(); | |
125 | printf("\n"); | |
126 | rl_forced_update_display(); | |
127 | } | |
718e3744 | 128 | } |
129 | ||
e42f5a37 | 130 | /* Signale wrapper for vtysh. We don't use sigevent because |
131 | * vtysh doesn't use threads. TODO */ | |
d62a17ae | 132 | static void vtysh_signal_set(int signo, void (*func)(int)) |
718e3744 | 133 | { |
d62a17ae | 134 | struct sigaction sig; |
135 | struct sigaction osig; | |
718e3744 | 136 | |
d62a17ae | 137 | sig.sa_handler = func; |
138 | sigemptyset(&sig.sa_mask); | |
139 | sig.sa_flags = 0; | |
718e3744 | 140 | #ifdef SA_RESTART |
d62a17ae | 141 | sig.sa_flags |= SA_RESTART; |
718e3744 | 142 | #endif /* SA_RESTART */ |
143 | ||
d62a17ae | 144 | sigaction(signo, &sig, &osig); |
718e3744 | 145 | } |
146 | ||
147 | /* Initialization of signal handles. */ | |
d62a17ae | 148 | static void vtysh_signal_init(void) |
718e3744 | 149 | { |
d62a17ae | 150 | vtysh_signal_set(SIGINT, sigint); |
151 | vtysh_signal_set(SIGTSTP, sigtstp); | |
152 | vtysh_signal_set(SIGPIPE, SIG_IGN); | |
718e3744 | 153 | } |
b094d260 | 154 | |
718e3744 | 155 | /* Help information display. */ |
d62a17ae | 156 | static void usage(int status) |
718e3744 | 157 | { |
d62a17ae | 158 | if (status != 0) |
159 | fprintf(stderr, "Try `%s --help' for more information.\n", | |
160 | progname); | |
161 | else | |
162 | printf("Usage : %s [OPTION...]\n\n" | |
69d4cc70 DA |
163 | "Integrated shell for FRR (version " FRR_VERSION |
164 | "). \n" | |
165 | "Configured with:\n " FRR_CONFIG_ARGS | |
166 | "\n\n" | |
d62a17ae | 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" | |
8bd33a03 | 176 | "-N --pathspace Insert prefix into config & socket paths\n" |
86b28610 | 177 | "-u --user Run as an unprivileged user\n" |
d62a17ae | 178 | "-w, --writeconfig Write integrated config (frr.conf) and exit\n" |
69d4cc70 | 179 | "-H, --histfile Override history file\n" |
916af655 | 180 | "-t, --timestamp Print a timestamp before going to shell or reading the configuration\n" |
f887c00a | 181 | " --no-fork Don't fork clients to handle daemons (slower for large configs)\n" |
d62a17ae | 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); | |
188 | ||
189 | exit(status); | |
718e3744 | 190 | } |
191 | ||
192 | /* VTY shell options, we use GNU getopt library. */ | |
87d79a9f | 193 | #define OPTION_VTYSOCK 1000 |
ce2e9ec3 | 194 | #define OPTION_CONFDIR 1001 |
f887c00a | 195 | #define OPTION_NOFORK 1002 |
d62a17ae | 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'}, | |
69d4cc70 | 205 | {"histfile", required_argument, NULL, 'H'}, |
d62a17ae | 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'}, | |
bd29d463 | 212 | {"pathspace", required_argument, NULL, 'N'}, |
86b28610 | 213 | {"user", no_argument, NULL, 'u'}, |
744bc17d | 214 | {"timestamp", no_argument, NULL, 't'}, |
f887c00a | 215 | {"no-fork", no_argument, NULL, OPTION_NOFORK}, |
d62a17ae | 216 | {0}}; |
b094d260 | 217 | |
4c92dd90 DL |
218 | bool vtysh_loop_exited; |
219 | ||
e6685141 | 220 | static struct event *vtysh_rl_read_thread; |
4c92dd90 | 221 | |
e6685141 | 222 | static void vtysh_rl_read(struct event *thread) |
4c92dd90 | 223 | { |
907a2395 DS |
224 | event_add_read(master, vtysh_rl_read, NULL, STDIN_FILENO, |
225 | &vtysh_rl_read_thread); | |
4c92dd90 DL |
226 | rl_callback_read_char(); |
227 | } | |
228 | ||
229 | /* Read a string, and return a pointer to it. Returns NULL on EOF. */ | |
230 | static void vtysh_rl_run(void) | |
231 | { | |
e6685141 | 232 | struct event thread; |
4c92dd90 | 233 | |
ce50d11c | 234 | master = event_master_create(NULL); |
4c92dd90 DL |
235 | |
236 | rl_callback_handler_install(vtysh_prompt(), vtysh_rl_callback); | |
907a2395 DS |
237 | event_add_read(master, vtysh_rl_read, NULL, STDIN_FILENO, |
238 | &vtysh_rl_read_thread); | |
4c92dd90 | 239 | |
de2754be DS |
240 | while (!vtysh_loop_exited && event_fetch(master, &thread)) |
241 | event_call(&thread); | |
4c92dd90 DL |
242 | |
243 | if (!vtysh_loop_exited) | |
244 | rl_callback_handler_remove(); | |
245 | ||
ce50d11c | 246 | event_master_free(master); |
718e3744 | 247 | } |
b094d260 | 248 | |
57fb9748 SH |
249 | static void log_it(const char *line) |
250 | { | |
d62a17ae | 251 | time_t t = time(NULL); |
a2700b50 | 252 | struct tm tmp; |
d62a17ae | 253 | const char *user = getenv("USER"); |
254 | char tod[64]; | |
57fb9748 | 255 | |
a2700b50 | 256 | localtime_r(&t, &tmp); |
d62a17ae | 257 | if (!user) |
258 | user = "boot"; | |
f03db93b | 259 | |
0d6f7fd6 | 260 | strftime(tod, sizeof(tod), "%Y%m%d-%H:%M.%S", &tmp); |
d62a17ae | 261 | |
262 | fprintf(logfile, "%s:%s %s\n", tod, user, line); | |
57fb9748 SH |
263 | } |
264 | ||
e43716f6 DS |
265 | static int flock_fd; |
266 | ||
d62a17ae | 267 | static void vtysh_flock_config(const char *flock_file) |
e43716f6 | 268 | { |
d62a17ae | 269 | int count = 0; |
270 | ||
271 | flock_fd = open(flock_file, O_RDONLY, 0644); | |
272 | if (flock_fd < 0) { | |
273 | fprintf(stderr, "Unable to create lock file: %s, %s\n", | |
274 | flock_file, safe_strerror(errno)); | |
275 | return; | |
276 | } | |
277 | ||
278 | while (count < 400 && (flock(flock_fd, LOCK_EX | LOCK_NB) < 0)) { | |
279 | count++; | |
280 | usleep(500000); | |
281 | } | |
282 | ||
283 | if (count >= 400) | |
284 | fprintf(stderr, | |
285 | "Flock of %s failed, continuing this may cause issues\n", | |
286 | flock_file); | |
e43716f6 DS |
287 | } |
288 | ||
d62a17ae | 289 | static void vtysh_unflock_config(void) |
e43716f6 | 290 | { |
d62a17ae | 291 | flock(flock_fd, LOCK_UN); |
292 | close(flock_fd); | |
e43716f6 DS |
293 | } |
294 | ||
32f3268f DL |
295 | void suid_on(void) |
296 | { | |
297 | if (elevuid != realuid && seteuid(elevuid)) { | |
298 | perror("seteuid(on)"); | |
299 | exit(1); | |
300 | } | |
301 | if (elevgid != realgid && setegid(elevgid)) { | |
302 | perror("setegid(on)"); | |
303 | exit(1); | |
304 | } | |
305 | } | |
306 | ||
307 | void suid_off(void) | |
308 | { | |
309 | if (elevuid != realuid && seteuid(realuid)) { | |
310 | perror("seteuid(off)"); | |
311 | exit(1); | |
312 | } | |
313 | if (elevgid != realgid && setegid(realgid)) { | |
314 | perror("setegid(off)"); | |
315 | exit(1); | |
316 | } | |
317 | } | |
318 | ||
718e3744 | 319 | /* VTY shell main routine. */ |
d62a17ae | 320 | int main(int argc, char **argv, char **env) |
718e3744 | 321 | { |
d62a17ae | 322 | char *p; |
323 | int opt; | |
324 | int dryrun = 0; | |
325 | int boot_flag = 0; | |
744bc17d | 326 | bool ts_flag = false; |
f887c00a | 327 | bool no_fork = false; |
d62a17ae | 328 | const char *daemon_name = NULL; |
329 | const char *inputfile = NULL; | |
d62a17ae | 330 | struct cmd_rec { |
331 | char *line; | |
332 | struct cmd_rec *next; | |
333 | } *cmd = NULL; | |
334 | struct cmd_rec *tail = NULL; | |
335 | int echo_command = 0; | |
336 | int no_error = 0; | |
337 | int markfile = 0; | |
338 | int writeconfig = 0; | |
339 | int ret = 0; | |
340 | char *homedir = NULL; | |
32f3268f | 341 | int ditch_suid = 0; |
9b8a8249 | 342 | char sysconfdir[MAXPATHLEN]; |
1dede1f8 | 343 | const char *pathspace_arg = NULL; |
8bd33a03 | 344 | char pathspace[MAXPATHLEN] = ""; |
69d4cc70 | 345 | const char *histfile = NULL; |
99a9f25c | 346 | const char *histfile_env = getenv("VTYSH_HISTFILE"); |
d62a17ae | 347 | |
32f3268f DL |
348 | /* SUID: drop down to calling user & go back up when needed */ |
349 | elevuid = geteuid(); | |
350 | elevgid = getegid(); | |
351 | realuid = getuid(); | |
352 | realgid = getgid(); | |
353 | suid_off(); | |
d62a17ae | 354 | |
186f6af2 LB |
355 | user_mode = 0; /* may be set in options processing */ |
356 | ||
d62a17ae | 357 | /* Preserve name of myself. */ |
358 | progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); | |
359 | ||
9b8a8249 | 360 | strlcpy(sysconfdir, frr_sysconfdir, sizeof(sysconfdir)); |
43e587c1 DS |
361 | |
362 | frr_init_vtydir(); | |
9b8a8249 DL |
363 | strlcpy(vtydir, frr_vtydir, sizeof(vtydir)); |
364 | ||
d62a17ae | 365 | /* Option handling. */ |
366 | while (1) { | |
744bc17d | 367 | opt = getopt_long(argc, argv, "be:c:d:nf:H:mEhCwN:ut", longopts, |
69d4cc70 | 368 | 0); |
d62a17ae | 369 | |
370 | if (opt == EOF) | |
371 | break; | |
372 | ||
373 | switch (opt) { | |
374 | case 0: | |
375 | break; | |
376 | case 'b': | |
377 | boot_flag = 1; | |
378 | break; | |
379 | case 'e': | |
380 | case 'c': { | |
381 | struct cmd_rec *cr; | |
382 | cr = XMALLOC(MTYPE_TMP, sizeof(*cr)); | |
383 | cr->line = optarg; | |
384 | cr->next = NULL; | |
385 | if (tail) | |
386 | tail->next = cr; | |
387 | else | |
388 | cmd = cr; | |
389 | tail = cr; | |
390 | } break; | |
391 | case OPTION_VTYSOCK: | |
32f3268f | 392 | ditch_suid = 1; /* option disables SUID */ |
9b8a8249 | 393 | strlcpy(vtydir, optarg, sizeof(vtydir)); |
d62a17ae | 394 | break; |
395 | case OPTION_CONFDIR: | |
32f3268f | 396 | ditch_suid = 1; /* option disables SUID */ |
45d00587 | 397 | snprintf(sysconfdir, sizeof(sysconfdir), "%s/", optarg); |
d62a17ae | 398 | break; |
f887c00a QY |
399 | case OPTION_NOFORK: |
400 | no_fork = true; | |
401 | break; | |
8bd33a03 DL |
402 | case 'N': |
403 | if (strchr(optarg, '/') || strchr(optarg, '.')) { | |
404 | fprintf(stderr, | |
405 | "slashes or dots are not permitted in the --pathspace option.\n"); | |
406 | exit(1); | |
407 | } | |
1dede1f8 CF |
408 | pathspace_arg = optarg; |
409 | snprintf(pathspace, sizeof(pathspace), "%s/", optarg); | |
8bd33a03 | 410 | break; |
d62a17ae | 411 | case 'd': |
412 | daemon_name = optarg; | |
413 | break; | |
414 | case 'f': | |
415 | inputfile = optarg; | |
416 | break; | |
417 | case 'm': | |
418 | markfile = 1; | |
419 | break; | |
420 | case 'n': | |
421 | no_error = 1; | |
422 | break; | |
423 | case 'E': | |
424 | echo_command = 1; | |
425 | break; | |
426 | case 'C': | |
427 | dryrun = 1; | |
428 | break; | |
86b28610 LB |
429 | case 'u': |
430 | user_mode = 1; | |
431 | break; | |
744bc17d CH |
432 | case 't': |
433 | ts_flag = true; | |
434 | break; | |
d62a17ae | 435 | case 'w': |
436 | writeconfig = 1; | |
437 | break; | |
438 | case 'h': | |
439 | usage(0); | |
440 | break; | |
69d4cc70 DA |
441 | case 'H': |
442 | histfile = optarg; | |
443 | break; | |
d62a17ae | 444 | default: |
445 | usage(1); | |
446 | break; | |
447 | } | |
448 | } | |
449 | ||
f887c00a QY |
450 | /* No need for forks if we're talking to 1 daemon */ |
451 | if (daemon_name) | |
452 | no_fork = true; | |
453 | ||
32f3268f DL |
454 | if (ditch_suid) { |
455 | elevuid = realuid; | |
456 | elevgid = realgid; | |
457 | } | |
458 | ||
d62a17ae | 459 | if (markfile + writeconfig + dryrun + boot_flag > 1) { |
460 | fprintf(stderr, | |
3efd0893 | 461 | "Invalid combination of arguments. Please specify at most one of:\n\t-b, -C, -m, -w\n"); |
d62a17ae | 462 | return 1; |
463 | } | |
464 | if (inputfile && (writeconfig || boot_flag)) { | |
465 | fprintf(stderr, | |
0b7d911e | 466 | "WARNING: Combining the -f option with -b or -w is NOT SUPPORTED since its\nresults are inconsistent!\n"); |
d62a17ae | 467 | } |
468 | ||
e82314b1 | 469 | snprintf(vtysh_config, sizeof(vtysh_config), "%s%s%s", sysconfdir, |
60466a63 | 470 | pathspace, VTYSH_CONFIG_NAME); |
e82314b1 | 471 | snprintf(frr_config, sizeof(frr_config), "%s%s%s", sysconfdir, |
60466a63 | 472 | pathspace, FRR_CONFIG_NAME); |
1dede1f8 CF |
473 | |
474 | if (pathspace_arg) { | |
475 | strlcat(vtydir, "/", sizeof(vtydir)); | |
476 | strlcat(vtydir, pathspace_arg, sizeof(vtydir)); | |
477 | } | |
9b8a8249 | 478 | |
d62a17ae | 479 | /* Initialize user input buffer. */ |
d62a17ae | 480 | setlinebuf(stdout); |
481 | ||
482 | /* Signal and others. */ | |
483 | vtysh_signal_init(); | |
484 | ||
485 | /* Make vty structure and register commands. */ | |
486 | vtysh_init_vty(); | |
487 | vtysh_init_cmd(); | |
488 | vtysh_user_init(); | |
489 | vtysh_config_init(); | |
490 | ||
491 | vty_init_vtysh(); | |
492 | ||
86b28610 LB |
493 | if (!user_mode) { |
494 | /* Read vtysh configuration file before connecting to daemons. | |
495 | * (file may not be readable to calling user in SUID mode) */ | |
496 | suid_on(); | |
ac768090 | 497 | vtysh_apply_config(vtysh_config, dryrun, false); |
86b28610 LB |
498 | suid_off(); |
499 | } | |
1f9128d6 QY |
500 | /* Error code library system */ |
501 | log_ref_init(); | |
502 | lib_error_init(); | |
d62a17ae | 503 | |
504 | if (markfile) { | |
505 | if (!inputfile) { | |
506 | fprintf(stderr, | |
507 | "-f option MUST be specified with -m option\n"); | |
95f7965d | 508 | return 1; |
d62a17ae | 509 | } |
510 | return (vtysh_mark_file(inputfile)); | |
511 | } | |
512 | ||
513 | /* Start execution only if not in dry-run mode */ | |
514 | if (dryrun && !cmd) { | |
515 | if (inputfile) { | |
ac768090 | 516 | ret = vtysh_apply_config(inputfile, dryrun, false); |
d62a17ae | 517 | } else { |
ac768090 | 518 | ret = vtysh_apply_config(frr_config, dryrun, false); |
d62a17ae | 519 | } |
520 | ||
521 | exit(ret); | |
718e3744 | 522 | } |
d62a17ae | 523 | |
47402c0f | 524 | if (dryrun && cmd && cmd->line) { |
f205fcdb LB |
525 | if (!user_mode) |
526 | vtysh_execute("enable"); | |
d62a17ae | 527 | while (cmd) { |
528 | struct cmd_rec *cr; | |
529 | char *cmdnow = cmd->line, *next; | |
530 | do { | |
531 | next = strchr(cmdnow, '\n'); | |
532 | if (next) | |
533 | *next++ = '\0'; | |
534 | ||
535 | if (echo_command) | |
536 | printf("%s%s\n", vtysh_prompt(), | |
537 | cmdnow); | |
538 | ||
539 | ret = vtysh_execute_no_pager(cmdnow); | |
540 | if (!no_error | |
541 | && !(ret == CMD_SUCCESS | |
542 | || ret == CMD_SUCCESS_DAEMON | |
543 | || ret == CMD_WARNING)) | |
544 | exit(1); | |
545 | } while ((cmdnow = next) != NULL); | |
546 | ||
547 | cr = cmd; | |
548 | cmd = cmd->next; | |
549 | XFREE(MTYPE_TMP, cr); | |
550 | } | |
551 | exit(ret); | |
0846286b | 552 | } |
d62a17ae | 553 | |
554 | /* Ignore error messages */ | |
555 | if (no_error) { | |
556 | if (freopen("/dev/null", "w", stdout) == NULL) { | |
557 | fprintf(stderr, | |
558 | "Exiting: Failed to duplicate stdout with -n option"); | |
559 | exit(1); | |
560 | } | |
0846286b | 561 | } |
d62a17ae | 562 | |
32f3268f DL |
563 | /* SUID: go back up elevated privs */ |
564 | suid_on(); | |
565 | ||
d62a17ae | 566 | /* Make sure we pass authentication before proceeding. */ |
567 | vtysh_auth(); | |
568 | ||
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"); | |
14275475 QY |
572 | if (geteuid() != 0) |
573 | fprintf(stderr, | |
574 | "Hint: if this seems wrong, try running me as a privileged user!\n"); | |
d62a17ae | 575 | if (no_error) |
576 | exit(0); | |
577 | else | |
578 | exit(1); | |
3221dca8 | 579 | } |
cd272640 | 580 | |
32f3268f DL |
581 | /* SUID: back down, don't need privs further on */ |
582 | suid_off(); | |
583 | ||
d62a17ae | 584 | if (writeconfig) { |
f205fcdb LB |
585 | if (user_mode) { |
586 | fprintf(stderr, | |
587 | "writeconfig cannot be used when running as an unprivileged user.\n"); | |
588 | if (no_error) | |
589 | exit(0); | |
590 | else | |
591 | exit(1); | |
592 | } | |
d62a17ae | 593 | vtysh_execute("enable"); |
594 | return vtysh_write_config_integrated(); | |
7a49a5b5 | 595 | } |
d62a17ae | 596 | |
ac768090 QY |
597 | if (boot_flag) |
598 | inputfile = frr_config; | |
599 | ||
600 | if (inputfile || boot_flag) { | |
d62a17ae | 601 | vtysh_flock_config(inputfile); |
ac768090 | 602 | ret = vtysh_apply_config(inputfile, dryrun, !no_fork); |
d62a17ae | 603 | vtysh_unflock_config(); |
ac768090 QY |
604 | |
605 | if (no_error) | |
606 | ret = 0; | |
607 | ||
d62a17ae | 608 | exit(ret); |
fba55c8a | 609 | } |
d62a17ae | 610 | |
611 | /* | |
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 | |
69d4cc70 | 614 | * the history information. |
11743d10 | 615 | * VTYSH_HISTFILE is preferred over command line |
69d4cc70 | 616 | * argument (-H/--histfile). |
d62a17ae | 617 | */ |
99a9f25c DS |
618 | if (histfile_env) { |
619 | strlcpy(history_file, histfile_env, sizeof(history_file)); | |
69d4cc70 DA |
620 | } else if (histfile) { |
621 | strlcpy(history_file, histfile, sizeof(history_file)); | |
622 | } else { | |
623 | homedir = vtysh_get_home(); | |
624 | if (homedir) | |
625 | snprintf(history_file, sizeof(history_file), | |
626 | "%s/.history_frr", homedir); | |
627 | } | |
628 | ||
629 | if (strlen(history_file) > 0) { | |
d62a17ae | 630 | if (read_history(history_file) != 0) { |
631 | int fp; | |
632 | ||
633 | fp = open(history_file, O_CREAT | O_EXCL, | |
634 | S_IRUSR | S_IWUSR); | |
cbb65f5e | 635 | if (fp != -1) |
d62a17ae | 636 | close(fp); |
637 | ||
638 | read_history(history_file); | |
639 | } | |
640 | } | |
641 | ||
32f3268f DL |
642 | if (getenv("VTYSH_LOG")) { |
643 | const char *logpath = getenv("VTYSH_LOG"); | |
644 | ||
645 | logfile = fopen(logpath, "a"); | |
646 | if (!logfile) { | |
647 | fprintf(stderr, "Failed to open logfile (%s): %s\n", | |
648 | logpath, strerror(errno)); | |
649 | exit(1); | |
650 | } | |
651 | } | |
652 | ||
d62a17ae | 653 | /* If eval mode. */ |
47402c0f | 654 | if (cmd && cmd->line) { |
d62a17ae | 655 | /* Enter into enable node. */ |
f205fcdb LB |
656 | if (!user_mode) |
657 | vtysh_execute("enable"); | |
d62a17ae | 658 | |
744bc17d CH |
659 | vtysh_add_timestamp = ts_flag; |
660 | ||
d62a17ae | 661 | while (cmd != NULL) { |
d62a17ae | 662 | char *eol; |
663 | ||
664 | while ((eol = strchr(cmd->line, '\n')) != NULL) { | |
665 | *eol = '\0'; | |
666 | ||
667 | add_history(cmd->line); | |
668 | append_history(1, history_file); | |
669 | ||
670 | if (echo_command) | |
671 | printf("%s%s\n", vtysh_prompt(), | |
672 | cmd->line); | |
673 | ||
674 | if (logfile) | |
675 | log_it(cmd->line); | |
676 | ||
677 | ret = vtysh_execute_no_pager(cmd->line); | |
678 | if (!no_error | |
679 | && !(ret == CMD_SUCCESS | |
680 | || ret == CMD_SUCCESS_DAEMON | |
681 | || ret == CMD_WARNING)) | |
682 | exit(1); | |
683 | ||
684 | cmd->line = eol + 1; | |
685 | } | |
686 | ||
687 | add_history(cmd->line); | |
688 | append_history(1, history_file); | |
689 | ||
690 | if (echo_command) | |
691 | printf("%s%s\n", vtysh_prompt(), cmd->line); | |
692 | ||
693 | if (logfile) | |
694 | log_it(cmd->line); | |
695 | ||
a4364a44 | 696 | /* |
76015847 QY |
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. | |
a4364a44 RM |
701 | */ |
702 | if (!vtysh_execute_command_questionmark(cmd->line)) | |
703 | ret = CMD_SUCCESS; | |
704 | else | |
705 | ret = vtysh_execute_no_pager(cmd->line); | |
706 | ||
d62a17ae | 707 | if (!no_error |
708 | && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON | |
709 | || ret == CMD_WARNING)) | |
710 | exit(1); | |
711 | ||
712 | { | |
713 | struct cmd_rec *cr; | |
714 | cr = cmd; | |
715 | cmd = cmd->next; | |
716 | XFREE(MTYPE_TMP, cr); | |
717 | } | |
718 | } | |
719 | ||
720 | history_truncate_file(history_file, 1000); | |
721 | exit(0); | |
722 | } | |
723 | ||
d62a17ae | 724 | vtysh_readline_init(); |
718e3744 | 725 | |
d62a17ae | 726 | vty_hello(vty); |
718e3744 | 727 | |
d62a17ae | 728 | /* Enter into enable node. */ |
f205fcdb LB |
729 | if (!user_mode) |
730 | vtysh_execute("enable"); | |
e7168df4 | 731 | |
744bc17d CH |
732 | vtysh_add_timestamp = ts_flag; |
733 | ||
d62a17ae | 734 | /* Main command loop. */ |
4c92dd90 | 735 | vtysh_rl_run(); |
718e3744 | 736 | |
193a5a95 QY |
737 | vtysh_uninit(); |
738 | ||
d62a17ae | 739 | history_truncate_file(history_file, 1000); |
740 | printf("\n"); | |
718e3744 | 741 | |
d62a17ae | 742 | /* Rest in peace. */ |
743 | exit(0); | |
718e3744 | 744 | } |