2 * onak-conf.c - Routines related to runtime config.
4 * Copyright 2002,2012 Jonathan McDowell <noodles@earth.li>
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; version 2 of the License.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program. If not, see <https://www.gnu.org/licenses/>.
24 #include "build-config.h"
29 #include "onak-conf.h"
32 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
36 * config - Runtime configuration for onak.
38 * This is the default config; normally overridden with values from the
41 struct onak_config config = {
61 .clean_policies = ONAK_CLEAN_DROP_V3_KEYS | ONAK_CLEAN_CHECK_SIGHASH,
67 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
72 while (cur != NULL && strcmp(name,
73 ((struct onak_db_config *) cur->object)->name)) {
77 return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
80 bool parsebool(char *str, bool fallback)
82 if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
83 !strcasecmp(str, "0")) {
85 } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
86 !strcasecmp(str, "1")) {
89 logthing(LOGTHING_CRITICAL,
90 "Couldn't parse %s as a boolean config variable, "
91 "returning fallback of '%s'.",
93 fallback ? "true" : "false");
99 * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
101 static bool parseoldconfigline(char *line)
103 if (line[0] == '#' || line[0] == 0) {
105 * Comment line, ignore.
107 } else if (!strncmp("db_dir ", line, 7)) {
108 config.backend->location = strdup(&line[7]);
109 } else if (!strncmp("debug ", line, 6)) {
111 * Not supported yet; ignore for compatibility with
114 } else if (!strncmp("default_language ", line, 17)) {
116 * Not supported yet; ignore for compatibility with
119 } else if (!strncmp("mail_delivery_client ", line, 21)) {
120 config.mta = strdup(&line[21]);
121 } else if (!strncmp("maintainer_email ", line, 17)) {
122 config.adminemail = strdup(&line[17]);
123 } else if (!strncmp("mail_intro_file ", line, 16)) {
125 * Not supported yet; ignore for compatibility with
128 } else if (!strncmp("help_dir ", line, 9)) {
130 * Not supported yet; ignore for compatibility with
133 } else if (!strncmp("max_last ", line, 9)) {
135 * Not supported yet; ignore for compatibility with
138 } else if (!strncmp("max_reply_keys ", line, 15)) {
139 config.maxkeys = atoi(&line[15]);
140 } else if (!strncmp("pg_dbhost ", line, 10)) {
141 config.backend->hostname = strdup(&line[10]);
142 } else if (!strncmp("pg_dbname ", line, 10)) {
143 config.backend->location = strdup(&line[10]);
144 } else if (!strncmp("pg_dbuser ", line, 10)) {
145 config.backend->username = strdup(&line[10]);
146 } else if (!strncmp("pg_dbpass ", line, 10)) {
147 config.backend->password = strdup(&line[10]);
148 } else if (!strncmp("syncsite ", line, 9)) {
150 lladd(config.syncsites, strdup(&line[9]));
151 } else if (!strncmp("logfile ", line, 8)) {
152 config.logfile = strdup(&line[8]);
153 } else if (!strncmp("loglevel ", line, 9)) {
154 setlogthreshold(atoi(&line[9]));
155 } else if (!strncmp("this_site ", line, 10)) {
156 config.thissite = strdup(&line[10]);
157 } else if (!strncmp("socket_name ", line, 12) ||
158 !strncmp("www_port ", line, 9)) {
160 * Not applicable; ignored for compatibility with pksd.
162 } else if (!strncmp("pks_bin_dir ", line, 12)) {
163 config.bin_dir = strdup(&line[12]);
164 } else if (!strncmp("mail_dir ", line, 9)) {
165 config.mail_dir = strdup(&line[9]);
166 } else if (!strncmp("db_backend ", line, 11)) {
167 config.backend->type = strdup(&line[11]);
168 config.backend->name = strdup(&line[11]);
169 config.db_backend = strdup(&line[11]);
170 } else if (!strncmp("backends_dir ", line, 13)) {
171 config.backends_dir = strdup(&line[13]);
172 } else if (!strncmp("use_keyd ", line, 9)) {
173 config.use_keyd = parsebool(&line[9],
175 } else if (!strncmp("sock_dir ", line, 9)) {
176 config.sock_dir = strdup(&line[9]);
177 } else if (!strncmp("check_sighash ", line, 9)) {
178 if (parsebool(&line[9], config.clean_policies &
179 ONAK_CLEAN_CHECK_SIGHASH)) {
180 config.clean_policies |=
181 ONAK_CLEAN_CHECK_SIGHASH;
183 config.clean_policies &=
184 ~ONAK_CLEAN_CHECK_SIGHASH;
194 * Parse a new style .ini config line, supporting [sections] and name=value
197 static bool parseconfigline(char *line)
199 /* Yes, this means we're not re-entrant. */
200 static char section[32] = "";
201 struct onak_db_config *backend;
205 if (line[0] == '#' || line[0] == ';' ||
208 * Comment line, ignore.
210 } else if (line[0] == '[') {
213 if (line[len - 1] != ']') {
214 logthing(LOGTHING_CRITICAL,
215 "Malformed section header '%s' in "
216 "config file.", line);
219 if (len > sizeof(section)) {
220 logthing(LOGTHING_CRITICAL,
221 "Section header '%s' is too long in "
222 "config file.", line);
226 strncpy(section, &line[1], len);
227 } else if ((value = strchr(line, '=')) != NULL) {
231 /* We can have multiple backend: sections */
232 if (!strncmp(section, "backend:", 8)) {
233 backend = find_db_backend_config(
234 config.backends, §ion[8]);
235 if (backend == NULL) {
237 sizeof(struct onak_db_config));
238 backend->name = strdup(§ion[8]);
239 config.backends = lladd(config.backends,
243 if (!strcmp(name, "type")) {
244 backend->type = strdup(value);
245 } else if (!strcmp(name, "location")) {
246 backend->location = strdup(value);
247 } else if (!strcmp(name, "hostname")) {
248 backend->location = strdup(value);
249 } else if (!strcmp(name, "username")) {
250 backend->location = strdup(value);
251 } else if (!strcmp(name, "password")) {
252 backend->location = strdup(value);
255 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
257 } else if (MATCH("main", "backend")) {
258 config.db_backend = strdup(value);
259 } else if (MATCH("main", "backends_dir")) {
260 config.backends_dir = strdup(value);
261 } else if (MATCH("main", "logfile")) {
262 config.logfile = strdup(value);
263 } else if (MATCH("main", "loglevel")) {
264 setlogthreshold(atoi(value));
265 } else if (MATCH("main", "use_keyd")) {
266 config.use_keyd = parsebool(value,
268 } else if (MATCH("main", "sock_dir")) {
269 config.sock_dir = strdup(value);
270 } else if (MATCH("main", "max_reply_keys")) {
271 config.maxkeys = atoi(value);
273 } else if (MATCH("mail", "maintainer_email")) {
274 config.adminemail = strdup(value);
275 } else if (MATCH("mail", "mail_dir")) {
276 config.mail_dir = strdup(value);
277 } else if (MATCH("mail", "mta")) {
278 config.mta = strdup(value);
279 } else if (MATCH("mail", "bin_dir")) {
280 config.bin_dir = strdup(value);
281 } else if (MATCH("mail", "this_site")) {
282 config.thissite = strdup(value);
283 } else if (MATCH("mail", "syncsite")) {
284 config.syncsites = lladd(config.syncsites,
286 /* [verification] section */
287 } else if (MATCH("verification", "blacklist")) {
288 array_load(&config.blacklist, value);
289 } else if (MATCH("verification", "drop_v3")) {
290 if (parsebool(value, config.clean_policies &
291 ONAK_CLEAN_DROP_V3_KEYS)) {
292 config.clean_policies |=
293 ONAK_CLEAN_DROP_V3_KEYS;
295 config.clean_policies &=
296 ~ONAK_CLEAN_DROP_V3_KEYS;
298 } else if (MATCH("verification", "check_sighash")) {
299 if (parsebool(value, config.clean_policies &
300 ONAK_CLEAN_CHECK_SIGHASH)) {
301 config.clean_policies |=
302 ONAK_CLEAN_CHECK_SIGHASH;
304 config.clean_policies &=
305 ~ONAK_CLEAN_CHECK_SIGHASH;
307 } else if (MATCH("verification", "check_packet_size")) {
308 if (parsebool(value, config.clean_policies &
309 ONAK_CLEAN_LARGE_PACKETS)) {
310 config.clean_policies |=
311 ONAK_CLEAN_LARGE_PACKETS;
313 config.clean_policies &=
314 ~ONAK_CLEAN_LARGE_PACKETS;
316 } else if (MATCH("verification", "require_other_sig")) {
318 if (parsebool(value, config.clean_policies &
319 ONAK_CLEAN_NEED_OTHER_SIG)) {
320 config.clean_policies |=
321 ONAK_CLEAN_NEED_OTHER_SIG;
323 config.clean_policies &=
324 ~ONAK_CLEAN_NEED_OTHER_SIG;
327 logthing(LOGTHING_ERROR,
328 "Compiled without crypto support, "
329 "require_other_sig not available.");
331 } else if (MATCH("verification", "update_only")) {
332 if (parsebool(value, config.clean_policies &
333 ONAK_CLEAN_UPDATE_ONLY)) {
334 config.clean_policies |=
335 ONAK_CLEAN_UPDATE_ONLY;
337 config.clean_policies &=
338 ~ONAK_CLEAN_UPDATE_ONLY;
340 } else if (MATCH("verification", "verify_signatures")) {
342 if (parsebool(value, config.clean_policies &
343 ONAK_CLEAN_VERIFY_SIGNATURES)) {
344 config.clean_policies |=
345 ONAK_CLEAN_VERIFY_SIGNATURES;
347 config.clean_policies &=
348 ~ONAK_CLEAN_VERIFY_SIGNATURES;
351 logthing(LOGTHING_ERROR,
352 "Compiled without crypto support, "
353 "verify_signatures not available.");
365 void readconfig(const char *configfile) {
371 struct onak_db_config *backend;
372 bool oldstyle = false;
378 * Try to find a config file to use. If one is explicitly provided,
379 * use that. Otherwise look in $XDG_CONFIG_HOME, $HOME and finally
380 * the build in configuration directory. We try an old style onak.conf
381 * first and then the new style onak.ini if that wasn't found - this
382 * is to prevent breaking existing installs on upgrade.
384 if (configfile == NULL) {
386 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
387 /* dir + / + onak.conf + NUL */
388 len = strlen(dir) + 1 + 9 + 1;
390 snprintf(conf, len, "%s/onak.conf", dir);
391 conffile = fopen(conf, "r");
392 if (conffile == NULL) {
393 /* Conveniently .ini is shorter than .conf */
394 snprintf(conf, len, "%s/onak.ini", dir);
395 conffile = fopen(conf, "r");
400 } else if ((dir = getenv("HOME")) != NULL) {
401 /* dir + /.config/onak.conf + NUL */
402 len = strlen(dir) + 18 + 1;
404 snprintf(conf, len, "%s/.config/onak.conf", dir);
405 conffile = fopen(conf, "r");
406 if (conffile == NULL) {
407 /* Conveniently .ini is shorter than .conf */
408 snprintf(conf, len, "%s/onak.ini", dir);
409 conffile = fopen(conf, "r");
415 if (conffile == NULL) {
416 conffile = fopen(CONFIGDIR "/onak.conf", "r");
417 if (conffile == NULL) {
418 conffile = fopen(CONFIGDIR "/onak.ini", "r");
425 * Explicitly provided config file; if the filename ends .conf
426 * assume it's old style.
428 len = strlen(configfile);
429 if (!strcmp(&configfile[len - 5], ".conf")) {
432 conffile = fopen(configfile, "r");
436 logthing(LOGTHING_CRITICAL, "Reading deprecated old-style "
437 "configuration file. This will not be "
438 "supported in the next release.");
441 if (conffile != NULL) {
442 if (!fgets(curline, 1023, conffile)) {
443 logthing(LOGTHING_CRITICAL,
444 "Problem reading configuration file.");
450 /* Add a single DB configuration */
451 backend = calloc(1, sizeof(*backend));
452 config.backend = backend;
453 config.backends = lladd(NULL, backend);
456 while (!feof(conffile)) {
457 /* Strip any trailing white space */
458 for (i = strlen(curline) - 1;
459 i >= 0 && isspace(curline[i]);
464 /* Strip any leading white space */
466 while (curline[i] != 0 && isspace(curline[i])) {
471 res = parseoldconfigline(&curline[i]);
473 res = parseconfigline(&curline[i]);
476 logthing(LOGTHING_ERROR,
477 "Unknown config line: %s", curline);
480 if (!fgets(curline, 1023, conffile) &&
482 logthing(LOGTHING_CRITICAL,
483 "Problem reading configuration file.");
489 if (config.db_backend == NULL) {
490 logthing(LOGTHING_CRITICAL,
491 "No database backend configured.");
492 } else if (!oldstyle) {
493 config.backend = find_db_backend_config(
494 config.backends, config.db_backend);
495 if (config.backend == NULL) {
496 logthing(LOGTHING_NOTICE,
497 "Couldn't find configuration for %s "
498 "backend.", config.db_backend);
502 logthing(LOGTHING_NOTICE,
503 "Couldn't open config file; using defaults.");
507 void writeconfig(const char *configfile)
513 conffile = fopen(configfile, "w");
518 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
519 fprintf(conffile, s "=%s\n", c); \
521 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
523 fprintf(conffile, "[main]\n");
524 WRITE_IF_NOT_NULL(config.backend->name, "backend");
525 WRITE_IF_NOT_NULL(config.backends_dir, "backends_dir");
526 WRITE_IF_NOT_NULL(config.logfile, "logfile");
527 fprintf(conffile, "loglevel=%d\n", getlogthreshold());
528 WRITE_BOOL(config.use_keyd, "use_keyd");
529 WRITE_IF_NOT_NULL(config.sock_dir, "sock_dir");
530 fprintf(conffile, "max_reply_keys=%d\n", config.maxkeys);
531 fprintf(conffile, "\n");
533 fprintf(conffile, "[verification]\n");
534 WRITE_BOOL(config.clean_policies & ONAK_CLEAN_CHECK_SIGHASH,
536 fprintf(conffile, "\n");
538 fprintf(conffile, "[mail]\n");
539 WRITE_IF_NOT_NULL(config.adminemail, "maintainer_email");
540 WRITE_IF_NOT_NULL(config.mail_dir, "mail_dir");
541 WRITE_IF_NOT_NULL(config.mta, "mta");
542 WRITE_IF_NOT_NULL(config.bin_dir, "bin_dir");
543 WRITE_IF_NOT_NULL(config.thissite, "this_site");
545 cur = config.syncsites;
546 while (cur != NULL) {
547 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
551 cur = config.backends;
552 while (cur != NULL) {
553 struct onak_db_config *backend =
554 (struct onak_db_config *) cur->object;
555 fprintf(conffile, "\n[backend:%s]\n", backend->name);
556 WRITE_IF_NOT_NULL(backend->type, "type");
557 WRITE_IF_NOT_NULL(backend->location, "location");
558 WRITE_IF_NOT_NULL(backend->hostname, "hostname");
559 WRITE_IF_NOT_NULL(backend->username, "username");
560 WRITE_IF_NOT_NULL(backend->password, "password");
569 void cleanupdbconfig(void *object)
571 struct onak_db_config *dbconfig = (struct onak_db_config *) object;
573 if (dbconfig->name != NULL) {
574 free(dbconfig->name);
575 dbconfig->name = NULL;
577 if (dbconfig->type != NULL) {
578 free(dbconfig->type);
579 dbconfig->type = NULL;
581 if (dbconfig->location != NULL) {
582 free(dbconfig->location);
583 dbconfig->location = NULL;
585 if (dbconfig->hostname != NULL) {
586 free(dbconfig->hostname);
587 dbconfig->hostname = NULL;
589 if (dbconfig->username != NULL) {
590 free(dbconfig->username);
591 dbconfig->username = NULL;
593 if (dbconfig->password != NULL) {
594 free(dbconfig->password);
595 dbconfig->password = NULL;
601 void cleanupconfig(void) {
602 /* Free any defined DB backend configuration first */
603 llfree(config.backends, cleanupdbconfig);
604 config.backends = NULL;
606 if (config.thissite != NULL) {
607 free(config.thissite);
608 config.thissite = NULL;
610 if (config.adminemail != NULL) {
611 free(config.adminemail);
612 config.adminemail = NULL;
614 if (config.mta != NULL) {
618 if (config.syncsites != NULL) {
619 llfree(config.syncsites, free);
620 config.syncsites = NULL;
622 if (config.logfile != NULL) {
623 free(config.logfile);
624 config.logfile = NULL;
626 if (config.db_backend != NULL) {
627 free(config.db_backend);
628 config.db_backend = NULL;
630 if (config.backends_dir != NULL) {
631 free(config.backends_dir);
632 config.backends_dir = NULL;
634 if (config.sock_dir != NULL) {
635 free(config.sock_dir);
636 config.sock_dir = NULL;
638 if (config.bin_dir != NULL) {
639 free(config.bin_dir);
640 config.bin_dir = NULL;
642 if (config.mail_dir != NULL) {
643 free(config.mail_dir);
644 config.mail_dir = NULL;
646 if (config.blacklist.count != 0) {
647 array_free(&config.blacklist);