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