]>
Commit | Line | Data |
---|---|---|
6b666d62 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # IPFire.org - A linux based firewall # | |
4 | # Copyright (C) 2023 IPFire Network Development Team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
4237caa2 | 21 | #include <errno.h> |
b076fa8b | 22 | #include <limits.h> |
c81a6335 | 23 | #include <stdio.h> |
6b666d62 | 24 | #include <stdlib.h> |
4237caa2 MT |
25 | #include <string.h> |
26 | #include <sys/queue.h> | |
c403eb4c | 27 | #include <unistd.h> |
6b666d62 | 28 | |
082d81a3 | 29 | #include "address.h" |
6b666d62 | 30 | #include "config.h" |
c81a6335 | 31 | #include "logging.h" |
4237caa2 MT |
32 | #include "string.h" |
33 | ||
34 | struct nw_config_entry { | |
35 | STAILQ_ENTRY(nw_config_entry) nodes; | |
36 | ||
37 | char key[NETWORK_CONFIG_KEY_MAX_LENGTH]; | |
38 | char value[NETWORK_CONFIG_KEY_MAX_LENGTH]; | |
39 | }; | |
6b666d62 | 40 | |
082d81a3 MT |
41 | struct nw_config_option { |
42 | STAILQ_ENTRY(nw_config_option) nodes; | |
43 | ||
44 | const char* key; | |
45 | void* data; | |
46 | ||
47 | // Callbacks | |
48 | nw_config_option_read_callback_t read_callback; | |
49 | nw_config_option_write_callback_t write_callback; | |
50 | }; | |
51 | ||
6b666d62 MT |
52 | struct nw_config { |
53 | int nrefs; | |
4237caa2 | 54 | |
b076fa8b MT |
55 | // The path to the configuration file |
56 | char path[PATH_MAX]; | |
57 | ||
082d81a3 MT |
58 | STAILQ_HEAD(config_entries, nw_config_entry) entries; |
59 | ||
60 | // Options | |
61 | STAILQ_HEAD(parser_entries, nw_config_option) options; | |
6b666d62 MT |
62 | }; |
63 | ||
4237caa2 MT |
64 | static void nw_config_entry_free(struct nw_config_entry* entry) { |
65 | free(entry); | |
66 | } | |
67 | ||
082d81a3 MT |
68 | static void nw_config_option_free(struct nw_config_option* option) { |
69 | free(option); | |
70 | } | |
71 | ||
4237caa2 | 72 | static struct nw_config_entry* nw_config_entry_create( |
2361667e | 73 | nw_config* config, const char* key) { |
4237caa2 MT |
74 | int r; |
75 | ||
76 | // Check input value | |
77 | if (!key) { | |
78 | errno = EINVAL; | |
79 | return NULL; | |
80 | } | |
81 | ||
82 | // Allocate a new object | |
83 | struct nw_config_entry* entry = calloc(1, sizeof(*entry)); | |
84 | if (!entry) | |
85 | return NULL; | |
86 | ||
87 | // Store the key | |
88 | r = nw_string_set(entry->key, key); | |
89 | if (r) | |
90 | goto ERROR; | |
91 | ||
92 | // Append the new entry | |
93 | STAILQ_INSERT_TAIL(&config->entries, entry, nodes); | |
94 | ||
82f84c5f MT |
95 | return entry; |
96 | ||
4237caa2 MT |
97 | ERROR: |
98 | nw_config_entry_free(entry); | |
99 | return NULL; | |
100 | } | |
101 | ||
2361667e | 102 | static void nw_config_free(nw_config* config) { |
082d81a3 MT |
103 | struct nw_config_option* option = NULL; |
104 | ||
91915f80 MT |
105 | // Flush all entries |
106 | nw_config_flush(config); | |
4237caa2 | 107 | |
082d81a3 MT |
108 | // Free all options |
109 | while (!STAILQ_EMPTY(&config->options)) { | |
110 | option = STAILQ_FIRST(&config->options); | |
111 | STAILQ_REMOVE_HEAD(&config->options, nodes); | |
112 | ||
113 | // Free the options | |
114 | nw_config_option_free(option); | |
115 | } | |
116 | ||
6b666d62 MT |
117 | free(config); |
118 | } | |
119 | ||
2361667e | 120 | int nw_config_create(nw_config** config, const char* path) { |
b076fa8b MT |
121 | int r; |
122 | ||
2361667e | 123 | nw_config* c = calloc(1, sizeof(*c)); |
6b666d62 MT |
124 | if (!c) |
125 | return 1; | |
126 | ||
127 | // Initialize reference counter | |
128 | c->nrefs = 1; | |
129 | ||
4237caa2 MT |
130 | // Initialise entries |
131 | STAILQ_INIT(&c->entries); | |
132 | ||
082d81a3 MT |
133 | // Initialise options |
134 | STAILQ_INIT(&c->options); | |
135 | ||
b076fa8b MT |
136 | // Store the path |
137 | if (path) { | |
138 | r = nw_string_set(c->path, path); | |
139 | if (r) | |
140 | goto ERROR; | |
141 | ||
142 | // Try to read the configuration from path | |
143 | r = nw_config_read(c); | |
144 | if (r) | |
145 | goto ERROR; | |
146 | } | |
147 | ||
6b666d62 MT |
148 | *config = c; |
149 | ||
150 | return 0; | |
b076fa8b MT |
151 | |
152 | ERROR: | |
153 | nw_config_free(c); | |
154 | ||
155 | return r; | |
6b666d62 MT |
156 | } |
157 | ||
2361667e | 158 | nw_config* nw_config_ref(nw_config* config) { |
6b666d62 MT |
159 | config->nrefs++; |
160 | ||
161 | return config; | |
162 | } | |
163 | ||
2361667e | 164 | nw_config* nw_config_unref(nw_config* config) { |
6b666d62 MT |
165 | if (--config->nrefs > 0) |
166 | return config; | |
167 | ||
168 | nw_config_free(config); | |
169 | return NULL; | |
170 | } | |
c81a6335 | 171 | |
c403eb4c MT |
172 | int nw_config_destroy(nw_config* config) { |
173 | int r; | |
174 | ||
175 | // Drop all entries | |
176 | r = nw_config_flush(config); | |
177 | if (r) | |
178 | return r; | |
179 | ||
180 | return unlink(config->path); | |
181 | } | |
182 | ||
082d81a3 MT |
183 | int nw_config_copy(nw_config* config, nw_config** copy) { |
184 | struct nw_config_entry* entry = NULL; | |
185 | nw_config* c = NULL; | |
186 | int r; | |
187 | ||
188 | // Create a new configuration | |
189 | r = nw_config_create(&c, NULL); | |
190 | if (r) | |
191 | return r; | |
192 | ||
193 | // Copy everything | |
194 | STAILQ_FOREACH(entry, &config->entries, nodes) { | |
195 | r = nw_config_set(c, entry->key, entry->value); | |
196 | if (r) | |
197 | goto ERROR; | |
198 | } | |
199 | ||
200 | *copy = c; | |
201 | return 0; | |
202 | ||
203 | ERROR: | |
204 | if (c) | |
205 | nw_config_unref(c); | |
206 | ||
207 | return r; | |
208 | } | |
209 | ||
2361667e | 210 | const char* nw_config_path(nw_config* config) { |
b076fa8b MT |
211 | if (*config->path) |
212 | return config->path; | |
213 | ||
214 | return NULL; | |
215 | } | |
216 | ||
2361667e | 217 | int nw_config_flush(nw_config* config) { |
91915f80 MT |
218 | struct nw_config_entry* entry = NULL; |
219 | ||
220 | while (!STAILQ_EMPTY(&config->entries)) { | |
221 | entry = STAILQ_FIRST(&config->entries); | |
222 | STAILQ_REMOVE_HEAD(&config->entries, nodes); | |
223 | ||
224 | // Free the entry | |
225 | nw_config_entry_free(entry); | |
226 | } | |
227 | ||
228 | return 0; | |
229 | } | |
230 | ||
2361667e | 231 | static int nw_config_readf(nw_config* config, FILE* f) { |
5ef56cff MT |
232 | char* line = NULL; |
233 | size_t length = 0; | |
234 | int r; | |
c81a6335 | 235 | |
5ef56cff MT |
236 | ssize_t bytes_read = 0; |
237 | ||
238 | char* key = NULL; | |
239 | char* val = NULL; | |
240 | ||
241 | for (;;) { | |
242 | // Read the next line | |
243 | bytes_read = getline(&line, &length, f); | |
244 | if (bytes_read < 0) | |
245 | break; | |
246 | ||
247 | // Key starts at the beginning of the line | |
248 | key = line; | |
249 | ||
250 | // Value starts after '=' | |
251 | val = strchr(line, '='); | |
252 | ||
253 | // Invalid line without a '=' character | |
254 | if (!val) | |
255 | continue; | |
256 | ||
257 | // Split the string | |
258 | *val++ = '\0'; | |
259 | ||
260 | // Strip any whitespace from value | |
261 | r = nw_string_strip(val); | |
262 | if (r) | |
263 | break; | |
264 | ||
265 | // Store the setting | |
266 | r = nw_config_set(config, key, val); | |
267 | if (r) | |
268 | break; | |
269 | } | |
270 | ||
271 | if (line) | |
272 | free(line); | |
273 | ||
274 | return r; | |
c81a6335 MT |
275 | } |
276 | ||
2361667e | 277 | int nw_config_read(nw_config* config) { |
c81a6335 MT |
278 | FILE* f = NULL; |
279 | int r; | |
280 | ||
b076fa8b MT |
281 | // We cannot read if path is not set |
282 | if (!*config->path) { | |
283 | errno = ENOTSUP; | |
284 | return 1; | |
285 | } | |
286 | ||
c81a6335 | 287 | // Open the file |
b076fa8b | 288 | f = fopen(config->path, "r"); |
c81a6335 | 289 | if (!f) { |
b076fa8b MT |
290 | // Silently ignore if the file does not exist |
291 | if (errno == ENOENT) | |
292 | return 0; | |
293 | ||
294 | ERROR("Could not read configuration file %s: %m\n", config->path); | |
c81a6335 MT |
295 | r = 1; |
296 | goto ERROR; | |
297 | } | |
298 | ||
299 | // Read from file | |
300 | r = nw_config_readf(config, f); | |
301 | ||
302 | ERROR: | |
303 | if (f) | |
304 | fclose(f); | |
305 | ||
306 | return r; | |
307 | } | |
4237caa2 | 308 | |
2361667e | 309 | static int nw_config_writef(nw_config* config, FILE* f) { |
d3dfdb77 MT |
310 | struct nw_config_entry* entry = NULL; |
311 | int r; | |
312 | ||
313 | STAILQ_FOREACH(entry, &config->entries, nodes) { | |
314 | // Skip if value is NULL | |
315 | if (!*entry->value) | |
316 | continue; | |
317 | ||
318 | // Write the entry | |
5ef56cff | 319 | r = fprintf(f, "%s=%s\n", entry->key, entry->value); |
d3dfdb77 MT |
320 | if (r < 0) { |
321 | ERROR("Failed to write configuration: %m\n"); | |
322 | return r; | |
323 | } | |
324 | } | |
325 | ||
326 | return 0; | |
327 | } | |
328 | ||
2361667e | 329 | int nw_config_write(nw_config* config) { |
d3dfdb77 MT |
330 | int r; |
331 | ||
b076fa8b MT |
332 | // We cannot write if path is not set |
333 | if (!*config->path) { | |
334 | errno = ENOTSUP; | |
335 | return 1; | |
336 | } | |
337 | ||
338 | FILE* f = fopen(config->path, "w"); | |
d3dfdb77 | 339 | if (!f) { |
b076fa8b | 340 | ERROR("Failed to open %s for writing: %m\n", config->path); |
d3dfdb77 MT |
341 | r = 1; |
342 | goto ERROR; | |
343 | } | |
344 | ||
345 | // Write configuration | |
346 | r = nw_config_writef(config, f); | |
347 | ||
348 | ERROR: | |
349 | if (f) | |
350 | fclose(f); | |
351 | ||
352 | return r; | |
353 | } | |
354 | ||
2361667e | 355 | static struct nw_config_entry* nw_config_find(nw_config* config, const char* key) { |
4237caa2 MT |
356 | struct nw_config_entry* entry = NULL; |
357 | ||
358 | STAILQ_FOREACH(entry, &config->entries, nodes) { | |
359 | // Key must match | |
360 | if (strcmp(entry->key, key) != 0) | |
361 | continue; | |
362 | ||
363 | // Match! | |
364 | return entry; | |
365 | } | |
366 | ||
367 | // No match | |
368 | return NULL; | |
369 | } | |
370 | ||
2361667e | 371 | int nw_config_del(nw_config* config, const char* key) { |
4237caa2 MT |
372 | struct nw_config_entry* entry = NULL; |
373 | ||
374 | // Find an entry matching the key | |
375 | entry = nw_config_find(config, key); | |
376 | ||
377 | // If there is no entry, there is nothing to do | |
378 | if (!entry) | |
379 | return 0; | |
380 | ||
381 | // Otherwise remove the object | |
382 | STAILQ_REMOVE(&config->entries, entry, nw_config_entry, nodes); | |
383 | ||
384 | // Free the entry | |
385 | nw_config_entry_free(entry); | |
386 | ||
387 | return 0; | |
388 | } | |
389 | ||
2361667e | 390 | const char* nw_config_get(nw_config* config, const char* key) { |
9368a163 MT |
391 | struct nw_config_entry* entry = nw_config_find(config, key); |
392 | ||
393 | // Return the value if found and set | |
394 | if (entry && *entry->value) | |
395 | return entry->value; | |
396 | ||
397 | // Otherwise return NULL | |
398 | return NULL; | |
399 | } | |
400 | ||
2361667e | 401 | int nw_config_set(nw_config* config, const char* key, const char* value) { |
4237caa2 MT |
402 | struct nw_config_entry* entry = NULL; |
403 | ||
082d81a3 MT |
404 | // Log the change |
405 | DEBUG("%p: Setting %s = %s\n", config, key, value); | |
406 | ||
4237caa2 MT |
407 | // Delete the entry if val is NULL |
408 | if (!value) | |
409 | return nw_config_del(config, key); | |
410 | ||
411 | // Find any existing entries | |
412 | entry = nw_config_find(config, key); | |
413 | ||
414 | // Create a new entry if it doesn't exist, yet | |
415 | if (!entry) { | |
416 | entry = nw_config_entry_create(config, key); | |
417 | if (!entry) | |
418 | return 1; | |
419 | } | |
420 | ||
421 | // Store the new value | |
422 | return nw_string_set(entry->value, value); | |
423 | } | |
d39683a6 | 424 | |
2361667e | 425 | int nw_config_get_int(nw_config* config, const char* key, const int __default) { |
92c8a4fe MT |
426 | char* p = NULL; |
427 | int r; | |
428 | ||
d39683a6 MT |
429 | const char* value = nw_config_get(config, key); |
430 | ||
431 | // Return zero if not set | |
432 | if (!value) | |
9368a163 | 433 | return __default; |
d39683a6 | 434 | |
92c8a4fe MT |
435 | // Parse the input |
436 | r = strtoul(value, &p, 10); | |
437 | ||
438 | // If we have characters following the input, we throw it away | |
439 | if (p) | |
440 | return __default; | |
441 | ||
442 | return r; | |
d39683a6 | 443 | } |
9368a163 | 444 | |
2361667e | 445 | int nw_config_set_int(nw_config* config, const char* key, const int value) { |
9368a163 MT |
446 | char __value[1024]; |
447 | int r; | |
448 | ||
449 | // Format the value as string | |
2d000517 | 450 | r = nw_string_format(__value, "%d", value); |
9368a163 MT |
451 | if (r) |
452 | return r; | |
453 | ||
454 | return nw_config_set(config, key, __value); | |
455 | } | |
8b0b5c6d MT |
456 | |
457 | static const char* nw_config_true[] = { | |
458 | "true", | |
459 | "yes", | |
460 | "1", | |
461 | NULL, | |
462 | }; | |
463 | ||
464 | int nw_config_get_bool(nw_config* config, const char* key) { | |
465 | const char* value = nw_config_get(config, key); | |
466 | ||
467 | // No value indicates false | |
468 | if (!value) | |
469 | return 0; | |
470 | ||
471 | // Check if we match any known true words | |
472 | for (const char** s = nw_config_true; *s; s++) { | |
8923434c | 473 | if (strcasecmp(value, *s) == 0) |
8b0b5c6d MT |
474 | return 1; |
475 | } | |
476 | ||
477 | // No match means false | |
478 | return 0; | |
479 | } | |
480 | ||
481 | int nw_config_set_bool(nw_config* config, const char* key, const int value) { | |
482 | return nw_config_set(config, key, value ? "true" : "false"); | |
483 | } | |
082d81a3 MT |
484 | |
485 | /* | |
486 | Options | |
487 | */ | |
488 | ||
489 | int nw_config_options_read(nw_config* config) { | |
490 | struct nw_config_option* option = NULL; | |
491 | int r; | |
492 | ||
493 | STAILQ_FOREACH(option, &config->options, nodes) { | |
494 | r = option->read_callback(config, option->key, option->data); | |
495 | if (r < 0) | |
496 | return r; | |
497 | } | |
498 | ||
499 | return 0; | |
500 | } | |
501 | ||
502 | int nw_config_options_write(nw_config* config) { | |
503 | struct nw_config_option* option = NULL; | |
504 | int r; | |
505 | ||
506 | STAILQ_FOREACH(option, &config->options, nodes) { | |
507 | r = option->write_callback(config, option->key, option->data); | |
508 | if (r < 0) | |
509 | return r; | |
510 | } | |
511 | ||
512 | return 0; | |
513 | } | |
514 | ||
515 | int nw_config_option_add(nw_config* config, const char* key, void* data, | |
516 | nw_config_option_read_callback_t read_callback, | |
517 | nw_config_option_write_callback_t write_callback) { | |
518 | // Check input | |
519 | if (!key || !data || !read_callback || !write_callback) | |
520 | return -EINVAL; | |
521 | ||
522 | // Allocate a new option | |
523 | struct nw_config_option* option = calloc(1, sizeof(*option)); | |
524 | if (!option) | |
525 | return -errno; | |
526 | ||
527 | // Set key | |
528 | option->key = key; | |
529 | ||
530 | // Set data | |
531 | option->data = data; | |
532 | ||
533 | // Set callbacks | |
534 | option->read_callback = read_callback; | |
535 | option->write_callback = write_callback; | |
536 | ||
537 | // Append the new option | |
538 | STAILQ_INSERT_TAIL(&config->options, option, nodes); | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
543 | int nw_config_read_int(nw_config* config, const char* key, void* data) { | |
544 | int* p = (int*)data; | |
545 | ||
546 | // Fetch the value | |
547 | *p = nw_config_get_int(config, key, -1); | |
548 | ||
549 | return 0; | |
550 | } | |
551 | ||
552 | int nw_config_write_int(nw_config* config, const char* key, const void* data) { | |
553 | return 0; | |
554 | } | |
555 | ||
556 | // String | |
557 | ||
558 | int nw_config_read_string(nw_config* config, const char* key, void* data) { | |
559 | const char** p = (const char**)data; | |
560 | ||
561 | // Fetch the value | |
562 | const char* value = nw_config_get(config, key); | |
563 | if (value) | |
564 | *p = value; | |
565 | ||
566 | return 0; | |
567 | } | |
568 | ||
569 | int nw_config_write_string(nw_config* config, const char* key, const void* data) { | |
570 | const char** value = (const char**)data; | |
571 | ||
572 | return nw_config_set(config, key, *value); | |
573 | } | |
574 | ||
575 | // Address | |
576 | ||
577 | int nw_config_read_address(nw_config* config, const char* key, void* data) { | |
578 | nw_address_t* address = (nw_address_t*)data; | |
579 | int r; | |
580 | ||
581 | // Fetch the value | |
582 | const char* value = nw_config_get(config, key); | |
583 | if (!value) | |
584 | return -EINVAL; | |
585 | ||
586 | r = nw_address_from_string(address, value); | |
587 | if (r < 0) | |
588 | ERROR("Could not parse address: %s\n", value); | |
589 | ||
590 | return r; | |
591 | } | |
592 | ||
593 | int nw_config_write_address(nw_config* config, const char* key, const void* data) { | |
594 | const nw_address_t* address = (nw_address_t*)data; | |
595 | int r; | |
596 | ||
597 | // Format the address to string | |
598 | char* value = nw_address_to_string(address); | |
599 | if (!value) | |
600 | return -errno; | |
601 | ||
602 | // Store the value | |
603 | r = nw_config_set(config, key, value); | |
604 | if (r < 0) | |
605 | goto ERROR; | |
606 | ||
607 | // Success | |
608 | r = 0; | |
609 | ||
610 | ERROR: | |
611 | if (value) | |
612 | free(value); | |
613 | ||
614 | return r; | |
615 | } |