]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/console/console.c
Change copyright as per agreement with FSFE
[bacula/bacula] / bacula / src / console / console.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2015 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    (void)WSACleanup();               /* Cleanup Windows sockets */
1293    lmgr_cleanup_main();
1294
1295    if (sig != 0) {
1296       exit(1);
1297    }
1298    return;
1299 }
1300
1301 /*
1302  * Make a quick check to see that we have all the
1303  * resources needed.
1304  */
1305 static int check_resources()
1306 {
1307    bool OK = true;
1308    DIRRES *director;
1309    bool tls_needed;
1310
1311    LockRes();
1312
1313    numdir = 0;
1314    foreach_res(director, R_DIRECTOR) {
1315
1316       numdir++;
1317       /* tls_require implies tls_enable */
1318       if (director->tls_require) {
1319          if (have_tls) {
1320             director->tls_enable = true;
1321          } else {
1322             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1323             OK = false;
1324             continue;
1325          }
1326       }
1327       tls_needed = director->tls_enable || director->tls_authenticate;
1328
1329       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && tls_needed) {
1330          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1331                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
1332                              " At least one CA certificate store is required.\n"),
1333                              director->hdr.name, configfile);
1334          OK = false;
1335       }
1336    }
1337
1338    if (numdir == 0) {
1339       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
1340                           "Without that I don't how to speak to the Director :-(\n"), configfile);
1341       OK = false;
1342    }
1343
1344    CONRES *cons;
1345    /* Loop over Consoles */
1346    foreach_res(cons, R_CONSOLE) {
1347       /* tls_require implies tls_enable */
1348       if (cons->tls_require) {
1349          if (have_tls) {
1350             cons->tls_enable = true;
1351          } else {
1352             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1353             OK = false;
1354             continue;
1355          }
1356       }
1357       tls_needed = cons->tls_enable || cons->tls_authenticate;
1358       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && tls_needed) {
1359          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1360                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
1361                              cons->hdr.name, configfile);
1362          OK = false;
1363       }
1364    }
1365
1366    UnlockRes();
1367
1368    return OK;
1369 }
1370
1371 /* @version */
1372 static int versioncmd(FILE *input, BSOCK *UA_sock)
1373 {
1374    senditf("Version: " VERSION " (" BDATE ") %s %s %s\n",
1375       HOST_OS, DISTNAME, DISTVER);
1376    return 1;
1377 }
1378
1379 /* @input <input-filename> */
1380 static int inputcmd(FILE *input, BSOCK *UA_sock)
1381 {
1382    FILE *fd;
1383
1384    if (argc > 2) {
1385       sendit(_("Too many arguments on input command.\n"));
1386       return 1;
1387    }
1388    if (argc == 1) {
1389       sendit(_("First argument to input command must be a filename.\n"));
1390       return 1;
1391    }
1392    fd = fopen(argk[1], "rb");
1393    if (!fd) {
1394       berrno be;
1395       senditf(_("Cannot open file %s for input. ERR=%s\n"),
1396          argk[1], be.bstrerror());
1397       return 1;
1398    }
1399    read_and_process_input(fd, UA_sock);
1400    fclose(fd);
1401    return 1;
1402 }
1403
1404 /* @tall <output-filename> */
1405 /* Send input/output to both terminal and specified file */
1406 static int teeallcmd(FILE *input, BSOCK *UA_sock)
1407 {
1408    teeout = true;
1409    teein = true;
1410    return do_outputcmd(input, UA_sock);
1411 }
1412
1413 /* @tee <output-filename> */
1414 /* Send output to both terminal and specified file */
1415 static int teecmd(FILE *input, BSOCK *UA_sock)
1416 {
1417    teeout = true;
1418    teein = false;
1419    return do_outputcmd(input, UA_sock);
1420 }
1421
1422 /* @output <output-filename> */
1423 /* Send output to specified "file" */
1424 static int outputcmd(FILE *input, BSOCK *UA_sock)
1425 {
1426    teeout = false;
1427    teein  = false;
1428    return do_outputcmd(input, UA_sock);
1429 }
1430
1431
1432 static int do_outputcmd(FILE *input, BSOCK *UA_sock)
1433 {
1434    FILE *fd;
1435    const char *mode = "a+b";
1436
1437    if (argc > 3) {
1438       sendit(_("Too many arguments on output/tee command.\n"));
1439       return 1;
1440    }
1441    if (argc == 1) {
1442       if (output != stdout) {
1443          fclose(output);
1444          output = stdout;
1445          teeout = false;
1446          teein = false;
1447       }
1448       return 1;
1449    }
1450    if (argc == 3) {
1451       mode = argk[2];
1452    }
1453    fd = fopen(argk[1], mode);
1454    if (!fd) {
1455       berrno be;
1456       senditf(_("Cannot open file %s for output. ERR=%s\n"),
1457          argk[1], be.bstrerror(errno));
1458       return 1;
1459    }
1460    output = fd;
1461    return 1;
1462 }
1463
1464 /*
1465  * @exec "some-command" [wait-seconds]
1466 */
1467 static int execcmd(FILE *input, BSOCK *UA_sock)
1468 {
1469    BPIPE *bpipe;
1470    char line[5000];
1471    int stat;
1472    int wait = 0;
1473
1474    if (argc > 3) {
1475       sendit(_("Too many arguments. Enclose command in double quotes.\n"));
1476       return 1;
1477    }
1478    if (argc == 3) {
1479       wait = atoi(argk[2]);
1480    }
1481    bpipe = open_bpipe(argk[1], wait, "r");
1482    if (!bpipe) {
1483       berrno be;
1484       senditf(_("Cannot popen(\"%s\", \"r\"): ERR=%s\n"),
1485          argk[1], be.bstrerror(errno));
1486       return 1;
1487    }
1488
1489    while (fgets(line, sizeof(line), bpipe->rfd)) {
1490       senditf("%s", line);
1491    }
1492    stat = close_bpipe(bpipe);
1493    if (stat != 0) {
1494       berrno be;
1495       be.set_errno(stat);
1496       senditf(_("@exec error: ERR=%s\n"), be.bstrerror());
1497    }
1498    return 1;
1499 }
1500
1501 /* @echo xxx yyy */
1502 static int echocmd(FILE *input, BSOCK *UA_sock)
1503 {
1504    for (int i=1; i < argc; i++) {
1505       senditf("%s ", argk[i]);
1506    }
1507    sendit("\n");
1508    return 1;
1509 }
1510
1511 /* @quit */
1512 static int quitcmd(FILE *input, BSOCK *UA_sock)
1513 {
1514    return 0;
1515 }
1516
1517 /* @help */
1518 static int helpcmd(FILE *input, BSOCK *UA_sock)
1519 {
1520    int i;
1521    for (i=0; i<comsize; i++) {
1522       senditf("  %-10s %s\n", commands[i].key, commands[i].help);
1523    }
1524    return 1;
1525 }
1526
1527
1528 /* @sleep secs */
1529 static int sleepcmd(FILE *input, BSOCK *UA_sock)
1530 {
1531    if (argc > 1) {
1532       sleep(atoi(argk[1]));
1533    }
1534    return 1;
1535 }
1536
1537 /* @time */
1538 static int timecmd(FILE *input, BSOCK *UA_sock)
1539 {
1540    char sdt[50];
1541    time_t ttime = time(NULL);
1542    struct tm tm;
1543    (void)localtime_r(&ttime, &tm);
1544    strftime(sdt, sizeof(sdt), "%d-%b-%Y %H:%M:%S", &tm);
1545    sendit("\n");
1546    return 1;
1547 }
1548
1549 /*
1550  * Send a line to the output file and or the terminal
1551  */
1552 void senditf(const char *fmt,...)
1553 {
1554    char buf[3000];
1555    va_list arg_ptr;
1556
1557    va_start(arg_ptr, fmt);
1558    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1559    va_end(arg_ptr);
1560    sendit(buf);
1561 }
1562
1563 void sendit(const char *buf)
1564 {
1565 #ifdef CONIO_FIX
1566    char obuf[3000];
1567    if (output == stdout || teeout) {
1568       const char *p, *q;
1569       /*
1570        * Here, we convert every \n into \r\n because the
1571        *  terminal is in raw mode when we are using
1572        *  conio.
1573        */
1574       for (p=q=buf; (p=strchr(q, '\n')); ) {
1575          int len = p - q;
1576          if (len > 0) {
1577             memcpy(obuf, q, len);
1578          }
1579          memcpy(obuf+len, "\r\n", 3);
1580          q = ++p;                    /* point after \n */
1581          fputs(obuf, output);
1582       }
1583       if (*q) {
1584          fputs(q, output);
1585       }
1586       fflush(output);
1587    }
1588    if (output != stdout) {
1589       fputs(buf, output);
1590    }
1591 #else
1592
1593    fputs(buf, output);
1594    fflush(output);
1595    if (teeout) {
1596       fputs(buf, stdout);
1597       fflush(stdout);
1598    }
1599 #endif
1600 }