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