1 /*
2 Copyright (C) 2011-2011 Bacula Systems(R) SA
3 Copyright (C) 2011-2012 Planets Communications B.V.
4 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
5
6 This program is Free Software; you can modify it under the terms of
7 version three of the GNU Affero General Public License as published by the
8 Free Software Foundation, which is listed in the file LICENSE.
9
10 This program is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19 */
20
21 /*
22 * Handle simple configuration file such as "ini" files.
23 * key1 = val # comment
24 * key2 = val # <type>
25 */
26
27 #include "include/bareos.h"
28 #include "ini.h"
29 #include "lib/berrno.h"
30 #include "lib/alist.h"
31
32 #define bfree_and_null_const(a) \
33 do { \
34 if (a) { \
35 free((void*)a); \
36 (a) = NULL; \
37 } \
38 } while (0)
39 static int dbglevel = 100;
40
41 /*
42 * We use this structure to associate a key to the function
43 */
44 struct ini_store {
45 const char* key;
46 const char* comment;
47 int type;
48 };
49
50 static struct ini_store funcs[]
51 = {{"@INT32@", "Integer", INI_CFG_TYPE_INT32},
52 {"@PINT32@", "Integer", INI_CFG_TYPE_PINT32},
53 {"@INT64@", "Integer", INI_CFG_TYPE_INT64},
54 {"@PINT64@", "Positive Integer", INI_CFG_TYPE_PINT64},
55 {"@NAME@", "Simple String", INI_CFG_TYPE_NAME},
56 {"@STR@", "String", INI_CFG_TYPE_STR},
57 {"@BOOL@", "on/off", INI_CFG_TYPE_BOOL},
58 {"@ALIST@", "String list", INI_CFG_TYPE_ALIST_STR},
59 {NULL, NULL, 0}};
60
61 /*
62 * Get handler code from storage type.
63 */
ini_get_store_code(int type)64 const char* ini_get_store_code(int type)
65 {
66 int i;
67
68 for (i = 0; funcs[i].key; i++) {
69 if (funcs[i].type == type) { return funcs[i].key; }
70 }
71 return NULL;
72 }
73
74 /*
75 * Get storage type from handler name.
76 */
IniGetStoreType(const char * key)77 int IniGetStoreType(const char* key)
78 {
79 int i;
80
81 for (i = 0; funcs[i].key; i++) {
82 if (!strcmp(funcs[i].key, key)) { return funcs[i].type; }
83 }
84 return 0;
85 }
86
87 /* ----------------------------------------------------------------
88 * Handle data type. Import/Export
89 * ----------------------------------------------------------------
90 */
IniStoreStr(LEX * lc,ConfigFile * inifile,ini_items * item)91 static bool IniStoreStr(LEX* lc, ConfigFile* inifile, ini_items* item)
92 {
93 if (!lc) {
94 Mmsg(inifile->edit, "%s", item->val.strval);
95 return true;
96 }
97 if (LexGetToken(lc, BCT_STRING) == BCT_ERROR) { return false; }
98 /*
99 * If already allocated, free first
100 */
101 if (item->found && item->val.strval) { free(item->val.strval); }
102 item->val.strval = strdup(lc->str);
103 ScanToEol(lc);
104 return true;
105 }
106
IniStoreName(LEX * lc,ConfigFile * inifile,ini_items * item)107 static bool IniStoreName(LEX* lc, ConfigFile* inifile, ini_items* item)
108 {
109 if (!lc) {
110 Mmsg(inifile->edit, "%s", item->val.nameval);
111 return true;
112 }
113 if (LexGetToken(lc, BCT_NAME) == BCT_ERROR) { return false; }
114 bstrncpy(item->val.nameval, lc->str, sizeof(item->val.nameval));
115 ScanToEol(lc);
116 return true;
117 }
118
IniStoreAlistStr(LEX * lc,ConfigFile * inifile,ini_items * item)119 static bool IniStoreAlistStr(LEX* lc, ConfigFile* inifile, ini_items* item)
120 {
121 alist* list;
122 if (!lc) {
123 /*
124 * TODO, write back the alist to edit buffer
125 */
126 return true;
127 }
128 if (LexGetToken(lc, BCT_STRING) == BCT_ERROR) { return false; }
129
130 if (item->val.alistval == NULL) {
131 list = new alist(10, owned_by_alist);
132 } else {
133 list = item->val.alistval;
134 }
135
136 Dmsg4(900, "Append %s to alist %p size=%d %s\n", lc->str, list, list->size(),
137 item->name);
138 list->append(strdup(lc->str));
139 item->val.alistval = list;
140
141 ScanToEol(lc);
142 return true;
143 }
144
ini_store_int64(LEX * lc,ConfigFile * inifile,ini_items * item)145 static bool ini_store_int64(LEX* lc, ConfigFile* inifile, ini_items* item)
146 {
147 if (!lc) {
148 Mmsg(inifile->edit, "%lld", item->val.int64val);
149 return true;
150 }
151 if (LexGetToken(lc, BCT_INT64) == BCT_ERROR) { return false; }
152 item->val.int64val = lc->u.int64_val;
153 ScanToEol(lc);
154 return true;
155 }
156
ini_store_pint64(LEX * lc,ConfigFile * inifile,ini_items * item)157 static bool ini_store_pint64(LEX* lc, ConfigFile* inifile, ini_items* item)
158 {
159 if (!lc) {
160 Mmsg(inifile->edit, "%lld", item->val.int64val);
161 return true;
162 }
163 if (LexGetToken(lc, BCT_PINT64) == BCT_ERROR) { return false; }
164 item->val.int64val = lc->u.pint64_val;
165 ScanToEol(lc);
166 return true;
167 }
168
ini_store_pint32(LEX * lc,ConfigFile * inifile,ini_items * item)169 static bool ini_store_pint32(LEX* lc, ConfigFile* inifile, ini_items* item)
170 {
171 if (!lc) {
172 Mmsg(inifile->edit, "%d", item->val.int32val);
173 return true;
174 }
175 if (LexGetToken(lc, BCT_PINT32) == BCT_ERROR) { return false; }
176 item->val.int32val = lc->u.pint32_val;
177 ScanToEol(lc);
178 return true;
179 }
180
ini_store_int32(LEX * lc,ConfigFile * inifile,ini_items * item)181 static bool ini_store_int32(LEX* lc, ConfigFile* inifile, ini_items* item)
182 {
183 if (!lc) {
184 Mmsg(inifile->edit, "%d", item->val.int32val);
185 return true;
186 }
187 if (LexGetToken(lc, BCT_INT32) == BCT_ERROR) { return false; }
188 item->val.int32val = lc->u.int32_val;
189 ScanToEol(lc);
190 return true;
191 }
192
IniStoreBool(LEX * lc,ConfigFile * inifile,ini_items * item)193 static bool IniStoreBool(LEX* lc, ConfigFile* inifile, ini_items* item)
194 {
195 if (!lc) {
196 Mmsg(inifile->edit, "%s", item->val.boolval ? "yes" : "no");
197 return true;
198 }
199 if (LexGetToken(lc, BCT_NAME) == BCT_ERROR) { return false; }
200 if (Bstrcasecmp(lc->str, "yes") || Bstrcasecmp(lc->str, "true")) {
201 item->val.boolval = true;
202 } else if (Bstrcasecmp(lc->str, "no") || Bstrcasecmp(lc->str, "false")) {
203 item->val.boolval = false;
204 } else {
205 /*
206 * YES and NO must not be translated
207 */
208 scan_err2(lc, _("Expect %s, got: %s"), "YES, NO, TRUE, or FALSE", lc->str);
209 return false;
210 }
211 ScanToEol(lc);
212 return true;
213 }
214
215 /*
216 * Format a scanner error message
217 */
s_err(const char * file,int line,LEX * lc,const char * msg,...)218 static void s_err(const char* file, int line, LEX* lc, const char* msg, ...)
219 {
220 va_list ap;
221 int len, maxlen;
222 ConfigFile* ini;
223 PoolMem buf(PM_MESSAGE);
224
225 while (1) {
226 maxlen = buf.size() - 1;
227 va_start(ap, msg);
228 len = Bvsnprintf(buf.c_str(), maxlen, msg, ap);
229 va_end(ap);
230
231 if (len < 0 || len >= (maxlen - 5)) {
232 buf.ReallocPm(maxlen + maxlen / 2);
233 continue;
234 }
235
236 break;
237 }
238
239 ini = (ConfigFile*)(lc->caller_ctx);
240 if (ini->jcr) { /* called from core */
241 Jmsg(ini->jcr, M_ERROR, 0,
242 _("Config file error: %s\n"
243 " : Line %d, col %d of file %s\n%s\n"),
244 buf.c_str(), lc->line_no, lc->col_no, lc->fname, lc->line);
245
246 // } else if (ini->ctx) { /* called from plugin */
247 // ini->bareos_core_functions->JobMessage(ini->ctx, __FILE__, __LINE__,
248 // M_FATAL, 0,
249 // _("Config file error: %s\n"
250 // " : Line %d, col %d of file %s\n%s\n"),
251 // buf.c_str(), lc->line_no, lc->col_no,
252 // lc->fname, lc->line);
253 //
254 } else { /* called from ??? */
255 e_msg(file, line, M_ERROR, 0,
256 _("Config file error: %s\n"
257 " : Line %d, col %d of file %s\n%s\n"),
258 buf.c_str(), lc->line_no, lc->col_no, lc->fname, lc->line);
259 }
260 }
261
262 /*
263 * Format a scanner error message
264 */
s_warn(const char * file,int line,LEX * lc,const char * msg,...)265 static void s_warn(const char* file, int line, LEX* lc, const char* msg, ...)
266 {
267 va_list ap;
268 int len, maxlen;
269 ConfigFile* ini;
270 PoolMem buf(PM_MESSAGE);
271
272 while (1) {
273 maxlen = buf.size() - 1;
274 va_start(ap, msg);
275 len = Bvsnprintf(buf.c_str(), maxlen, msg, ap);
276 va_end(ap);
277
278 if (len < 0 || len >= (maxlen - 5)) {
279 buf.ReallocPm(maxlen + maxlen / 2);
280 continue;
281 }
282
283 break;
284 }
285
286 ini = (ConfigFile*)(lc->caller_ctx);
287 if (ini->jcr) { /* called from core */
288 Jmsg(ini->jcr, M_WARNING, 0,
289 _("Config file warning: %s\n"
290 " : Line %d, col %d of file %s\n%s\n"),
291 buf.c_str(), lc->line_no, lc->col_no, lc->fname, lc->line);
292
293 // } else if (ini->ctx) { /* called from plugin */
294 // ini->bareos_core_functions->JobMessage(ini->ctx, __FILE__, __LINE__,
295 // M_WARNING, 0,
296 // _("Config file warning: %s\n"
297 // " : Line %d, col %d of file %s\n%s\n"),
298 // buf.c_str(), lc->line_no, lc->col_no,
299 // lc->fname, lc->line);
300 //
301 } else { /* called from ??? */
302 p_msg(file, line, 0,
303 _("Config file warning: %s\n"
304 " : Line %d, col %d of file %s\n%s\n"),
305 buf.c_str(), lc->line_no, lc->col_no, lc->fname, lc->line);
306 }
307 }
308
309 /*
310 * Reset free items
311 */
ClearItems()312 void ConfigFile::ClearItems()
313 {
314 if (!items) { return; }
315
316 for (int i = 0; items[i].name; i++) {
317 if (items[i].found) {
318 /*
319 * Special members require delete or free
320 */
321 switch (items[i].type) {
322 case INI_CFG_TYPE_STR:
323 free(items[i].val.strval);
324 items[i].val.strval = NULL;
325 break;
326 case INI_CFG_TYPE_ALIST_STR:
327 delete items[i].val.alistval;
328 items[i].val.alistval = NULL;
329 break;
330 default:
331 break;
332 }
333 items[i].found = false;
334 }
335 }
336 }
337
FreeItems()338 void ConfigFile::FreeItems()
339 {
340 if (items_allocated) {
341 for (int i = 0; items[i].name; i++) {
342 bfree_and_null_const(items[i].name);
343 bfree_and_null_const(items[i].comment);
344 }
345 free(items);
346 }
347 items = NULL;
348 items_allocated = false;
349 }
350
351 /*
352 * Get a particular item from the items list
353 */
GetItem(const char * name)354 int ConfigFile::GetItem(const char* name)
355 {
356 if (!items) { return -1; }
357
358 for (int i = 0; i < MAX_INI_ITEMS && items[i].name; i++) {
359 if (Bstrcasecmp(name, items[i].name)) { return i; }
360 }
361 return -1;
362 }
363
364 /*
365 * Dump a buffer to a file in the working directory
366 * Needed to unserialise() a config
367 */
DumpString(const char * buf,int32_t len)368 bool ConfigFile::DumpString(const char* buf, int32_t len)
369 {
370 FILE* fp;
371 bool ret = false;
372
373 if (!out_fname) {
374 out_fname = GetPoolMemory(PM_FNAME);
375 MakeUniqueFilename(out_fname, (int)(intptr_t)this, (char*)"configfile");
376 }
377
378 fp = fopen(out_fname, "wb");
379 if (!fp) { return ret; }
380
381 if (fwrite(buf, len, 1, fp) == 1) { ret = true; }
382
383 fclose(fp);
384 return ret;
385 }
386
387 /*
388 * Dump the item table format to a text file (used by plugin)
389 */
Serialize(const char * fname)390 bool ConfigFile::Serialize(const char* fname)
391 {
392 FILE* fp;
393 int32_t len;
394 bool ret = false;
395 PoolMem tmp(PM_MESSAGE);
396
397 if (!items) { return ret; }
398
399 fp = fopen(fname, "w");
400 if (!fp) { return ret; }
401
402 len = Serialize(&tmp);
403 if (fwrite(tmp.c_str(), len, 1, fp) == 1) { ret = true; }
404
405 fclose(fp);
406 return ret;
407 }
408
409 /*
410 * Dump the item table format to a text file (used by plugin)
411 */
Serialize(PoolMem * buf)412 int ConfigFile::Serialize(PoolMem* buf)
413 {
414 int len;
415 PoolMem tmp(PM_MESSAGE);
416
417 if (!items) {
418 char* p;
419
420 p = buf->c_str();
421 p[0] = '\0';
422 return 0;
423 }
424
425 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
426 for (int i = 0; items[i].name; i++) {
427 if (items[i].comment) {
428 Mmsg(tmp, "OptPrompt=%s\n", items[i].comment);
429 PmStrcat(buf, tmp.c_str());
430 }
431 if (items[i].default_value) {
432 Mmsg(tmp, "OptDefault=%s\n", items[i].default_value);
433 PmStrcat(buf, tmp.c_str());
434 }
435 if (items[i].required) {
436 Mmsg(tmp, "OptRequired=yes\n");
437 PmStrcat(buf, tmp.c_str());
438 }
439
440 /* variable = @INT64@ */
441 Mmsg(tmp, "%s=%s\n\n", items[i].name, ini_get_store_code(items[i].type));
442 len = PmStrcat(buf, tmp.c_str());
443 }
444
445 return len;
446 }
447
448 /*
449 * Dump the item table content to a text file (used by director)
450 */
DumpResults(PoolMem * buf)451 int ConfigFile::DumpResults(PoolMem* buf)
452 {
453 int len;
454 PoolMem tmp(PM_MESSAGE);
455
456 if (!items) {
457 char* p;
458
459 p = buf->c_str();
460 p[0] = '\0';
461 return 0;
462 }
463 len = Mmsg(buf, "# Plugin configuration file\n# Version %d\n", version);
464
465 for (int i = 0; items[i].name; i++) {
466 if (items[i].found) {
467 switch (items[i].type) {
468 case INI_CFG_TYPE_INT32:
469 ini_store_int32(NULL, this, &items[i]);
470 break;
471 case INI_CFG_TYPE_PINT32:
472 ini_store_pint32(NULL, this, &items[i]);
473 break;
474 case INI_CFG_TYPE_INT64:
475 ini_store_int64(NULL, this, &items[i]);
476 break;
477 case INI_CFG_TYPE_PINT64:
478 ini_store_pint64(NULL, this, &items[i]);
479 break;
480 case INI_CFG_TYPE_NAME:
481 IniStoreName(NULL, this, &items[i]);
482 break;
483 case INI_CFG_TYPE_STR:
484 IniStoreStr(NULL, this, &items[i]);
485 break;
486 case INI_CFG_TYPE_BOOL:
487 IniStoreBool(NULL, this, &items[i]);
488 break;
489 case INI_CFG_TYPE_ALIST_STR:
490 IniStoreAlistStr(NULL, this, &items[i]);
491 break;
492 default:
493 break;
494 }
495 if (items[i].comment && *items[i].comment) {
496 Mmsg(tmp, "# %s\n", items[i].comment);
497 PmStrcat(buf, tmp.c_str());
498 }
499 Mmsg(tmp, "%s=%s\n\n", items[i].name, this->edit);
500 len = PmStrcat(buf, tmp.c_str());
501 }
502 }
503
504 return len;
505 }
506
507 /*
508 * Parse a config file used by Plugin/Director
509 */
parse(const char * fname)510 bool ConfigFile::parse(const char* fname)
511 {
512 int token, i;
513 bool ret = false;
514
515 if (!items) { return false; }
516
517 if ((lc = lex_open_file(lc, fname, s_err, s_warn)) == NULL) {
518 BErrNo be;
519 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"), fname,
520 be.bstrerror());
521 return false;
522 }
523 lc->options |= LOPT_NO_EXTERN;
524 lc->caller_ctx = (void*)this;
525
526 while ((token = LexGetToken(lc, BCT_ALL)) != BCT_EOF) {
527 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
528 if (token == BCT_EOL) { continue; }
529 for (i = 0; items[i].name; i++) {
530 if (Bstrcasecmp(items[i].name, lc->str)) {
531 if ((token = LexGetToken(lc, BCT_EQUALS)) == BCT_ERROR) {
532 Dmsg1(dbglevel, "in BCT_IDENT got token=%s\n", lex_tok_to_str(token));
533 break;
534 }
535
536 Dmsg1(dbglevel, "calling handler for %s\n", items[i].name);
537
538 /*
539 * Call item handler
540 */
541 switch (items[i].type) {
542 case INI_CFG_TYPE_INT32:
543 ret = ini_store_int32(lc, this, &items[i]);
544 break;
545 case INI_CFG_TYPE_PINT32:
546 ret = ini_store_pint32(lc, this, &items[i]);
547 break;
548 case INI_CFG_TYPE_INT64:
549 ret = ini_store_int64(lc, this, &items[i]);
550 break;
551 case INI_CFG_TYPE_PINT64:
552 ret = ini_store_pint64(lc, this, &items[i]);
553 break;
554 case INI_CFG_TYPE_NAME:
555 ret = IniStoreName(lc, this, &items[i]);
556 break;
557 case INI_CFG_TYPE_STR:
558 ret = IniStoreStr(lc, this, &items[i]);
559 break;
560 case INI_CFG_TYPE_BOOL:
561 ret = IniStoreBool(lc, this, &items[i]);
562 break;
563 case INI_CFG_TYPE_ALIST_STR:
564 ret = IniStoreAlistStr(lc, this, &items[i]);
565 break;
566 default:
567 break;
568 }
569 i = -1;
570 break;
571 }
572 }
573 if (i >= 0) {
574 Dmsg1(dbglevel, "Keyword = %s\n", lc->str);
575 scan_err1(lc, "Keyword %s not found", lc->str);
576 /*
577 * We can raise an error here
578 */
579 break;
580 }
581 if (!ret) { break; }
582 }
583
584 for (i = 0; items[i].name; i++) {
585 if (items[i].required && !items[i].found) {
586 scan_err1(lc, "%s required but not found", items[i].name);
587 ret = false;
588 }
589 }
590
591 lc = LexCloseFile(lc);
592
593 return ret;
594 }
595
596 /*
597 * Analyse the content of a ini file to build the item list
598 * It uses special syntax for datatype. Used by Director on Restore object
599 *
600 * OptPrompt = "Variable1"
601 * OptRequired
602 * OptDefault = 100
603 * Variable1 = @PINT32@
604 * ...
605 */
UnSerialize(const char * fname)606 bool ConfigFile::UnSerialize(const char* fname)
607 {
608 int token, i, nb = 0;
609 bool ret = false;
610 const char** assign;
611
612 /*
613 * At this time, we allow only 32 different items
614 */
615 int s = MAX_INI_ITEMS * sizeof(struct ini_items);
616
617 items = (struct ini_items*)malloc(s);
618 memset(items, 0, s);
619 items_allocated = true;
620
621 /*
622 * Parse the file and generate the items structure on the fly
623 */
624 if ((lc = lex_open_file(lc, fname, s_err, s_warn)) == NULL) {
625 BErrNo be;
626 Emsg2(M_ERROR, 0, _("Cannot open config file %s: %s\n"), fname,
627 be.bstrerror());
628 return false;
629 }
630 lc->options |= LOPT_NO_EXTERN;
631 lc->caller_ctx = (void*)this;
632
633 while ((token = LexGetToken(lc, BCT_ALL)) != BCT_EOF) {
634 Dmsg1(dbglevel, "parse got token=%s\n", lex_tok_to_str(token));
635
636 if (token == BCT_EOL) { continue; }
637
638 ret = false;
639 assign = NULL;
640
641 if (nb >= MAX_INI_ITEMS) { break; }
642
643 if (Bstrcasecmp("optprompt", lc->str)) {
644 assign = &(items[nb].comment);
645 } else if (Bstrcasecmp("optdefault", lc->str)) {
646 assign = &(items[nb].default_value);
647 } else if (Bstrcasecmp("optrequired", lc->str)) {
648 items[nb].required = true; /* Don't use argument */
649 ScanToEol(lc);
650 continue;
651 } else {
652 items[nb].name = strdup(lc->str);
653 }
654
655 token = LexGetToken(lc, BCT_ALL);
656 Dmsg1(dbglevel, "in BCT_IDENT got token=%s\n", lex_tok_to_str(token));
657
658 if (token != BCT_EQUALS) {
659 scan_err1(lc, "expected an equals, got: %s", lc->str);
660 break;
661 }
662
663 /*
664 * We may allow blank variable
665 */
666 if (LexGetToken(lc, BCT_STRING) == BCT_ERROR) { break; }
667
668 if (assign) {
669 *assign = strdup(lc->str);
670
671 } else {
672 if ((items[nb].type = IniGetStoreType(lc->str)) == 0) {
673 scan_err1(lc, "expected a data type, got: %s", lc->str);
674 break;
675 }
676 nb++;
677 }
678 ScanToEol(lc);
679 ret = true;
680 }
681
682 if (!ret) {
683 for (i = 0; i < nb; i++) {
684 bfree_and_null_const(items[i].name);
685 bfree_and_null_const(items[i].comment);
686 bfree_and_null_const(items[i].default_value);
687 items[i].type = 0;
688 items[i].required = false;
689 }
690 }
691
692 lc = LexCloseFile(lc);
693 return ret;
694 }
695