1 /*
2    Bacula(R) - The Network Backup Solution
3 
4    Copyright (C) 2000-2020 Kern Sibbald
5 
6    The original author of Bacula is Kern Sibbald, with contributions
7    from many others, a complete list can be found in the file AUTHORS.
8 
9    You may use this file and others of this release according to the
10    license defined in the LICENSE file, which includes the Affero General
11    Public License, v3.0 ("AGPLv3") and some additional permissions and
12    terms pursuant to its AGPLv3 Section 7.
13 
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16 
17    Bacula(R) is a registered trademark of Kern Sibbald.
18    The licensor of Bacula is the Free Software Foundation Europe
19    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
20    Switzerland, email:ftf@fsfeurope.org.
21 */
22 /*
23  *
24  *   Bacula Console .conf to Json program.
25  *
26  *     Kern Sibbald, September MMXII
27  *
28  */
29 
30 #include "bacula.h"
31 #include "lib/breg.h"
32 #include "console_conf.h"
33 #include "jcr.h"
34 
35 /* Imported variables */
36 #if defined(_MSC_VER)
37 extern "C" { // work around visual compiler mangling variables
38    extern URES res_all;
39 }
40 #else
41 extern URES res_all;
42 #endif
43 extern s_kw msg_types[];
44 extern RES_TABLE resources[];
45 
46 /* Exported functions */
47 void senditf(const char *fmt, ...);
48 void sendit(const char *buf);
49 
50 /* Imported functions */
51 extern bool parse_cons_config(CONFIG *config, const char *configfile, int exit_code);
52 
53 typedef struct
54 {
55    /* default                   { { "Director": { "Name": aa, ...} }, { "Job": {..} */
56    bool do_list;             /* [ {}, {}, ..] or { "aa": {}, "bb": {}, ...} */
57    bool do_one;              /* { "Name": "aa", "Description": "test, ... } */
58    bool do_only_data;        /* [ {}, {}, {}, ] */
59    char *resource_type;
60    char *resource_name;
61    regex_t directive_reg;
62 } display_filter;
63 
64 /* Forward referenced functions */
65 static void terminate_console(int sig);
66 static int check_resources();
67 //static void ressendit(void *ua, const char *fmt, ...);
68 //static void dump_resource_types();
69 //static void dump_directives();
70 static void dump_json(display_filter *filter);
71 
72 /* Static variables */
73 static char *configfile = NULL;
74 static FILE *output = stdout;
75 static int numdir;
76 static POOLMEM *args;
77 static CONFIG *config;
78 
79 
80 #define CONFIG_FILE "bconsole.conf"   /* default configuration file */
81 
usage()82 static void usage()
83 {
84    fprintf(stderr, _(
85 PROG_COPYRIGHT
86 "\n%sVersion: " VERSION " (" BDATE ") %s %s %s\n\n"
87 "Usage: bconsjson [options] [config_file]\n"
88 "       -r <res>    get resource type <res>\n"
89 "       -n <name>   get resource <name>\n"
90 "       -l <dirs>   get only directives matching dirs (use with -r)\n"
91 "       -D          get only data\n"
92 "       -c <file>   set configuration file to file\n"
93 "       -d <nn>     set debug level to <nn>\n"
94 "       -dt         print timestamp in debug output\n"
95 "       -t          test - read configuration and exit\n"
96 "       -v          verbose\n"
97 "       -?          print this message.\n"
98 "\n"), 2012, BDEMO, HOST_OS, DISTNAME, DISTVER);
99 
100    exit(1);
101 }
102 
103 /*********************************************************************
104  *
105  *        Bacula console conf to Json
106  *
107  */
main(int argc,char * argv[])108 int main(int argc, char *argv[])
109 {
110    int ch;
111    bool test_config = false;
112    display_filter filter;
113    memset(&filter, 0, sizeof(filter));
114    int rtn = 0;
115 
116    setlocale(LC_ALL, "");
117    bindtextdomain("bacula", LOCALEDIR);
118    textdomain("bacula");
119 
120    if (init_crypto() != 0) {
121       Emsg0(M_ERROR_TERM, 0, _("Cryptography library initialization failed.\n"));
122    }
123    init_stack_dump();
124    init_signals(terminate_console);
125    lmgr_init_thread();
126    my_name_is(argc, argv, "bconsole");
127    init_msg(NULL, NULL);
128    working_directory = "/tmp";
129    args = get_pool_memory(PM_FNAME);
130 
131    while ((ch = getopt(argc, argv, "n:vDabc:d:jl:r:t?")) != -1) {
132       switch (ch) {
133       case 'D':
134          filter.do_only_data = true;
135          break;
136       case 'a':
137 //         list_all = true;
138          break;
139 
140       case 'c':                    /* configuration file */
141          if (configfile != NULL) {
142             free(configfile);
143          }
144          configfile = bstrdup(optarg);
145          break;
146 
147          break;
148 
149       case 'd':
150          if (*optarg == 't') {
151             dbg_timestamp = true;
152          } else {
153             debug_level = atoi(optarg);
154             if (debug_level <= 0) {
155                debug_level = 1;
156             }
157          }
158 
159       case 'l':
160          filter.do_list = true;
161          if (regcomp(&filter.directive_reg, optarg, REG_EXTENDED) != 0) {
162             Jmsg((JCR *)NULL, M_ERROR_TERM, 0,
163                  _("Please use valid -l argument: %s\n"), optarg);
164          }
165          break;
166 
167       case 'r':
168          filter.resource_type = optarg;
169          break;
170 
171       case 'n':
172          filter.resource_name = optarg;
173          break;
174 
175       case 't':
176          test_config = true;
177          break;
178 
179       case 'v':                    /* verbose */
180          verbose++;
181          break;
182 
183       case '?':
184       default:
185          usage();
186          exit(1);
187       }
188    }
189    argc -= optind;
190    argv += optind;
191 
192    OSDependentInit();
193 
194    if (argc) {
195       usage();
196       exit(1);
197    }
198 
199    if (filter.do_list && !filter.resource_type) {
200       usage();
201    }
202 
203    if (filter.resource_type && filter.resource_name) {
204       filter.do_one = true;
205    }
206 
207    if (configfile == NULL || configfile[0] == 0) {
208       configfile = bstrdup(CONFIG_FILE);
209    }
210 
211    if (test_config && verbose > 0) {
212       char buf[1024];
213       find_config_file(configfile, buf, sizeof(buf));
214       printf("config_file=%s\n", buf);
215    }
216 
217    config = New(CONFIG());
218    config->encode_password(false);
219    parse_cons_config(config, configfile, M_ERROR_TERM);
220 
221    if (!check_resources()) {
222       Emsg1(M_ERROR_TERM, 0, _("Please correct configuration file: %s\n"), configfile);
223    }
224 
225    if (test_config) {
226       terminate_console(0);
227       exit(0);
228    }
229 
230    dump_json(&filter);
231 
232    terminate_console(0);
233    return rtn;
234 }
235 
236 /* Cleanup and then exit */
terminate_console(int sig)237 static void terminate_console(int sig)
238 {
239    static bool already_here = false;
240 
241    if (already_here) {                /* avoid recursive temination problems */
242       exit(1);
243    }
244    already_here = true;
245    stop_watchdog();
246    delete config;
247    config = NULL;
248    free(res_head);
249    res_head = NULL;
250    free_pool_memory(args);
251    lmgr_cleanup_main();
252 
253    if (sig != 0) {
254       exit(1);
255    }
256    return;
257 }
258 
259 
260 /*
261  * Dump out all resources in json format.
262  * Note!!!! This routine must be in this file rather
263  *  than in src/lib/parser_conf.c otherwise the pointers
264  *  will be all messed up.
265  */
dump_json(display_filter * filter)266 static void dump_json(display_filter *filter)
267 {
268    int resinx, item, sz;
269    int directives;
270    bool first_res;
271    bool first_directive;
272    RES_ITEM *items;
273    RES *res;
274    HPKT hpkt;
275    regmatch_t pmatch[32];
276 
277    init_hpkt(hpkt);
278 
279    /* List resources and directives */
280    if (filter->do_only_data) {
281       printf("[");
282 
283    /* { "aa": { "Name": "aa",.. }, "bb": { "Name": "bb", ... }
284     * or print a single item
285     */
286    } else if (filter->do_one ||  filter->do_list) {
287       printf("{");
288 
289    } else {
290    /* [ { "Client": { "Name": "aa",.. } }, { "Director": { "Name": "bb", ... } } ]*/
291       printf("[");
292    }
293 
294    first_res = true;
295    /* Loop over all resource types */
296    for (resinx=0; resources[resinx].name; resinx++) {
297       /* Skip this resource type */
298       if (filter->resource_type &&
299           strcasecmp(filter->resource_type, resources[resinx].name) != 0) {
300          continue;
301       }
302 
303       directives = 0;
304       /* Loop over all resources of this type */
305       foreach_rblist(res, res_head[resinx]->res_list) {
306          hpkt.res = res;
307          items = resources[resinx].items;
308          if (!items) {
309             break;
310          }
311 
312          sz = get_resource_size(resinx + r_first);
313          if (sz < 0) {
314             Dmsg1(0, "Unknown resource type %d\n", resinx);
315             continue;
316          }
317 
318          /* Copy the resource into res_all */
319          memcpy(&res_all, res, sz);
320 
321          if (filter->resource_name) {
322             bool skip=true;
323             /* The Name should be at the first place, so this is not a real loop */
324             for (item=0; items[item].name; item++) {
325                if (strcasecmp(items[item].name, "Name") == 0) {
326                   if (strcasecmp(*(items[item].value), filter->resource_name) == 0) {
327                      skip = false;
328                   }
329                   break;
330                }
331             }
332             if (skip) {         /* The name doesn't match, so skip it */
333                continue;
334             }
335          }
336 
337          if (first_res) {
338             printf("\n");
339 
340          } else {
341             printf(",\n");
342          }
343 
344          if (filter->do_only_data) {
345             printf(" {");
346 
347          } else if (filter->do_one) {
348             /* Nothing to print */
349 
350          /* When sending the list, the form is:
351           *  { aa: { Name: aa, Description: aadesc...}, bb: { Name: bb
352           */
353          } else if (filter->do_list) {
354             /* Search and display Name, should be the first item */
355             for (item=0; items[item].name; item++) {
356                if (strcmp(items[item].name, "Name") == 0) {
357                   printf("%s: {\n", quote_string(hpkt.edbuf, *items[item].value));
358                   break;
359                }
360             }
361          } else {
362             /* Begin new resource */
363             printf("{\n  \"%s\": {", resources[resinx].name);
364          }
365 
366          first_res = false;
367          first_directive = true;
368          directives = 0;
369 
370          for (item=0; items[item].name; item++) {
371             /* Check user argument -l */
372             if (filter->do_list &&
373                 regexec(&filter->directive_reg,
374                         items[item].name, 32, pmatch, 0) != 0)
375             {
376                continue;
377             }
378 
379             hpkt.ritem = &items[item];
380             if (bit_is_set(item, res_all.hdr.item_present)) {
381                if (!first_directive) printf(",");
382                /* 1: found, 0: not found, -1 found but empty */
383                int ret = display_global_item(hpkt);
384                if (ret == -1) {
385                   /* Do not print a comma after this empty directive */
386                   first_directive = 0;
387                } else if (ret == 1) {
388                   /* Fall-through wanted */
389 
390                } else {
391                   printf("\n      \"%s\": null", items[item].name);
392                }
393                directives++;
394                first_directive = false;
395             }
396             if (items[item].flags & ITEM_LAST) {
397                display_last(hpkt);    /* If last bit set always call to cleanup */
398             }
399          }
400          /* { "aa": { "Name": "aa",.. }, "bb": { "Name": "bb", ... } */
401          if (filter->do_only_data || filter->do_list) {
402             printf("\n }"); /* Finish the Resource with a single } */
403 
404          } else {
405             if (filter->do_one) {
406                /* don't print anything */
407             } else if (directives) {
408                printf("\n   }\n}");  /* end of resource */
409             } else {
410                printf("}\n}");
411             }
412          }
413       } /* End loop over all resources of this type */
414    } /* End loop all resource types */
415 
416    if (filter->do_only_data) {
417       printf("\n]\n");
418 
419    } else  if (filter->do_one || filter->do_list) {
420       printf("\n}\n");
421 
422    } else {
423       printf("\n]\n");
424    }
425    term_hpkt(hpkt);
426 }
427 
428 
429 /*
430  * Make a quick check to see that we have all the
431  * resources needed.
432  */
check_resources()433 static int check_resources()
434 {
435    bool OK = true;
436    DIRRES *director;
437    bool tls_needed;
438 
439    LockRes();
440 
441    numdir = 0;
442    foreach_res(director, R_DIRECTOR) {
443 
444       numdir++;
445       /* tls_require implies tls_enable */
446       if (director->tls_require) {
447          if (have_tls) {
448             if (director->tls_ca_certfile || director->tls_ca_certdir) {
449                director->tls_enable = true;
450             }
451          } else {
452             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
453             OK = false;
454             continue;
455          }
456       }
457 
458       tls_needed = director->tls_enable || director->tls_authenticate;
459 
460       if ((!director->tls_ca_certfile && !director->tls_ca_certdir) && tls_needed) {
461          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
462                              " or \"TLS CA Certificate Dir\" are defined for Director \"%s\" in %s."
463                              " At least one CA certificate store is required.\n"),
464                              director->hdr.name, configfile);
465          OK = false;
466       }
467    }
468 
469    if (numdir == 0) {
470       Emsg1(M_FATAL, 0, _("No Director resource defined in %s\n"
471                           "Without that I don't how to speak to the Director :-(\n"), configfile);
472       OK = false;
473    }
474 
475    CONRES *cons;
476    /* Loop over Consoles */
477    foreach_res(cons, R_CONSOLE) {
478       /* tls_require implies tls_enable */
479       if (cons->tls_require) {
480          if (have_tls) {
481             cons->tls_enable = true;
482          } else {
483             Jmsg(NULL, M_FATAL, 0, _("TLS required but not configured in Bacula.\n"));
484             OK = false;
485             continue;
486          }
487       }
488       tls_needed = cons->tls_enable || cons->tls_authenticate;
489       if ((!cons->tls_ca_certfile && !cons->tls_ca_certdir) && tls_needed) {
490          Emsg2(M_FATAL, 0, _("Neither \"TLS CA Certificate\""
491                              " or \"TLS CA Certificate Dir\" are defined for Console \"%s\" in %s.\n"),
492                              cons->hdr.name, configfile);
493          OK = false;
494       }
495    }
496 
497    UnlockRes();
498 
499    return OK;
500 }
501 
502 #ifdef needed
ressendit(void * sock,const char * fmt,...)503 static void ressendit(void *sock, const char *fmt, ...)
504 {
505    char buf[3000];
506    va_list arg_ptr;
507 
508    va_start(arg_ptr, fmt);
509    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
510    va_end(arg_ptr);
511    sendit(buf);
512 }
513 
dump_resource_types()514 static void dump_resource_types()
515 {
516    int i;
517    bool first;
518 
519    /* List resources and their code */
520    printf("[\n");
521    first = true;
522    for (i=0; resources[i].name; i++) {
523       if (!first) {
524          printf(",\n");
525       }
526       printf("   \"%s\": %d", resources[i].name, resources[i].rcode);
527       first = false;
528    }
529    printf("\n]\n");
530 }
531 
dump_directives()532 static void dump_directives()
533 {
534    int i, j;
535    bool first_res;
536    bool first_directive;
537    RES_ITEM *items;
538 
539    /* List resources and directives */
540    printf("[\n");
541    first_res = true;
542    for (i=0; resources[i].name; i++) {
543       if (!first_res) {
544          printf(",\n");
545       }
546       printf("{\n   \"%s\": {\n", resources[i].name);
547       first_res = false;
548       first_directive = true;
549       items = resources[i].items;
550       for (j=0; items[j].name; j++) {
551          if (!first_directive) {
552             printf(",\n");
553          }
554          printf("      \"%s\": null", items[j].name);
555          first_directive = false;
556       }
557       printf("\n   }");  /* end of resource */
558    }
559    printf("\n]\n");
560 }
561 
562 /*
563  * Send a line to the output file and or the terminal
564  */
senditf(const char * fmt,...)565 void senditf(const char *fmt,...)
566 {
567    char buf[3000];
568    va_list arg_ptr;
569 
570    va_start(arg_ptr, fmt);
571    bvsnprintf(buf, sizeof(buf), (char *)fmt, arg_ptr);
572    va_end(arg_ptr);
573    sendit(buf);
574 }
575 
576 static bool teeout = false;               /* output to output and stdout */
577 
sendit(const char * buf)578 void sendit(const char *buf)
579 {
580 #ifdef CONIO_FIX
581    char obuf[3000];
582    if (output == stdout || teeout) {
583       const char *p, *q;
584       /*
585        * Here, we convert every \n into \r\n because the
586        *  terminal is in raw mode when we are using
587        *  conio.
588        */
589       for (p=q=buf; (p=strchr(q, '\n')); ) {
590          int len = p - q;
591          if (len > 0) {
592             memcpy(obuf, q, len);
593          }
594          memcpy(obuf+len, "\r\n", 3);
595          q = ++p;                    /* point after \n */
596          fputs(obuf, output);
597       }
598       if (*q) {
599          fputs(q, output);
600       }
601       fflush(output);
602    }
603    if (output != stdout) {
604       fputs(buf, output);
605    }
606 #else
607 
608    fputs(buf, output);
609    fflush(output);
610    if (teeout) {
611       fputs(buf, stdout);
612       fflush(stdout);
613    }
614 #endif
615 }
616 #endif
617