]> git.proxmox.com Git - mirror_frr.git/blame - vtysh/vtysh_main.c
vtysh: add -u/--user flag to run commands without enable
[mirror_frr.git] / vtysh / vtysh_main.c
CommitLineData
718e3744 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 *
896014f4
DL
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
718e3744 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>
e43716f6
DS
27#include <sys/file.h>
28#include <unistd.h>
718e3744 29
30#include <readline/readline.h>
31#include <readline/history.h>
32
5e4fa164 33#include <lib/version.h>
718e3744 34#include "getopt.h"
35#include "command.h"
f366ad31 36#include "memory.h"
4201dd11 37#include "linklist.h"
fc7948fa 38#include "memory_vty.h"
eb05883f 39#include "libfrr.h"
718e3744 40
41#include "vtysh/vtysh.h"
42#include "vtysh/vtysh_user.h"
b094d260 43
718e3744 44/* VTY shell program name. */
45char *progname;
46
32f3268f
DL
47/* SUID mode */
48static uid_t elevuid, realuid;
49static gid_t elevgid, realgid;
50
9b8a8249
DL
51#define VTYSH_CONFIG_NAME "vtysh.conf"
52#define FRR_CONFIG_NAME "frr.conf"
53
67e29abc 54/* Configuration file name and directory. */
9b8a8249
DL
55static char vtysh_config[MAXPATHLEN];
56char frr_config[MAXPATHLEN];
57char vtydir[MAXPATHLEN];
58static char history_file[MAXPATHLEN];
718e3744 59
718e3744 60/* Flag for indicate executing child command. */
61int execute_flag = 0;
62
86b28610
LB
63/* Flag to indicate if in user/unprivileged mode. */
64int user_mode = 0;
65
718e3744 66/* For sigsetjmp() & siglongjmp(). */
67static sigjmp_buf jmpbuf;
68
69/* Flag for avoid recursive siglongjmp() call. */
70static int jmpflag = 0;
71
72/* A static variable for holding the line. */
73static char *line_read;
74
75/* Master of threads. */
76struct thread_master *master;
b094d260 77
57fb9748
SH
78/* Command logging */
79FILE *logfile;
80
718e3744 81/* SIGTSTP handler. This function care user's ^Z input. */
d62a17ae 82static void sigtstp(int sig)
718e3744 83{
d62a17ae 84 /* Execute "end" command. */
85 vtysh_execute("end");
718e3744 86
d62a17ae 87 /* Initialize readline. */
88 rl_initialize();
89 printf("\n");
718e3744 90
d62a17ae 91 /* Check jmpflag for duplicate siglongjmp(). */
92 if (!jmpflag)
93 return;
718e3744 94
d62a17ae 95 jmpflag = 0;
96
97 /* Back to main command loop. */
98 siglongjmp(jmpbuf, 1);
718e3744 99}
100
101/* SIGINT handler. This function care user's ^Z input. */
d62a17ae 102static void sigint(int sig)
718e3744 103{
d62a17ae 104 /* Check this process is not child process. */
105 if (!execute_flag) {
106 rl_initialize();
107 printf("\n");
108 rl_forced_update_display();
109 }
718e3744 110}
111
e42f5a37 112/* Signale wrapper for vtysh. We don't use sigevent because
113 * vtysh doesn't use threads. TODO */
d62a17ae 114static void vtysh_signal_set(int signo, void (*func)(int))
718e3744 115{
d62a17ae 116 struct sigaction sig;
117 struct sigaction osig;
718e3744 118
d62a17ae 119 sig.sa_handler = func;
120 sigemptyset(&sig.sa_mask);
121 sig.sa_flags = 0;
718e3744 122#ifdef SA_RESTART
d62a17ae 123 sig.sa_flags |= SA_RESTART;
718e3744 124#endif /* SA_RESTART */
125
d62a17ae 126 sigaction(signo, &sig, &osig);
718e3744 127}
128
129/* Initialization of signal handles. */
d62a17ae 130static void vtysh_signal_init(void)
718e3744 131{
d62a17ae 132 vtysh_signal_set(SIGINT, sigint);
133 vtysh_signal_set(SIGTSTP, sigtstp);
134 vtysh_signal_set(SIGPIPE, SIG_IGN);
718e3744 135}
b094d260 136
718e3744 137/* Help information display. */
d62a17ae 138static void usage(int status)
718e3744 139{
d62a17ae 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"
8bd33a03 155 "-N --pathspace Insert prefix into config & socket paths\n"
86b28610 156 "-u --user Run as an unprivileged user\n"
d62a17ae 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);
718e3744 166}
167
168/* VTY shell options, we use GNU getopt library. */
87d79a9f 169#define OPTION_VTYSOCK 1000
ce2e9ec3 170#define OPTION_CONFDIR 1001
d62a17ae 171struct 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'},
bd29d463 186 {"pathspace", required_argument, NULL, 'N'},
86b28610 187 {"user", no_argument, NULL, 'u'},
d62a17ae 188 {0}};
b094d260 189
718e3744 190/* Read a string, and return a pointer to it. Returns NULL on EOF. */
d62a17ae 191static char *vtysh_rl_gets(void)
718e3744 192{
d62a17ae 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);
718e3744 216}
b094d260 217
57fb9748
SH
218static void log_it(const char *line)
219{
d62a17ae 220 time_t t = time(NULL);
221 struct tm *tmp = localtime(&t);
222 const char *user = getenv("USER");
223 char tod[64];
57fb9748 224
d62a17ae 225 if (!user)
226 user = "boot";
f03db93b 227
d62a17ae 228 strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp);
229
230 fprintf(logfile, "%s:%s %s\n", tod, user, line);
57fb9748
SH
231}
232
e43716f6
DS
233static int flock_fd;
234
d62a17ae 235static void vtysh_flock_config(const char *flock_file)
e43716f6 236{
d62a17ae 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);
e43716f6
DS
255}
256
d62a17ae 257static void vtysh_unflock_config(void)
e43716f6 258{
d62a17ae 259 flock(flock_fd, LOCK_UN);
260 close(flock_fd);
e43716f6
DS
261}
262
32f3268f
DL
263void 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
275void 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
718e3744 287/* VTY shell main routine. */
d62a17ae 288int main(int argc, char **argv, char **env)
718e3744 289{
d62a17ae 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;
d62a17ae 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;
32f3268f 307 int ditch_suid = 0;
9b8a8249 308 char sysconfdir[MAXPATHLEN];
8bd33a03 309 char pathspace[MAXPATHLEN] = "";
d62a17ae 310
32f3268f
DL
311 /* SUID: drop down to calling user & go back up when needed */
312 elevuid = geteuid();
313 elevgid = getegid();
314 realuid = getuid();
315 realgid = getgid();
316 suid_off();
d62a17ae 317
318 /* Preserve name of myself. */
319 progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
320
9b8a8249
DL
321 strlcpy(sysconfdir, frr_sysconfdir, sizeof(sysconfdir));
322 strlcpy(vtydir, frr_vtydir, sizeof(vtydir));
323
d62a17ae 324 /* Option handling. */
325 while (1) {
86b28610 326 opt = getopt_long(argc, argv, "be:c:d:nf:mEhCwN:u", longopts, 0);
d62a17ae 327
328 if (opt == EOF)
329 break;
330
331 switch (opt) {
332 case 0:
333 break;
334 case 'b':
335 boot_flag = 1;
336 break;
337 case 'e':
338 case 'c': {
339 struct cmd_rec *cr;
340 cr = XMALLOC(MTYPE_TMP, sizeof(*cr));
341 cr->line = optarg;
342 cr->next = NULL;
343 if (tail)
344 tail->next = cr;
345 else
346 cmd = cr;
347 tail = cr;
348 } break;
349 case OPTION_VTYSOCK:
32f3268f 350 ditch_suid = 1; /* option disables SUID */
9b8a8249 351 strlcpy(vtydir, optarg, sizeof(vtydir));
d62a17ae 352 break;
353 case OPTION_CONFDIR:
32f3268f 354 ditch_suid = 1; /* option disables SUID */
9b8a8249 355 strlcpy(sysconfdir, optarg, sizeof(sysconfdir));
d62a17ae 356 break;
8bd33a03
DL
357 case 'N':
358 if (strchr(optarg, '/') || strchr(optarg, '.')) {
359 fprintf(stderr,
360 "slashes or dots are not permitted in the --pathspace option.\n");
361 exit(1);
362 }
363 snprintf(pathspace, sizeof(pathspace), "/%s", optarg);
364 break;
d62a17ae 365 case 'd':
366 daemon_name = optarg;
367 break;
368 case 'f':
369 inputfile = optarg;
370 break;
371 case 'm':
372 markfile = 1;
373 break;
374 case 'n':
375 no_error = 1;
376 break;
377 case 'E':
378 echo_command = 1;
379 break;
380 case 'C':
381 dryrun = 1;
382 break;
86b28610
LB
383 case 'u':
384 user_mode = 1;
385 break;
d62a17ae 386 case 'w':
387 writeconfig = 1;
388 break;
389 case 'h':
390 usage(0);
391 break;
392 default:
393 usage(1);
394 break;
395 }
396 }
397
32f3268f
DL
398 if (ditch_suid) {
399 elevuid = realuid;
400 elevgid = realgid;
401 }
402
d62a17ae 403 if (markfile + writeconfig + dryrun + boot_flag > 1) {
404 fprintf(stderr,
405 "Invalid combination of arguments. Please specify at "
406 "most one of:\n\t-b, -C, -m, -w\n");
407 return 1;
408 }
409 if (inputfile && (writeconfig || boot_flag)) {
410 fprintf(stderr,
411 "WARNING: Combinining the -f option with -b or -w is "
412 "NOT SUPPORTED since its\nresults are inconsistent!\n");
413 }
414
e82314b1 415 snprintf(vtysh_config, sizeof(vtysh_config), "%s%s%s", sysconfdir,
60466a63 416 pathspace, VTYSH_CONFIG_NAME);
e82314b1 417 snprintf(frr_config, sizeof(frr_config), "%s%s%s", sysconfdir,
60466a63 418 pathspace, FRR_CONFIG_NAME);
8bd33a03 419 strlcat(vtydir, pathspace, sizeof(vtydir));
9b8a8249 420
d62a17ae 421 /* Initialize user input buffer. */
422 line_read = NULL;
423 setlinebuf(stdout);
424
425 /* Signal and others. */
426 vtysh_signal_init();
427
428 /* Make vty structure and register commands. */
429 vtysh_init_vty();
430 vtysh_init_cmd();
431 vtysh_user_init();
432 vtysh_config_init();
433
434 vty_init_vtysh();
435
86b28610
LB
436 if (!user_mode) {
437 /* Read vtysh configuration file before connecting to daemons.
438 * (file may not be readable to calling user in SUID mode) */
439 suid_on();
440 vtysh_read_config(vtysh_config);
441 suid_off();
442 }
d62a17ae 443
444 if (markfile) {
445 if (!inputfile) {
446 fprintf(stderr,
447 "-f option MUST be specified with -m option\n");
448 return (1);
449 }
450 return (vtysh_mark_file(inputfile));
451 }
452
453 /* Start execution only if not in dry-run mode */
454 if (dryrun && !cmd) {
455 if (inputfile) {
456 ret = vtysh_read_config(inputfile);
457 } else {
9b8a8249 458 ret = vtysh_read_config(frr_config);
d62a17ae 459 }
460
461 exit(ret);
718e3744 462 }
d62a17ae 463
47402c0f 464 if (dryrun && cmd && cmd->line) {
d62a17ae 465 vtysh_execute("enable");
466 while (cmd) {
467 struct cmd_rec *cr;
468 char *cmdnow = cmd->line, *next;
469 do {
470 next = strchr(cmdnow, '\n');
471 if (next)
472 *next++ = '\0';
473
474 if (echo_command)
475 printf("%s%s\n", vtysh_prompt(),
476 cmdnow);
477
478 ret = vtysh_execute_no_pager(cmdnow);
479 if (!no_error
480 && !(ret == CMD_SUCCESS
481 || ret == CMD_SUCCESS_DAEMON
482 || ret == CMD_WARNING))
483 exit(1);
484 } while ((cmdnow = next) != NULL);
485
486 cr = cmd;
487 cmd = cmd->next;
488 XFREE(MTYPE_TMP, cr);
489 }
490 exit(ret);
0846286b 491 }
d62a17ae 492
493 /* Ignore error messages */
494 if (no_error) {
495 if (freopen("/dev/null", "w", stdout) == NULL) {
496 fprintf(stderr,
497 "Exiting: Failed to duplicate stdout with -n option");
498 exit(1);
499 }
0846286b 500 }
d62a17ae 501
32f3268f
DL
502 /* SUID: go back up elevated privs */
503 suid_on();
504
d62a17ae 505 /* Make sure we pass authentication before proceeding. */
506 vtysh_auth();
507
508 /* Do not connect until we have passed authentication. */
509 if (vtysh_connect_all(daemon_name) <= 0) {
510 fprintf(stderr, "Exiting: failed to connect to any daemons.\n");
511 if (no_error)
512 exit(0);
513 else
514 exit(1);
3221dca8 515 }
cd272640 516
32f3268f
DL
517 /* SUID: back down, don't need privs further on */
518 suid_off();
519
d62a17ae 520 if (writeconfig) {
521 vtysh_execute("enable");
522 return vtysh_write_config_integrated();
7a49a5b5 523 }
d62a17ae 524
525 if (inputfile) {
526 vtysh_flock_config(inputfile);
527 ret = vtysh_read_config(inputfile);
528 vtysh_unflock_config();
529 exit(ret);
fba55c8a 530 }
d62a17ae 531
532 /*
533 * Setup history file for use by both -c and regular input
534 * If we can't find the home directory, then don't store
535 * the history information
536 */
537 homedir = vtysh_get_home();
538 if (homedir) {
996c9314
LB
539 snprintf(history_file, sizeof(history_file), "%s/.history_frr",
540 homedir);
d62a17ae 541 if (read_history(history_file) != 0) {
542 int fp;
543
544 fp = open(history_file, O_CREAT | O_EXCL,
545 S_IRUSR | S_IWUSR);
cbb65f5e 546 if (fp != -1)
d62a17ae 547 close(fp);
548
549 read_history(history_file);
550 }
551 }
552
32f3268f
DL
553 if (getenv("VTYSH_LOG")) {
554 const char *logpath = getenv("VTYSH_LOG");
555
556 logfile = fopen(logpath, "a");
557 if (!logfile) {
558 fprintf(stderr, "Failed to open logfile (%s): %s\n",
559 logpath, strerror(errno));
560 exit(1);
561 }
562 }
563
d62a17ae 564 /* If eval mode. */
47402c0f 565 if (cmd && cmd->line) {
d62a17ae 566 /* Enter into enable node. */
567 vtysh_execute("enable");
568
569 while (cmd != NULL) {
570 int ret;
571 char *eol;
572
573 while ((eol = strchr(cmd->line, '\n')) != NULL) {
574 *eol = '\0';
575
576 add_history(cmd->line);
577 append_history(1, history_file);
578
579 if (echo_command)
580 printf("%s%s\n", vtysh_prompt(),
581 cmd->line);
582
583 if (logfile)
584 log_it(cmd->line);
585
586 ret = vtysh_execute_no_pager(cmd->line);
587 if (!no_error
588 && !(ret == CMD_SUCCESS
589 || ret == CMD_SUCCESS_DAEMON
590 || ret == CMD_WARNING))
591 exit(1);
592
593 cmd->line = eol + 1;
594 }
595
596 add_history(cmd->line);
597 append_history(1, history_file);
598
599 if (echo_command)
600 printf("%s%s\n", vtysh_prompt(), 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 || ret == CMD_SUCCESS_DAEMON
608 || ret == CMD_WARNING))
609 exit(1);
610
611 {
612 struct cmd_rec *cr;
613 cr = cmd;
614 cmd = cmd->next;
615 XFREE(MTYPE_TMP, cr);
616 }
617 }
618
619 history_truncate_file(history_file, 1000);
620 exit(0);
621 }
622
623 /* Boot startup configuration file. */
624 if (boot_flag) {
9b8a8249
DL
625 vtysh_flock_config(frr_config);
626 int ret = vtysh_read_config(frr_config);
d62a17ae 627 vtysh_unflock_config();
628 if (ret) {
629 fprintf(stderr,
630 "Configuration file[%s] processing failure: %d\n",
9b8a8249 631 frr_config, ret);
d62a17ae 632 if (no_error)
633 exit(0);
634 else
635 exit(ret);
636 } else
637 exit(0);
e7168df4 638 }
718e3744 639
d62a17ae 640 vtysh_pager_init();
718e3744 641
d62a17ae 642 vtysh_readline_init();
718e3744 643
d62a17ae 644 vty_hello(vty);
718e3744 645
d62a17ae 646 /* Enter into enable node. */
647 vtysh_execute("enable");
e7168df4 648
d62a17ae 649 /* Preparation for longjmp() in sigtstp(). */
650 sigsetjmp(jmpbuf, 1);
651 jmpflag = 1;
718e3744 652
d62a17ae 653 /* Main command loop. */
654 while (vtysh_rl_gets())
655 vtysh_execute(line_read);
718e3744 656
193a5a95
QY
657 vtysh_uninit();
658
d62a17ae 659 history_truncate_file(history_file, 1000);
660 printf("\n");
718e3744 661
d62a17ae 662 /* Rest in peace. */
663 exit(0);
718e3744 664}