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