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