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