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