]> the.earth.li Git - onak.git/blob - onak-conf.c
e6b645c48057bf4bf10fb4143ebcd82f87a1dc14
[onak.git] / onak-conf.c
1 /*
2  * onak-conf.c - Routines related to runtime config.
3  *
4  * Copyright 2002,2012 Jonathan McDowell <noodles@earth.li>
5  *
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.
9  *
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
13  * more details.
14  *
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/>.
17  */
18
19 #include "config.h"
20
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <strings.h>
26
27 #include "cleankey.h"
28 #include "ll.h"
29 #include "log.h"
30 #include "onak-conf.h"
31
32 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
33
34 /*
35  *      config - Runtime configuration for onak.
36  *
37  *      This is the default config; normally overridden with values from the
38  *      config file.
39  */
40 struct onak_config config = {
41         .maxkeys = 128,
42         .thissite = NULL,
43         .adminemail = NULL,
44         .mta = NULL,
45         .syncsites = NULL,
46         .logfile = NULL,
47
48         .use_keyd = false,
49         .sock_dir = NULL,
50
51         .backends = NULL,
52         .backends_dir = NULL,
53
54         .dbinit = DBINIT,
55
56         .clean_policies = ONAK_CLEAN_CHECK_SIGHASH,
57
58         .bin_dir = NULL,
59         .mail_dir = NULL,
60 };
61
62 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
63 {
64         struct ll *cur;
65
66         cur = backends;
67         while (cur != NULL && strcmp(name,
68                         ((struct onak_db_config *) cur->object)->name)) {
69                 cur = cur->next;
70         }
71
72         return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
73 }
74
75 bool parsebool(char *str, bool fallback)
76 {
77         if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
78                         !strcasecmp(str, "0")) {
79                 return false;
80         } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
81                         !strcasecmp(str, "1")) {
82                 return true;
83         } else {
84                 logthing(LOGTHING_CRITICAL,
85                         "Couldn't parse %s as a boolean config variable, "
86                         "returning fallback of '%s'.",
87                         str,
88                         fallback ? "true" : "false");
89                 return fallback;
90         }
91 }
92
93 /*
94  * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
95  */
96 static bool parseoldconfigline(char *line)
97 {
98         if (line[0] == '#' || line[0] == 0) {
99                 /*
100                  * Comment line, ignore.
101                  */
102         } else if (!strncmp("db_dir ", line, 7)) {
103                 config.backend->location = strdup(&line[7]);
104         } else if (!strncmp("debug ", line, 6)) {
105                 /*
106                  * Not supported yet; ignore for compatibility with
107                  * pksd.
108                  */
109         } else if (!strncmp("default_language ", line, 17)) {
110                 /*
111                  * Not supported yet; ignore for compatibility with
112                  * pksd.
113                  */
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)) {
119                 /*
120                  * Not supported yet; ignore for compatibility with
121                  * pksd.
122                  */
123         } else if (!strncmp("help_dir ", line, 9)) {
124                 /*
125                  * Not supported yet; ignore for compatibility with
126                  * pksd.
127                  */
128         } else if (!strncmp("max_last ", line, 9)) {
129                 /*
130                  * Not supported yet; ignore for compatibility with
131                  * pksd.
132                  */
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)) {
144                 config.syncsites =
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)) {
154                 /*
155                  * Not applicable; ignored for compatibility with pksd.
156                  */
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],
169                                         config.use_keyd);
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;
177                 } else {
178                         config.clean_policies &=
179                                 ~ONAK_CLEAN_CHECK_SIGHASH;
180                 }
181         } else {
182                 return false;
183         }
184
185         return true;
186 }
187
188 /*
189  * Parse a new style .ini config line, supporting [sections] and name=value
190  * format.
191  */
192 static bool parseconfigline(char *line)
193 {
194         /* Yes, this means we're not re-entrant. */
195         static char section[32] = "";
196         struct onak_db_config *backend;
197         size_t len;
198         char *name, *value;
199
200         if (line[0] == '#' || line[0] == ';' ||
201                         line[0] == 0) {
202                 /*
203                  * Comment line, ignore.
204                  */
205         } else if (line[0] == '[') {
206                 /* Section name */
207                 len = strlen(line);
208                 if (line[len - 1] != ']') {
209                         logthing(LOGTHING_CRITICAL,
210                                 "Malformed section header '%s' in "
211                                 "config file.", line);
212                         return false;
213                 }
214                 if (len > sizeof(section)) {
215                         logthing(LOGTHING_CRITICAL,
216                                 "Section header '%s' is too long in "
217                                 "config file.", line);
218                         return false;
219                 }
220                 line[len - 1] = 0;
221                 strncpy(section, &line[1], len);
222         } else if ((value = strchr(line, '=')) != NULL) {
223                 name = line;
224                 *value++ = 0;
225
226                 /* We can have multiple backend: sections */
227                 if (!strncmp(section, "backend:", 8)) {
228                         backend = find_db_backend_config(
229                                 config.backends, &section[8]);
230                         if (backend == NULL) {
231                                 backend = calloc(1,
232                                         sizeof(struct onak_db_config));
233                                 backend->name = strdup(&section[8]);
234                                 config.backends = lladd(config.backends,
235                                                         backend);
236                         }
237
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);
248                         }
249
250 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
251                 /* [main] section */
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,
262                                         config.use_keyd);
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);
267                 /* [mail] section */
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,
280                                 strdup(value));
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;
287                         } else {
288                                 config.clean_policies &=
289                                         ~ONAK_CLEAN_CHECK_SIGHASH;
290                         }
291                 } else {
292                         return false;
293                 }
294         } else {
295                 return false;
296         }
297
298         return true;
299 }
300
301 void readconfig(const char *configfile) {
302         FILE *conffile;
303         char  curline[1024];
304         int   i;
305         char *dir, *conf;
306         size_t len;
307         struct onak_db_config *backend;
308         bool oldstyle = false;
309         bool res;
310
311         curline[1023] = 0;
312
313         /*
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.
319          */
320         if (configfile == NULL) {
321                 conffile = NULL;
322                 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
323                         /* dir + / + onak.conf + NUL */
324                         len = strlen(dir) + 1 + 9 + 1;
325                         conf = malloc(len);
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");
332                         } else {
333                                 oldstyle = true;
334                         }
335                         free(conf);
336                 } else if ((dir = getenv("HOME")) != NULL) {
337                         /* dir + /.config/onak.conf + NUL */
338                         len = strlen(dir) + 18 + 1;
339                         conf = malloc(len);
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");
346                         } else {
347                                 oldstyle = true;
348                         }
349                         free(conf);
350                 }
351                 if (conffile == NULL) {
352                         conffile = fopen(CONFIGDIR "/onak.conf", "r");
353                         if (conffile == NULL) {
354                                 conffile = fopen(CONFIGDIR "/onak.ini", "r");
355                         } else {
356                                 oldstyle = true;
357                         }
358                 }
359         } else {
360                 /*
361                  * Explicitly provided config file; if the filename ends .conf
362                  * assume it's old style.
363                  */
364                 len = strlen(configfile);
365                 if (!strcmp(&configfile[len - 5], ".conf")) {
366                         oldstyle = true;
367                 }
368                 conffile = fopen(configfile, "r");
369         }
370
371         if (conffile != NULL) {
372                 if (!fgets(curline, 1023, conffile)) {
373                         logthing(LOGTHING_CRITICAL,
374                                 "Problem reading configuration file.");
375                         fclose(conffile);
376                         return;
377                 }
378
379                 if (oldstyle) {
380                         /* Add a single DB configuration */
381                         backend = calloc(1, sizeof(*backend));
382                         config.backend = backend;
383                         config.backends = lladd(NULL, backend);
384                 }
385
386                 while (!feof(conffile)) {
387                         /* Strip any trailing white space */
388                         for (i = strlen(curline) - 1;
389                                         i >= 0 && isspace(curline[i]);
390                                         i--) {
391                                 curline[i] = 0;
392                         }
393
394                         /* Strip any leading white space */
395                         i = 0;
396                         while (curline[i] != 0 && isspace(curline[i])) {
397                                 i++;
398                         }
399
400                         if (oldstyle) {
401                                 res = parseoldconfigline(&curline[i]);
402                         } else {
403                                 res = parseconfigline(&curline[i]);
404                         }
405                         if (!res) {
406                                 logthing(LOGTHING_ERROR,
407                                         "Unknown config line: %s", curline);
408                         }
409
410                         if (!fgets(curline, 1023, conffile) &&
411                                         !feof(conffile)) {
412                                 logthing(LOGTHING_CRITICAL,
413                                         "Problem reading configuration file.");
414                                 break;
415                         }
416                 }
417                 fclose(conffile);
418
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);
429                         }
430                 }
431         } else {
432                 logthing(LOGTHING_NOTICE,
433                                 "Couldn't open config file; using defaults.");
434         }
435 }
436
437 void writeconfig(const char *configfile)
438 {
439         FILE *conffile;
440         struct ll *cur;
441
442         if (configfile) {
443                 conffile = fopen(configfile, "w");
444         } else {
445                 conffile = stdout;
446         }
447
448 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
449         fprintf(conffile, s "=%s\n", c); \
450 }
451 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
452
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");
462
463         fprintf(conffile, "[verification]\n");
464         WRITE_BOOL(config.clean_policies & ONAK_CLEAN_CHECK_SIGHASH,
465                         "check_sighash");
466         fprintf(conffile, "\n");
467
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");
474
475         cur = config.syncsites;
476         while (cur != NULL) {
477                 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
478                 cur = cur->next;
479         }
480
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");
491                 cur = cur->next;
492         }
493
494         if (configfile) {
495                 fclose(conffile);
496         }
497 }
498
499 void cleanupdbconfig(void *object)
500 {
501         struct onak_db_config *dbconfig = (struct onak_db_config *) object;
502
503         if (dbconfig->name != NULL) {
504                 free(dbconfig->name);
505                 dbconfig->name = NULL;
506         }
507         if (dbconfig->type != NULL) {
508                 free(dbconfig->type);
509                 dbconfig->type = NULL;
510         }
511         if (dbconfig->location != NULL) {
512                 free(dbconfig->location);
513                 dbconfig->location = NULL;
514         }
515         if (dbconfig->hostname != NULL) {
516                 free(dbconfig->hostname);
517                 dbconfig->hostname = NULL;
518         }
519         if (dbconfig->username != NULL) {
520                 free(dbconfig->username);
521                 dbconfig->username = NULL;
522         }
523         if (dbconfig->password != NULL) {
524                 free(dbconfig->password);
525                 dbconfig->password = NULL;
526         }
527
528         free(dbconfig);
529 }
530
531 void cleanupconfig(void) {
532         /* Free any defined DB backend configuration first */
533         llfree(config.backends, cleanupdbconfig);
534         config.backends = NULL;
535
536         if (config.thissite != NULL) {
537                 free(config.thissite);
538                 config.thissite = NULL;
539         }
540         if (config.adminemail != NULL) {
541                 free(config.adminemail);
542                 config.adminemail = NULL;
543         }
544         if (config.mta != NULL) {
545                 free(config.mta);
546                 config.mta = NULL;
547         }
548         if (config.syncsites != NULL) {
549                 llfree(config.syncsites, free);
550                 config.syncsites = NULL;
551         }
552         if (config.logfile != NULL) {
553                 free(config.logfile);
554                 config.logfile = NULL;
555         }
556         if (config.db_backend != NULL) {
557                 free(config.db_backend);
558                 config.db_backend = NULL;
559         }
560         if (config.backends_dir != NULL) {
561                 free(config.backends_dir);
562                 config.backends_dir = NULL;
563         }
564         if (config.sock_dir != NULL) {
565                 free(config.sock_dir);
566                 config.sock_dir = NULL;
567         }
568         if (config.bin_dir != NULL) {
569                 free(config.bin_dir);
570                 config.bin_dir = NULL;
571         }
572         if (config.mail_dir != NULL) {
573                 free(config.mail_dir);
574                 config.mail_dir = NULL;
575         }
576 }