]> git.proxmox.com Git - mirror_frr.git/blame - vtysh/vtysh_main.c
vtysh: cleanup SUID handling
[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
67e29abc 51/* Configuration file name and directory. */
ce2e9ec3 52static char vtysh_config_always[MAXPATHLEN] = SYSCONFDIR VTYSH_DEFAULT_CONFIG;
a0b4b67e 53static char quagga_config_default[MAXPATHLEN] = SYSCONFDIR FRR_DEFAULT_CONFIG;
cb947ba3 54char *quagga_config = quagga_config_default;
3f4ab7f9 55char history_file[MAXPATHLEN];
718e3744 56
718e3744 57/* Flag for indicate executing child command. */
58int execute_flag = 0;
59
87d79a9f 60/* VTY Socket prefix */
d62a17ae 61const char *vty_sock_path = NULL;
87d79a9f 62
718e3744 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"
152 "-w, --writeconfig Write integrated config (frr.conf) and exit\n"
153 "-h, --help Display this help and exit\n\n"
154 "Note that multiple commands may be executed from the command\n"
155 "line by passing multiple -c args, or by embedding linefeed\n"
156 "characters in one or more of the commands.\n\n"
157 "Report bugs to %s\n",
158 progname, FRR_BUG_ADDRESS);
159
160 exit(status);
718e3744 161}
162
163/* VTY shell options, we use GNU getopt library. */
87d79a9f 164#define OPTION_VTYSOCK 1000
ce2e9ec3 165#define OPTION_CONFDIR 1001
d62a17ae 166struct option longopts[] = {
167 {"boot", no_argument, NULL, 'b'},
168 /* For compatibility with older zebra/quagga versions */
169 {"eval", required_argument, NULL, 'e'},
170 {"command", required_argument, NULL, 'c'},
171 {"daemon", required_argument, NULL, 'd'},
172 {"vty_socket", required_argument, NULL, OPTION_VTYSOCK},
173 {"config_dir", required_argument, NULL, OPTION_CONFDIR},
174 {"inputfile", required_argument, NULL, 'f'},
175 {"echo", no_argument, NULL, 'E'},
176 {"dryrun", no_argument, NULL, 'C'},
177 {"help", no_argument, NULL, 'h'},
178 {"noerror", no_argument, NULL, 'n'},
179 {"mark", no_argument, NULL, 'm'},
180 {"writeconfig", no_argument, NULL, 'w'},
181 {0}};
b094d260 182
718e3744 183/* Read a string, and return a pointer to it. Returns NULL on EOF. */
d62a17ae 184static char *vtysh_rl_gets(void)
718e3744 185{
d62a17ae 186 HIST_ENTRY *last;
187 /* If the buffer has already been allocated, return the memory
188 * to the free pool. */
189 if (line_read) {
190 free(line_read);
191 line_read = NULL;
192 }
193
194 /* Get a line from the user. Change prompt according to node. XXX. */
195 line_read = readline(vtysh_prompt());
196
197 /* If the line has any text in it, save it on the history. But only if
198 * last command in history isn't the same one. */
199 if (line_read && *line_read) {
200 using_history();
201 last = previous_history();
202 if (!last || strcmp(last->line, line_read) != 0) {
203 add_history(line_read);
204 append_history(1, history_file);
205 }
206 }
207
208 return (line_read);
718e3744 209}
b094d260 210
57fb9748
SH
211static void log_it(const char *line)
212{
d62a17ae 213 time_t t = time(NULL);
214 struct tm *tmp = localtime(&t);
215 const char *user = getenv("USER");
216 char tod[64];
57fb9748 217
d62a17ae 218 if (!user)
219 user = "boot";
f03db93b 220
d62a17ae 221 strftime(tod, sizeof tod, "%Y%m%d-%H:%M.%S", tmp);
222
223 fprintf(logfile, "%s:%s %s\n", tod, user, line);
57fb9748
SH
224}
225
e43716f6
DS
226static int flock_fd;
227
d62a17ae 228static void vtysh_flock_config(const char *flock_file)
e43716f6 229{
d62a17ae 230 int count = 0;
231
232 flock_fd = open(flock_file, O_RDONLY, 0644);
233 if (flock_fd < 0) {
234 fprintf(stderr, "Unable to create lock file: %s, %s\n",
235 flock_file, safe_strerror(errno));
236 return;
237 }
238
239 while (count < 400 && (flock(flock_fd, LOCK_EX | LOCK_NB) < 0)) {
240 count++;
241 usleep(500000);
242 }
243
244 if (count >= 400)
245 fprintf(stderr,
246 "Flock of %s failed, continuing this may cause issues\n",
247 flock_file);
e43716f6
DS
248}
249
d62a17ae 250static void vtysh_unflock_config(void)
e43716f6 251{
d62a17ae 252 flock(flock_fd, LOCK_UN);
253 close(flock_fd);
e43716f6
DS
254}
255
32f3268f
DL
256void suid_on(void)
257{
258 if (elevuid != realuid && seteuid(elevuid)) {
259 perror("seteuid(on)");
260 exit(1);
261 }
262 if (elevgid != realgid && setegid(elevgid)) {
263 perror("setegid(on)");
264 exit(1);
265 }
266}
267
268void suid_off(void)
269{
270 if (elevuid != realuid && seteuid(realuid)) {
271 perror("seteuid(off)");
272 exit(1);
273 }
274 if (elevgid != realgid && setegid(realgid)) {
275 perror("setegid(off)");
276 exit(1);
277 }
278}
279
718e3744 280/* VTY shell main routine. */
d62a17ae 281int main(int argc, char **argv, char **env)
718e3744 282{
d62a17ae 283 char *p;
284 int opt;
285 int dryrun = 0;
286 int boot_flag = 0;
287 const char *daemon_name = NULL;
288 const char *inputfile = NULL;
289 const char *vtysh_configfile_name;
290 struct cmd_rec {
291 char *line;
292 struct cmd_rec *next;
293 } *cmd = NULL;
294 struct cmd_rec *tail = NULL;
295 int echo_command = 0;
296 int no_error = 0;
297 int markfile = 0;
298 int writeconfig = 0;
299 int ret = 0;
300 char *homedir = NULL;
32f3268f 301 int ditch_suid = 0;
d62a17ae 302
32f3268f
DL
303 /* SUID: drop down to calling user & go back up when needed */
304 elevuid = geteuid();
305 elevgid = getegid();
306 realuid = getuid();
307 realgid = getgid();
308 suid_off();
d62a17ae 309
310 /* Preserve name of myself. */
311 progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
312
d62a17ae 313 /* Option handling. */
314 while (1) {
315 opt = getopt_long(argc, argv, "be:c:d:nf:mEhCw", longopts, 0);
316
317 if (opt == EOF)
318 break;
319
320 switch (opt) {
321 case 0:
322 break;
323 case 'b':
324 boot_flag = 1;
325 break;
326 case 'e':
327 case 'c': {
328 struct cmd_rec *cr;
329 cr = XMALLOC(MTYPE_TMP, sizeof(*cr));
330 cr->line = optarg;
331 cr->next = NULL;
332 if (tail)
333 tail->next = cr;
334 else
335 cmd = cr;
336 tail = cr;
337 } break;
338 case OPTION_VTYSOCK:
32f3268f 339 ditch_suid = 1; /* option disables SUID */
d62a17ae 340 vty_sock_path = optarg;
341 break;
342 case OPTION_CONFDIR:
32f3268f 343 ditch_suid = 1; /* option disables SUID */
d62a17ae 344 /*
345 * Overwrite location for vtysh.conf
346 */
347 vtysh_configfile_name =
348 strrchr(VTYSH_DEFAULT_CONFIG, '/');
349 if (vtysh_configfile_name)
350 /* skip '/' */
351 vtysh_configfile_name++;
352 else
353 /*
354 * VTYSH_DEFAULT_CONFIG configured with relative
355 * path
356 * during config? Should really never happen for
357 * sensible config
358 */
359 vtysh_configfile_name =
360 (char *)VTYSH_DEFAULT_CONFIG;
361 strlcpy(vtysh_config_always, optarg,
362 sizeof(vtysh_config_always));
363 strlcat(vtysh_config_always, "/",
364 sizeof(vtysh_config_always));
365 strlcat(vtysh_config_always, vtysh_configfile_name,
366 sizeof(vtysh_config_always));
367 /*
368 * Overwrite location for frr.conf
369 */
370 vtysh_configfile_name =
371 strrchr(FRR_DEFAULT_CONFIG, '/');
372 if (vtysh_configfile_name)
373 /* skip '/' */
374 vtysh_configfile_name++;
375 else
376 /*
377 * FRR_DEFAULT_CONFIG configured with relative
378 * path
379 * during config? Should really never happen for
380 * sensible config
381 */
382 vtysh_configfile_name =
383 (char *)FRR_DEFAULT_CONFIG;
384 strlcpy(quagga_config_default, optarg,
385 sizeof(vtysh_config_always));
386 strlcat(quagga_config_default, "/",
387 sizeof(vtysh_config_always));
388 strlcat(quagga_config_default, vtysh_configfile_name,
389 sizeof(quagga_config_default));
390 break;
391 case 'd':
392 daemon_name = optarg;
393 break;
394 case 'f':
395 inputfile = optarg;
396 break;
397 case 'm':
398 markfile = 1;
399 break;
400 case 'n':
401 no_error = 1;
402 break;
403 case 'E':
404 echo_command = 1;
405 break;
406 case 'C':
407 dryrun = 1;
408 break;
409 case 'w':
410 writeconfig = 1;
411 break;
412 case 'h':
413 usage(0);
414 break;
415 default:
416 usage(1);
417 break;
418 }
419 }
420
32f3268f
DL
421 if (ditch_suid) {
422 elevuid = realuid;
423 elevgid = realgid;
424 }
425
d62a17ae 426 if (!vty_sock_path)
427 vty_sock_path = frr_vtydir;
428
429 if (markfile + writeconfig + dryrun + boot_flag > 1) {
430 fprintf(stderr,
431 "Invalid combination of arguments. Please specify at "
432 "most one of:\n\t-b, -C, -m, -w\n");
433 return 1;
434 }
435 if (inputfile && (writeconfig || boot_flag)) {
436 fprintf(stderr,
437 "WARNING: Combinining the -f option with -b or -w is "
438 "NOT SUPPORTED since its\nresults are inconsistent!\n");
439 }
440
441 /* Initialize user input buffer. */
442 line_read = NULL;
443 setlinebuf(stdout);
444
445 /* Signal and others. */
446 vtysh_signal_init();
447
448 /* Make vty structure and register commands. */
449 vtysh_init_vty();
450 vtysh_init_cmd();
451 vtysh_user_init();
452 vtysh_config_init();
453
454 vty_init_vtysh();
455
32f3268f
DL
456 /* Read vtysh configuration file before connecting to daemons.
457 * (file may not be readable to calling user in SUID mode) */
458 suid_on();
d62a17ae 459 vtysh_read_config(vtysh_config_always);
32f3268f 460 suid_off();
d62a17ae 461
462 if (markfile) {
463 if (!inputfile) {
464 fprintf(stderr,
465 "-f option MUST be specified with -m option\n");
466 return (1);
467 }
468 return (vtysh_mark_file(inputfile));
469 }
470
471 /* Start execution only if not in dry-run mode */
472 if (dryrun && !cmd) {
473 if (inputfile) {
474 ret = vtysh_read_config(inputfile);
475 } else {
476 ret = vtysh_read_config(quagga_config_default);
477 }
478
479 exit(ret);
718e3744 480 }
d62a17ae 481
482 if (dryrun && cmd) {
483 vtysh_execute("enable");
484 while (cmd) {
485 struct cmd_rec *cr;
486 char *cmdnow = cmd->line, *next;
487 do {
488 next = strchr(cmdnow, '\n');
489 if (next)
490 *next++ = '\0';
491
492 if (echo_command)
493 printf("%s%s\n", vtysh_prompt(),
494 cmdnow);
495
496 ret = vtysh_execute_no_pager(cmdnow);
497 if (!no_error
498 && !(ret == CMD_SUCCESS
499 || ret == CMD_SUCCESS_DAEMON
500 || ret == CMD_WARNING))
501 exit(1);
502 } while ((cmdnow = next) != NULL);
503
504 cr = cmd;
505 cmd = cmd->next;
506 XFREE(MTYPE_TMP, cr);
507 }
508 exit(ret);
0846286b 509 }
d62a17ae 510
511 /* Ignore error messages */
512 if (no_error) {
513 if (freopen("/dev/null", "w", stdout) == NULL) {
514 fprintf(stderr,
515 "Exiting: Failed to duplicate stdout with -n option");
516 exit(1);
517 }
0846286b 518 }
d62a17ae 519
32f3268f
DL
520 /* SUID: go back up elevated privs */
521 suid_on();
522
d62a17ae 523 /* Make sure we pass authentication before proceeding. */
524 vtysh_auth();
525
526 /* Do not connect until we have passed authentication. */
527 if (vtysh_connect_all(daemon_name) <= 0) {
528 fprintf(stderr, "Exiting: failed to connect to any daemons.\n");
529 if (no_error)
530 exit(0);
531 else
532 exit(1);
3221dca8 533 }
cd272640 534
32f3268f
DL
535 /* SUID: back down, don't need privs further on */
536 suid_off();
537
d62a17ae 538 if (writeconfig) {
539 vtysh_execute("enable");
540 return vtysh_write_config_integrated();
7a49a5b5 541 }
d62a17ae 542
543 if (inputfile) {
544 vtysh_flock_config(inputfile);
545 ret = vtysh_read_config(inputfile);
546 vtysh_unflock_config();
547 exit(ret);
fba55c8a 548 }
d62a17ae 549
550 /*
551 * Setup history file for use by both -c and regular input
552 * If we can't find the home directory, then don't store
553 * the history information
554 */
555 homedir = vtysh_get_home();
556 if (homedir) {
557 snprintf(history_file, sizeof(history_file),
558 "%s/.history_quagga", homedir);
559 if (read_history(history_file) != 0) {
560 int fp;
561
562 fp = open(history_file, O_CREAT | O_EXCL,
563 S_IRUSR | S_IWUSR);
564 if (fp)
565 close(fp);
566
567 read_history(history_file);
568 }
569 }
570
32f3268f
DL
571 if (getenv("VTYSH_LOG")) {
572 const char *logpath = getenv("VTYSH_LOG");
573
574 logfile = fopen(logpath, "a");
575 if (!logfile) {
576 fprintf(stderr, "Failed to open logfile (%s): %s\n",
577 logpath, strerror(errno));
578 exit(1);
579 }
580 }
581
d62a17ae 582 /* If eval mode. */
583 if (cmd) {
584 /* Enter into enable node. */
585 vtysh_execute("enable");
586
587 while (cmd != NULL) {
588 int ret;
589 char *eol;
590
591 while ((eol = strchr(cmd->line, '\n')) != NULL) {
592 *eol = '\0';
593
594 add_history(cmd->line);
595 append_history(1, history_file);
596
597 if (echo_command)
598 printf("%s%s\n", vtysh_prompt(),
599 cmd->line);
600
601 if (logfile)
602 log_it(cmd->line);
603
604 ret = vtysh_execute_no_pager(cmd->line);
605 if (!no_error
606 && !(ret == CMD_SUCCESS
607 || ret == CMD_SUCCESS_DAEMON
608 || ret == CMD_WARNING))
609 exit(1);
610
611 cmd->line = eol + 1;
612 }
613
614 add_history(cmd->line);
615 append_history(1, history_file);
616
617 if (echo_command)
618 printf("%s%s\n", vtysh_prompt(), cmd->line);
619
620 if (logfile)
621 log_it(cmd->line);
622
623 ret = vtysh_execute_no_pager(cmd->line);
624 if (!no_error
625 && !(ret == CMD_SUCCESS || ret == CMD_SUCCESS_DAEMON
626 || ret == CMD_WARNING))
627 exit(1);
628
629 {
630 struct cmd_rec *cr;
631 cr = cmd;
632 cmd = cmd->next;
633 XFREE(MTYPE_TMP, cr);
634 }
635 }
636
637 history_truncate_file(history_file, 1000);
638 exit(0);
639 }
640
641 /* Boot startup configuration file. */
642 if (boot_flag) {
643 vtysh_flock_config(quagga_config);
644 int ret = vtysh_read_config(quagga_config);
645 vtysh_unflock_config();
646 if (ret) {
647 fprintf(stderr,
648 "Configuration file[%s] processing failure: %d\n",
649 quagga_config, ret);
650 if (no_error)
651 exit(0);
652 else
653 exit(ret);
654 } else
655 exit(0);
e7168df4 656 }
718e3744 657
d62a17ae 658 vtysh_pager_init();
718e3744 659
d62a17ae 660 vtysh_readline_init();
718e3744 661
d62a17ae 662 vty_hello(vty);
718e3744 663
d62a17ae 664 /* Enter into enable node. */
665 vtysh_execute("enable");
e7168df4 666
d62a17ae 667 /* Preparation for longjmp() in sigtstp(). */
668 sigsetjmp(jmpbuf, 1);
669 jmpflag = 1;
718e3744 670
d62a17ae 671 /* Main command loop. */
672 while (vtysh_rl_gets())
673 vtysh_execute(line_read);
718e3744 674
d62a17ae 675 history_truncate_file(history_file, 1000);
676 printf("\n");
718e3744 677
d62a17ae 678 /* Rest in peace. */
679 exit(0);
718e3744 680}