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