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