]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/console/console.c
5890afe3dce98cf3e600f8c6cc69b34547534e85
[bacula/bacula] / bacula / src / console / console.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 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
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, "", HOST_OS, DISTNAME, DISTVER);
137 }
138
139
140 extern "C"
141 void got_sigstop(int sig)
142 {
143    stop = true;
144 }
145
146 extern "C"
147 void got_sigcontinue(int sig)
148 {
149    stop = false;
150 }
151
152 extern "C"
153 void got_sigtout(int sig)
154 {
155 // printf("Got tout\n");
156 }
157
158 extern "C"
159 void got_sigtin(int sig)
160 {
161 // printf("Got tin\n");
162 }
163
164
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
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
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
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  */
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 /* Get the first keyword of the line */
413 static char *
414 get_first_keyword()
415 {
416    char *ret=NULL;
417    int len;
418    char *first_space = strchr(rl_line_buffer, ' ');
419    if (first_space) {
420       len = first_space - rl_line_buffer;
421       ret = (char *) malloc((len + 1) * sizeof(char));
422       memcpy(ret, rl_line_buffer, len);
423       ret[len]=0;
424    }
425    return ret;
426 }
427
428 /*
429  * Return the command before the current point.
430  * Set nb to the number of command to skip
431  */
432 static char *
433 get_previous_keyword(int current_point, int nb)
434 {
435    int i, end=-1, start, inquotes=0;
436    char *s=NULL;
437
438    while (nb-- >= 0) {
439       /* first we look for a space before the current word */
440       for (i = current_point; i >= 0; i--) {
441          if (rl_line_buffer[i] == ' ' || rl_line_buffer[i] == '=') {
442             break;
443          }
444       }
445
446       /* find the end of the command */
447       for (; i >= 0; i--) {
448          if (rl_line_buffer[i] != ' ') {
449             end = i;
450             break;
451          }
452       }
453
454       /* no end of string */
455       if (end == -1) {
456          return NULL;
457       }
458
459       /* look for the start of the command */
460       for (start = end; start > 0; start--) {
461          if (rl_line_buffer[start] == '"') {
462             inquotes = !inquotes;
463          }
464          if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0) {
465             break;
466          }
467          current_point = start;
468       }
469    }
470
471    s = (char *)malloc(end - start + 2);
472    memcpy(s, rl_line_buffer + start, end - start + 1);
473    s[end - start + 1] = 0;
474
475    //  printf("=======> %i:%i <%s>\n", start, end, s);
476
477    return s;
478 }
479
480 /* Simple structure that will contain the completion list */
481 struct ItemList {
482    alist list;
483 };
484
485 static ItemList *items = NULL;
486 void init_items()
487 {
488    if (!items) {
489       items = (ItemList*) malloc(sizeof(ItemList));
490       memset(items, 0, sizeof(ItemList));
491
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  */
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 */
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 */
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  */
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
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 */
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 };
662 #define key_size ((int)(sizeof(cpl_keywords)/sizeof(struct cpl_keywords_t)))
663
664 /* Attempt to complete on the contents of TEXT.  START and END bound the
665  * region of rl_line_buffer that contains the word to complete.  TEXT is
666  * the word to complete.  We can use the entire contents of rl_line_buffer
667  * in case we want to do some simple parsing.  Return the array of matches,
668  * or NULL if there aren't any.
669  */
670 static char **readline_completion(const char *text, int start, int end)
671 {
672    bool found=false;
673    char **matches;
674    char *s, *cmd;
675    matches = (char **)NULL;
676
677    /* If this word is at the start of the line, then it is a command
678     * to complete.  Otherwise it is the name of a file in the current
679     * directory.
680     */
681    s = get_previous_keyword(start, 0);
682    cmd = get_first_keyword();
683    if (s) {
684       for (int i=0; i < key_size; i++) {
685          if (!strcasecmp(s, cpl_keywords[i].key)) {
686             cpl_item = cpl_keywords[i].cmd;
687             cpl_type = ITEM_ARG;
688             matches = rl_completion_matches(text, cpl_generator);
689             found=true;
690             break;
691          }
692       }
693
694       if (!found) {             /* we try to get help with the first command */
695          cpl_item = cmd;
696          cpl_type = ITEM_HELP;
697          /* we don't want to append " " at the end */
698          rl_completion_suppress_append=true;
699          matches = rl_completion_matches(text, cpl_generator);
700       }
701       free(s);
702    } else {                     /* nothing on the line, display all commands */
703       cpl_item = ".help all";
704       cpl_type = ITEM_ARG;
705       matches = rl_completion_matches(text, cpl_generator);
706    }
707    if (cmd) {
708       free(cmd);
709    }
710    return (matches);
711 }
712
713 static char eol = '\0';
714 static int eolcmd(FILE *input, BSOCK *UA_sock)
715 {
716    if ((argc > 1) && (strchr("!$%&'()*+,-/:;<>?[]^`{|}~", argk[1][0]) != NULL)) {
717       eol = argk[1][0];
718    } else if (argc == 1) {
719       eol = '\0';
720    } else {
721       sendit(_("Illegal separator character.\n"));
722    }
723    return 1;
724 }
725
726 /*
727  * Return 1 if OK
728  *        0 if no input
729  *       -1 error (must stop)
730  */
731 int
732 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
733 {
734    static char *line = NULL;
735    static char *next = NULL;
736    static int do_history = 0;
737    char *command;
738
739    if (line == NULL) {
740       do_history = 0;
741       rl_catch_signals = 0;              /* do it ourselves */
742       /* Here, readline does ***real*** malloc
743        * so, be we have to use the real free
744        */
745       line = readline((char *)prompt);   /* cast needed for old readlines */
746       if (!line) {
747          return -1;                      /* error return and exit */
748       }
749       strip_trailing_junk(line);
750       command = line;
751    } else if (next) {
752       command = next + 1;
753    } else {
754      sendit(_("Command logic problem\n"));
755      sock->msglen = 0;
756      sock->msg[0] = 0;
757      return 0;                  /* No input */
758    }
759
760    /*
761     * Split "line" into multiple commands separated by the eol character.
762     *   Each part is pointed to by "next" until finally it becomes null.
763     */
764    if (eol == '\0') {
765       next = NULL;
766    } else {
767       next = strchr(command, eol);
768       if (next) {
769          *next = '\0';
770       }
771    }
772    if (command != line && isatty(fileno(input))) {
773       senditf("%s%s\n", prompt, command);
774
775    } else {
776       /* Send the intput to the output file if needed */
777       if (teein && output != stdout) {
778          fputs(prompt, output);
779          fputs(command, output);
780          fputs("\n", output);
781       }
782    }
783
784    sock->msglen = pm_strcpy(&sock->msg, command);
785    if (sock->msglen) {
786       do_history++;
787    }
788
789    if (!next) {
790       if (do_history) {
791         add_history(line);
792       }
793       actuallyfree(line);       /* allocated by readline() malloc */
794       line = NULL;
795    }
796    return 1;                    /* OK */
797 }
798
799 #else /* no readline, do it ourselves */
800
801 #ifdef HAVE_CONIO
802 static bool bisatty(int fd)
803 {
804    if (no_conio) {
805       return false;
806    }
807    return isatty(fd);
808 }
809 #endif
810
811 /*
812  *   Returns: 1 if data available
813  *            0 if timeout
814  *           -1 if error
815  */
816 static int
817 wait_for_data(int fd, int sec)
818 {
819 #if defined(HAVE_WIN32)
820    return 1;
821 #else
822    for ( ;; ) {
823       switch(fd_wait_data(fd, WAIT_READ, sec, 0)) {
824       case 0:                         /* timeout */
825          return 0;
826       case -1:
827          if (errno == EINTR || errno == EAGAIN) {
828             continue;
829          }
830          return -1;                  /* error return */
831       default:
832          return 1;
833       }
834    }
835 #endif
836 }
837
838 /*
839  * Get next input command from terminal.
840  *
841  *   Returns: 1 if got input
842  *            0 if timeout
843  *           -1 if EOF or error
844  */
845 int
846 get_cmd(FILE *input, const char *prompt, BSOCK *sock, int sec)
847 {
848    if (!stop) {
849       if (output == stdout || teeout) {
850          sendit(prompt);
851       }
852    }
853 again:
854    switch (wait_for_data(fileno(input), sec)) {
855    case 0:
856       return 0;                    /* timeout */
857    case -1:
858       return -1;                   /* error */
859    default:
860       if (stop) {
861          sleep(1);
862          goto again;
863       }
864 #ifdef HAVE_CONIO
865       if (bisatty(fileno(input))) {
866          input_line(sock->msg, sizeof_pool_memory(sock->msg)-1);
867          break;
868       }
869 #endif
870 #ifdef HAVE_WIN32 /* use special console for input on win32 */
871       if (input == stdin) {
872          if (win32_cgets(sock->msg, sizeof_pool_memory(sock->msg)-1) == NULL) {
873             return -1;
874          }
875       }
876       else
877 #endif
878       if (bfgets(sock->msg, input) == NULL) {
879          return -1;
880
881       }
882       break;
883    }
884    if (usrbrk()) {
885       clrbrk();
886    }
887    strip_trailing_junk(sock->msg);
888    sock->msglen = strlen(sock->msg);
889
890    /* Send input to log file if needed */
891    if (teein && output != stdout) {
892       fputs(sock->msg, output);
893       fputs("\n", output);
894    }
895
896    return 1;
897 }
898
899 #endif /* ! HAVE_READLINE */
900
901 /* Routine to return true if user types break */
902 int usrbrk()
903 {
904    return brkflg;
905 }
906
907 /* Clear break flag */
908 void clrbrk()
909 {
910    brkflg = 0;
911 }
912
913 /* Interrupt caught here */
914 static void sigintcatcher(int sig)
915 {
916    brkflg++;
917    if (brkflg > 3) {
918       terminate_console(sig);
919    }
920    signal(SIGINT, sigintcatcher);
921 }
922
923 /* Trap Ctl-C */
924 void trapctlc()
925 {
926    signal(SIGINT, sigintcatcher);
927 }
928
929 static int console_update_history(const char *histfile)
930 {
931    int ret=0;
932
933 #ifdef HAVE_READLINE
934 /*
935  * first, try to truncate the history file, and if it
936  * fails, the file is probably not present, and we
937  * can use write_history to create it
938  */
939
940    if (history_truncate_file(histfile, 100) == 0) {
941       ret = append_history(history_length, histfile);
942    } else {
943       ret = write_history(histfile);
944    }
945 #endif
946
947    return ret;
948 }
949
950 static int console_init_history(const char *histfile)
951 {
952    int ret=0;
953
954 #ifdef HAVE_READLINE
955    using_history();
956    ret = read_history(histfile);
957    /* Tell the completer that we want a complete . */
958    rl_completion_entry_function = dummy_completion_function;
959    rl_attempted_completion_function = readline_completion;
960    rl_filename_completion_desired = 0;
961    stifle_history(100);
962 #endif
963
964    return ret;
965 }
966
967 static bool select_director(const char *director, const char *console,
968                             DIRRES **ret_dir, CONRES **ret_cons)
969 {
970    int numcon=0, numdir=0;
971    int i=0, item=0;
972    BSOCK *UA_sock;
973    DIRRES *dir = NULL;
974    CONRES *cons = NULL;
975
976    *ret_cons = NULL;
977    *ret_dir = NULL;
978
979    LockRes();
980    numdir = 0;
981    foreach_res(dir, R_DIRECTOR) {
982       numdir++;
983    }
984    numcon = 0;
985    foreach_res(cons, R_CONSOLE) {
986       numcon++;
987    }
988    UnlockRes();
989
990    if (numdir == 1) {           /* No choose */
991       dir = (DIRRES *)GetNextRes(R_DIRECTOR, NULL);
992    }
993
994    if (director) {    /* Command line choice overwrite the no choose option */
995       LockRes();
996       foreach_res(dir, R_DIRECTOR) {
997          if (bstrcasecmp(dir->hdr.name, director)) {
998             break;
999          }
1000       }
1001       UnlockRes();
1002       if (!dir) {               /* Can't find Director used as argument */
1003          senditf(_("Can't find %s in Director list\n"), director);
1004          return 0;
1005       }
1006    }
1007
1008    if (dir == NULL) {               /* prompt for director */
1009       UA_sock = new_bsock();
1010 try_again:
1011       sendit(_("Available Directors:\n"));
1012       LockRes();
1013       numdir = 0;
1014       foreach_res(dir, R_DIRECTOR) {
1015          senditf( _("%2d:  %s at %s:%d\n"), 1+numdir++, dir->hdr.name,
1016                   dir->address, dir->DIRport);
1017       }
1018       UnlockRes();
1019       if (get_cmd(stdin, _("Select Director by entering a number: "),
1020                   UA_sock, 600) < 0)
1021       {
1022          (void)WSACleanup();               /* Cleanup Windows sockets */
1023          return 0;
1024       }
1025       if (!is_a_number(UA_sock->msg)) {
1026          senditf(_("%s is not a number. You must enter a number between "
1027                    "1 and %d\n"),
1028                  UA_sock->msg, numdir);
1029          goto try_again;
1030       }
1031       item = atoi(UA_sock->msg);
1032       if (item < 0 || item > numdir) {
1033          senditf(_("You must enter a number between 1 and %d\n"), numdir);
1034          goto try_again;
1035       }
1036       free_bsock(UA_sock);
1037       LockRes();
1038       for (i=0; i<item; i++) {
1039          dir = (DIRRES *)GetNextRes(R_DIRECTOR, (RES *)dir);
1040       }
1041       UnlockRes();
1042    }
1043    LockRes();
1044    /* Look for a console linked to this director */
1045    for (i=0; i<numcon; i++) {
1046       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
1047       if (console) {
1048          if (strcmp(cons->hdr.name, console) == 0) {
1049             break;
1050          }
1051       } else if (cons->director && strcasecmp(cons->director, dir->hdr.name) == 0) {
1052          break;
1053       }
1054       if (i == (numcon - 1)) {
1055          cons = NULL;
1056       }
1057    }
1058
1059    if (cons == NULL && console != NULL) {
1060       UnlockRes();
1061       senditf(_("Can't find %s in Console list\n"), console);
1062       return 0;
1063    }
1064
1065    /* Look for the first non-linked console */
1066    if (cons == NULL) {
1067       for (i=0; i<numcon; i++) {
1068          cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)cons);
1069          if (cons->director == NULL) {
1070             break;
1071          }
1072          if (i == (numcon - 1)) {
1073             cons = NULL;
1074          }
1075       }
1076    }
1077
1078    /* If no console, take first one */
1079    if (!cons) {
1080       cons = (CONRES *)GetNextRes(R_CONSOLE, (RES *)NULL);
1081    }
1082    UnlockRes();
1083
1084    *ret_dir = dir;
1085    *ret_cons = cons;
1086
1087    return 1;
1088 }
1089
1090 /*********************************************************************
1091  *
1092  *         Main Bacula Console -- User Interface Program
1093  *
1094  */
1095 int main(int argc, char *argv[])
1096 {
1097    int ch;
1098    char *director = NULL;
1099    char *console = NULL;
1100    bool list_directors=false, list_consoles=false;
1101    bool no_signals = false;
1102    bool test_config = false;
1103    JCR jcr;
1104    utime_t heart_beat;
1105
1106    setlocale(LC_ALL, "");
1107    bindtextdomain("bacula", LOCALEDIR);
1108    textdomain("bacula");
1109
1110    init_stack_dump();
1111    lmgr_init_thread();
1112    my_name_is(argc, argv, "bconsole");
1113    init_msg(NULL, NULL);
1114    working_directory = "/tmp";
1115    args = get_pool_memory(PM_FNAME);
1116
1117    while ((ch = getopt(argc, argv, "D:lc:d:nstu:?C:L")) != -1) {
1118       switch (ch) {
1119       case 'D':                    /* Director */
1120          if (director) {
1121             free(director);
1122          }
1123          director = bstrdup(optarg);
1124          break;
1125
1126       case 'C':                    /* Console */
1127          if (console) {
1128             free(console);
1129          }
1130          console = bstrdup(optarg);
1131          break;
1132
1133       case 'L':                    /* Console */
1134          list_consoles = true;
1135          test_config = true;
1136          break;
1137
1138       case 'l':
1139          list_directors = true;
1140          test_config = true;
1141          break;
1142
1143       case 'c':                    /* configuration file */
1144          if (configfile != NULL) {
1145             free(configfile);
1146          }
1147          configfile = bstrdup(optarg);
1148          break;
1149
1150       case 'd':
1151          if (*optarg == 't') {
1152             dbg_timestamp = true;
1153          } else {
1154             debug_level = atoi(optarg);
1155             if (debug_level <= 0) {
1156                debug_level = 1;
1157             }
1158          }
1159          break;
1160
1161       case 'n':                    /* no conio */
1162          no_conio = true;
1163          break;
1164
1165       case 's':                    /* turn off signals */
1166          no_signals = true;
1167          break;
1168
1169       case 't':
1170          test_config = true;
1171          break;
1172
1173       case 'u':
1174          timeout = atoi(optarg);
1175          break;
1176
1177       case '?':
1178       default:
1179          usage();
1180          exit(1);
1181       }
1182    }
1183    argc -= optind;
1184    argv += optind;
1185
1186    if (!no_signals) {
1187       init_signals(terminate_console);
1188    }
1189
1190
1191 #if !defined(HAVE_WIN32)
1192    /* Override Bacula default signals */
1193    signal(SIGQUIT, SIG_IGN);
1194    signal(SIGTSTP, got_sigstop);
1195    signal(SIGCONT, got_sigcontinue);
1196    signal(SIGTTIN, got_sigtin);
1197    signal(SIGTTOU, got_sigtout);
1198    trapctlc();
1199 #endif
1200
1201    OSDependentInit();
1202
1203    if (argc) {
1204       usage();
1205       exit(1);
1206    }
1207
1208    if (configfile == NULL) {
1209       configfile = bstrdup(CONFIG_FILE);
1210    }
1211
1212    config = New(CONFIG());
1213    parse_cons_config(config, configfile, M_ERROR_TERM);
1214
1215    if (init_crypto() != 0) {
1216       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
1217    }
1218
1219    if (!check_resources()) {
1220       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
1221    }
1222
1223    if (!no_conio) {
1224       con_init(stdin);
1225    }
1226
1227    if (list_directors) {
1228       LockRes();
1229       foreach_res(dir, R_DIRECTOR) {
1230          senditf("%s\n", dir->hdr.name);
1231       }
1232       UnlockRes();
1233    }
1234
1235    if (list_consoles) {
1236       LockRes();
1237       foreach_res(cons, R_CONSOLE) {
1238          senditf("%s\n", cons->hdr.name);
1239       }
1240       UnlockRes();
1241    }
1242
1243    if (test_config) {
1244       terminate_console(0);
1245       exit(0);
1246    }
1247
1248    memset(&jcr, 0, sizeof(jcr));
1249
1250    (void)WSA_Init();                        /* Initialize Windows sockets */
1251
1252    start_watchdog();                        /* Start socket watchdog */
1253
1254    if (!select_director(director, console, &dir, &cons)) {
1255       terminate_console(0);
1256       return 1;
1257    }
1258
1259    senditf(_("Connecting to Director %s:%d\n"), dir->address,dir->DIRport);
1260
1261    char buf[1024];
1262    /* Initialize Console TLS context */
1263    if (cons && (cons->tls_enable || cons->tls_require)) {
1264       /* Generate passphrase prompt */
1265       bsnprintf(buf, sizeof(buf), "Passphrase for Console \"%s\" TLS private key: ", cons->hdr.name);
1266
1267       /* Initialize TLS context:
1268        * Args: CA certfile, CA certdir, Certfile, Keyfile,
1269        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer
1270        */
1271       cons->tls_ctx = new_tls_context(cons->tls_ca_certfile,
1272          cons->tls_ca_certdir, cons->tls_certfile,
1273          cons->tls_keyfile, tls_pem_callback, &buf, NULL, true);
1274
1275       if (!cons->tls_ctx) {
1276          senditf(_("Failed to initialize TLS context for Console \"%s\".\n"),
1277             dir->hdr.name);
1278          terminate_console(0);
1279          return 1;
1280       }
1281    }
1282
1283    /* Initialize Director TLS context */
1284    if (dir->tls_enable || dir->tls_require) {
1285       /* Generate passphrase prompt */
1286       bsnprintf(buf, sizeof(buf), "Passphrase for Director \"%s\" TLS private key: ", dir->hdr.name);
1287
1288       /* Initialize TLS context:
1289        * Args: CA certfile, CA certdir, Certfile, Keyfile,
1290        * Keyfile PEM Callback, Keyfile CB Userdata, DHfile, Verify Peer */
1291       dir->tls_ctx = new_tls_context(dir->tls_ca_certfile,
1292          dir->tls_ca_certdir, dir->tls_certfile,
1293          dir->tls_keyfile, tls_pem_callback, &buf, NULL, true);
1294
1295       if (!dir->tls_ctx) {
1296          senditf(_("Failed to initialize TLS context for Director \"%s\".\n"),
1297             dir->hdr.name);
1298          terminate_console(0);
1299          return 1;
1300       }
1301    }
1302
1303    if (dir->heartbeat_interval) {
1304       heart_beat = dir->heartbeat_interval;
1305    } else if (cons) {
1306       heart_beat = cons->heartbeat_interval;
1307    } else {
1308       heart_beat = 0;
1309    }
1310    if (!UA_sock) {
1311       UA_sock = new_bsock();
1312    }
1313    if (!UA_sock->connect(NULL, 5, 15, heart_beat, "Director daemon", dir->address,
1314                           NULL, dir->DIRport, 0)) {
1315       UA_sock->destroy();
1316       UA_sock = NULL;
1317       terminate_console(0);
1318       return 1;
1319    }
1320    jcr.dir_bsock = UA_sock;
1321
1322    /* If cons==NULL, default console will be used */
1323    if (!authenticate_director(UA_sock, dir, cons)) {
1324       terminate_console(0);
1325       return 1;
1326    }
1327
1328    Dmsg0(40, "Opened connection with Director daemon\n");
1329
1330    sendit(_("Enter a period to cancel a command.\n"));
1331
1332    /* Read/Update history file if HOME exists */
1333    POOL_MEM history_file;
1334
1335    /* Run commands in ~/.bconsolerc if any */
1336    char *env = getenv("HOME");
1337    if (env) {
1338       FILE *fd;
1339       pm_strcpy(&UA_sock->msg, env);
1340       pm_strcat(&UA_sock->msg, "/.bconsolerc");
1341       fd = fopen(UA_sock->msg, "rb");
1342       if (fd) {
1343          read_and_process_input(fd, UA_sock);
1344          fclose(fd);
1345       }
1346
1347       pm_strcpy(history_file, env);
1348       pm_strcat(history_file, "/.bconsole_history");
1349       console_init_history(history_file.c_str());
1350    }
1351
1352    read_and_process_input(stdin, UA_sock);
1353
1354    if (UA_sock) {
1355       UA_sock->signal(BNET_TERMINATE); /* send EOF */
1356       UA_sock->close();
1357    }
1358
1359    if (env) {
1360       console_update_history(history_file.c_str());
1361    }
1362
1363    terminate_console(0);
1364    return 0;
1365 }
1366
1367 /* Cleanup and then exit */
1368 static void terminate_console(int sig)
1369 {
1370
1371    static bool already_here = false;
1372
1373    if (already_here) {                /* avoid recursive temination problems */
1374       exit(1);
1375    }
1376    already_here = true;
1377    stop_watchdog();
1378    delete(config);
1379    config = NULL;
1380    cleanup_crypto();
1381    free(res_head);
1382    res_head = NULL;
1383    free_pool_memory(args);
1384 #if defined(HAVE_CONIO)
1385    if (!no_conio) {
1386       con_term();
1387    }
1388 #elif defined(HAVE_READLINE)
1389    rl_cleanup_after_signal();
1390 #else /* !HAVE_CONIO && !HAVE_READLINE */
1391 #endif
1392    (void)WSACleanup();               /* Cleanup Windows sockets */
1393    lmgr_cleanup_main();
1394
1395    if (sig != 0) {
1396       exit(1);
1397    }
1398    return;
1399 }
1400
1401 /*
1402  * Make a quick check to see that we have all the
1403  * resources needed.
1404  */
1405 static int check_resources()
1406 {
1407    bool OK = true;
1408    DIRRES *director;
1409    bool tls_needed;
1410
1411    LockRes();
1412
1413    numdir = 0;
1414    foreach_res(director, R_DIRECTOR) {
1415
1416       numdir++;
1417       /* tls_require implies tls_enable */
1418       if (director->tls_require) {
1419          if (have_tls) {
1420             director->tls_enable = true;
1421          } else {
1422             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1423             OK = false;
1424             continue;
1425          }
1426       }
1427       tls_needed = director->tls_enable || director->tls_authenticate;
1428
1429       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && tls_needed) {
1430          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1431                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
1432                              " At least one CA certificate store is required.\n"),
1433                              director->hdr.name, configfile);
1434          OK = false;
1435       }
1436    }
1437
1438    if (numdir == 0) {
1439       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
1440                           "Without that I don't how to speak to the Director :-(\n"), configfile);
1441       OK = false;
1442    }
1443
1444    CONRES *cons;
1445    /* Loop over Consoles */
1446    foreach_res(cons, R_CONSOLE) {
1447       /* tls_require implies tls_enable */
1448       if (cons->tls_require) {
1449          if (have_tls) {
1450             cons->tls_enable = true;
1451          } else {
1452             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
1453             OK = false;
1454             continue;
1455          }
1456       }
1457       tls_needed = cons->tls_enable || cons->tls_authenticate;
1458       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && tls_needed) {
1459          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
1460                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
1461                              cons->hdr.name, configfile);
1462          OK = false;
1463       }
1464    }
1465
1466    UnlockRes();
1467
1468    return OK;
1469 }
1470
1471 /* @version */
1472 static int versioncmd(FILE *input, BSOCK *UA_sock)
1473 {
1474    senditf("Version: " VERSION " (" BDATE ") %s %s %s\n",
1475       HOST_OS, DISTNAME, DISTVER);
1476    return 1;
1477 }
1478
1479 /* @input <input-filename> */
1480 static int inputcmd(FILE *input, BSOCK *UA_sock)
1481 {
1482    FILE *fd;
1483
1484    if (argc > 2) {
1485       sendit(_("Too many arguments on input command.\n"));
1486       return 1;
1487    }
1488    if (argc == 1) {
1489       sendit(_("First argument to input command must be a filename.\n"));
1490       return 1;
1491    }
1492    fd = fopen(argk[1], "rb");
1493    if (!fd) {
1494       berrno be;
1495       senditf(_("Cannot open file %s for input. ERR=%s\n"),
1496          argk[1], be.bstrerror());
1497       return 1;
1498    }
1499    read_and_process_input(fd, UA_sock);
1500    fclose(fd);
1501    return 1;
1502 }
1503
1504 /* @tall <output-filename> */
1505 /* Send input/output to both terminal and specified file */
1506 static int teeallcmd(FILE *input, BSOCK *UA_sock)
1507 {
1508    teeout = true;
1509    teein = true;
1510    return do_outputcmd(input, UA_sock);
1511 }
1512
1513 /* @tee <output-filename> */
1514 /* Send output to both terminal and specified file */
1515 static int teecmd(FILE *input, BSOCK *UA_sock)
1516 {
1517    teeout = true;
1518    teein = false;
1519    return do_outputcmd(input, UA_sock);
1520 }
1521
1522 /* @output <output-filename> */
1523 /* Send output to specified "file" */
1524 static int outputcmd(FILE *input, BSOCK *UA_sock)
1525 {
1526    teeout = false;
1527    teein  = false;
1528    return do_outputcmd(input, UA_sock);
1529 }
1530
1531
1532 static int do_outputcmd(FILE *input, BSOCK *UA_sock)
1533 {
1534    FILE *fd;
1535    const char *mode = "a+b";
1536
1537    if (argc > 3) {
1538       sendit(_("Too many arguments on output/tee command.\n"));
1539       return 1;
1540    }
1541    if (argc == 1) {
1542       if (output != stdout) {
1543          fclose(output);
1544          output = stdout;
1545          teeout = false;
1546          teein = false;
1547       }
1548       return 1;
1549    }
1550    if (argc == 3) {
1551       mode = argk[2];
1552    }
1553    fd = fopen(argk[1], mode);
1554    if (!fd) {
1555       berrno be;
1556       senditf(_("Cannot open file %s for output. ERR=%s\n"),
1557          argk[1], be.bstrerror(errno));
1558       return 1;
1559    }
1560    output = fd;
1561    return 1;
1562 }
1563
1564 /*
1565  * @exec "some-command" [wait-seconds]
1566 */
1567 static int execcmd(FILE *input, BSOCK *UA_sock)
1568 {
1569    BPIPE *bpipe;
1570    char line[5000];
1571    int stat;
1572    int wait = 0;
1573    char *cmd;
1574
1575    if (argc > 3) {
1576       sendit(_("Too many arguments. Enclose command in double quotes.\n"));
1577       return 1;
1578    }
1579
1580    /* old syntax */
1581    if (argc == 3) {
1582       wait = atoi(argk[2]);
1583    }
1584    cmd = argk[1];
1585
1586    /* handle cmd=XXXX and wait=XXXX */
1587    for (int i=1; i<argc; i++) {
1588       if (strcmp(argk[i], "cmd") == 0) {
1589          cmd = argv[i];
1590       }
1591       if (strcmp(argk[i], "wait") == 0) {
1592          wait = atoi(argv[i]);
1593       }
1594    }
1595
1596    bpipe = open_bpipe(cmd, wait, "r");
1597    if (!bpipe) {
1598       berrno be;
1599       senditf(_("Cannot popen(\"%s\", \"r\"): ERR=%s\n"),
1600          argk[1], be.bstrerror(errno));
1601       return 1;
1602    }
1603
1604    while (fgets(line, sizeof(line), bpipe->rfd)) {
1605       senditf("%s", line);
1606    }
1607    stat = close_bpipe(bpipe);
1608    if (stat != 0) {
1609       berrno be;
1610       be.set_errno(stat);
1611       senditf(_("@exec error: ERR=%s\n"), be.bstrerror());
1612    }
1613    return 1;
1614 }
1615
1616 /* @echo xxx yyy */
1617 static int echocmd(FILE *input, BSOCK *UA_sock)
1618 {
1619    for (int i=1; i < argc; i++) {
1620       senditf("%s ", argk[i]);
1621    }
1622    sendit("\n");
1623    return 1;
1624 }
1625
1626 /* @quit */
1627 static int quitcmd(FILE *input, BSOCK *UA_sock)
1628 {
1629    return 0;
1630 }
1631
1632 /* @help */
1633 static int helpcmd(FILE *input, BSOCK *UA_sock)
1634 {
1635    int i;
1636    for (i=0; i<comsize; i++) {
1637       senditf("  %-10s %s\n", commands[i].key, commands[i].help);
1638    }
1639    return 1;
1640 }
1641
1642
1643 /* @sleep secs */
1644 static int sleepcmd(FILE *input, BSOCK *UA_sock)
1645 {
1646    if (argc > 1) {
1647       sleep(atoi(argk[1]));
1648    }
1649    return 1;
1650 }
1651
1652 /* @putfile key /path/to/file
1653  *
1654  * The Key parameter is needed to use the file on the director side.
1655  */
1656 static int putfilecmd(FILE *input, BSOCK *UA_sock)
1657 {
1658    int i = 0;
1659    const char *key = "putfile";
1660    const char *fname;
1661    FILE *fp;
1662
1663    if (argc != 3) {
1664       sendit("Usage: @putfile key file\n");
1665       return 1;
1666    }
1667
1668    key = argk[1];
1669    fname = argk[2];
1670
1671    if (!key || !fname) {
1672       senditf("Syntax error in @putfile command\n");
1673       return 1;
1674    }
1675
1676    fp = fopen(fname, "r");
1677    if (!fp) {
1678       berrno be;
1679       senditf("Unable to open %s. ERR=%s\n", fname, be.bstrerror(errno));
1680       return 1;
1681    }
1682
1683    UA_sock->fsend(".putfile key=\"%s\"", key);
1684
1685    /* Just read the file and send it to the director */
1686    while (!feof(fp)) {
1687       i = fread(UA_sock->msg, 1, sizeof_pool_memory(UA_sock->msg) - 1, fp);
1688       if (i > 0) {
1689          UA_sock->msg[i] = 0;
1690          UA_sock->msglen = i;
1691          UA_sock->send();
1692       }
1693    }
1694
1695    UA_sock->signal(BNET_EOD);
1696    fclose(fp);
1697
1698    /* Get the file name associated */
1699    while (UA_sock->recv() > 0) {
1700       senditf("%s", UA_sock->msg);
1701    }
1702    return 1;
1703 }
1704
1705 /* @time */
1706 static int timecmd(FILE *input, BSOCK *UA_sock)
1707 {
1708    char sdt[50];
1709    time_t ttime = time(NULL);
1710    struct tm tm;
1711    (void)localtime_r(&ttime, &tm);
1712    strftime(sdt, sizeof(sdt), "%d-%b-%Y %H:%M:%S", &tm);
1713    sendit("\n");
1714    return 1;
1715 }
1716
1717 /*
1718  * Send a line to the output file and or the terminal
1719  */
1720 void senditf(const char *fmt,...)
1721 {
1722    char buf[3000];
1723    va_list arg_ptr;
1724
1725    va_start(arg_ptr, fmt);
1726    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
1727    va_end(arg_ptr);
1728    sendit(buf);
1729 }
1730
1731 void sendit(const char *buf)
1732 {
1733 #ifdef CONIO_FIX
1734    char obuf[3000];
1735    if (output == stdout || teeout) {
1736       const char *p, *q;
1737       /*
1738        * Here, we convert every \n into \r\n because the
1739        *  terminal is in raw mode when we are using
1740        *  conio.
1741        */
1742       for (p=q=buf; (p=strchr(q, '\n')); ) {
1743          int len = p - q;
1744          if (len > 0) {
1745             memcpy(obuf, q, len);
1746          }
1747          memcpy(obuf+len, "\r\n", 3);
1748          q = ++p;                    /* point after \n */
1749          fputs(obuf, output);
1750       }
1751       if (*q) {
1752          fputs(q, output);
1753       }
1754       fflush(output);
1755    }
1756    if (output != stdout) {
1757       fputs(buf, output);
1758    }
1759 #else
1760
1761    fputs(buf, output);
1762    fflush(output);
1763    if (teeout) {
1764       fputs(buf, stdout);
1765       fflush(stdout);
1766    }
1767 #endif
1768 }