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/>.
30 #include "onak-conf.h"
32 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
35 * config - Runtime configuration for onak.
37 * This is the default config; normally overridden with values from the
40 struct onak_config config = {
56 .clean_policies = ONAK_CLEAN_CHECK_SIGHASH,
62 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
67 while (cur != NULL && strcmp(name,
68 ((struct onak_db_config *) cur->object)->name)) {
72 return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
75 bool parsebool(char *str, bool fallback)
77 if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
78 !strcasecmp(str, "0")) {
80 } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
81 !strcasecmp(str, "1")) {
84 logthing(LOGTHING_CRITICAL,
85 "Couldn't parse %s as a boolean config variable, "
86 "returning fallback of '%s'.",
88 fallback ? "true" : "false");
94 * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
96 static bool parseoldconfigline(char *line)
98 if (line[0] == '#' || line[0] == 0) {
100 * Comment line, ignore.
102 } else if (!strncmp("db_dir ", line, 7)) {
103 config.backend->location = strdup(&line[7]);
104 } else if (!strncmp("debug ", line, 6)) {
106 * Not supported yet; ignore for compatibility with
109 } else if (!strncmp("default_language ", line, 17)) {
111 * Not supported yet; ignore for compatibility with
114 } else if (!strncmp("mail_delivery_client ", line, 21)) {
115 config.mta = strdup(&line[21]);
116 } else if (!strncmp("maintainer_email ", line, 17)) {
117 config.adminemail = strdup(&line[17]);
118 } else if (!strncmp("mail_intro_file ", line, 16)) {
120 * Not supported yet; ignore for compatibility with
123 } else if (!strncmp("help_dir ", line, 9)) {
125 * Not supported yet; ignore for compatibility with
128 } else if (!strncmp("max_last ", line, 9)) {
130 * Not supported yet; ignore for compatibility with
133 } else if (!strncmp("max_reply_keys ", line, 15)) {
134 config.maxkeys = atoi(&line[15]);
135 } else if (!strncmp("pg_dbhost ", line, 10)) {
136 config.backend->hostname = strdup(&line[10]);
137 } else if (!strncmp("pg_dbname ", line, 10)) {
138 config.backend->location = strdup(&line[10]);
139 } else if (!strncmp("pg_dbuser ", line, 10)) {
140 config.backend->username = strdup(&line[10]);
141 } else if (!strncmp("pg_dbpass ", line, 10)) {
142 config.backend->password = strdup(&line[10]);
143 } else if (!strncmp("syncsite ", line, 9)) {
145 lladd(config.syncsites, strdup(&line[9]));
146 } else if (!strncmp("logfile ", line, 8)) {
147 config.logfile = strdup(&line[8]);
148 } else if (!strncmp("loglevel ", line, 9)) {
149 setlogthreshold(atoi(&line[9]));
150 } else if (!strncmp("this_site ", line, 10)) {
151 config.thissite = strdup(&line[10]);
152 } else if (!strncmp("socket_name ", line, 12) ||
153 !strncmp("www_port ", line, 9)) {
155 * Not applicable; ignored for compatibility with pksd.
157 } else if (!strncmp("pks_bin_dir ", line, 12)) {
158 config.bin_dir = strdup(&line[12]);
159 } else if (!strncmp("mail_dir ", line, 9)) {
160 config.mail_dir = strdup(&line[9]);
161 } else if (!strncmp("db_backend ", line, 11)) {
162 config.backend->type = strdup(&line[11]);
163 config.backend->name = strdup(&line[11]);
164 config.db_backend = strdup(&line[11]);
165 } else if (!strncmp("backends_dir ", line, 13)) {
166 config.backends_dir = strdup(&line[13]);
167 } else if (!strncmp("use_keyd ", line, 9)) {
168 config.use_keyd = parsebool(&line[9],
170 } else if (!strncmp("sock_dir ", line, 9)) {
171 config.sock_dir = strdup(&line[9]);
172 } else if (!strncmp("check_sighash ", line, 9)) {
173 if (parsebool(&line[9], config.clean_policies &
174 ONAK_CLEAN_CHECK_SIGHASH)) {
175 config.clean_policies |=
176 ONAK_CLEAN_CHECK_SIGHASH;
178 config.clean_policies &=
179 ~ONAK_CLEAN_CHECK_SIGHASH;
189 * Parse a new style .ini config line, supporting [sections] and name=value
192 static bool parseconfigline(char *line)
194 /* Yes, this means we're not re-entrant. */
195 static char section[32] = "";
196 struct onak_db_config *backend;
200 if (line[0] == '#' || line[0] == ';' ||
203 * Comment line, ignore.
205 } else if (line[0] == '[') {
208 if (line[len - 1] != ']') {
209 logthing(LOGTHING_CRITICAL,
210 "Malformed section header '%s' in "
211 "config file.", line);
214 if (len > sizeof(section)) {
215 logthing(LOGTHING_CRITICAL,
216 "Section header '%s' is too long in "
217 "config file.", line);
221 strncpy(section, &line[1], len);
222 } else if ((value = strchr(line, '=')) != NULL) {
226 /* We can have multiple backend: sections */
227 if (!strncmp(section, "backend:", 8)) {
228 backend = find_db_backend_config(
229 config.backends, §ion[8]);
230 if (backend == NULL) {
232 sizeof(struct onak_db_config));
233 backend->name = strdup(§ion[8]);
234 config.backends = lladd(config.backends,
238 if (!strcmp(name, "type")) {
239 backend->type = strdup(value);
240 } else if (!strcmp(name, "location")) {
241 backend->location = strdup(value);
242 } else if (!strcmp(name, "hostname")) {
243 backend->location = strdup(value);
244 } else if (!strcmp(name, "username")) {
245 backend->location = strdup(value);
246 } else if (!strcmp(name, "password")) {
247 backend->location = strdup(value);
250 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
252 } else if (MATCH("main", "backend")) {
253 config.db_backend = strdup(value);
254 } else if (MATCH("main", "backends_dir")) {
255 config.backends_dir = strdup(value);
256 } else if (MATCH("main", "logfile")) {
257 config.logfile = strdup(value);
258 } else if (MATCH("main", "loglevel")) {
259 setlogthreshold(atoi(value));
260 } else if (MATCH("main", "use_keyd")) {
261 config.use_keyd = parsebool(value,
263 } else if (MATCH("main", "sock_dir")) {
264 config.sock_dir = strdup(value);
265 } else if (MATCH("main", "max_reply_keys")) {
266 config.maxkeys = atoi(value);
268 } else if (MATCH("mail", "maintainer_email")) {
269 config.adminemail = strdup(value);
270 } else if (MATCH("mail", "mail_dir")) {
271 config.mail_dir = strdup(value);
272 } else if (MATCH("mail", "mta")) {
273 config.mta = strdup(value);
274 } else if (MATCH("mail", "bin_dir")) {
275 config.bin_dir = strdup(value);
276 } else if (MATCH("mail", "this_site")) {
277 config.thissite = strdup(value);
278 } else if (MATCH("mail", "syncsite")) {
279 config.syncsites = lladd(config.syncsites,
281 /* [verification] section */
282 } else if (MATCH("verification", "check_sighash")) {
283 if (parsebool(value, config.clean_policies &
284 ONAK_CLEAN_CHECK_SIGHASH)) {
285 config.clean_policies |=
286 ONAK_CLEAN_CHECK_SIGHASH;
288 config.clean_policies &=
289 ~ONAK_CLEAN_CHECK_SIGHASH;
301 void readconfig(const char *configfile) {
307 struct onak_db_config *backend;
308 bool oldstyle = false;
314 * Try to find a config file to use. If one is explicitly provided,
315 * use that. Otherwise look in $XDG_CONFIG_HOME, $HOME and finally
316 * the build in configuration directory. We try an old style onak.conf
317 * first and then the new style onak.ini if that wasn't found - this
318 * is to prevent breaking existing installs on upgrade.
320 if (configfile == NULL) {
322 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
323 /* dir + / + onak.conf + NUL */
324 len = strlen(dir) + 1 + 9 + 1;
326 snprintf(conf, len, "%s/onak.conf", dir);
327 conffile = fopen(conf, "r");
328 if (conffile == NULL) {
329 /* Conveniently .ini is shorter than .conf */
330 snprintf(conf, len, "%s/onak.ini", dir);
331 conffile = fopen(conf, "r");
336 } else if ((dir = getenv("HOME")) != NULL) {
337 /* dir + /.config/onak.conf + NUL */
338 len = strlen(dir) + 18 + 1;
340 snprintf(conf, len, "%s/.config/onak.conf", dir);
341 conffile = fopen(conf, "r");
342 if (conffile == NULL) {
343 /* Conveniently .ini is shorter than .conf */
344 snprintf(conf, len, "%s/onak.ini", dir);
345 conffile = fopen(conf, "r");
351 if (conffile == NULL) {
352 conffile = fopen(CONFIGDIR "/onak.conf", "r");
353 if (conffile == NULL) {
354 conffile = fopen(CONFIGDIR "/onak.ini", "r");
361 * Explicitly provided config file; if the filename ends .conf
362 * assume it's old style.
364 len = strlen(configfile);
365 if (!strcmp(&configfile[len - 5], ".conf")) {
368 conffile = fopen(configfile, "r");
371 if (conffile != NULL) {
372 if (!fgets(curline, 1023, conffile)) {
373 logthing(LOGTHING_CRITICAL,
374 "Problem reading configuration file.");
380 /* Add a single DB configuration */
381 backend = calloc(1, sizeof(*backend));
382 config.backend = backend;
383 config.backends = lladd(NULL, backend);
386 while (!feof(conffile)) {
387 /* Strip any trailing white space */
388 for (i = strlen(curline) - 1;
389 i >= 0 && isspace(curline[i]);
394 /* Strip any leading white space */
396 while (curline[i] != 0 && isspace(curline[i])) {
401 res = parseoldconfigline(&curline[i]);
403 res = parseconfigline(&curline[i]);
406 logthing(LOGTHING_ERROR,
407 "Unknown config line: %s", curline);
410 if (!fgets(curline, 1023, conffile) &&
412 logthing(LOGTHING_CRITICAL,
413 "Problem reading configuration file.");
419 if (config.db_backend == NULL) {
420 logthing(LOGTHING_CRITICAL,
421 "No database backend configured.");
422 } else if (!oldstyle) {
423 config.backend = find_db_backend_config(
424 config.backends, config.db_backend);
425 if (config.backend == NULL) {
426 logthing(LOGTHING_NOTICE,
427 "Couldn't find configuration for %s "
428 "backend.", config.db_backend);
432 logthing(LOGTHING_NOTICE,
433 "Couldn't open config file; using defaults.");
437 void writeconfig(const char *configfile)
443 conffile = fopen(configfile, "w");
448 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
449 fprintf(conffile, s "=%s\n", c); \
451 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
453 fprintf(conffile, "[main]\n");
454 WRITE_IF_NOT_NULL(config.backend->name, "backend");
455 WRITE_IF_NOT_NULL(config.backends_dir, "backends_dir");
456 WRITE_IF_NOT_NULL(config.logfile, "logfile");
457 fprintf(conffile, "loglevel=%d\n", getlogthreshold());
458 WRITE_BOOL(config.use_keyd, "use_keyd");
459 WRITE_IF_NOT_NULL(config.sock_dir, "sock_dir");
460 fprintf(conffile, "max_reply_keys=%d\n", config.maxkeys);
461 fprintf(conffile, "\n");
463 fprintf(conffile, "[verification]\n");
464 WRITE_BOOL(config.clean_policies & ONAK_CLEAN_CHECK_SIGHASH,
466 fprintf(conffile, "\n");
468 fprintf(conffile, "[mail]\n");
469 WRITE_IF_NOT_NULL(config.adminemail, "maintainer_email");
470 WRITE_IF_NOT_NULL(config.mail_dir, "mail_dir");
471 WRITE_IF_NOT_NULL(config.mta, "mta");
472 WRITE_IF_NOT_NULL(config.bin_dir, "bin_dir");
473 WRITE_IF_NOT_NULL(config.thissite, "this_site");
475 cur = config.syncsites;
476 while (cur != NULL) {
477 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
481 cur = config.backends;
482 while (cur != NULL) {
483 struct onak_db_config *backend =
484 (struct onak_db_config *) cur->object;
485 fprintf(conffile, "\n[backend:%s]\n", backend->name);
486 WRITE_IF_NOT_NULL(backend->type, "type");
487 WRITE_IF_NOT_NULL(backend->location, "location");
488 WRITE_IF_NOT_NULL(backend->hostname, "hostname");
489 WRITE_IF_NOT_NULL(backend->username, "username");
490 WRITE_IF_NOT_NULL(backend->password, "password");
499 void cleanupdbconfig(void *object)
501 struct onak_db_config *dbconfig = (struct onak_db_config *) object;
503 if (dbconfig->name != NULL) {
504 free(dbconfig->name);
505 dbconfig->name = NULL;
507 if (dbconfig->type != NULL) {
508 free(dbconfig->type);
509 dbconfig->type = NULL;
511 if (dbconfig->location != NULL) {
512 free(dbconfig->location);
513 dbconfig->location = NULL;
515 if (dbconfig->hostname != NULL) {
516 free(dbconfig->hostname);
517 dbconfig->hostname = NULL;
519 if (dbconfig->username != NULL) {
520 free(dbconfig->username);
521 dbconfig->username = NULL;
523 if (dbconfig->password != NULL) {
524 free(dbconfig->password);
525 dbconfig->password = NULL;
531 void cleanupconfig(void) {
532 /* Free any defined DB backend configuration first */
533 llfree(config.backends, cleanupdbconfig);
534 config.backends = NULL;
536 if (config.thissite != NULL) {
537 free(config.thissite);
538 config.thissite = NULL;
540 if (config.adminemail != NULL) {
541 free(config.adminemail);
542 config.adminemail = NULL;
544 if (config.mta != NULL) {
548 if (config.syncsites != NULL) {
549 llfree(config.syncsites, free);
550 config.syncsites = NULL;
552 if (config.logfile != NULL) {
553 free(config.logfile);
554 config.logfile = NULL;
556 if (config.db_backend != NULL) {
557 free(config.db_backend);
558 config.db_backend = NULL;
560 if (config.backends_dir != NULL) {
561 free(config.backends_dir);
562 config.backends_dir = NULL;
564 if (config.sock_dir != NULL) {
565 free(config.sock_dir);
566 config.sock_dir = NULL;
568 if (config.bin_dir != NULL) {
569 free(config.bin_dir);
570 config.bin_dir = NULL;
572 if (config.mail_dir != NULL) {
573 free(config.mail_dir);
574 config.mail_dir = NULL;