1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2012 Planets Communications B.V.
6 Copyright (C) 2013-2018 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, September MM
25 */
26 /**
27 * @file
28 * Bareos Console interface to the Director
29 */
30
31 #include "include/bareos.h"
32 #include "console/console_conf.h"
33 #include "console/console_globals.h"
34 #include "console/auth_pam.h"
35 #include "console/console_output.h"
36 #include "console/connect_to_director.h"
37 #include "include/jcr.h"
38 #include "lib/bnet.h"
39 #include "lib/bstringlist.h"
40 #include "lib/qualified_resource_name_type_converter.h"
41 #include <stdio.h>
42 #include <fstream>
43 #include <string>
44
45 #define ConInit(x)
46 #define ConTerm()
47 #define ConSetZedKeys()
48 #define trapctlc()
49 #define clrbrk()
50 #define usrbrk() 0
51
52 #if defined(HAVE_WIN32)
53 #define isatty(fd) (fd==0)
54 #endif
55
56 using namespace console;
57
58 static void TerminateConsole(int sig);
59 static int CheckResources();
60 int GetCmd(FILE *input, const char *prompt, BareosSocket *sock, int sec);
61 static int DoOutputcmd(FILE *input, BareosSocket *UA_sock);
62
63 extern "C" void GotSigstop(int sig);
64 extern "C" void GotSigcontinue(int sig);
65 extern "C" void GotSigtout(int sig);
66 extern "C" void GotSigtin(int sig);
67
68 static char *configfile = NULL;
69 static BareosSocket *UA_sock = NULL;
70 static bool stop = false;
71 static int timeout = 0;
72 static int argc;
73 static int numdir;
74 static POOLMEM *args;
75 static char *argk[MAX_CMD_ARGS];
76 static char *argv[MAX_CMD_ARGS];
77 static bool file_selection = false;
78
79 #if defined(HAVE_PAM)
80 static bool force_send_pam_credentials_unencrypted = false;
81 static bool use_pam_credentials_file = false;
82 static std::string pam_credentials_filename;
83 static const std::string program_arguments {"D:lc:d:np:ostu:x:?"};
84 #else
85 static const std::string program_arguments {"D:lc:d:nstu:x:?"};
86 #endif
87
88 /* Command prototypes */
89 static int Versioncmd(FILE *input, BareosSocket *UA_sock);
90 static int InputCmd(FILE *input, BareosSocket *UA_sock);
91 static int OutputCmd(FILE *input, BareosSocket *UA_sock);
92 static int TeeCmd(FILE *input, BareosSocket *UA_sock);
93 static int QuitCmd(FILE *input, BareosSocket *UA_sock);
94 static int HelpCmd(FILE *input, BareosSocket *UA_sock);
95 static int EchoCmd(FILE *input, BareosSocket *UA_sock);
96 static int TimeCmd(FILE *input, BareosSocket *UA_sock);
97 static int SleepCmd(FILE *input, BareosSocket *UA_sock);
98 static int ExecCmd(FILE *input, BareosSocket *UA_sock);
99 static int EolCmd(FILE *input, BareosSocket *UA_sock);
100
101 #ifndef HAVE_REGEX_H
102 #include "lib/bregex.h"
103 #else
104 #include <regex.h>
105 #endif
106
usage()107 static void usage()
108 {
109 fprintf(stderr, _(
110 PROG_COPYRIGHT
111 "\nVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
112 "Usage: bconsole [-s] [-c config_file] [-d debug_level]\n"
113 " -D <dir> select a Director\n"
114 " -l list Directors defined\n"
115 " -c <path> specify configuration file or directory\n"
116 #if defined(HAVE_PAM)
117 " -p <file> specify pam credentials file\n"
118 " -o send pam credentials over unencrypted connection\n"
119 #endif
120 " -d <nn> set debug level to <nn>\n"
121 " -dt print timestamp in debug output\n"
122 " -s no signals\n"
123 " -u <nn> set command execution timeout to <nn> seconds\n"
124 " -t test - read configuration and exit\n"
125 " -xc print configuration and exit\n"
126 " -xs print configuration file schema in JSON format and exit\n"
127 " -? print this message.\n"
128 "\n"), 2000, HOST_OS, DISTNAME, DISTVER);
129 }
130
131 extern "C"
GotSigstop(int sig)132 void GotSigstop(int sig)
133 {
134 stop = true;
135 }
136
137 extern "C"
GotSigcontinue(int sig)138 void GotSigcontinue(int sig)
139 {
140 stop = false;
141 }
142
143 extern "C"
GotSigtout(int sig)144 void GotSigtout(int sig)
145 {
146 // printf("Got tout\n");
147 }
148
149 extern "C"
GotSigtin(int sig)150 void GotSigtin(int sig)
151 {
152 // printf("Got tin\n");
153 }
154
ZedKeyscmd(FILE * input,BareosSocket * UA_sock)155 static int ZedKeyscmd(FILE *input, BareosSocket *UA_sock)
156 {
157 ConSetZedKeys();
158 return 1;
159 }
160
161 /**
162 * These are the @commands that run only in bconsole
163 */
164 struct cmdstruct {
165 const char *key;
166 int (*func)(FILE *input, BareosSocket *UA_sock);
167 const char *help;
168 };
169 static struct cmdstruct commands[] = {
170 { N_("input"), InputCmd, _("input from file")},
171 { N_("output"), OutputCmd, _("output to file")},
172 { N_("quit"), QuitCmd, _("quit")},
173 { N_("tee"), TeeCmd, _("output to file and terminal")},
174 { N_("sleep"), SleepCmd, _("sleep specified time")},
175 { N_("time"), TimeCmd, _("print current time")},
176 { N_("version"), Versioncmd, _("print Console's version")},
177 { N_("echo"), EchoCmd, _("echo command string")},
178 { N_("exec"), ExecCmd, _("execute an external command")},
179 { N_("exit"), QuitCmd, _("exit = quit")},
180 { N_("zed_keys"), ZedKeyscmd, _("zed_keys = use zed keys instead of bash keys")},
181 { N_("help"), HelpCmd, _("help listing")},
182 { N_("separator"), EolCmd, _("set command separator")},
183 };
184 #define comsize ((int)(sizeof(commands)/sizeof(struct cmdstruct)))
185
Do_a_command(FILE * input,BareosSocket * UA_sock)186 static int Do_a_command(FILE *input, BareosSocket *UA_sock)
187 {
188 unsigned int i;
189 int status;
190 int found;
191 int len;
192 char *cmd;
193
194 found = 0;
195 status = 1;
196
197 Dmsg1(120, "Command: %s\n", UA_sock->msg);
198 if (argc == 0) {
199 return 1;
200 }
201
202 cmd = argk[0]+1;
203 if (*cmd == '#') { /* comment */
204 return 1;
205 }
206 len = strlen(cmd);
207 for (i=0; i<comsize; i++) { /* search for command */
208 if (bstrncasecmp(cmd, _(commands[i].key), len)) {
209 status = (*commands[i].func)(input, UA_sock); /* go execute command */
210 found = 1;
211 break;
212 }
213 }
214 if (!found) {
215 PmStrcat(UA_sock->msg, _(": is an invalid command\n"));
216 UA_sock->message_length = strlen(UA_sock->msg);
217 ConsoleOutput(UA_sock->msg);
218 }
219 return status;
220 }
221
ReadAndProcessInput(FILE * input,BareosSocket * UA_sock)222 static void ReadAndProcessInput(FILE *input, BareosSocket *UA_sock)
223 {
224 const char *prompt = "*";
225 bool at_prompt = false;
226 int tty_input = isatty(fileno(input));
227 int status;
228 btimer_t *tid = NULL;
229
230 while (1) {
231 if (at_prompt) { /* don't prompt multiple times */
232 prompt = "";
233 } else {
234 prompt = "*";
235 at_prompt = true;
236 }
237 if (tty_input) {
238 status = GetCmd(input, prompt, UA_sock, 30);
239 if (usrbrk() == 1) {
240 clrbrk();
241 }
242 if (usrbrk()) {
243 break;
244 }
245 } else {
246 /*
247 * Reading input from a file
248 */
249 int len = SizeofPoolMemory(UA_sock->msg) - 1;
250 if (usrbrk()) {
251 break;
252 }
253 if (fgets(UA_sock->msg, len, input) == NULL) {
254 status = -1;
255 } else {
256 ConsoleOutput(UA_sock->msg); /* echo to terminal */
257 StripTrailingJunk(UA_sock->msg);
258 UA_sock->message_length = strlen(UA_sock->msg);
259 status = 1;
260 }
261 }
262 if (status < 0) {
263 break; /* error or interrupt */
264 } else if (status == 0) { /* timeout */
265 if (bstrcmp(prompt, "*")) {
266 tid = StartBsockTimer(UA_sock, timeout);
267 UA_sock->fsend(".messages");
268 StopBsockTimer(tid);
269 } else {
270 continue;
271 }
272 } else {
273 at_prompt = false;
274 /*
275 * @ => internal command for us
276 */
277 if (UA_sock->msg[0] == '@') {
278 ParseArgs(UA_sock->msg, args, &argc, argk, argv, MAX_CMD_ARGS);
279 if (!Do_a_command(input, UA_sock)) {
280 break;
281 }
282 continue;
283 }
284 tid = StartBsockTimer(UA_sock, timeout);
285 if (!UA_sock->send()) { /* send command */
286 StopBsockTimer(tid);
287 break; /* error */
288 }
289 StopBsockTimer(tid);
290 }
291
292 if (bstrcmp(UA_sock->msg, ".quit") || bstrcmp(UA_sock->msg, ".exit")) {
293 break;
294 }
295
296 tid = StartBsockTimer(UA_sock, timeout);
297 while ((status = UA_sock->recv()) >= 0 ||
298 ((status == BNET_SIGNAL) && (
299 (UA_sock->message_length != BNET_EOD) &&
300 (UA_sock->message_length != BNET_MAIN_PROMPT) &&
301 (UA_sock->message_length != BNET_SUB_PROMPT)))) {
302 if (status == BNET_SIGNAL) {
303 if (UA_sock->message_length == BNET_START_RTREE) {
304 file_selection = true;
305 } else if (UA_sock->message_length == BNET_END_RTREE) {
306 file_selection = false;
307 }
308 continue;
309 }
310
311 if (at_prompt) {
312 if (!stop) {
313 ConsoleOutput("\n");
314 }
315 at_prompt = false;
316 }
317
318 /*
319 * Suppress output if running
320 * in background or user hit ctl-c
321 */
322 if (!stop && !usrbrk()) {
323 if (UA_sock->msg) {
324 ConsoleOutput(UA_sock->msg);
325 }
326 }
327 }
328 StopBsockTimer(tid);
329
330 if (usrbrk() > 1) {
331 break;
332 } else {
333 clrbrk();
334 }
335 if (!stop) {
336 fflush(stdout);
337 }
338
339 if (IsBnetStop(UA_sock)) {
340 break; /* error or term */
341 } else if (status == BNET_SIGNAL) {
342 if (UA_sock->message_length == BNET_SUB_PROMPT) {
343 at_prompt = true;
344 }
345 Dmsg1(100, "Got poll %s\n", BnetSigToAscii(UA_sock));
346 }
347 }
348 }
349
350
351 #include <readline/readline.h>
352 #include <readline/history.h>
353 #include "lib/edit.h"
354 #include "lib/tls_openssl.h"
355 #include "lib/bsignal.h"
356
357 /**
358 * Get the first keyword of the line
359 */
get_first_keyword()360 static char *get_first_keyword()
361 {
362 char *ret = NULL;
363 int len;
364 char *first_space = strchr(rl_line_buffer, ' ');
365 if (first_space) {
366 len = first_space - rl_line_buffer;
367 ret = (char *) malloc((len + 1) * sizeof(char));
368 memcpy(ret, rl_line_buffer, len);
369 ret[len]=0;
370 }
371 return ret;
372 }
373
374 /**
375 * Return the command before the current point.
376 * Set nb to the number of command to skip
377 */
get_previous_keyword(int current_point,int nb)378 static char *get_previous_keyword(int current_point, int nb)
379 {
380 int i, end=-1, start, inquotes=0;
381 char *s = NULL;
382
383 while (nb-- >= 0) {
384 /*
385 * First we look for a space before the current word
386 */
387 for (i = current_point; i >= 0; i--) {
388 if (rl_line_buffer[i] == ' ' || rl_line_buffer[i] == '=') {
389 break;
390 }
391 }
392
393 for (; i >= 0; i--) {
394 if (rl_line_buffer[i] != ' ') {
395 end = i; /* end of command */
396 break;
397 }
398 }
399
400 if (end == -1) {
401 return NULL; /* no end found */
402 }
403
404 for (start = end; start > 0; start--) {
405 if (rl_line_buffer[start] == '"') {
406 inquotes = !inquotes;
407 }
408 if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0) {
409 break;
410 }
411 current_point = start; /* start of command */
412 }
413 }
414
415 s = (char *)malloc(end - start + 2);
416 memcpy(s, rl_line_buffer + start, end - start + 1);
417 s[end - start + 1] = 0;
418
419 // printf("=======> %i:%i <%s>\n", start, end, s);
420
421 return s;
422 }
423
424 struct ItemList {
425 alist list; /* holds the completion list */
426 };
427
428 static ItemList *items = NULL;
init_items()429 void init_items()
430 {
431 if (!items) {
432 items = (ItemList*) malloc(sizeof(ItemList));
433 items = new(items) ItemList(); /* placement new instead of memset */
434 } else {
435 items->list.destroy();
436 items->list.init();
437 }
438 }
439
440 /**
441 * Match a regexp and add the result to the items list
442 * This function is recursive
443 */
match_kw(regex_t * preg,const char * what,int len,POOLMEM * & buf)444 static void match_kw(regex_t *preg, const char *what, int len, POOLMEM *&buf)
445 {
446 int rc, size;
447 int nmatch = 20;
448 regmatch_t pmatch[20];
449
450 if (len <= 0) {
451 return;
452 }
453 rc = regexec(preg, what, nmatch, pmatch, 0);
454 if (rc == 0) {
455 size = pmatch[1].rm_eo - pmatch[1].rm_so;
456 buf = CheckPoolMemorySize(buf, size + 1);
457 memcpy(buf, what + pmatch[1].rm_so, size);
458 buf[size] = '\0';
459
460 items->list.append(bstrdup(buf));
461
462 /* search for next keyword */
463 match_kw(preg, what + pmatch[1].rm_eo, len - pmatch[1].rm_eo, buf);
464 }
465 }
466
467 /* fill the items list with the output of the help command */
GetArguments(const char * what)468 void GetArguments(const char *what)
469 {
470 regex_t preg;
471 POOLMEM *buf;
472 int rc;
473 init_items();
474
475 rc = regcomp(&preg, "(([a-z_]+=)|([a-z]+)( |$))", REG_EXTENDED);
476 if (rc != 0) {
477 return;
478 }
479
480 buf = GetPoolMemory(PM_MESSAGE);
481 UA_sock->fsend(".help item=%s", what);
482 while (UA_sock->recv() > 0) {
483 StripTrailingJunk(UA_sock->msg);
484 match_kw(&preg, UA_sock->msg, UA_sock->message_length, buf);
485 }
486 FreePoolMemory(buf);
487 regfree(&preg);
488 }
489
490 /* retreive a simple list (.pool, .client) and store it into items */
GetItems(const char * what)491 static void GetItems(const char *what)
492 {
493 init_items();
494
495 UA_sock->fsend("%s", what);
496 while (UA_sock->recv() > 0) {
497 StripTrailingJunk(UA_sock->msg);
498 items->list.append(bstrdup(UA_sock->msg));
499 }
500 }
501
502 typedef enum
503 {
504 ITEM_ARG, /* item with simple list like .jobs */
505 ITEM_HELP /* use help item=xxx and detect all arguments */
506 } cpl_item_t;
507
item_generator(const char * text,int state,const char * item,cpl_item_t type)508 static char *item_generator(const char *text, int state,
509 const char *item, cpl_item_t type)
510 {
511 static int list_index, len;
512 char *name;
513
514 if (!state)
515 {
516 list_index = 0;
517 len = strlen(text);
518 switch(type) {
519 case ITEM_ARG:
520 GetItems(item);
521 break;
522 case ITEM_HELP:
523 GetArguments(item);
524 break;
525 }
526 }
527
528 while (items && list_index < items->list.size())
529 {
530 name = (char *)items->list[list_index];
531 list_index++;
532
533 if (bstrncmp(name, text, len)) {
534 char *ret = (char *) actuallymalloc(strlen(name)+1);
535 strcpy(ret, name);
536 return ret;
537 }
538 }
539
540 /* no match */
541 return ((char *)NULL);
542 }
543
544 static const char *cpl_item;
545 static cpl_item_t cpl_type;
546
cpl_generator(const char * text,int state)547 static char *cpl_generator(const char *text, int state)
548 {
549 return item_generator(text, state, cpl_item, cpl_type);
550 }
551
552 /* do not use the default filename completion */
dummy_completion_function(const char * text,int state)553 static char *dummy_completion_function(const char *text, int state)
554 {
555 return NULL;
556 }
557
558 struct cpl_keywords_t {
559 const char *key;
560 const char *cmd;
561 bool file_selection;
562 };
563
564 static struct cpl_keywords_t cpl_keywords[] = {
565 { "pool=", ".pool", false },
566 { "nextpool=", ".pool", false },
567 { "fileset=", ".fileset", false },
568 { "client=", ".client", false },
569 { "jobdefs=", ".jobdefs", false },
570 { "job=", ".jobs", false },
571 { "restore_job=",".jobs type=R", false },
572 { "level=", ".level", false },
573 { "storage=", ".storage", false },
574 { "schedule=", ".schedule", false },
575 { "volume=", ".media", false },
576 { "oldvolume=", ".media", false },
577 { "volstatus=", ".volstatus", false },
578 { "catalog=", ".catalogs", false },
579 { "message=", ".msgs", false },
580 { "profile=", ".profiles", false },
581 { "actiononpurge=", ".actiononpurge", false },
582 { "ls", ".ls", true },
583 { "cd", ".lsdir", true },
584 { "add", ".ls", true },
585 { "mark", ".ls", true },
586 { "m", ".ls", true },
587 { "delete", ".lsmark", true },
588 { "unmark", ".lsmark", true }
589 };
590 #define key_size ((int)(sizeof(cpl_keywords)/sizeof(struct cpl_keywords_t)))
591
592 /* Attempt to complete on the contents of TEXT. START and END bound the
593 * region of rl_line_buffer that contains the word to complete. TEXT is
594 * the word to complete. We can use the entire contents of rl_line_buffer
595 * in case we want to do some simple parsing. Return the array of matches,
596 * or NULL if there aren't any.
597 */
readline_completion(const char * text,int start,int end)598 static char **readline_completion(const char *text, int start, int end)
599 {
600 bool found = false;
601 char **matches;
602 char *s, *cmd;
603 matches = (char **)NULL;
604
605 /* If this word is at the start of the line, then it is a command
606 * to complete. Otherwise it is the name of a file in the current
607 * directory.
608 */
609 s = get_previous_keyword(start, 0);
610 cmd = get_first_keyword();
611 if (s) {
612 for (int i = 0; i < key_size; i++) {
613 /*
614 * See if this keyword is allowed with the current file_selection setting.
615 */
616 if (cpl_keywords[i].file_selection != file_selection) {
617 continue;
618 }
619
620 if (Bstrcasecmp(s, cpl_keywords[i].key)) {
621 cpl_item = cpl_keywords[i].cmd;
622 cpl_type = ITEM_ARG;
623 matches = rl_completion_matches(text, cpl_generator);
624 found = true;
625 break;
626 }
627 }
628
629 if (!found) { /* try to get help with the first command */
630 cpl_item = cmd;
631 cpl_type = ITEM_HELP;
632 /* do not append space at the end */
633 rl_completion_suppress_append = true;
634 matches = rl_completion_matches(text, cpl_generator);
635 }
636 free(s);
637 } else { /* nothing on the line, display all commands */
638 cpl_item = ".help all";
639 cpl_type = ITEM_ARG;
640 matches = rl_completion_matches(text, cpl_generator);
641 }
642 if (cmd) {
643 free(cmd);
644 }
645 return (matches);
646 }
647
648 static char eol = '\0';
EolCmd(FILE * input,BareosSocket * UA_sock)649 static int EolCmd(FILE *input, BareosSocket *UA_sock)
650 {
651 if ((argc > 1) && (strchr("!$%&'()*+,-/:;<>?[]^`{|}~", argk[1][0]) != NULL)) {
652 eol = argk[1][0];
653 } else if (argc == 1) {
654 eol = '\0';
655 } else {
656 ConsoleOutput(_("Illegal separator character.\n"));
657 }
658 return 1;
659 }
660
661 /**
662 * Return 1 if OK
663 * 0 if no input
664 * -1 error (must stop)
665 */
GetCmd(FILE * input,const char * prompt,BareosSocket * sock,int sec)666 int GetCmd(FILE *input, const char *prompt, BareosSocket *sock, int sec)
667 {
668 static char *line = NULL;
669 static char *next = NULL;
670 static int do_history = 0;
671 char *command;
672
673 do_history = 0;
674 rl_catch_signals = 0; /* do it ourselves */
675
676 line = readline((char *)prompt); /* cast needed for old readlines */
677 if (!line) {
678 return -1;
679 }
680 StripTrailingJunk(line);
681 command = line;
682
683 /*
684 * Split "line" into multiple commands separated by the eol character.
685 * Each part is pointed to by "next" until finally it becomes null.
686 */
687 if (eol == '\0') {
688 next = NULL;
689 } else {
690 next = strchr(command, eol);
691 if (next) {
692 *next = '\0';
693 }
694 }
695 if (command != line && isatty(fileno(input))) {
696 ConsoleOutputFormat("%s%s\n", prompt, command);
697 }
698
699 sock->message_length = PmStrcpy(sock->msg, command);
700 if (sock->message_length) {
701 do_history++;
702 }
703
704 if (!next) {
705 if (do_history) {
706 add_history(line);
707 }
708 Actuallyfree(line); /* allocated by readline() malloc */
709 line = NULL;
710 }
711 return 1; /* OK */
712 }
713
ConsoleUpdateHistory(const char * histfile)714 static int ConsoleUpdateHistory(const char *histfile)
715 {
716 int ret = 0;
717
718 int max_history_length,
719 truncate_entries;
720
721 max_history_length = (console_resource)
722 ? console_resource->history_length
723 : 100;
724 truncate_entries = max_history_length - history_length;
725 if (truncate_entries < 0) {
726 truncate_entries = 0;
727 }
728
729 if (history_truncate_file(histfile, truncate_entries) == 0) {
730 ret = append_history(history_length, histfile);
731 } else {
732 ret = write_history(histfile);
733 }
734
735 return ret;
736 }
737
ConsoleInitHistory(const char * histfile)738 static int ConsoleInitHistory(const char *histfile)
739 {
740 int ret = 0;
741
742 int max_history_length;
743
744 using_history();
745
746 max_history_length = (console_resource)
747 ? console_resource->history_length
748 : 100;
749 history_truncate_file(histfile, max_history_length);
750
751 ret = read_history(histfile);
752
753 rl_completion_entry_function = dummy_completion_function;
754 rl_attempted_completion_function = readline_completion;
755 rl_filename_completion_desired = 0;
756 stifle_history(max_history_length);
757
758 return ret;
759 }
760
SelectDirector(const char * director,DirectorResource ** ret_dir,ConsoleResource ** ret_cons)761 static bool SelectDirector(const char *director, DirectorResource **ret_dir, ConsoleResource **ret_cons)
762 {
763 int numcon=0, numdir=0;
764 int i=0, item=0;
765 BareosSocket *UA_sock;
766 DirectorResource *director_resource_tmp = NULL;
767 ConsoleResource *console_resource_tmp = NULL;
768
769 *ret_cons = NULL;
770 *ret_dir = NULL;
771
772 LockRes(console::my_config);
773 numdir = 0;
774 foreach_res(director_resource_tmp, R_DIRECTOR) {
775 numdir++;
776 }
777 numcon = 0;
778 foreach_res(console_resource_tmp, R_CONSOLE) {
779 numcon++;
780 }
781 UnlockRes(my_config);
782
783 if (numdir == 1) { /* No choose */
784 director_resource_tmp = (DirectorResource *)my_config->GetNextRes(R_DIRECTOR, NULL);
785 }
786
787 if (director) { /* Command line choice overwrite the no choose option */
788 LockRes(my_config);
789 foreach_res(director_resource_tmp, R_DIRECTOR) {
790 if (bstrcmp(director_resource_tmp->name(), director)) {
791 break;
792 }
793 }
794 UnlockRes(my_config);
795 if (!director_resource_tmp) { /* Can't find Director used as argument */
796 ConsoleOutputFormat(_("Can't find %s in Director list\n"), director);
797 return 0;
798 }
799 }
800
801 if (!director_resource_tmp) { /* prompt for director */
802 UA_sock = New(BareosSocketTCP);
803 try_again:
804 ConsoleOutput(_("Available Directors:\n"));
805 LockRes(my_config);
806 numdir = 0;
807 foreach_res(director_resource_tmp, R_DIRECTOR) {
808 ConsoleOutputFormat( _("%2d: %s at %s:%d\n"), 1+numdir++, director_resource_tmp->name(),
809 director_resource_tmp->address, director_resource_tmp->DIRport);
810 }
811 UnlockRes(my_config);
812 if (GetCmd(stdin, _("Select Director by entering a number: "), UA_sock, 600) < 0) {
813 WSACleanup(); /* Cleanup Windows sockets */
814 return 0;
815 }
816 if (!Is_a_number(UA_sock->msg)) {
817 ConsoleOutputFormat(_("%s is not a number. You must enter a number between "
818 "1 and %d\n"),
819 UA_sock->msg, numdir);
820 goto try_again;
821 }
822 item = atoi(UA_sock->msg);
823 if (item < 0 || item > numdir) {
824 ConsoleOutputFormat(_("You must enter a number between 1 and %d\n"), numdir);
825 goto try_again;
826 }
827 delete UA_sock;
828 LockRes(my_config);
829 for (i=0; i<item; i++) {
830 director_resource_tmp = (DirectorResource *)my_config->GetNextRes(R_DIRECTOR,
831 (CommonResourceHeader *)director_resource_tmp);
832 }
833 UnlockRes(my_config);
834 }
835
836 /*
837 * Look for a console linked to this director
838 */
839 LockRes(my_config);
840 for (i=0; i<numcon; i++) {
841 console_resource_tmp = (ConsoleResource *)my_config->GetNextRes(R_CONSOLE, (CommonResourceHeader *)console_resource_tmp);
842 if (console_resource_tmp->director && bstrcmp(console_resource_tmp->director, director_resource_tmp->name())) {
843 break;
844 }
845 console_resource_tmp = NULL;
846 }
847
848 /*
849 * Look for the first non-linked console
850 */
851 if (console_resource_tmp == NULL) {
852 for (i=0; i<numcon; i++) {
853 console_resource_tmp = (ConsoleResource *)my_config->GetNextRes(R_CONSOLE, (CommonResourceHeader *)console_resource_tmp);
854 if (console_resource_tmp->director == NULL)
855 break;
856 console_resource_tmp = NULL;
857 }
858 }
859
860 /*
861 * If no console, take first one
862 */
863 if (!console_resource_tmp) {
864 console_resource_tmp = (ConsoleResource *)my_config->GetNextRes(R_CONSOLE, (CommonResourceHeader *)NULL);
865 }
866 UnlockRes(my_config);
867
868 *ret_dir = director_resource_tmp;
869 *ret_cons = console_resource_tmp;
870
871 return 1;
872 }
873
874 #if defined(HAVE_PAM)
ReadPamCredentialsFile(const std::string & pam_credentials_filename)875 static BStringList ReadPamCredentialsFile(const std::string &pam_credentials_filename)
876 {
877 std::ifstream s(pam_credentials_filename);
878 std::string user, pw;
879 if (!s.is_open()) {
880 Emsg0(M_ERROR_TERM, 0, _("Could not open PAM credentials file.\n"));
881 return BStringList();
882 } else {
883 std::getline(s, user);
884 std::getline(s, pw);
885 if (user.empty() || pw.empty()) {
886 Emsg0(M_ERROR_TERM, 0, _("Could not read user or password.\n"));
887 return BStringList();
888 }
889 }
890 BStringList args;
891 args << user << pw;
892 return args;
893 }
894
ExaminePamAuthentication(bool use_pam_credentials_file,const std::string & pam_credentials_filename)895 static bool ExaminePamAuthentication(bool use_pam_credentials_file, const std::string &pam_credentials_filename)
896 {
897 if (!UA_sock->tls_conn && !force_send_pam_credentials_unencrypted) {
898 ConsoleOutput("Canceled because password would be sent unencrypted!\n");
899 return false;
900 }
901 if (use_pam_credentials_file) {
902 BStringList args(ReadPamCredentialsFile(pam_credentials_filename));
903 if(args.empty()) {
904 return false;
905 }
906 UA_sock->FormatAndSendResponseMessage(kMessageIdPamUserCredentials, args);
907 } else {
908 UA_sock->FormatAndSendResponseMessage(kMessageIdPamInteractive, std::string());
909 if (!ConsolePamAuthenticate(stdin, UA_sock)) {
910 TerminateConsole(0);
911 return false;
912 }
913 }
914 return true;
915 }
916 #endif /* HAVE_PAM */
917
918 /*
919 * Main Bareos Console -- User Interface Program
920 */
main(int argc,char * argv[])921 int main(int argc, char *argv[])
922 {
923 int ch;
924 char *director = NULL;
925 bool list_directors = false;
926 bool no_signals = false;
927 bool test_config = false;
928 bool export_config = false;
929 bool export_config_schema = false;
930 JobControlRecord jcr;
931 PoolMem history_file;
932
933 setlocale(LC_ALL, "");
934 bindtextdomain("bareos", LOCALEDIR);
935 textdomain("bareos");
936
937 InitStackDump();
938 LmgrInitThread();
939 MyNameIs(argc, argv, "bconsole");
940 InitMsg(NULL, NULL);
941 working_directory = "/tmp";
942 args = GetPoolMemory(PM_FNAME);
943
944 while ((ch = getopt(argc, argv, program_arguments.c_str())) != -1) {
945 switch (ch) {
946 case 'D': /* Director */
947 if (director) {
948 free(director);
949 }
950 director = bstrdup(optarg);
951 break;
952
953 case 'l':
954 list_directors = true;
955 test_config = true;
956 break;
957
958 case 'c': /* configuration file */
959 if (configfile != NULL) {
960 free(configfile);
961 }
962 configfile = bstrdup(optarg);
963 break;
964
965 case 'd':
966 if (*optarg == 't') {
967 dbg_timestamp = true;
968 } else {
969 debug_level = atoi(optarg);
970 if (debug_level <= 0) {
971 debug_level = 1;
972 }
973 }
974 break;
975
976 #if defined(HAVE_PAM)
977 case 'p':
978 pam_credentials_filename = optarg;
979 if (pam_credentials_filename.empty()) {
980 Emsg0(M_ERROR_TERM, 0, _("No filename given for -p.\n"));
981 usage();
982 } else {
983 if (FILE *f = fopen(pam_credentials_filename.c_str(), "r+")) {
984 use_pam_credentials_file = true;
985 fclose(f);
986 } else { /* file cannot be opened, i.e. does not exist */
987 Emsg0(M_ERROR_TERM, 0, _("Could not open file for -p.\n"));
988 }
989 }
990 break;
991
992 case 'o':
993 force_send_pam_credentials_unencrypted = true;
994 break;
995 #endif /* HAVE_PAM */
996
997 case 's': /* turn off signals */
998 no_signals = true;
999 break;
1000
1001 case 't':
1002 test_config = true;
1003 break;
1004
1005 case 'u':
1006 timeout = atoi(optarg);
1007 break;
1008
1009 case 'x': /* export configuration/schema and exit */
1010 if (*optarg == 's') {
1011 export_config_schema = true;
1012 } else if (*optarg == 'c') {
1013 export_config = true;
1014 } else {
1015 usage();
1016 }
1017 break;
1018
1019 case '?':
1020 default:
1021 usage();
1022 exit(1);
1023 }
1024 }
1025 argc -= optind;
1026 argv += optind;
1027
1028 if (!no_signals) {
1029 InitSignals(TerminateConsole);
1030 }
1031
1032 #if !defined(HAVE_WIN32)
1033 /* Override Bareos default signals */
1034 signal(SIGQUIT, SIG_IGN);
1035 signal(SIGTSTP, GotSigstop);
1036 signal(SIGCONT, GotSigcontinue);
1037 signal(SIGTTIN, GotSigtin);
1038 signal(SIGTTOU, GotSigtout);
1039 trapctlc();
1040 #endif
1041
1042 OSDependentInit();
1043
1044 if (argc) {
1045 usage();
1046 exit(1);
1047 }
1048
1049 if (export_config_schema) {
1050 PoolMem buffer;
1051
1052 my_config = InitConsConfig(configfile, M_ERROR_TERM);
1053 PrintConfigSchemaJson(buffer);
1054 printf("%s\n", buffer.c_str());
1055 exit(0);
1056 }
1057
1058 my_config = InitConsConfig(configfile, M_ERROR_TERM);
1059 my_config->ParseConfig();
1060
1061 if (export_config) {
1062 my_config->DumpResources(PrintMessage, NULL);
1063 exit(0);
1064 }
1065
1066 if (InitCrypto() != 0) {
1067 Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
1068 }
1069
1070 if (!CheckResources()) {
1071 Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), my_config->get_base_config_path().c_str());
1072 }
1073
1074 ConInit(stdin);
1075
1076 if (list_directors) {
1077 LockRes(my_config);
1078 foreach_res(director_resource, R_DIRECTOR) {
1079 ConsoleOutputFormat("%s\n", director_resource->name());
1080 }
1081 UnlockRes(my_config);
1082 }
1083
1084 if (test_config) {
1085 TerminateConsole(0);
1086 exit(0);
1087 }
1088
1089 memset(&jcr, 0, sizeof(jcr));
1090
1091 (void)WSA_Init(); /* Initialize Windows sockets */
1092
1093 StartWatchdog(); /* Start socket watchdog */
1094
1095 if (!SelectDirector(director, &director_resource, &console_resource)) {
1096 return 1;
1097 }
1098
1099 ConsoleOutputFormat(_("Connecting to Director %s:%d\n"), director_resource->address,director_resource->DIRport);
1100
1101 utime_t heart_beat;
1102 if (director_resource->heartbeat_interval) {
1103 heart_beat = director_resource->heartbeat_interval;
1104 } else if (console_resource) {
1105 heart_beat = console_resource->heartbeat_interval;
1106 } else {
1107 heart_beat = 0;
1108 }
1109
1110 uint32_t response_id;
1111 BStringList response_args;
1112
1113 UA_sock = ConnectToDirector(jcr, heart_beat, response_args, response_id);
1114 if (!UA_sock) {
1115 ConsoleOutput(_("Failed to connect to Director. Giving up.\n"));
1116 TerminateConsole(0);
1117 return 1;
1118 }
1119
1120 UA_sock->OutputCipherMessageString(ConsoleOutput);
1121
1122 if (response_id == kMessageIdPamRequired) {
1123 #if defined(HAVE_PAM)
1124 if (!ExaminePamAuthentication(use_pam_credentials_file, pam_credentials_filename)) {
1125 ConsoleOutput(_("PAM authentication failed. Giving up.\n"));
1126 TerminateConsole(0);
1127 return 1;
1128 }
1129 response_args.clear();
1130 if (!UA_sock->ReceiveAndEvaluateResponseMessage(response_id, response_args)) {
1131 ConsoleOutput(_("PAM authentication failed. Giving up.\n"));
1132 TerminateConsole(0);
1133 return 1;
1134 }
1135 #else
1136 ConsoleOutput(
1137 _("PAM authentication requested by Director, however this console "
1138 "does not have this feature. Giving up.\n"));
1139 TerminateConsole(0);
1140 return 1;
1141 #endif /* HAVE_PAM */
1142 } /* kMessageIdPamRequired */
1143
1144 if (response_id == kMessageIdOk) {
1145 ConsoleOutput(response_args.JoinReadable().c_str());
1146 ConsoleOutput("\n");
1147 }
1148
1149 response_args.clear();
1150 if (!UA_sock->ReceiveAndEvaluateResponseMessage(response_id, response_args)) {
1151 Dmsg0(200, "Could not receive the response message\n");
1152 TerminateConsole(0);
1153 return 1;
1154 }
1155
1156 if (response_id != kMessageIdInfoMessage) {
1157 Dmsg0(200, "Could not receive the response message\n");
1158 TerminateConsole(0);
1159 return 1;
1160 }
1161 response_args.PopFront();
1162 ConsoleOutput(response_args.JoinReadable().c_str());
1163 ConsoleOutput("\n");
1164
1165 Dmsg0(40, "Opened connection with Director daemon\n");
1166
1167 ConsoleOutput(_("\nEnter a period to cancel a command.\n"));
1168
1169 #if defined(HAVE_WIN32)
1170 char *env = getenv("USERPROFILE");
1171 #else
1172 char *env = getenv("HOME");
1173 #endif
1174
1175 /*
1176 * Run commands in ~/.bconsolerc if any
1177 */
1178 if (env) {
1179 FILE *fp;
1180
1181 PmStrcpy(UA_sock->msg, env);
1182 PmStrcat(UA_sock->msg, "/.bconsolerc");
1183 fp = fopen(UA_sock->msg, "rb");
1184 if (fp) {
1185 ReadAndProcessInput(fp, UA_sock);
1186 fclose(fp);
1187 }
1188 }
1189
1190 if (me && me->history_file) {
1191 PmStrcpy(history_file, me->history_file);
1192 ConsoleInitHistory(history_file.c_str());
1193 } else {
1194 if (env) {
1195 PmStrcpy(history_file, env);
1196 PmStrcat(history_file, "/.bconsole_history");
1197 ConsoleInitHistory(history_file.c_str());
1198 } else {
1199 PmStrcpy(history_file, "");
1200 }
1201 }
1202
1203 ReadAndProcessInput(stdin, UA_sock);
1204
1205 if (UA_sock) {
1206 UA_sock->signal(BNET_TERMINATE); /* send EOF */
1207 UA_sock->close();
1208 }
1209
1210 if (history_file.size()) {
1211 ConsoleUpdateHistory(history_file.c_str());
1212 }
1213
1214 TerminateConsole(0);
1215 return 0;
1216 }
1217
TerminateConsole(int sig)1218 static void TerminateConsole(int sig)
1219 {
1220 static bool already_here = false;
1221
1222 if (already_here) { /* avoid recursive temination problems */
1223 exit(1);
1224 }
1225 already_here = true;
1226 StopWatchdog();
1227 delete my_config;
1228 my_config = NULL;
1229 CleanupCrypto();
1230 FreePoolMemory(args);
1231 ConTerm();
1232 WSACleanup(); /* Cleanup Windows sockets */
1233 LmgrCleanupMain();
1234
1235 if (sig != 0) {
1236 exit(1);
1237 }
1238 return;
1239 }
1240
CheckResources()1241 static int CheckResources()
1242 {
1243 bool OK = true;
1244 DirectorResource *director;
1245
1246 LockRes(my_config);
1247
1248 numdir = 0;
1249 foreach_res(director, R_DIRECTOR) {
1250 numdir++;
1251 }
1252
1253 if (numdir == 0) {
1254 const std::string &configfile = my_config->get_base_config_path();
1255 Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
1256 "Without that I don't how to speak to the Director :-(\n"), configfile.c_str());
1257 OK = false;
1258 }
1259
1260 me = (ConsoleResource *)my_config->GetNextRes(R_CONSOLE, NULL);
1261
1262 UnlockRes(my_config);
1263
1264 return OK;
1265 }
1266
1267 /* @version */
Versioncmd(FILE * input,BareosSocket * UA_sock)1268 static int Versioncmd(FILE *input, BareosSocket *UA_sock)
1269 {
1270 ConsoleOutputFormat("Version: " VERSION " (" BDATE ") %s %s %s\n",
1271 HOST_OS, DISTNAME, DISTVER);
1272 return 1;
1273 }
1274
1275 /* @input <input-filename> */
InputCmd(FILE * input,BareosSocket * UA_sock)1276 static int InputCmd(FILE *input, BareosSocket *UA_sock)
1277 {
1278 FILE *fd;
1279
1280 if (argc > 2) {
1281 ConsoleOutput(_("Too many arguments on input command.\n"));
1282 return 1;
1283 }
1284 if (argc == 1) {
1285 ConsoleOutput(_("First argument to input command must be a filename.\n"));
1286 return 1;
1287 }
1288 fd = fopen(argk[1], "rb");
1289 if (!fd) {
1290 BErrNo be;
1291 ConsoleOutputFormat(_("Cannot open file %s for input. ERR=%s\n"),
1292 argk[1], be.bstrerror());
1293 return 1;
1294 }
1295 ReadAndProcessInput(fd, UA_sock);
1296 fclose(fd);
1297 return 1;
1298 }
1299
1300 /* @tee <output-filename> */
1301 /* Send output to both terminal and specified file */
TeeCmd(FILE * input,BareosSocket * UA_sock)1302 static int TeeCmd(FILE *input, BareosSocket *UA_sock)
1303 {
1304 EnableTeeOut();
1305 return DoOutputcmd(input, UA_sock);
1306 }
1307
1308 /* @output <output-filename> */
1309 /* Send output to specified "file" */
OutputCmd(FILE * input,BareosSocket * UA_sock)1310 static int OutputCmd(FILE *input, BareosSocket *UA_sock)
1311 {
1312 DisableTeeOut();
1313 return DoOutputcmd(input, UA_sock);
1314 }
1315
DoOutputcmd(FILE * input,BareosSocket * UA_sock)1316 static int DoOutputcmd(FILE *input, BareosSocket *UA_sock)
1317 {
1318 FILE *file;
1319 const char *mode = "a+b";
1320
1321 if (argc > 3) {
1322 ConsoleOutput(_("Too many arguments on output/tee command.\n"));
1323 return 1;
1324 }
1325 if (argc == 1) {
1326 CloseTeeFile();
1327 return 1;
1328 }
1329 if (argc == 3) {
1330 mode = argk[2];
1331 }
1332 file = fopen(argk[1], mode);
1333 if (!file) {
1334 BErrNo be;
1335 ConsoleOutputFormat(_("Cannot open file %s for output. ERR=%s\n"),
1336 argk[1], be.bstrerror(errno));
1337 return 1;
1338 }
1339 SetTeeFile(file);
1340 return 1;
1341 }
1342
1343 /**
1344 * @exec "some-command" [wait-seconds]
1345 */
ExecCmd(FILE * input,BareosSocket * UA_sock)1346 static int ExecCmd(FILE *input, BareosSocket *UA_sock)
1347 {
1348 Bpipe *bpipe;
1349 char line[5000];
1350 int status;
1351 int wait = 0;
1352
1353 if (argc > 3) {
1354 ConsoleOutput(_("Too many arguments. Enclose command in double quotes.\n"));
1355 return 1;
1356 }
1357 if (argc == 3) {
1358 wait = atoi(argk[2]);
1359 }
1360 bpipe = OpenBpipe(argk[1], wait, "r");
1361 if (!bpipe) {
1362 BErrNo be;
1363 ConsoleOutputFormat(_("Cannot popen(\"%s\", \"r\"): ERR=%s\n"),
1364 argk[1], be.bstrerror(errno));
1365 return 1;
1366 }
1367
1368 while (fgets(line, sizeof(line), bpipe->rfd)) {
1369 ConsoleOutputFormat("%s", line);
1370 }
1371 status = CloseBpipe(bpipe);
1372 if (status != 0) {
1373 BErrNo be;
1374 be.SetErrno(status);
1375 ConsoleOutputFormat(_("Autochanger error: ERR=%s\n"), be.bstrerror());
1376 }
1377 return 1;
1378 }
1379
1380 /* @echo xxx yyy */
EchoCmd(FILE * input,BareosSocket * UA_sock)1381 static int EchoCmd(FILE *input, BareosSocket *UA_sock)
1382 {
1383 for (int i=1; i < argc; i++) {
1384 ConsoleOutputFormat("%s ", argk[i]);
1385 }
1386 ConsoleOutput("\n");
1387 return 1;
1388 }
1389
1390 /* @quit */
QuitCmd(FILE * input,BareosSocket * UA_sock)1391 static int QuitCmd(FILE *input, BareosSocket *UA_sock)
1392 {
1393 return 0;
1394 }
1395
1396 /* @help */
HelpCmd(FILE * input,BareosSocket * UA_sock)1397 static int HelpCmd(FILE *input, BareosSocket *UA_sock)
1398 {
1399 int i;
1400 for (i=0; i<comsize; i++) {
1401 ConsoleOutputFormat(" %-10s %s\n", commands[i].key, commands[i].help);
1402 }
1403 return 1;
1404 }
1405
1406 /* @sleep secs */
SleepCmd(FILE * input,BareosSocket * UA_sock)1407 static int SleepCmd(FILE *input, BareosSocket *UA_sock)
1408 {
1409 if (argc > 1) {
1410 sleep(atoi(argk[1]));
1411 }
1412 return 1;
1413 }
1414
1415 /* @time */
TimeCmd(FILE * input,BareosSocket * UA_sock)1416 static int TimeCmd(FILE *input, BareosSocket *UA_sock)
1417 {
1418 char sdt[50];
1419
1420 bstrftimes(sdt, sizeof(sdt), time(NULL));
1421 ConsoleOutputFormat("%s\n", sdt);
1422 return 1;
1423 }
1424
1425