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