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