]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/console/console.c
40a1dd4382b9dd57178946afe3b672f8df8c4cce
[bacula/bacula] / bacula / src / console / console.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2016 Kern Sibbald
5
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13
14    This notice must be preserved when any source code is 
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *
21  *   Bacula Console interface to the Director
22  *
23  *     Kern Sibbald, September MM
24  *
25  */
26
27 #include "bacula.h"
28 #include "console_conf.h"
29 #include "jcr.h"
30
31
32 #ifdef HAVE_CONIO
33 #include "conio.h"
34 //#define CONIO_FIX 1
35 #else
36 #define con_init(x)
37 #define con_term()
38 #define con_set_zed_keys();
39 #define trapctlc()
40 #define clrbrk()
41 #define usrbrk() 0
42 #endif
43
44 #if defined(HAVE_WIN32)
45 #define isatty(fd) (fd==0)
46 #endif
47
48 /* Exported variables */
49
50 //extern int rl_catch_signals;
51
52 /* Imported functions */
53 int authenticate_director(BSOCK *dir, DIRRES *director, CONRES *cons);
54 extern bool parse_cons_config(CONFIG *config, const char *configfile, int exit_code);
55
56 /* Forward referenced functions */
57 static void terminate_console(int sig);
58 static int check_resources();
59 int get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec);
60 static int do_outputcmd(FILE *input, BSOCK *UA_sock);
61 void senditf(const char *fmt, ...);
62 void sendit(const char *buf);
63
64 extern "C" void got_sigstop(int sig);
65 extern "C" void got_sigcontinue(int sig);
66 extern "C" void got_sigtout(int sig);
67 extern "C" void got_sigtin(int sig);
68
69
70 /* Static variables */
71 static char *configfile = NULL;
72 static BSOCK *UA_sock = NULL;
73 static DIRRES *dir = NULL;
74 static CONRES *cons = NULL;
75 static FILE *output = stdout;
76 static bool teeout = false;               /* output to output and stdout */
77 static bool teein = false;                /* input to output and stdout */
78 static bool stop = false;
79 static bool no_conio = false;
80 static int timeout = 0;
81 static int argc;
82 static int numdir;
83 static POOLMEM *args;
84 static char *argk[MAX_CMD_ARGS];
85 static char *argv[MAX_CMD_ARGS];
86 static CONFIG *config;
87
88
89 /* Command prototypes */
90 static int versioncmd(FILE *input, BSOCK *UA_sock);
91 static int inputcmd(FILE *input, BSOCK *UA_sock);
92 static int outputcmd(FILE *input, BSOCK *UA_sock);
93 static int teecmd(FILE *input, BSOCK *UA_sock);
94 static int teeallcmd(FILE *input, BSOCK *UA_sock);
95 static int quitcmd(FILE *input, BSOCK *UA_sock);
96 static int helpcmd(FILE *input, BSOCK *UA_sock);
97 static int echocmd(FILE *input, BSOCK *UA_sock);
98 static int timecmd(FILE *input, BSOCK *UA_sock);
99 static int sleepcmd(FILE *input, BSOCK *UA_sock);
100 static int execcmd(FILE *input, BSOCK *UA_sock);
101
102 #ifdef HAVE_READLINE
103 static int eolcmd(FILE *input, BSOCK *UA_sock);
104
105 # ifndef HAVE_REGEX_H
106 #  include "lib/bregex.h"
107 # else
108 #  include <regex.h>
109 # endif
110
111 #endif
112
113
114 #define CONFIG_FILE "bconsole.conf"   /* default configuration file */
115
116 static void usage()
117 {
118    fprintf(stderr, _(
119 PROG_COPYRIGHT
120 "\n%sVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
121 "Usage: bconsole [-s] [-c config_file] [-d debug_level]\n"
122 "       -D <dir>    select a Director\n"
123 "       -l          list Directors defined\n"
124 "       -c <file>   set configuration file to file\n"
125 "       -d <nn>     set debug level to <nn>\n"
126 "       -dt         print timestamp in debug output\n"
127 "       -n          no conio\n"
128 "       -s          no signals\n"
129 "       -u <nn>     set command execution timeout to <nn> seconds\n"
130 "       -t          test - read configuration and exit\n"
131 "       -?          print this message.\n"
132 "\n"), 2000, "", HOST_OS, DISTNAME, DISTVER);
133 }
134
135
136 extern "C"
137 void got_sigstop(int sig)
138 {
139    stop = true;
140 }
141
142 extern "C"
143 void got_sigcontinue(int sig)
144 {
145    stop = false;
146 }
147
148 extern "C"
149 void got_sigtout(int sig)
150 {
151 // printf("Got tout\n");
152 }
153
154 extern "C"
155 void got_sigtin(int sig)
156 {
157 // printf("Got tin\n");
158 }
159
160
161 static int zed_keyscmd(FILE *input, BSOCK *UA_sock)
162 {
163    con_set_zed_keys();
164    return 1;
165 }
166
167 /*
168  * These are the @command
169  */
170 struct cmdstruct { const char *key; int (*func)(FILE *input, BSOCK *UA_sock); const char *help; };
171 static struct cmdstruct commands[] = {
172  { N_("input"),      inputcmd,     _("input from file")},
173  { N_("output"),     outputcmd,    _("output to file")},
174  { N_("quit"),       quitcmd,      _("quit")},
175  { N_("tee"),        teecmd,       _("output to file and terminal")},
176  { N_("tall"),       teeallcmd,    _("output everything to file and terminal (tee all)")},
177  { N_("sleep"),      sleepcmd,     _("sleep specified time")},
178  { N_("time"),       timecmd,      _("print current time")},
179  { N_("version"),    versioncmd,   _("print Console's version")},
180  { N_("echo"),       echocmd,      _("echo command string")},
181  { N_("exec"),       execcmd,      _("execute an external command")},
182  { N_("exit"),       quitcmd,      _("exit = quit")},
183  { N_("zed_keys"),   zed_keyscmd,  _("zed_keys = use zed keys instead of bash keys")},
184  { N_("help"),       helpcmd,      _("help listing")},
185 #ifdef HAVE_READLINE
186  { N_("separator"),  eolcmd,       _("set command separator")},
187 #endif
188              };
189 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
190
191 static int do_a_command(FILE *input, BSOCK *UA_sock)
192 {
193    unsigned int i;
194    int stat;
195    int found;
196    int len;
197    char *cmd;
198
199    found = 0;
200    stat = 1;
201
202    Dmsg1(120, "Command: %s\n", UA_sock->msg);
203    if (argc == 0) {
204       return 1;
205    }
206
207    cmd = argk[0]+1;
208    if (*cmd == '#') {                 /* comment */
209       return 1;
210    }
211    len = strlen(cmd);
212    for (i=0; i<comsize; i++) {     /* search for command */
213       if (strncasecmp(cmd,  _(commands[i].key), len) == 0) {
214          stat = (*commands[i].func)(input, UA_sock);   /* go execute command */
215          found = 1;
216          break;
217       }
218    }
219    if (!found) {
220       pm_strcat(&UA_sock->msg, _(": is an invalid command\n"));
221       UA_sock->msglen = strlen(UA_sock->msg);
222       sendit(UA_sock->msg);
223    }
224    return stat;
225 }
226
227 static void read_and_process_input(FILE *input, BSOCK *UA_sock)
228 {
229    const char *prompt = "*";
230    bool at_prompt = false;
231    int tty_input = isatty(fileno(input));
232    int stat;
233    btimer_t *tid=NULL;
234
235    for ( ;; ) {
236       if (at_prompt) {                /* don't prompt multiple times */
237          prompt = "";
238       } else {
239          prompt = "*";
240          at_prompt = true;
241       }
242       if (tty_input) {
243          stat = get_cmd(input, prompt, UA_sock, 30);
244          if (usrbrk() == 1) {
245             clrbrk();
246          }
247          if (usrbrk()) {
248             break;
249          }
250       } else {
251          /* Reading input from a file */
252          if (usrbrk()) {
253             break;
254          }
255          if (bfgets(UA_sock->msg, input) == NULL) {
256             stat = -1;
257          } else {
258             sendit(UA_sock->msg);  /* echo to terminal */
259             strip_trailing_junk(UA_sock->msg);
260             UA_sock->msglen = strlen(UA_sock->msg);
261             stat = 1;
262          }
263       }
264       if (stat < 0) {
265          break;                       /* error or interrupt */
266       } else if (stat == 0) {         /* timeout */
267          if (strcmp(prompt, "*") == 0) {
268             tid = start_bsock_timer(UA_sock, timeout);
269             UA_sock->fsend(".messages");
270             stop_bsock_timer(tid);
271          } else {
272             continue;
273          }
274       } else {
275          at_prompt = false;
276          /* @ => internal command for us */
277          if (UA_sock->msg[0] == '@') {
278             parse_args(UA_sock->msg, &args, &argc, argk, argv, MAX_CMD_ARGS);
279             if (!do_a_command(input, UA_sock)) {
280                break;
281             }
282             continue;
283          }
284          tid = start_bsock_timer(UA_sock, timeout);
285          if (!UA_sock->send()) {   /* send command */
286             stop_bsock_timer(tid);
287             break;                    /* error */
288          }
289          stop_bsock_timer(tid);
290       }
291       if (strcasecmp(UA_sock->msg, ".quit") == 0 || strcasecmp(UA_sock->msg, ".exit") == 0) {
292          break;
293       }
294       tid = start_bsock_timer(UA_sock, timeout);
295       while (1) {
296          stat = UA_sock->recv();
297
298          if (stat < 0) {
299             break;
300          }
301
302          if (at_prompt) {
303             if (!stop) {
304                sendit("\n");
305             }
306             at_prompt = false;
307          }
308          /* Suppress output if running in background or user hit ctl-c */
309          if (!stop && !usrbrk()) {
310             sendit(UA_sock->msg);
311          }
312       }
313       stop_bsock_timer(tid);
314       if (usrbrk() > 1) {
315          break;
316       } else {
317          clrbrk();
318       }
319       if (!stop) {
320          fflush(stdout);
321       }
322       if (UA_sock->is_stop()) {
323          break;                       /* error or term */
324       } else if (stat == BNET_SIGNAL) {
325          if (UA_sock->msglen == BNET_SUB_PROMPT) {
326             at_prompt = true;
327          }
328          Dmsg1(100, "Got poll %s\n", bnet_sig_to_ascii(UA_sock->msglen));
329       }
330    }
331 }
332
333 /*
334  * Call-back for reading a passphrase for an encrypted PEM file
335  * This function uses getpass(),
336  *  which uses a static buffer and is NOT thread-safe.
337  */
338 static int tls_pem_callback(char *buf, int size, const void *userdata)
339 {
340 #ifdef HAVE_TLS
341    const char *prompt = (const char *)userdata;
342 # if defined(HAVE_WIN32)
343    sendit(prompt);
344    if (win32_cgets(buf, size) == NULL) {
345       buf[0] = 0;
346       return 0;
347    } else {
348       return strlen(buf);
349    }
350 # else
351    char *passwd;
352
353    passwd = getpass(prompt);
354    bstrncpy(buf, passwd, size);
355    return strlen(buf);
356 # endif
357 #else
358    buf[0] = 0;
359    return 0;
360 #endif
361 }
362
363 #ifdef HAVE_READLINE
364 #define READLINE_LIBRARY 1
365 #include "readline.h"
366 #include "history.h"
367
368 /* Get the first keyword of the line */
369 static char *
370 get_first_keyword()
371 {
372    char *ret=NULL;
373    int len;
374    char *first_space = strchr(rl_line_buffer, ' ');
375    if (first_space) {
376       len = first_space - rl_line_buffer;
377       ret = (char *) malloc((len + 1) * sizeof(char));
378       memcpy(ret, rl_line_buffer, len);
379       ret[len]=0;
380    }
381    return ret;
382 }
383
384 /*
385  * Return the command before the current point.
386  * Set nb to the number of command to skip
387  */
388 static char *
389 get_previous_keyword(int current_point, int nb)
390 {
391    int i, end=-1, start, inquotes=0;
392    char *s=NULL;
393
394    while (nb-- >= 0) {
395       /* first we look for a space before the current word */
396       for (i = current_point; i >= 0; i--) {
397          if (rl_line_buffer[i] == ' ' || rl_line_buffer[i] == '=') {
398             break;
399          }
400       }
401
402       /* find the end of the command */
403       for (; i >= 0; i--) {
404          if (rl_line_buffer[i] != ' ') {
405             end = i;
406             break;
407          }
408       }
409
410       /* no end of string */
411       if (end == -1) {
412          return NULL;
413       }
414
415       /* look for the start of the command */
416       for (start = end; start > 0; start--) {
417          if (rl_line_buffer[start] == '"') {
418             inquotes = !inquotes;
419          }
420          if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0) {
421             break;
422          }
423          current_point = start;
424       }
425    }
426
427    s = (char *)malloc(end - start + 2);
428    memcpy(s, rl_line_buffer + start, end - start + 1);
429    s[end - start + 1] = 0;
430
431    //  printf("=======> %i:%i <%s>\n", start, end, s);
432
433    return s;
434 }
435
436 /* Simple structure that will contain the completion list */
437 struct ItemList {
438    alist list;
439 };
440
441 static ItemList *items = NULL;
442 void init_items()
443 {
444    if (!items) {
445       items = (ItemList*) malloc(sizeof(ItemList));
446       memset(items, 0, sizeof(ItemList));
447
448    } else {
449       items->list.destroy();
450    }
451
452    items->list.init();
453 }
454
455 /* Match a regexp and add the result to the items list
456  * This function is recursive
457  */
458 static void match_kw(regex_t *preg, const char *what, int len, POOLMEM **buf)
459 {
460    int rc, size;
461    int nmatch=20;
462    regmatch_t pmatch[nmatch];
463
464    if (len <= 0) {
465       return;
466    }
467    rc = regexec(preg, what, nmatch, pmatch,  0);
468    if (rc == 0) {
469 #if 0
470       Pmsg1(0, "\n\n%s\n0123456789012345678901234567890123456789\n        10         20         30\n", what);
471       Pmsg2(0, "%i-%i\n", pmatch[0].rm_so, pmatch[0].rm_eo);
472       Pmsg2(0, "%i-%i\n", pmatch[1].rm_so, pmatch[1].rm_eo);
473       Pmsg2(0, "%i-%i\n", pmatch[2].rm_so, pmatch[2].rm_eo);
474       Pmsg2(0, "%i-%i\n", pmatch[3].rm_so, pmatch[3].rm_eo);
475 #endif
476       size = pmatch[1].rm_eo - pmatch[1].rm_so;
477       *buf = check_pool_memory_size(*buf, size + 1);
478       memcpy(*buf, what+pmatch[1].rm_so, size);
479       (*buf)[size] = 0;
480
481       items->list.append(bstrdup(*buf));
482       /* We search for the next keyword in the line */
483       match_kw(preg, what + pmatch[1].rm_eo, len - pmatch[1].rm_eo, buf);
484    }
485 }
486
487 /* fill the items list with the output of the help command */
488 void get_arguments(const char *what)
489 {
490    regex_t preg;
491    POOLMEM *buf;
492    int rc;
493    init_items();
494
495    rc = regcomp(&preg, "(([a-z]+=)|([a-z]+)( |$))", REG_EXTENDED);
496    if (rc != 0) {
497       return;
498    }
499
500    buf = get_pool_memory(PM_MESSAGE);
501    UA_sock->fsend(".help item=%s", what);
502    while (UA_sock->recv() > 0) {
503       strip_trailing_junk(UA_sock->msg);
504       match_kw(&preg, UA_sock->msg, UA_sock->msglen, &buf);
505    }
506    free_pool_memory(buf);
507    regfree(&preg);
508 }
509
510 /* retreive a simple list (.pool, .client) and store it into items */
511 void get_items(const char *what)
512 {
513    init_items();
514
515    UA_sock->fsend("%s", what);
516    while (UA_sock->recv() > 0) {
517       strip_trailing_junk(UA_sock->msg);
518       items->list.append(bstrdup(UA_sock->msg));
519    }
520 }
521
522 typedef enum
523 {
524    ITEM_ARG,       /* item with simple list like .jobs */
525    ITEM_HELP       /* use help item=xxx and detect all arguments */
526 } cpl_item_t;
527
528 /* Generator function for command completion.  STATE lets us know whether
529  * to start from scratch; without any state (i.e. STATE == 0), then we
530  * start at the top of the list.
531  */
532 static char *item_generator(const char *text, int state,
533                             const char *item, cpl_item_t type)
534 {
535   static int list_index, len;
536   char *name;
537
538   /* If this is a new word to complete, initialize now.  This includes
539    * saving the length of TEXT for efficiency, and initializing the index
540    *  variable to 0.
541    */
542   if (!state)
543   {
544      list_index = 0;
545      len = strlen(text);
546      switch(type) {
547      case ITEM_ARG:
548         get_items(item);
549         break;
550      case ITEM_HELP:
551         get_arguments(item);
552         break;
553      }
554   }
555
556   /* Return the next name which partially matches from the command list. */
557   while (items && list_index < items->list.size())
558   {
559      name = (char *)items->list[list_index];
560      list_index++;
561
562      if (strncmp(name, text, len) == 0) {
563         char *ret = (char *) actuallymalloc(strlen(name)+1);
564         strcpy(ret, name);
565         return ret;
566      }
567   }
568
569   /* If no names matched, then return NULL. */
570   return ((char *)NULL);
571 }
572
573 /* gobal variables for the type and the item to search
574  * the readline API doesn' permit to pass user data.
575  */
576 static const char *cpl_item;
577 static cpl_item_t cpl_type;
578
579 static char *cpl_generator(const char *text, int state)
580 {
581    return item_generator(text, state, cpl_item, cpl_type);
582 }
583
584 /* this function is used to not use the default filename completion */
585 static char *dummy_completion_function(const char *text, int state)
586 {
587    return NULL;
588 }
589
590 struct cpl_keywords_t {
591    const char *key;
592    const char *cmd;
593 };
594
595 static struct cpl_keywords_t cpl_keywords[] = {
596    {"pool=",      ".pool"          },
597    {"fileset=",   ".fileset"       },
598    {"client=",    ".client"        },
599    {"job=",       ".jobs"          },
600    {"restore_job=",".jobs type=R"  },
601    {"level=",     ".level"         },
602    {"storage=",   ".storage"       },
603    {"schedule=",  ".schedule"      },
604    {"volume=",    ".media"         },
605    {"oldvolume=", ".media"         },
606    {"volstatus=", ".volstatus"     },
607    {"ls",         ".ls"            },
608    {"cd",         ".lsdir"         },
609    {"mark",       ".ls"            },
610    {"m",          ".ls"            },
611    {"unmark",     ".lsmark"        },
612    {"catalog=",   ".catalogs"      },
613    {"actiononpurge=", ".actiononpurge" },
614    {"tags=",      ".tags"          },
615    {"recylepool=", ".pool"         },
616    {"allfrompool=",".pool"         }
617 };
618 #define key_size ((int)(sizeof(cpl_keywords)/sizeof(struct cpl_keywords_t)))
619
620 /* Attempt to complete on the contents of TEXT.  START and END bound the
621  * region of rl_line_buffer that contains the word to complete.  TEXT is
622  * the word to complete.  We can use the entire contents of rl_line_buffer
623  * in case we want to do some simple parsing.  Return the array of matches,
624  * or NULL if there aren't any.
625  */
626 static char **readline_completion(const char *text, int start, int end)
627 {
628    bool found=false;
629    char **matches;
630    char *s, *cmd;
631    matches = (char **)NULL;
632
633    /* If this word is at the start of the line, then it is a command
634     * to complete.  Otherwise it is the name of a file in the current
635     * directory.
636     */
637    s = get_previous_keyword(start, 0);
638    cmd = get_first_keyword();
639    if (s) {
640       for (int i=0; i < key_size; i++) {
641          if (!strcasecmp(s, cpl_keywords[i].key)) {
642             cpl_item = cpl_keywords[i].cmd;
643             cpl_type = ITEM_ARG;
644             matches = rl_completion_matches(text, cpl_generator);
645             found=true;
646             break;
647          }
648       }
649
650       if (!found) {             /* we try to get help with the first command */
651          cpl_item = cmd;
652          cpl_type = ITEM_HELP;
653          /* we don't want to append " " at the end */
654          rl_completion_suppress_append=true;
655          matches = rl_completion_matches(text, cpl_generator);
656       }
657       free(s);
658    } else {                     /* nothing on the line, display all commands */
659       cpl_item = ".help all";
660       cpl_type = ITEM_ARG;
661       matches = rl_completion_matches(text, cpl_generator);
662    }
663    if (cmd) {
664       free(cmd);
665    }
666    return (matches);
667 }
668
669 static char eol = '\0';
670 static int eolcmd(FILE *input, BSOCK *UA_sock)
671 {
672    if ((argc > 1) && (strchr("!$%&'()*+,-/:;<>?[]^`{|}~", argk[1][0]) != NULL)) {
673       eol = argk[1][0];
674    } else if (argc == 1) {
675       eol = '\0';
676    } else {
677       sendit(_("Illegal separator character.\n"));
678    }
679    return 1;
680 }
681
682 /*
683  * Return 1 if OK
684  *        0 if no input
685  *       -1 error (must stop)
686  */
687 int
688 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
689 {
690    static char *line = NULL;
691    static char *next = NULL;
692    static int do_history = 0;
693    char *command;
694
695    if (line == NULL) {
696       do_history = 0;
697       rl_catch_signals = 0;              /* do it ourselves */
698       /* Here, readline does ***real*** malloc
699        * so, be we have to use the real free
700        */
701       line = readline((char *)prompt);   /* cast needed for old readlines */
702       if (!line) {
703          return -1;                      /* error return and exit */
704       }
705       strip_trailing_junk(line);
706       command = line;
707    } else if (next) {
708       command = next + 1;
709    } else {
710      sendit(_("Command logic problem\n"));
711      sock->msglen = 0;
712      sock->msg[0] = 0;
713      return 0;                  /* No input */
714    }
715
716    /*
717     * Split "line" into multiple commands separated by the eol character.
718     *   Each part is pointed to by "next" until finally it becomes null.
719     */
720    if (eol == '\0') {
721       next = NULL;
722    } else {
723       next = strchr(command, eol);
724       if (next) {
725          *next = '\0';
726       }
727    }
728    if (command != line && isatty(fileno(input))) {
729       senditf("%s%s\n", prompt, command);
730
731    } else {
732       /* Send the intput to the output file if needed */
733       if (teein && output != stdout) {
734          fputs(prompt, output);
735          fputs(command, output);
736          fputs("\n", output);
737       }
738    }
739
740    sock->msglen = pm_strcpy(&sock->msg, command);
741    if (sock->msglen) {
742       do_history++;
743    }
744
745    if (!next) {
746       if (do_history) {
747         add_history(line);
748       }
749       actuallyfree(line);       /* allocated by readline() malloc */
750       line = NULL;
751    }
752    return 1;                    /* OK */
753 }
754
755 #else /* no readline, do it ourselves */
756
757 #ifdef HAVE_CONIO
758 static bool bisatty(int fd)
759 {
760    if (no_conio) {
761       return false;
762    }
763    return isatty(fd);
764 }
765 #endif
766
767 /*
768  *   Returns: 1 if data available
769  *            0 if timeout
770  *           -1 if error
771  */
772 static int
773 wait_for_data(int fd, int sec)
774 {
775 #if defined(HAVE_WIN32)
776    return 1;
777 #else
778    fd_set fdset;
779    struct timeval tv;
780
781    tv.tv_sec = sec;
782    tv.tv_usec = 0;
783    for ( ;; ) {
784       FD_ZERO(&fdset);
785       FD_SET((unsigned)fd, &fdset);
786       switch(select(fd + 1, &fdset, NULL, NULL, &tv)) {
787       case 0:                         /* timeout */
788          return 0;
789       case -1:
790          if (errno == EINTR || errno == EAGAIN) {
791             continue;
792          }
793          return -1;                  /* error return */
794       default:
795          return 1;
796       }
797    }
798 #endif
799 }
800
801 /*
802  * Get next input command from terminal.
803  *
804  *   Returns: 1 if got input
805  *            0 if timeout
806  *           -1 if EOF or error
807  */
808 int
809 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
810 {
811    int len;
812    if (!stop) {
813       if (output == stdout || teeout) {
814          sendit(prompt);
815       }
816    }
817 again:
818    switch (wait_for_data(fileno(input), sec)) {
819    case 0:
820       return 0;                    /* timeout */
821    case -1:
822       return -1;                   /* error */
823    default:
824       len = sizeof_pool_memory(sock->msg) - 1;
825       if (stop) {
826          sleep(1);
827          goto again;
828       }
829 #ifdef HAVE_CONIO
830       if (bisatty(fileno(input))) {
831          input_line(sock->msg, len);
832          break;
833       }
834 #endif
835 #ifdef HAVE_WIN32 /* use special console for input on win32 */
836       if (input == stdin) {
837          if (win32_cgets(sock->msg, len) == NULL) {
838             return -1;
839          }
840       }
841       else
842 #endif
843       if (bfgets(sock->msg, input) == NULL) {
844          return -1;
845
846       }
847       break;
848    }
849    if (usrbrk()) {
850       clrbrk();
851    }
852    strip_trailing_junk(sock->msg);
853    sock->msglen = strlen(sock->msg);
854
855    /* Send input to log file if needed */
856    if (teein && output != stdout) {
857       fputs(sock->msg, output);
858       fputs("\n", output);
859    }
860
861    return 1;
862 }
863
864 #endif /* ! HAVE_READLINE */
865
866
867 static int console_update_history(const char *histfile)
868 {
869    int ret=0;
870
871 #ifdef HAVE_READLINE
872 /*
873  * first, try to truncate the history file, and if it
874  * fails, the file is probably not present, and we
875  * can use write_history to create it
876  */
877
878    if (history_truncate_file(histfile, 100) == 0) {
879       ret = append_history(history_length, histfile);
880    } else {
881       ret = write_history(histfile);
882    }
883 #endif
884
885    return ret;
886 }
887
888 static int console_init_history(const char *histfile)
889 {
890    int ret=0;
891
892 #ifdef HAVE_READLINE
893    using_history();
894    ret = read_history(histfile);
895    /* Tell the completer that we want a complete . */
896    rl_completion_entry_function = dummy_completion_function;
897    rl_attempted_completion_function = readline_completion;
898    rl_filename_completion_desired = 0;
899    stifle_history(100);
900 #endif
901
902    return ret;
903 }
904
905 static bool select_director(const char *director, DIRRES **ret_dir, CONRES **ret_cons)
906 {
907    int numcon=0, numdir=0;
908    int i=0, item=0;
909    BSOCK *UA_sock;
910    DIRRES *dir = NULL;
911    CONRES *cons = NULL;
912
913    *ret_cons = NULL;
914    *ret_dir = NULL;
915
916    LockRes();
917    numdir = 0;
918    foreach_res(dir, R_DIRECTOR) {
919       numdir++;
920    }
921    numcon = 0;
922    foreach_res(cons, R_CONSOLE) {
923       numcon++;
924    }
925    UnlockRes();
926
927    if (numdir == 1) {           /* No choose */
928       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
929    }
930
931    if (director) {    /* Command line choice overwrite the no choose option */
932       LockRes();
933       foreach_res(dir, R_DIRECTOR) {
934          if (bstrcasecmp(dir->hdr.name, director)) {
935             break;
936          }
937       }
938       UnlockRes();
939       if (!dir) {               /* Can't find Director used as argument */
940          senditf(_("Can't find %s in Director list\n"), director);
941          return 0;
942       }
943    }
944
945    if (dir == NULL) {               /* prompt for director */
946       UA_sock = new_bsock();
947 try_again:
948       sendit(_("Available Directors:\n"));
949       LockRes();
950       numdir = 0;
951       foreach_res(dir, R_DIRECTOR) {
952          senditf( _("%2d:  %s at %s:%d\n"), 1+numdir++, dir->hdr.name,
953                   dir->address, dir->DIRport);
954       }
955       UnlockRes();
956       if (get_cmd(stdin, _("Select Director by entering a number: "),
957                   UA_sock, 600) < 0)
958       {
959          (void)WSACleanup();               /* Cleanup Windows sockets */
960          return 0;
961       }
962       if (!is_a_number(UA_sock->msg)) {
963          senditf(_("%s is not a number. You must enter a number between "
964                    "1 and %d\n"),
965                  UA_sock->msg, numdir);
966          goto try_again;
967       }
968       item = atoi(UA_sock->msg);
969       if (item < 0 || item > numdir) {
970          senditf(_("You must enter a number between 1 and %d\n"), numdir);
971          goto try_again;
972       }
973       free_bsock(UA_sock);
974       LockRes();
975       for (i=0; i<item; i++) {
976          dir = (DIRRES *)GetNextRes(R_DIRECTOR, (RES *)dir);
977       }
978       UnlockRes();
979    }
980    LockRes();
981    /* Look for a console linked to this director */
982    for (i=0; i<numcon; i++) {
983       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
984       if (cons->director && strcasecmp(cons->director, dir->hdr.name) == 0) {
985          break;
986       }
987       if (i == (numcon - 1)) {
988          cons = NULL;
989       }
990    }
991
992    /* Look for the first non-linked console */
993    if (cons == NULL) {
994       for (i=0; i<numcon; i++) {
995          cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
996          if (cons->director == NULL) {
997             break;
998          }
999          if (i == (numcon - 1)) {
1000             cons = NULL;
1001          }
1002       }
1003    }
1004
1005    /* If no console, take first one */
1006    if (!cons) {
1007       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
1008    }
1009    UnlockRes();
1010
1011    *ret_dir = dir;
1012    *ret_cons = cons;
1013
1014    return 1;
1015 }
1016
1017 /*********************************************************************
1018  *
1019  *         Main Bacula Console -- User Interface Program
1020  *
1021  */
1022 int main(int argc, char *argv[])
1023 {
1024    int ch;
1025    char *director = NULL;
1026    bool list_directors=false;
1027    bool no_signals = false;
1028    bool test_config = false;
1029    JCR jcr;
1030    utime_t heart_beat;
1031
1032    setlocale(LC_ALL, "");
1033    bindtextdomain("bacula", LOCALEDIR);
1034    textdomain("bacula");
1035
1036    init_stack_dump();
1037    lmgr_init_thread();
1038    my_name_is(argc, argv, "bconsole");
1039    init_msg(NULL, NULL);
1040    working_directory = "/tmp";
1041    args = get_pool_memory(PM_FNAME);
1042
1043    while ((ch = getopt(argc, argv, "D:lc:d:nstu:?")) != -1) {
1044       switch (ch) {
1045       case 'D':                    /* Director */
1046          if (director) {
1047             free(director);
1048          }
1049          director = bstrdup(optarg);
1050          break;
1051
1052       case 'l':
1053          list_directors = true;
1054          test_config = true;
1055          break;
1056
1057       case 'c':                    /* configuration file */
1058          if (configfile != NULL) {
1059             free(configfile);
1060          }
1061          configfile = bstrdup(optarg);
1062          break;
1063
1064       case 'd':
1065          if (*optarg == 't') {
1066             dbg_timestamp = true;
1067          } else {
1068             debug_level = atoi(optarg);
1069             if (debug_level <= 0) {
1070                debug_level = 1;
1071             }
1072          }
1073          break;
1074
1075       case 'n':                    /* no conio */
1076          no_conio = true;
1077          break;
1078
1079       case 's':                    /* turn off signals */
1080          no_signals = true;
1081          break;
1082
1083       case 't':
1084          test_config = true;
1085          break;
1086
1087       case 'u':
1088          timeout = atoi(optarg);
1089          break;
1090
1091       case '?':
1092       default:
1093          usage();
1094          exit(1);
1095       }
1096    }
1097    argc -= optind;
1098    argv += optind;
1099
1100    if (!no_signals) {
1101       init_signals(terminate_console);
1102    }
1103
1104
1105 #if !defined(HAVE_WIN32)
1106    /* Override Bacula default signals */
1107    signal(SIGQUIT, SIG_IGN);
1108    signal(SIGTSTP, got_sigstop);
1109    signal(SIGCONT, got_sigcontinue);
1110    signal(SIGTTIN, got_sigtin);
1111    signal(SIGTTOU, got_sigtout);
1112    trapctlc();
1113 #endif
1114
1115    OSDependentInit();
1116
1117    if (argc) {
1118       usage();
1119       exit(1);
1120    }
1121
1122    if (configfile == NULL) {
1123       configfile = bstrdup(CONFIG_FILE);
1124    }
1125
1126    config = new_config_parser();
1127    parse_cons_config(config, configfile, M_ERROR_TERM);
1128
1129    if (init_crypto() != 0) {
1130       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
1131    }
1132
1133    if (!check_resources()) {
1134       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
1135    }
1136
1137    if (!no_conio) {
1138       con_init(stdin);
1139    }
1140
1141    if (list_directors) {
1142       LockRes();
1143       foreach_res(dir, R_DIRECTOR) {
1144          senditf("%s\n", dir->hdr.name);
1145       }
1146       UnlockRes();
1147    }
1148
1149    if (test_config) {
1150       terminate_console(0);
1151       exit(0);
1152    }
1153
1154    memset(&jcr, 0, sizeof(jcr));
1155
1156    (void)WSA_Init();                        /* Initialize Windows sockets */
1157
1158    start_watchdog();                        /* Start socket watchdog */
1159
1160    if (!select_director(director, &dir, &cons)) {
1161       terminate_console(0);
1162       return 1;
1163    }
1164
1165    senditf(_("Connecting to Director %s:%d\n"), dir->address,dir->DIRport);
1166
1167    char buf[1024];
1168    /* Initialize Console TLS context */
1169    if (cons && (cons->tls_enable || cons->tls_require)) {
1170       /* Generate passphrase prompt */
1171       bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ", cons->hdr.name);
1172
1173       /* Initialize TLS context:
1174        * Args: CA certfile, CA certdir, Certfile, Keyfile,
1175        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
1176        */
1177       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
1178          cons->tls_ca_certdir, cons->tls_certfile,
1179          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
1180
1181       if (!cons->tls_ctx) {
1182          senditf(_("Failed to initialize TLS context for Console \"%s\".\n"),
1183             dir->hdr.name);
1184          terminate_console(0);
1185          return 1;
1186       }
1187    }
1188
1189    /* Initialize Director TLS context */
1190    if (dir->tls_enable || dir->tls_require) {
1191       /* Generate passphrase prompt */
1192       bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ", dir->hdr.name);
1193
1194       /* Initialize TLS context:
1195        * Args: CA certfile, CA certdir, Certfile, Keyfile,
1196        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
1197       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
1198          dir->tls_ca_certdir, dir->tls_certfile,
1199          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
1200
1201       if (!dir->tls_ctx) {
1202          senditf(_("Failed to initialize TLS context for Director \"%s\".\n"),
1203             dir->hdr.name);
1204          terminate_console(0);
1205          return 1;
1206       }
1207    }
1208
1209    if (dir->heartbeat_interval) {
1210       heart_beat = dir->heartbeat_interval;
1211    } else if (cons) {
1212       heart_beat = cons->heartbeat_interval;
1213    } else {
1214       heart_beat = 0;
1215    }
1216    if (!UA_sock) {
1217       UA_sock = new_bsock();
1218    }
1219    if (!UA_sock->connect(NULL, 5, 15, heart_beat, "Director daemon", dir->address,
1220                           NULL, dir->DIRport, 0)) {
1221       UA_sock->destroy();
1222       UA_sock = NULL;
1223       terminate_console(0);
1224       return 1;
1225    }
1226    jcr.dir_bsock = UA_sock;
1227
1228    /* If cons==NULL, default console will be used */
1229    if (!authenticate_director(UA_sock, dir, cons)) {
1230       terminate_console(0);
1231       return 1;
1232    }
1233
1234    Dmsg0(40, "Opened connection with Director daemon\n");
1235
1236    sendit(_("Enter a period to cancel a command.\n"));
1237
1238    /* Read/Update history file if HOME exists */
1239    POOL_MEM history_file;
1240
1241    /* Run commands in ~/.bconsolerc if any */
1242    char *env = getenv("HOME");
1243    if (env) {
1244       FILE *fd;
1245       pm_strcpy(&UA_sock->msg, env);
1246       pm_strcat(&UA_sock->msg, "/.bconsolerc");
1247       fd = fopen(UA_sock->msg, "rb");
1248       if (fd) {
1249          read_and_process_input(fd, UA_sock);
1250          fclose(fd);
1251       }
1252
1253       pm_strcpy(history_file, env);
1254       pm_strcat(history_file, "/.bconsole_history");
1255       console_init_history(history_file.c_str());
1256    }
1257
1258    read_and_process_input(stdin, UA_sock);
1259
1260    if (UA_sock) {
1261       UA_sock->signal(BNET_TERMINATE); /* send EOF */
1262       UA_sock->close();
1263    }
1264
1265    if (env) {
1266       console_update_history(history_file.c_str());
1267    }
1268
1269    terminate_console(0);
1270    return 0;
1271 }
1272
1273 /* Cleanup and then exit */
1274 static void terminate_console(int sig)
1275 {
1276
1277    static bool already_here = false;
1278
1279    if (already_here) {                /* avoid recursive temination problems */
1280       exit(1);
1281    }
1282    already_here = true;
1283    stop_watchdog();
1284    config->free_resources();
1285    free(config);
1286    config = NULL;
1287    cleanup_crypto();
1288    free_pool_memory(args);
1289    if (!no_conio) {
1290       con_term();
1291    }
1292 #ifdef HAVE_READLINE
1293    rl_restore_state(NULL);
1294 #endif
1295    (void)WSACleanup();               /* Cleanup Windows sockets */
1296    lmgr_cleanup_main();
1297
1298    if (sig != 0) {
1299       exit(1);
1300    }
1301    return;
1302 }
1303
1304 /*
1305  * Make a quick check to see that we have all the
1306  * resources needed.
1307  */
1308 static int check_resources()
1309 {
1310    bool OK = true;
1311    DIRRES *director;
1312    bool tls_needed;
1313
1314    LockRes();
1315
1316    numdir = 0;
1317    foreach_res(director, R_DIRECTOR) {
1318
1319       numdir++;
1320       /* tls_require implies tls_enable */
1321       if (director->tls_require) {
1322          if (have_tls) {
1323             director->tls_enable = true;
1324          } else {
1325             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1326             OK = false;
1327             continue;
1328          }
1329       }
1330       tls_needed = director->tls_enable || director->tls_authenticate;
1331
1332       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && tls_needed) {
1333          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1334                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
1335                              " At least one CA certificate store is required.\n"),
1336                              director->hdr.name, configfile);
1337          OK = false;
1338       }
1339    }
1340
1341    if (numdir == 0) {
1342       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
1343                           "Without that I don't how to speak to the Director :-(\n"), configfile);
1344       OK = false;
1345    }
1346
1347    CONRES *cons;
1348    /* Loop over Consoles */
1349    foreach_res(cons, R_CONSOLE) {
1350       /* tls_require implies tls_enable */
1351       if (cons->tls_require) {
1352          if (have_tls) {
1353             cons->tls_enable = true;
1354          } else {
1355             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1356             OK = false;
1357             continue;
1358          }
1359       }
1360       tls_needed = cons->tls_enable || cons->tls_authenticate;
1361       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && tls_needed) {
1362          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1363                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
1364                              cons->hdr.name, configfile);
1365          OK = false;
1366       }
1367    }
1368
1369    UnlockRes();
1370
1371    return OK;
1372 }
1373
1374 /* @version */
1375 static int versioncmd(FILE *input, BSOCK *UA_sock)
1376 {
1377    senditf("Version: " VERSION " (" BDATE ") %s %s %s\n",
1378       HOST_OS, DISTNAME, DISTVER);
1379    return 1;
1380 }
1381
1382 /* @input <input-filename> */
1383 static int inputcmd(FILE *input, BSOCK *UA_sock)
1384 {
1385    FILE *fd;
1386
1387    if (argc > 2) {
1388       sendit(_("Too many arguments on input command.\n"));
1389       return 1;
1390    }
1391    if (argc == 1) {
1392       sendit(_("First argument to input command must be a filename.\n"));
1393       return 1;
1394    }
1395    fd = fopen(argk[1], "rb");
1396    if (!fd) {
1397       berrno be;
1398       senditf(_("Cannot open file %s for input. ERR=%s\n"),
1399          argk[1], be.bstrerror());
1400       return 1;
1401    }
1402    read_and_process_input(fd, UA_sock);
1403    fclose(fd);
1404    return 1;
1405 }
1406
1407 /* @tall <output-filename> */
1408 /* Send input/output to both terminal and specified file */
1409 static int teeallcmd(FILE *input, BSOCK *UA_sock)
1410 {
1411    teeout = true;
1412    teein = true;
1413    return do_outputcmd(input, UA_sock);
1414 }
1415
1416 /* @tee <output-filename> */
1417 /* Send output to both terminal and specified file */
1418 static int teecmd(FILE *input, BSOCK *UA_sock)
1419 {
1420    teeout = true;
1421    teein = false;
1422    return do_outputcmd(input, UA_sock);
1423 }
1424
1425 /* @output <output-filename> */
1426 /* Send output to specified "file" */
1427 static int outputcmd(FILE *input, BSOCK *UA_sock)
1428 {
1429    teeout = false;
1430    teein  = false;
1431    return do_outputcmd(input, UA_sock);
1432 }
1433
1434
1435 static int do_outputcmd(FILE *input, BSOCK *UA_sock)
1436 {
1437    FILE *fd;
1438    const char *mode = "a+b";
1439
1440    if (argc > 3) {
1441       sendit(_("Too many arguments on output/tee command.\n"));
1442       return 1;
1443    }
1444    if (argc == 1) {
1445       if (output != stdout) {
1446          fclose(output);
1447          output = stdout;
1448          teeout = false;
1449          teein = false;
1450       }
1451       return 1;
1452    }
1453    if (argc == 3) {
1454       mode = argk[2];
1455    }
1456    fd = fopen(argk[1], mode);
1457    if (!fd) {
1458       berrno be;
1459       senditf(_("Cannot open file %s for output. ERR=%s\n"),
1460          argk[1], be.bstrerror(errno));
1461       return 1;
1462    }
1463    output = fd;
1464    return 1;
1465 }
1466
1467 /*
1468  * @exec "some-command" [wait-seconds]
1469 */
1470 static int execcmd(FILE *input, BSOCK *UA_sock)
1471 {
1472    BPIPE *bpipe;
1473    char line[5000];
1474    int stat;
1475    int wait = 0;
1476
1477    if (argc > 3) {
1478       sendit(_("Too many arguments. Enclose command in double quotes.\n"));
1479       return 1;
1480    }
1481    if (argc == 3) {
1482       wait = atoi(argk[2]);
1483    }
1484    bpipe = open_bpipe(argk[1], wait, "r");
1485    if (!bpipe) {
1486       berrno be;
1487       senditf(_("Cannot popen(\"%s\", \"r\"): ERR=%s\n"),
1488          argk[1], be.bstrerror(errno));
1489       return 1;
1490    }
1491
1492    while (fgets(line, sizeof(line), bpipe->rfd)) {
1493       senditf("%s", line);
1494    }
1495    stat = close_bpipe(bpipe);
1496    if (stat != 0) {
1497       berrno be;
1498       be.set_errno(stat);
1499       senditf(_("@exec error: ERR=%s\n"), be.bstrerror());
1500    }
1501    return 1;
1502 }
1503
1504 /* @echo xxx yyy */
1505 static int echocmd(FILE *input, BSOCK *UA_sock)
1506 {
1507    for (int i=1; i < argc; i++) {
1508       senditf("%s ", argk[i]);
1509    }
1510    sendit("\n");
1511    return 1;
1512 }
1513
1514 /* @quit */
1515 static int quitcmd(FILE *input, BSOCK *UA_sock)
1516 {
1517    return 0;
1518 }
1519
1520 /* @help */
1521 static int helpcmd(FILE *input, BSOCK *UA_sock)
1522 {
1523    int i;
1524    for (i=0; i<comsize; i++) {
1525       senditf("  %-10s %s\n", commands[i].key, commands[i].help);
1526    }
1527    return 1;
1528 }
1529
1530
1531 /* @sleep secs */
1532 static int sleepcmd(FILE *input, BSOCK *UA_sock)
1533 {
1534    if (argc > 1) {
1535       sleep(atoi(argk[1]));
1536    }
1537    return 1;
1538 }
1539
1540 /* @time */
1541 static int timecmd(FILE *input, BSOCK *UA_sock)
1542 {
1543    char sdt[50];
1544    time_t ttime = time(NULL);
1545    struct tm tm;
1546    (void)localtime_r(&ttime, &tm);
1547    strftime(sdt, sizeof(sdt), "%d-%b-%Y %H:%M:%S", &tm);
1548    sendit("\n");
1549    return 1;
1550 }
1551
1552 /*
1553  * Send a line to the output file and or the terminal
1554  */
1555 void senditf(const char *fmt,...)
1556 {
1557    char buf[3000];
1558    va_list arg_ptr;
1559
1560    va_start(arg_ptr, fmt);
1561    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1562    va_end(arg_ptr);
1563    sendit(buf);
1564 }
1565
1566 void sendit(const char *buf)
1567 {
1568 #ifdef CONIO_FIX
1569    char obuf[3000];
1570    if (output == stdout || teeout) {
1571       const char *p, *q;
1572       /*
1573        * Here, we convert every \n into \r\n because the
1574        *  terminal is in raw mode when we are using
1575        *  conio.
1576        */
1577       for (p=q=buf; (p=strchr(q, '\n')); ) {
1578          int len = p - q;
1579          if (len > 0) {
1580             memcpy(obuf, q, len);
1581          }
1582          memcpy(obuf+len, "\r\n", 3);
1583          q = ++p;                    /* point after \n */
1584          fputs(obuf, output);
1585       }
1586       if (*q) {
1587          fputs(q, output);
1588       }
1589       fflush(output);
1590    }
1591    if (output != stdout) {
1592       fputs(buf, output);
1593    }
1594 #else
1595
1596    fputs(buf, output);
1597    fflush(output);
1598    if (teeout) {
1599       fputs(buf, stdout);
1600       fflush(stdout);
1601    }
1602 #endif
1603 }