1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2015-2016 Bareos GmbH & Co. KG
5 
6    This program is Free Software; you can redistribute it and/or
7    modify it under the terms of version three of the GNU Affero General Public
8    License as published by the Free Software Foundation and included
9    in the file LICENSE.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14    Affero General Public License for more details.
15 
16    You should have received a copy of the GNU Affero General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19    02110-1301, USA.
20 */
21 /*
22  * Written by Marco van Wieringen, January 2015
23  */
24 /**
25  * @file
26  * Interactive configuration engine for director.
27  */
28 
29 #include "include/bareos.h"
30 #include "dird.h"
31 #include "dird/dird_globals.h"
32 #include "dird/ua_select.h"
33 #include "lib/parse_conf.h"
34 
35 namespace directordaemon {
36 
ConfigureLexErrorHandler(const char * file,int line,LEX * lc,PoolMem & msg)37 static void ConfigureLexErrorHandler(const char* file,
38                                      int line,
39                                      LEX* lc,
40                                      PoolMem& msg)
41 {
42   UaContext* ua;
43 
44   lc->error_counter++;
45   if (lc->caller_ctx) {
46     ua = (UaContext*)(lc->caller_ctx);
47     ua->ErrorMsg("configure error: %s\n", msg.c_str());
48   }
49 }
50 
ConfigureLexErrorHandler(const char * file,int line,LEX * lc,const char * msg,...)51 static void ConfigureLexErrorHandler(const char* file,
52                                      int line,
53                                      LEX* lc,
54                                      const char* msg,
55                                      ...)
56 {
57   /*
58    * This function is an error handler, used by lex.
59    */
60   PoolMem buf(PM_NAME);
61   va_list ap;
62 
63   va_start(ap, msg);
64   buf.Bvsprintf(msg, ap);
65   va_end(ap);
66 
67   ConfigureLexErrorHandler(file, line, lc, buf);
68 }
69 
configure_write_resource(const char * filename,const char * resourcetype,const char * name,const char * content,const bool overwrite=false)70 static inline bool configure_write_resource(const char* filename,
71                                             const char* resourcetype,
72                                             const char* name,
73                                             const char* content,
74                                             const bool overwrite = false)
75 {
76   bool result = false;
77   size_t len;
78   int fd;
79   int flags = O_CREAT | O_WRONLY | O_TRUNC;
80 
81   if (!overwrite) { flags |= O_EXCL; }
82 
83   if ((fd = open(filename, flags, 0640)) >= 0) {
84     len = strlen(content);
85     write(fd, content, len);
86     close(fd);
87     result = true;
88   }
89 
90   return result;
91 }
92 
config_get_res_item(UaContext * ua,ResourceTable * res_table,const char * key,const char * value)93 static inline ResourceItem* config_get_res_item(UaContext* ua,
94                                                 ResourceTable* res_table,
95                                                 const char* key,
96                                                 const char* value)
97 {
98   ResourceItem* item = NULL;
99   const char* errorcharmsg = NULL;
100 
101   if (res_table) {
102     item = my_config->GetResourceItem(res_table->items, key);
103     if (!item) {
104       ua->ErrorMsg("Resource \"%s\" does not permit directive \"%s\".\n",
105                    res_table->name, key);
106       return NULL;
107     }
108   }
109 
110   /*
111    * Check against dangerous characters ('@', ';').
112    * Could be less strict, if this characters are quoted,
113    * but it is easier to handle it like this.
114    */
115   if (strchr(value, '@')) { errorcharmsg = "'@' (include)"; }
116   if (strchr(value, ';')) { errorcharmsg = "';' (end of directive)"; }
117   if (errorcharmsg) {
118     if (ua) {
119       ua->ErrorMsg(
120           "Could not add directive \"%s\": character %s is forbidden.\n", key,
121           errorcharmsg);
122     }
123     return NULL;
124   }
125 
126   return item;
127 }
128 
config_add_directive(UaContext * ua,ResourceTable * res_table,const char * key,const char * value,PoolMem & resource,int indent=2)129 static inline bool config_add_directive(UaContext* ua,
130                                         ResourceTable* res_table,
131                                         const char* key,
132                                         const char* value,
133                                         PoolMem& resource,
134                                         int indent = 2)
135 {
136   PoolMem temp(PM_MESSAGE);
137   ResourceItem* item = NULL;
138 
139   item = config_get_res_item(ua, res_table, key, value);
140   if (res_table && (!item)) { return false; }
141 
142   /*
143    * Use item->name instead of key for uniform formatting.
144    */
145   if (item) { key = item->name; }
146 
147   /* TODO: check type, to see if quotes should be used */
148   temp.bsprintf("%-*s%s = %s\n", indent, "", key, value);
149   resource.strcat(temp);
150 
151   return true;
152 }
153 
configure_create_resource_string(UaContext * ua,int first_parameter,ResourceTable * res_table,PoolMem & resourcename,PoolMem & resource)154 static inline bool configure_create_resource_string(UaContext* ua,
155                                                     int first_parameter,
156                                                     ResourceTable* res_table,
157                                                     PoolMem& resourcename,
158                                                     PoolMem& resource)
159 {
160   resource.strcat(res_table->name);
161   resource.strcat(" {\n");
162 
163   /*
164    * Is the name of the resource already given as value of the resource type?
165    * E.g. configure add client=newclient address=127.0.0.1 ...
166    * instead of configure add client name=newclient address=127.0.0.1 ...
167    */
168   if (ua->argv[first_parameter - 1]) {
169     resourcename.strcat(ua->argv[first_parameter - 1]);
170     if (!config_add_directive(ua, res_table, "name", resourcename.c_str(),
171                               resource)) {
172       return false;
173     }
174   }
175 
176   for (int i = first_parameter; i < ua->argc; i++) {
177     if (!ua->argv[i]) {
178       ua->ErrorMsg("Missing value for directive \"%s\"\n", ua->argk[i]);
179       return false;
180     }
181     if (Bstrcasecmp(ua->argk[i], "name")) { resourcename.strcat(ua->argv[i]); }
182     if (!config_add_directive(ua, res_table, ua->argk[i], ua->argv[i],
183                               resource)) {
184       return false;
185     }
186   }
187   resource.strcat("}\n");
188 
189   if (strlen(resourcename.c_str()) <= 0) {
190     ua->ErrorMsg("Resource \"%s\": missing name parameter.\n", res_table->name);
191     return false;
192   }
193 
194   return true;
195 }
196 
ConfigureCreateFdResourceString(UaContext * ua,PoolMem & resource,const char * clientname)197 static inline bool ConfigureCreateFdResourceString(UaContext* ua,
198                                                    PoolMem& resource,
199                                                    const char* clientname)
200 {
201   ClientResource* client;
202   s_password* password;
203   PoolMem temp(PM_MESSAGE);
204 
205   client = ua->GetClientResWithName(clientname);
206   if (!client) { return false; }
207   password = &client->password_;
208 
209   resource.strcat("Director {\n");
210   config_add_directive(NULL, NULL, "Name", me->resource_name_, resource);
211 
212   switch (password->encoding) {
213     case p_encoding_clear:
214       Mmsg(temp, "\"%s\"", password->value);
215       break;
216     case p_encoding_md5:
217       Mmsg(temp, "\"[md5]%s\"", password->value);
218       break;
219     default:
220       break;
221   }
222   config_add_directive(NULL, NULL, "Password", temp.c_str(), resource);
223 
224   resource.strcat("}\n");
225 
226   return true;
227 }
228 
229 /**
230  * Create a bareos-fd director resource file
231  * that corresponds to our client definition.
232  */
ConfigureCreateFdResource(UaContext * ua,const char * clientname)233 static inline bool ConfigureCreateFdResource(UaContext* ua,
234                                              const char* clientname)
235 {
236   PoolMem resource(PM_MESSAGE);
237   PoolMem filename_tmp(PM_FNAME);
238   PoolMem filename(PM_FNAME);
239   PoolMem basedir(PM_FNAME);
240   PoolMem temp(PM_MESSAGE);
241   const char* dirname = NULL;
242   const bool error_if_exists = false;
243   const bool create_directories = true;
244   const bool overwrite = true;
245 
246   if (!ConfigureCreateFdResourceString(ua, resource, clientname)) {
247     return false;
248   }
249 
250   /*
251    * Get the path where the resource should get stored.
252    */
253   basedir.bsprintf("bareos-dir-export/client/%s/bareos-fd.d", clientname);
254   dirname = my_config->GetNextRes(R_DIRECTOR, NULL)->resource_name_;
255   if (!my_config->GetPathOfNewResource(filename, temp, basedir.c_str(),
256                                        "director", dirname, error_if_exists,
257                                        create_directories)) {
258     ua->ErrorMsg("%s", temp.c_str());
259     return false;
260   }
261   filename_tmp.strcpy(temp);
262 
263   /*
264    * Write resource to file.
265    */
266   if (!configure_write_resource(filename.c_str(), "filedaemon-export",
267                                 clientname, resource.c_str(), overwrite)) {
268     ua->ErrorMsg("failed to write filedaemon config resource file\n");
269     return false;
270   }
271 
272   ua->send->ObjectStart("export");
273   ua->send->ObjectKeyValue("clientname", clientname);
274   ua->send->ObjectKeyValue("component", "bareos-fd");
275   ua->send->ObjectKeyValue("resource", "director");
276   ua->send->ObjectKeyValue("name", dirname);
277   ua->send->ObjectKeyValue("filename", filename.c_str(),
278                            "Exported resource file \"%s\":\n");
279   ua->send->ObjectKeyValue("content", resource.c_str(), "%s");
280   ua->send->ObjectEnd("export");
281 
282   return true;
283 }
284 
285 /**
286  * To add a resource during runtime, the following approach is used:
287  *
288  * - Create a temporary file which contains the new resource.
289  * - Use the existing parsing functions to add the new resource to the
290  * configured resources.
291  *   - on error:
292  *     - remove the resource and the temporary file.
293  *   - on success:
294  *     - move the new temporary resource file to a place, where it will also be
295  * loaded on restart
296  *       (<CONFIGDIR>/bareos-dir.d/<resourcetype>/<name_of_the_resource>.conf).
297  *
298  * This way, the existing parsing functionality is used.
299  */
ConfigureAddResource(UaContext * ua,int first_parameter,ResourceTable * res_table)300 static inline bool ConfigureAddResource(UaContext* ua,
301                                         int first_parameter,
302                                         ResourceTable* res_table)
303 {
304   PoolMem resource(PM_MESSAGE);
305   PoolMem name(PM_MESSAGE);
306   PoolMem filename_tmp(PM_FNAME);
307   PoolMem filename(PM_FNAME);
308   PoolMem temp(PM_FNAME);
309   JobResource* res = NULL;
310 
311   if (!configure_create_resource_string(ua, first_parameter, res_table, name,
312                                         resource)) {
313     return false;
314   }
315 
316   if (my_config->GetResWithName(res_table->rcode, name.c_str())) {
317     ua->ErrorMsg("Resource \"%s\" with name \"%s\" already exists.\n",
318                  res_table->name, name.c_str());
319     return false;
320   }
321 
322   if (!my_config->GetPathOfNewResource(filename, temp, NULL, res_table->name,
323                                        name.c_str(), true)) {
324     ua->ErrorMsg("%s", temp.c_str());
325     return false;
326   } else {
327     filename_tmp.strcpy(temp);
328   }
329 
330   if (!configure_write_resource(filename_tmp.c_str(), res_table->name,
331                                 name.c_str(), resource.c_str())) {
332     ua->ErrorMsg("failed to write config resource file\n");
333     return false;
334   }
335 
336   my_config->err_type_ = M_ERROR;
337   if (!my_config->ParseConfigFile(filename_tmp.c_str(), ua,
338                                   ConfigureLexErrorHandler, NULL)) {
339     unlink(filename_tmp.c_str());
340     my_config->RemoveResource(res_table->rcode, name.c_str());
341     return false;
342   }
343 
344   /*
345    * ParseConfigFile has already done some validation.
346    * However, it skipped at least some checks for R_JOB
347    * (reason: a job can get values from jobdefs,
348    * and the value propagation happens after reading the full configuration)
349    * therefore we explicitly check the new resource here.
350    */
351   if ((res_table->rcode == R_JOB) || (res_table->rcode == R_JOBDEFS)) {
352     res =
353         (JobResource*)my_config->GetResWithName(res_table->rcode, name.c_str());
354     PropagateJobdefs(res_table->rcode, res);
355     if (!ValidateResource(res_table->rcode, res_table->items,
356                           (BareosResource*)res)) {
357       ua->ErrorMsg("failed to create config resource \"%s\"\n", name.c_str());
358       unlink(filename_tmp.c_str());
359       my_config->RemoveResource(res_table->rcode, name.c_str());
360       return false;
361     }
362   }
363 
364   /*
365    * new config resource is working fine. Rename file to its permanent name.
366    */
367   if (rename(filename_tmp.c_str(), filename.c_str()) != 0) {
368     ua->ErrorMsg("failed to create config file \"%s\"\n", filename.c_str());
369     unlink(filename_tmp.c_str());
370     my_config->RemoveResource(res_table->rcode, name.c_str());
371     return false;
372   }
373 
374   /*
375    * When adding a client, also create the client configuration file.
376    */
377   if (res_table->rcode == R_CLIENT) {
378     ConfigureCreateFdResource(ua, name.c_str());
379   }
380 
381   ua->send->ObjectStart("add");
382   ua->send->ObjectKeyValue("resource", res_table->name);
383   ua->send->ObjectKeyValue("name", name.c_str());
384   ua->send->ObjectKeyValue("filename", filename.c_str(),
385                            "Created resource config file \"%s\":\n");
386   ua->send->ObjectKeyValue("content", resource.c_str(), "%s");
387   ua->send->ObjectEnd("add");
388 
389   return true;
390 }
391 
ConfigureAdd(UaContext * ua,int resource_type_parameter)392 static inline bool ConfigureAdd(UaContext* ua, int resource_type_parameter)
393 {
394   bool result = false;
395   ResourceTable* res_table = NULL;
396 
397   res_table = my_config->GetResourceTable(ua->argk[resource_type_parameter]);
398   if (!res_table) {
399     ua->ErrorMsg(_("invalid resource type %s.\n"),
400                  ua->argk[resource_type_parameter]);
401     return false;
402   }
403 
404   if (res_table->rcode == R_DIRECTOR) {
405     ua->ErrorMsg(_("Only one Director resource allowed.\n"));
406     return false;
407   }
408 
409   ua->send->ObjectStart("configure");
410   result = ConfigureAddResource(ua, resource_type_parameter + 1, res_table);
411   ua->send->ObjectEnd("configure");
412 
413   return result;
414 }
415 
ConfigureExportUsage(UaContext * ua)416 static inline void ConfigureExportUsage(UaContext* ua)
417 {
418   ua->ErrorMsg(_("usage: configure export client=<clientname>\n"));
419 }
420 
ConfigureExport(UaContext * ua)421 static inline bool ConfigureExport(UaContext* ua)
422 {
423   bool result = false;
424   int i;
425 
426   i = FindArgWithValue(ua, NT_("client"));
427   if (i < 0) {
428     ConfigureExportUsage(ua);
429     return false;
430   }
431 
432   if (!ua->GetClientResWithName(ua->argv[i])) {
433     ConfigureExportUsage(ua);
434     return false;
435   }
436 
437   ua->send->ObjectStart("configure");
438   result = ConfigureCreateFdResource(ua, ua->argv[i]);
439   ua->send->ObjectEnd("configure");
440 
441   return result;
442 }
443 
ConfigureCmd(UaContext * ua,const char * cmd)444 bool ConfigureCmd(UaContext* ua, const char* cmd)
445 {
446   bool result = false;
447 
448   if (!(my_config->IsUsingConfigIncludeDir())) {
449     ua->WarningMsg(
450         _("It seems that the configuration is not adapted to the include "
451           "directory structure. "
452           "This means, that the configure command may not work as expected. "
453           "Your configuration changes may not survive a reload/restart. "));
454   }
455 
456   if (ua->argc < 3) {
457     ua->ErrorMsg(
458         _("usage:\n"
459           "  configure add <resourcetype> <key1>=<value1> ...\n"
460           "  configure export client=<clientname>\n"));
461     return false;
462   }
463 
464   if (Bstrcasecmp(ua->argk[1], NT_("add"))) {
465     result = ConfigureAdd(ua, 2);
466   } else if (Bstrcasecmp(ua->argk[1], NT_("export"))) {
467     result = ConfigureExport(ua);
468   } else {
469     ua->ErrorMsg(_("invalid subcommand %s.\n"), ua->argk[1]);
470     return false;
471   }
472 
473   return result;
474 }
475 } /* namespace directordaemon */
476