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