3 * Bacula Console interface to the Director
5 * Kern Sibbald, September MM
10 Copyright (C) 2000-2006 Kern Sibbald
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 version 2 as amended with additional clauses defined in the
15 file LICENSE in the main source directory.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 the file LICENSE for additional details.
25 #include "console_conf.h"
35 #define con_set_zed_keys();
41 #if defined(HAVE_WIN32)
42 #define isatty(fd) (fd==0)
45 /* Exported variables */
47 extern int rl_catch_signals;
49 /* Imported functions */
50 int authenticate_director(JCR *jcr, DIRRES *director, CONRES *cons);
52 /* Forward referenced functions */
53 static void terminate_console(int sig);
54 static int check_resources();
55 int get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec);
56 static int do_outputcmd(FILE *input, BSOCK *UA_sock);
57 void senditf(const char *fmt, ...);
58 void sendit(const char *buf);
60 extern "C" void got_sigstop(int sig);
61 extern "C" void got_sigcontinue(int sig);
62 extern "C" void got_sigtout(int sig);
63 extern "C" void got_sigtin(int sig);
66 /* Static variables */
67 static char *configfile = NULL;
68 static BSOCK *UA_sock = NULL;
70 static FILE *output = stdout;
71 static bool teeout = false; /* output to output and stdout */
72 static bool stop = false;
73 static bool no_conio = false;
77 static char *argk[MAX_CMD_ARGS];
78 static char *argv[MAX_CMD_ARGS];
81 /* Command prototypes */
82 static int versioncmd(FILE *input, BSOCK *UA_sock);
83 static int inputcmd(FILE *input, BSOCK *UA_sock);
84 static int outputcmd(FILE *input, BSOCK *UA_sock);
85 static int teecmd(FILE *input, BSOCK *UA_sock);
86 static int quitcmd(FILE *input, BSOCK *UA_sock);
87 static int timecmd(FILE *input, BSOCK *UA_sock);
88 static int sleepcmd(FILE *input, BSOCK *UA_sock);
91 #define CONFIG_FILE "./bconsole.conf" /* default configuration file */
96 "Copyright (C) 2000-%s Kern Sibbald\n"
97 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
98 "Usage: bconsole [-s] [-c config_file] [-d debug_level]\n"
99 " -c <file> set configuration file to file\n"
100 " -dnn set debug level to nn\n"
103 " -t test - read configuration and exit\n"
104 " -? print this message.\n"
105 "\n"), BYEAR, HOST_OS, DISTNAME, DISTVER);
110 void got_sigstop(int sig)
116 void got_sigcontinue(int sig)
122 void got_sigtout(int sig)
124 // printf("Got tout\n");
128 void got_sigtin(int sig)
130 // printf("Got tin\n");
134 static int zed_keyscmd(FILE *input, BSOCK *UA_sock)
141 * These are the @command
143 struct cmdstruct { const char *key; int (*func)(FILE *input, BSOCK *UA_sock); const char *help; };
144 static struct cmdstruct commands[] = {
145 { N_("input"), inputcmd, _("input from file")},
146 { N_("output"), outputcmd, _("output to file")},
147 { N_("quit"), quitcmd, _("quit")},
148 { N_("tee"), teecmd, _("output to file and terminal")},
149 { N_("sleep"), sleepcmd, _("sleep specified time")},
150 { N_("time"), timecmd, _("print current time")},
151 { N_("version"), versioncmd, _("print Console's version")},
152 { N_("exit"), quitcmd, _("exit = quit")},
153 { N_("zed_keyst"), zed_keyscmd, _("zed_keys = use zed keys instead of bash keys")},
155 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
157 static int do_a_command(FILE *input, BSOCK *UA_sock)
168 Dmsg1(120, "Command: %s\n", UA_sock->msg);
174 if (*cmd == '#') { /* comment */
178 for (i=0; i<comsize; i++) { /* search for command */
179 if (strncasecmp(cmd, _(commands[i].key), len) == 0) {
180 stat = (*commands[i].func)(input, UA_sock); /* go execute command */
186 pm_strcat(&UA_sock->msg, _(": is an illegal command\n"));
187 UA_sock->msglen = strlen(UA_sock->msg);
188 sendit(UA_sock->msg);
194 static void read_and_process_input(FILE *input, BSOCK *UA_sock)
196 const char *prompt = "*";
197 bool at_prompt = false;
198 int tty_input = isatty(fileno(input));
202 if (at_prompt) { /* don't prompt multiple times */
209 stat = get_cmd(input, prompt, UA_sock, 30);
217 /* Reading input from a file */
218 int len = sizeof_pool_memory(UA_sock->msg) - 1;
222 if (fgets(UA_sock->msg, len, input) == NULL) {
225 sendit(UA_sock->msg); /* echo to terminal */
226 strip_trailing_junk(UA_sock->msg);
227 UA_sock->msglen = strlen(UA_sock->msg);
232 break; /* error or interrupt */
233 } else if (stat == 0) { /* timeout */
234 if (strcmp(prompt, "*") == 0) {
235 bnet_fsend(UA_sock, ".messages");
241 /* @ => internal command for us */
242 if (UA_sock->msg[0] == '@') {
243 parse_args(UA_sock->msg, &args, &argc, argk, argv, MAX_CMD_ARGS);
244 if (!do_a_command(input, UA_sock)) {
249 if (!bnet_send(UA_sock)) { /* send command */
253 if (strcmp(UA_sock->msg, ".quit") == 0 || strcmp(UA_sock->msg, ".exit") == 0) {
256 while ((stat = bnet_recv(UA_sock)) >= 0) {
263 /* Suppress output if running in background or user hit ctl-c */
264 if (!stop && !usrbrk()) {
265 sendit(UA_sock->msg);
276 if (is_bnet_stop(UA_sock)) {
277 break; /* error or term */
278 } else if (stat == BNET_SIGNAL) {
279 if (UA_sock->msglen == BNET_PROMPT) {
282 Dmsg1(100, "Got poll %s\n", bnet_sig_to_ascii(UA_sock));
288 * Call-back for reading a passphrase for an encrypted PEM file
289 * This function uses getpass(),
290 * which uses a static buffer and is NOT thread-safe.
292 static int tls_pem_callback(char *buf, int size, const void *userdata)
296 const char *prompt = (const char *)userdata;
298 if (win32_cgets(buf, size) == NULL) {
305 const char *prompt = (const char *)userdata;
308 passwd = getpass(prompt);
309 bstrncpy(buf, passwd, size);
319 /*********************************************************************
321 * Main Bacula Console -- User Interface Program
324 int main(int argc, char *argv[])
327 bool no_signals = false;
328 bool test_config = false;
331 setlocale(LC_ALL, "");
332 bindtextdomain("bacula", LOCALEDIR);
333 textdomain("bacula");
336 my_name_is(argc, argv, "bconsole");
337 init_msg(NULL, NULL);
338 working_directory = "/tmp";
339 args = get_pool_memory(PM_FNAME);
341 while ((ch = getopt(argc, argv, "bc:d:nst?")) != -1) {
343 case 'c': /* configuration file */
344 if (configfile != NULL) {
347 configfile = bstrdup(optarg);
351 debug_level = atoi(optarg);
352 if (debug_level <= 0) {
357 case 'n': /* no conio */
361 case 's': /* turn off signals */
379 init_signals(terminate_console);
383 #if !defined(HAVE_WIN32)
384 /* Override Bacula default signals */
385 signal(SIGQUIT, SIG_IGN);
386 signal(SIGTSTP, got_sigstop);
387 signal(SIGCONT, got_sigcontinue);
388 signal(SIGTTIN, got_sigtin);
389 signal(SIGTTOU, got_sigtout);
400 if (configfile == NULL) {
401 configfile = bstrdup(CONFIG_FILE);
404 parse_config(configfile);
406 if (init_crypto() != 0) {
407 Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
410 if (!check_resources()) {
411 Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
419 terminate_console(0);
423 memset(&jcr, 0, sizeof(jcr));
425 (void)WSA_Init(); /* Initialize Windows sockets */
428 struct sockaddr client_addr;
429 memset(&client_addr, 0, sizeof(client_addr));
430 UA_sock = init_bsock(NULL, 0, "", "", 0, &client_addr);
432 sendit(_("Available Directors:\n"));
435 foreach_res(dir, R_DIRECTOR) {
436 senditf( _("%d %s at %s:%d\n"), 1+numdir++, dir->hdr.name, dir->address,
440 if (get_cmd(stdin, _("Select Director: "), UA_sock, 600) < 0) {
441 (void)WSACleanup(); /* Cleanup Windows sockets */
444 item = atoi(UA_sock->msg);
445 if (item < 0 || item > numdir) {
446 senditf(_("You must enter a number between 1 and %d\n"), numdir);
451 for (i=0; i<item; i++) {
452 dir = (DIRRES *)GetNextRes(R_DIRECTOR, (RES *)dir);
458 dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
463 CONRES *cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
466 senditf(_("Connecting to Director %s:%d\n"), dir->address,dir->DIRport);
469 /* Initialize Console TLS context */
470 if (cons && (cons->tls_enable || cons->tls_require)) {
471 /* Generate passphrase prompt */
472 bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ", cons->hdr.name);
474 /* Initialize TLS context:
475 * Args: CA certfile, CA certdir, Certfile, Keyfile,
476 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
477 cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
478 cons->tls_ca_certdir, cons->tls_certfile,
479 cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
481 if (!cons->tls_ctx) {
482 senditf(_("Failed to initialize TLS context for Console \"%s\".\n"),
484 terminate_console(0);
490 /* Initialize Director TLS context */
491 if (dir->tls_enable || dir->tls_require) {
492 /* Generate passphrase prompt */
493 bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ", dir->hdr.name);
495 /* Initialize TLS context:
496 * Args: CA certfile, CA certdir, Certfile, Keyfile,
497 * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
498 dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
499 dir->tls_ca_certdir, dir->tls_certfile,
500 dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
503 senditf(_("Failed to initialize TLS context for Director \"%s\".\n"),
505 terminate_console(0);
510 UA_sock = bnet_connect(NULL, 5, 15, "Director daemon", dir->address,
511 NULL, dir->DIRport, 0);
512 if (UA_sock == NULL) {
513 terminate_console(0);
516 jcr.dir_bsock = UA_sock;
518 /* If cons==NULL, default console will be used */
519 if (!authenticate_director(&jcr, dir, cons)) {
520 terminate_console(0);
524 Dmsg0(40, "Opened connection with Director daemon\n");
526 sendit(_("Enter a period to cancel a command.\n"));
528 /* Run commands in ~/.bconsolerc if any */
529 char *env = getenv("HOME");
532 pm_strcpy(&UA_sock->msg, env);
533 pm_strcat(&UA_sock->msg, "/.bconsolerc");
534 fd = fopen(UA_sock->msg, "rb");
536 read_and_process_input(fd, UA_sock);
541 read_and_process_input(stdin, UA_sock);
544 bnet_sig(UA_sock, BNET_TERMINATE); /* send EOF */
548 terminate_console(0);
553 /* Cleanup and then exit */
554 static void terminate_console(int sig)
557 static bool already_here = false;
559 if (already_here) { /* avoid recursive temination problems */
564 free_pool_memory(args);
568 (void)WSACleanup(); /* Cleanup Windows sockets */
576 * Make a quick check to see that we have all the
579 static int check_resources()
587 foreach_res(director, R_DIRECTOR) {
590 /* tls_require implies tls_enable */
591 if (director->tls_require) {
593 director->tls_enable = true;
595 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
601 if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && director->tls_enable) {
602 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
603 " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
604 " At least one CA certificate store is required.\n"),
605 director->hdr.name, configfile);
611 Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
612 "Without that I don't how to speak to the Director :-(\n"), configfile);
617 /* Loop over Consoles */
618 foreach_res(cons, R_CONSOLE) {
619 /* tls_require implies tls_enable */
620 if (cons->tls_require) {
622 cons->tls_enable = true;
624 Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
630 if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && cons->tls_enable) {
631 Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
632 " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
633 cons->hdr.name, configfile);
645 #define READLINE_LIBRARY 1
647 #include "readline.h"
652 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
656 rl_catch_signals = 0; /* do it ourselves */
657 line = readline((char *)prompt); /* cast needed for old readlines */
662 strip_trailing_junk(line);
663 sock->msglen = pm_strcpy(&sock->msg, line);
665 add_history(sock->msg);
671 #else /* no readline, do it ourselves */
673 static bool bisatty(int fd)
682 * Returns: 1 if data available
687 wait_for_data(int fd, int sec)
689 #if defined(HAVE_WIN32)
699 FD_SET((unsigned)fd, &fdset);
700 switch(select(fd + 1, &fdset, NULL, NULL, &tv)) {
701 case 0: /* timeout */
704 if (errno == EINTR || errno == EAGAIN) {
707 return -1; /* error return */
716 * Get next input command from terminal.
718 * Returns: 1 if got input
723 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
727 if (output == stdout || teeout) {
732 switch (wait_for_data(fileno(input), sec)) {
734 return 0; /* timeout */
736 return -1; /* error */
738 len = sizeof_pool_memory(sock->msg) - 1;
744 if (bisatty(fileno(input))) {
745 input_line(sock->msg, len);
749 #ifdef HAVE_WIN32 /* use special console for input on win32 */
750 if (input == stdin) {
751 if (win32_cgets(sock->msg, len) == NULL) {
757 if (fgets(sock->msg, len, input) == NULL) {
766 strip_trailing_junk(sock->msg);
767 sock->msglen = strlen(sock->msg);
771 #endif /* end non-readline code */
773 static int versioncmd(FILE *input, BSOCK *UA_sock)
775 senditf("Version: " VERSION " (" BDATE ") %s %s %s\n",
776 HOST_OS, DISTNAME, DISTVER);
780 static int inputcmd(FILE *input, BSOCK *UA_sock)
785 sendit(_("Too many arguments on input command.\n"));
789 sendit(_("First argument to input command must be a filename.\n"));
792 fd = fopen(argk[1], "rb");
794 senditf(_("Cannot open file %s for input. ERR=%s\n"),
795 argk[1], strerror(errno));
798 read_and_process_input(fd, UA_sock);
803 /* Send output to both termina and specified file */
804 static int teecmd(FILE *input, BSOCK *UA_sock)
807 return do_outputcmd(input, UA_sock);
810 /* Send output to specified "file" */
811 static int outputcmd(FILE *input, BSOCK *UA_sock)
814 return do_outputcmd(input, UA_sock);
818 static int do_outputcmd(FILE *input, BSOCK *UA_sock)
821 const char *mode = "a+b";
824 sendit(_("Too many arguments on output/tee command.\n"));
828 if (output != stdout) {
838 fd = fopen(argk[1], mode);
840 senditf(_("Cannot open file %s for output. ERR=%s\n"),
841 argk[1], strerror(errno));
848 static int quitcmd(FILE *input, BSOCK *UA_sock)
853 static int sleepcmd(FILE *input, BSOCK *UA_sock)
856 sleep(atoi(argk[1]));
862 static int timecmd(FILE *input, BSOCK *UA_sock)
865 time_t ttime = time(NULL);
867 (void)localtime_r(&ttime, &tm);
868 strftime(sdt, sizeof(sdt), "%d-%b-%Y %H:%M:%S", &tm);
874 * Send a line to the output file and or the terminal
876 void senditf(const char *fmt,...)
881 va_start(arg_ptr, fmt);
882 bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
887 void sendit(const char *buf)
891 if (output == stdout || teeout) {
894 * Here, we convert every \n into \r\n because the
895 * terminal is in raw mode when we are using
898 for (p=q=buf; (p=strchr(q, '\n')); ) {
901 memcpy(obuf, q, len);
903 memcpy(obuf+len, "\r\n", 3);
904 q = ++p; /* point after \n */
912 if (output != stdout) {