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