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/>.
29 #include "onak-conf.h"
31 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
34 * config - Runtime configuration for onak.
36 * This is the default config; normally overridden with values from the
39 struct onak_config config = {
55 .check_sighash = true,
61 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
66 while (cur != NULL && strcmp(name,
67 ((struct onak_db_config *) cur->object)->name)) {
71 return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
74 bool parsebool(char *str, bool fallback)
76 if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
77 !strcasecmp(str, "0")) {
79 } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
80 !strcasecmp(str, "1")) {
83 logthing(LOGTHING_CRITICAL,
84 "Couldn't parse %s as a boolean config variable, "
85 "returning fallback of '%s'.",
87 fallback ? "true" : "false");
93 * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
95 static bool parseoldconfigline(char *line)
97 if (line[0] == '#' || line[0] == 0) {
99 * Comment line, ignore.
101 } else if (!strncmp("db_dir ", line, 7)) {
102 config.backend->location = strdup(&line[7]);
103 } else if (!strncmp("debug ", line, 6)) {
105 * Not supported yet; ignore for compatibility with
108 } else if (!strncmp("default_language ", line, 17)) {
110 * Not supported yet; ignore for compatibility with
113 } else if (!strncmp("mail_delivery_client ", line, 21)) {
114 config.mta = strdup(&line[21]);
115 } else if (!strncmp("maintainer_email ", line, 17)) {
116 config.adminemail = strdup(&line[17]);
117 } else if (!strncmp("mail_intro_file ", line, 16)) {
119 * Not supported yet; ignore for compatibility with
122 } else if (!strncmp("help_dir ", line, 9)) {
124 * Not supported yet; ignore for compatibility with
127 } else if (!strncmp("max_last ", line, 9)) {
129 * Not supported yet; ignore for compatibility with
132 } else if (!strncmp("max_reply_keys ", line, 15)) {
133 config.maxkeys = atoi(&line[15]);
134 } else if (!strncmp("pg_dbhost ", line, 10)) {
135 config.backend->hostname = strdup(&line[10]);
136 } else if (!strncmp("pg_dbname ", line, 10)) {
137 config.backend->location = strdup(&line[10]);
138 } else if (!strncmp("pg_dbuser ", line, 10)) {
139 config.backend->username = strdup(&line[10]);
140 } else if (!strncmp("pg_dbpass ", line, 10)) {
141 config.backend->password = strdup(&line[10]);
142 } else if (!strncmp("syncsite ", line, 9)) {
144 lladd(config.syncsites, strdup(&line[9]));
145 } else if (!strncmp("logfile ", line, 8)) {
146 config.logfile = strdup(&line[8]);
147 } else if (!strncmp("loglevel ", line, 9)) {
148 setlogthreshold(atoi(&line[9]));
149 } else if (!strncmp("this_site ", line, 10)) {
150 config.thissite = strdup(&line[10]);
151 } else if (!strncmp("socket_name ", line, 12) ||
152 !strncmp("www_port ", line, 9)) {
154 * Not applicable; ignored for compatibility with pksd.
156 } else if (!strncmp("pks_bin_dir ", line, 12)) {
157 config.bin_dir = strdup(&line[12]);
158 } else if (!strncmp("mail_dir ", line, 9)) {
159 config.mail_dir = strdup(&line[9]);
160 } else if (!strncmp("db_backend ", line, 11)) {
161 config.backend->type = strdup(&line[11]);
162 config.backend->name = strdup(&line[11]);
163 config.db_backend = strdup(&line[11]);
164 } else if (!strncmp("backends_dir ", line, 13)) {
165 config.backends_dir = strdup(&line[13]);
166 } else if (!strncmp("use_keyd ", line, 9)) {
167 config.use_keyd = parsebool(&line[9],
169 } else if (!strncmp("sock_dir ", line, 9)) {
170 config.sock_dir = strdup(&line[9]);
171 } else if (!strncmp("check_sighash ", line, 9)) {
172 config.check_sighash = parsebool(&line[9],
173 config.check_sighash);
182 * Parse a new style .ini config line, supporting [sections] and name=value
185 static bool parseconfigline(char *line)
187 /* Yes, this means we're not re-entrant. */
188 static char section[32] = "";
189 struct onak_db_config *backend;
193 if (line[0] == '#' || line[0] == ';' ||
196 * Comment line, ignore.
198 } else if (line[0] == '[') {
201 if (line[len - 1] != ']') {
202 logthing(LOGTHING_CRITICAL,
203 "Malformed section header '%s' in "
204 "config file.", line);
207 if (len > sizeof(section)) {
208 logthing(LOGTHING_CRITICAL,
209 "Section header '%s' is too long in "
210 "config file.", line);
214 strncpy(section, &line[1], len);
215 } else if ((value = strchr(line, '=')) != NULL) {
219 /* We can have multiple backend: sections */
220 if (!strncmp(section, "backend:", 8)) {
221 backend = find_db_backend_config(
222 config.backends, §ion[8]);
223 if (backend == NULL) {
225 sizeof(struct onak_db_config));
226 backend->name = strdup(§ion[8]);
227 config.backends = lladd(config.backends,
231 if (!strcmp(name, "type")) {
232 backend->type = strdup(value);
233 } else if (!strcmp(name, "location")) {
234 backend->location = strdup(value);
235 } else if (!strcmp(name, "hostname")) {
236 backend->location = strdup(value);
237 } else if (!strcmp(name, "username")) {
238 backend->location = strdup(value);
239 } else if (!strcmp(name, "password")) {
240 backend->location = strdup(value);
243 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
245 } else if (MATCH("main", "backend")) {
246 config.db_backend = strdup(value);
247 } else if (MATCH("main", "backends_dir")) {
248 config.backends_dir = strdup(value);
249 } else if (MATCH("main", "logfile")) {
250 config.logfile = strdup(value);
251 } else if (MATCH("main", "loglevel")) {
252 setlogthreshold(atoi(value));
253 } else if (MATCH("main", "use_keyd")) {
254 config.use_keyd = parsebool(value,
256 } else if (MATCH("main", "sock_dir")) {
257 config.sock_dir = strdup(value);
258 } else if (MATCH("main", "max_reply_keys")) {
259 config.maxkeys = atoi(value);
261 } else if (MATCH("mail", "maintainer_email")) {
262 config.adminemail = strdup(value);
263 } else if (MATCH("mail", "mail_dir")) {
264 config.mail_dir = strdup(value);
265 } else if (MATCH("mail", "mta")) {
266 config.mta = strdup(value);
267 } else if (MATCH("mail", "bin_dir")) {
268 config.bin_dir = strdup(value);
269 } else if (MATCH("mail", "this_site")) {
270 config.thissite = strdup(value);
271 } else if (MATCH("mail", "syncsite")) {
272 config.syncsites = lladd(config.syncsites,
274 /* [verification] section */
275 } else if (MATCH("verification", "check_sighash")) {
276 config.check_sighash = parsebool(value,
277 config.check_sighash);
288 void readconfig(const char *configfile) {
294 struct onak_db_config *backend;
295 bool oldstyle = false;
301 * Try to find a config file to use. If one is explicitly provided,
302 * use that. Otherwise look in $XDG_CONFIG_HOME, $HOME and finally
303 * the build in configuration directory. We try an old style onak.conf
304 * first and then the new style onak.ini if that wasn't found - this
305 * is to prevent breaking existing installs on upgrade.
307 if (configfile == NULL) {
309 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
310 /* dir + / + onak.conf + NUL */
311 len = strlen(dir) + 1 + 9 + 1;
313 snprintf(conf, len, "%s/onak.conf", dir);
314 conffile = fopen(conf, "r");
315 if (conffile == NULL) {
316 /* Conveniently .ini is shorter than .conf */
317 snprintf(conf, len, "%s/onak.ini", dir);
318 conffile = fopen(conf, "r");
323 } else if ((dir = getenv("HOME")) != NULL) {
324 /* dir + /.config/onak.conf + NUL */
325 len = strlen(dir) + 18 + 1;
327 snprintf(conf, len, "%s/.config/onak.conf", dir);
328 conffile = fopen(conf, "r");
329 if (conffile == NULL) {
330 /* Conveniently .ini is shorter than .conf */
331 snprintf(conf, len, "%s/onak.ini", dir);
332 conffile = fopen(conf, "r");
338 if (conffile == NULL) {
339 conffile = fopen(CONFIGDIR "/onak.conf", "r");
340 if (conffile == NULL) {
341 conffile = fopen(CONFIGDIR "/onak.ini", "r");
348 * Explicitly provided config file; if the filename ends .conf
349 * assume it's old style.
351 len = strlen(configfile);
352 if (!strcmp(&configfile[len - 5], ".conf")) {
355 conffile = fopen(configfile, "r");
358 if (conffile != NULL) {
359 if (!fgets(curline, 1023, conffile)) {
360 logthing(LOGTHING_CRITICAL,
361 "Problem reading configuration file.");
367 /* Add a single DB configuration */
368 backend = calloc(1, sizeof(*backend));
369 config.backend = backend;
370 config.backends = lladd(NULL, backend);
373 while (!feof(conffile)) {
374 /* Strip any trailing white space */
375 for (i = strlen(curline) - 1;
376 i >= 0 && isspace(curline[i]);
381 /* Strip any leading white space */
383 while (curline[i] != 0 && isspace(curline[i])) {
388 res = parseoldconfigline(&curline[i]);
390 res = parseconfigline(&curline[i]);
393 logthing(LOGTHING_ERROR,
394 "Unknown config line: %s", curline);
397 if (!fgets(curline, 1023, conffile) &&
399 logthing(LOGTHING_CRITICAL,
400 "Problem reading configuration file.");
406 if (config.db_backend == NULL) {
407 logthing(LOGTHING_CRITICAL,
408 "No database backend configured.");
409 } else if (!oldstyle) {
410 config.backend = find_db_backend_config(
411 config.backends, config.db_backend);
412 if (config.backend == NULL) {
413 logthing(LOGTHING_NOTICE,
414 "Couldn't find configuration for %s "
415 "backend.", config.db_backend);
419 logthing(LOGTHING_NOTICE,
420 "Couldn't open config file; using defaults.");
424 void writeconfig(const char *configfile)
430 conffile = fopen(configfile, "w");
435 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
436 fprintf(conffile, s "=%s\n", c); \
438 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
440 fprintf(conffile, "[main]\n");
441 WRITE_IF_NOT_NULL(config.backend->name, "backend");
442 WRITE_IF_NOT_NULL(config.backends_dir, "backends_dir");
443 WRITE_IF_NOT_NULL(config.logfile, "logfile");
444 fprintf(conffile, "loglevel=%d\n", getlogthreshold());
445 WRITE_BOOL(config.use_keyd, "use_keyd");
446 WRITE_IF_NOT_NULL(config.sock_dir, "sock_dir");
447 fprintf(conffile, "max_reply_keys=%d\n", config.maxkeys);
448 fprintf(conffile, "\n");
450 fprintf(conffile, "[verification]\n");
451 WRITE_BOOL(config.check_sighash, "check_sighash");
452 fprintf(conffile, "\n");
454 fprintf(conffile, "[mail]\n");
455 WRITE_IF_NOT_NULL(config.adminemail, "maintainer_email");
456 WRITE_IF_NOT_NULL(config.mail_dir, "mail_dir");
457 WRITE_IF_NOT_NULL(config.mta, "mta");
458 WRITE_IF_NOT_NULL(config.bin_dir, "bin_dir");
459 WRITE_IF_NOT_NULL(config.thissite, "this_site");
461 cur = config.syncsites;
462 while (cur != NULL) {
463 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
467 cur = config.backends;
468 while (cur != NULL) {
469 struct onak_db_config *backend =
470 (struct onak_db_config *) cur->object;
471 fprintf(conffile, "\n[backend:%s]\n", backend->name);
472 WRITE_IF_NOT_NULL(backend->type, "type");
473 WRITE_IF_NOT_NULL(backend->location, "location");
474 WRITE_IF_NOT_NULL(backend->hostname, "hostname");
475 WRITE_IF_NOT_NULL(backend->username, "username");
476 WRITE_IF_NOT_NULL(backend->password, "password");
485 void cleanupdbconfig(void *object)
487 struct onak_db_config *dbconfig = (struct onak_db_config *) object;
489 if (dbconfig->name != NULL) {
490 free(dbconfig->name);
491 dbconfig->name = NULL;
493 if (dbconfig->type != NULL) {
494 free(dbconfig->type);
495 dbconfig->type = NULL;
497 if (dbconfig->location != NULL) {
498 free(dbconfig->location);
499 dbconfig->location = NULL;
501 if (dbconfig->hostname != NULL) {
502 free(dbconfig->hostname);
503 dbconfig->hostname = NULL;
505 if (dbconfig->username != NULL) {
506 free(dbconfig->username);
507 dbconfig->username = NULL;
509 if (dbconfig->password != NULL) {
510 free(dbconfig->password);
511 dbconfig->password = NULL;
517 void cleanupconfig(void) {
518 /* Free any defined DB backend configuration first */
519 llfree(config.backends, cleanupdbconfig);
520 config.backends = NULL;
522 if (config.thissite != NULL) {
523 free(config.thissite);
524 config.thissite = NULL;
526 if (config.adminemail != NULL) {
527 free(config.adminemail);
528 config.adminemail = NULL;
530 if (config.mta != NULL) {
534 if (config.syncsites != NULL) {
535 llfree(config.syncsites, free);
536 config.syncsites = NULL;
538 if (config.logfile != NULL) {
539 free(config.logfile);
540 config.logfile = NULL;
542 if (config.db_backend != NULL) {
543 free(config.db_backend);
544 config.db_backend = NULL;
546 if (config.backends_dir != NULL) {
547 free(config.backends_dir);
548 config.backends_dir = NULL;
550 if (config.sock_dir != NULL) {
551 free(config.sock_dir);
552 config.sock_dir = NULL;
554 if (config.bin_dir != NULL) {
555 free(config.bin_dir);
556 config.bin_dir = NULL;
558 if (config.mail_dir != NULL) {
559 free(config.mail_dir);
560 config.mail_dir = NULL;