]> git.ipfire.org Git - people/ms/network.git/blame - src/networkd/config.c
config: Extend the parser to easier read/write configs
[people/ms/network.git] / src / networkd / config.c
CommitLineData
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
34struct 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
41struct 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
52struct 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
64static void nw_config_entry_free(struct nw_config_entry* entry) {
65 free(entry);
66}
67
082d81a3
MT
68static void nw_config_option_free(struct nw_config_option* option) {
69 free(option);
70}
71
4237caa2 72static 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
97ERROR:
98 nw_config_entry_free(entry);
99 return NULL;
100}
101
2361667e 102static 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 120int 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
152ERROR:
153 nw_config_free(c);
154
155 return r;
6b666d62
MT
156}
157
2361667e 158nw_config* nw_config_ref(nw_config* config) {
6b666d62
MT
159 config->nrefs++;
160
161 return config;
162}
163
2361667e 164nw_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
172int 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
183int 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
203ERROR:
204 if (c)
205 nw_config_unref(c);
206
207 return r;
208}
209
2361667e 210const char* nw_config_path(nw_config* config) {
b076fa8b
MT
211 if (*config->path)
212 return config->path;
213
214 return NULL;
215}
216
2361667e 217int 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 231static 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 277int 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
302ERROR:
303 if (f)
304 fclose(f);
305
306 return r;
307}
4237caa2 308
2361667e 309static 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 329int 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
348ERROR:
349 if (f)
350 fclose(f);
351
352 return r;
353}
354
2361667e 355static 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 371int 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 390const 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 401int 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 425int 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 445int 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
457static const char* nw_config_true[] = {
458 "true",
459 "yes",
460 "1",
461 NULL,
462};
463
464int 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
481int 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
489int 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
502int 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
515int 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
543int 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
552int nw_config_write_int(nw_config* config, const char* key, const void* data) {
553 return 0;
554}
555
556// String
557
558int 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
569int 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
577int 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
593int 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
610ERROR:
611 if (value)
612 free(value);
613
614 return r;
615}