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