]> the.earth.li Git - onak.git/blob - onak-conf.c
0.6.0 release
[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 #include <ctype.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <strings.h>
23
24 #include "build-config.h"
25
26 #include "cleankey.h"
27 #include "ll.h"
28 #include "log.h"
29 #include "onak-conf.h"
30
31 #ifdef DBINIT
32 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
33 #endif
34
35 /*
36  *      config - Runtime configuration for onak.
37  *
38  *      This is the default config; normally overridden with values from the
39  *      config file.
40  */
41 struct onak_config config = {
42         .maxkeys = 128,
43         .thissite = NULL,
44         .adminemail = NULL,
45         .mta = NULL,
46         .syncsites = NULL,
47         .logfile = NULL,
48
49         .use_keyd = false,
50         .sock_dir = NULL,
51
52         .backends = NULL,
53         .backends_dir = NULL,
54
55 #ifdef DBINIT
56         .dbinit = DBINIT,
57 #else
58         .dbinit = NULL,
59 #endif
60
61         .clean_policies = ONAK_CLEAN_DROP_V3_KEYS | ONAK_CLEAN_CHECK_SIGHASH,
62
63         .bin_dir = NULL,
64         .mail_dir = NULL,
65 };
66
67 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
68 {
69         struct ll *cur;
70
71         cur = backends;
72         while (cur != NULL && strcmp(name,
73                         ((struct onak_db_config *) cur->object)->name)) {
74                 cur = cur->next;
75         }
76
77         return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
78 }
79
80 bool parsebool(char *str, bool fallback)
81 {
82         if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
83                         !strcasecmp(str, "0")) {
84                 return false;
85         } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
86                         !strcasecmp(str, "1")) {
87                 return true;
88         } else {
89                 logthing(LOGTHING_CRITICAL,
90                         "Couldn't parse %s as a boolean config variable, "
91                         "returning fallback of '%s'.",
92                         str,
93                         fallback ? "true" : "false");
94                 return fallback;
95         }
96 }
97
98 /*
99  * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
100  */
101 static bool parseoldconfigline(char *line)
102 {
103         if (line[0] == '#' || line[0] == 0) {
104                 /*
105                  * Comment line, ignore.
106                  */
107         } else if (!strncmp("db_dir ", line, 7)) {
108                 config.backend->location = strdup(&line[7]);
109         } else if (!strncmp("debug ", line, 6)) {
110                 /*
111                  * Not supported yet; ignore for compatibility with
112                  * pksd.
113                  */
114         } else if (!strncmp("default_language ", line, 17)) {
115                 /*
116                  * Not supported yet; ignore for compatibility with
117                  * pksd.
118                  */
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)) {
124                 /*
125                  * Not supported yet; ignore for compatibility with
126                  * pksd.
127                  */
128         } else if (!strncmp("help_dir ", line, 9)) {
129                 /*
130                  * Not supported yet; ignore for compatibility with
131                  * pksd.
132                  */
133         } else if (!strncmp("max_last ", line, 9)) {
134                 /*
135                  * Not supported yet; ignore for compatibility with
136                  * pksd.
137                  */
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)) {
149                 config.syncsites =
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)) {
159                 /*
160                  * Not applicable; ignored for compatibility with pksd.
161                  */
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],
174                                         config.use_keyd);
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;
182                 } else {
183                         config.clean_policies &=
184                                 ~ONAK_CLEAN_CHECK_SIGHASH;
185                 }
186         } else {
187                 return false;
188         }
189
190         return true;
191 }
192
193 /*
194  * Parse a new style .ini config line, supporting [sections] and name=value
195  * format.
196  */
197 static bool parseconfigline(char *line)
198 {
199         /* Yes, this means we're not re-entrant. */
200         static char section[32] = "";
201         struct onak_db_config *backend;
202         size_t len;
203         char *name, *value;
204
205         if (line[0] == '#' || line[0] == ';' ||
206                         line[0] == 0) {
207                 /*
208                  * Comment line, ignore.
209                  */
210         } else if (line[0] == '[') {
211                 /* Section name */
212                 len = strlen(line);
213                 if (line[len - 1] != ']') {
214                         logthing(LOGTHING_CRITICAL,
215                                 "Malformed section header '%s' in "
216                                 "config file.", line);
217                         return false;
218                 }
219                 if (len > sizeof(section)) {
220                         logthing(LOGTHING_CRITICAL,
221                                 "Section header '%s' is too long in "
222                                 "config file.", line);
223                         return false;
224                 }
225                 line[len - 1] = 0;
226                 strncpy(section, &line[1], len);
227         } else if ((value = strchr(line, '=')) != NULL) {
228                 name = line;
229                 *value++ = 0;
230
231                 /* We can have multiple backend: sections */
232                 if (!strncmp(section, "backend:", 8)) {
233                         backend = find_db_backend_config(
234                                 config.backends, &section[8]);
235                         if (backend == NULL) {
236                                 backend = calloc(1,
237                                         sizeof(struct onak_db_config));
238                                 backend->name = strdup(&section[8]);
239                                 config.backends = lladd(config.backends,
240                                                         backend);
241                         }
242
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);
253                         }
254
255 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
256                 /* [main] section */
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,
267                                         config.use_keyd);
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);
272                 /* [mail] section */
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,
285                                 strdup(value));
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;
294                         } else {
295                                 config.clean_policies &=
296                                         ~ONAK_CLEAN_DROP_V3_KEYS;
297                         }
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;
303                         } else {
304                                 config.clean_policies &=
305                                         ~ONAK_CLEAN_CHECK_SIGHASH;
306                         }
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;
312                         } else {
313                                 config.clean_policies &=
314                                         ~ONAK_CLEAN_LARGE_PACKETS;
315                         }
316                 } else if (MATCH("verification", "require_other_sig")) {
317 #if HAVE_CRYPTO
318                         if (parsebool(value, config.clean_policies &
319                                         ONAK_CLEAN_NEED_OTHER_SIG)) {
320                                 config.clean_policies |=
321                                         ONAK_CLEAN_NEED_OTHER_SIG;
322                         } else {
323                                 config.clean_policies &=
324                                         ~ONAK_CLEAN_NEED_OTHER_SIG;
325                         }
326 #else
327                         logthing(LOGTHING_ERROR,
328                                         "Compiled without crypto support, "
329                                         "require_other_sig not available.");
330 #endif
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;
336                         } else {
337                                 config.clean_policies &=
338                                         ~ONAK_CLEAN_UPDATE_ONLY;
339                         }
340                 } else if (MATCH("verification", "verify_signatures")) {
341 #if HAVE_CRYPTO
342                         if (parsebool(value, config.clean_policies &
343                                         ONAK_CLEAN_VERIFY_SIGNATURES)) {
344                                 config.clean_policies |=
345                                         ONAK_CLEAN_VERIFY_SIGNATURES;
346                         } else {
347                                 config.clean_policies &=
348                                         ~ONAK_CLEAN_VERIFY_SIGNATURES;
349                         }
350 #else
351                         logthing(LOGTHING_ERROR,
352                                         "Compiled without crypto support, "
353                                         "verify_signatures not available.");
354 #endif
355                 } else {
356                         return false;
357                 }
358         } else {
359                 return false;
360         }
361
362         return true;
363 }
364
365 void readconfig(const char *configfile) {
366         FILE *conffile;
367         char  curline[1024];
368         int   i;
369         char *dir, *conf;
370         size_t len;
371         struct onak_db_config *backend;
372         bool oldstyle = false;
373         bool res;
374
375         curline[1023] = 0;
376
377         /*
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.
383          */
384         if (configfile == NULL) {
385                 conffile = NULL;
386                 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
387                         /* dir + / + onak.conf + NUL */
388                         len = strlen(dir) + 1 + 9 + 1;
389                         conf = malloc(len);
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");
396                         } else {
397                                 oldstyle = true;
398                         }
399                         free(conf);
400                 } else if ((dir = getenv("HOME")) != NULL) {
401                         /* dir + /.config/onak.conf + NUL */
402                         len = strlen(dir) + 18 + 1;
403                         conf = malloc(len);
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");
410                         } else {
411                                 oldstyle = true;
412                         }
413                         free(conf);
414                 }
415                 if (conffile == NULL) {
416                         conffile = fopen(CONFIGDIR "/onak.conf", "r");
417                         if (conffile == NULL) {
418                                 conffile = fopen(CONFIGDIR "/onak.ini", "r");
419                         } else {
420                                 oldstyle = true;
421                         }
422                 }
423         } else {
424                 /*
425                  * Explicitly provided config file; if the filename ends .conf
426                  * assume it's old style.
427                  */
428                 len = strlen(configfile);
429                 if (!strcmp(&configfile[len - 5], ".conf")) {
430                         oldstyle = true;
431                 }
432                 conffile = fopen(configfile, "r");
433         }
434
435         if (oldstyle) {
436                 logthing(LOGTHING_CRITICAL, "Reading deprecated old-style "
437                                 "configuration file. This will not be "
438                                 "supported in the next release.");
439         }
440
441         if (conffile != NULL) {
442                 if (!fgets(curline, 1023, conffile)) {
443                         logthing(LOGTHING_CRITICAL,
444                                 "Problem reading configuration file.");
445                         fclose(conffile);
446                         return;
447                 }
448
449                 if (oldstyle) {
450                         /* Add a single DB configuration */
451                         backend = calloc(1, sizeof(*backend));
452                         config.backend = backend;
453                         config.backends = lladd(NULL, backend);
454                 }
455
456                 while (!feof(conffile)) {
457                         /* Strip any trailing white space */
458                         for (i = strlen(curline) - 1;
459                                         i >= 0 && isspace(curline[i]);
460                                         i--) {
461                                 curline[i] = 0;
462                         }
463
464                         /* Strip any leading white space */
465                         i = 0;
466                         while (curline[i] != 0 && isspace(curline[i])) {
467                                 i++;
468                         }
469
470                         if (oldstyle) {
471                                 res = parseoldconfigline(&curline[i]);
472                         } else {
473                                 res = parseconfigline(&curline[i]);
474                         }
475                         if (!res) {
476                                 logthing(LOGTHING_ERROR,
477                                         "Unknown config line: %s", curline);
478                         }
479
480                         if (!fgets(curline, 1023, conffile) &&
481                                         !feof(conffile)) {
482                                 logthing(LOGTHING_CRITICAL,
483                                         "Problem reading configuration file.");
484                                 break;
485                         }
486                 }
487                 fclose(conffile);
488
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);
499                         }
500                 }
501         } else {
502                 logthing(LOGTHING_NOTICE,
503                                 "Couldn't open config file; using defaults.");
504         }
505 }
506
507 void writeconfig(const char *configfile)
508 {
509         FILE *conffile;
510         struct ll *cur;
511
512         if (configfile) {
513                 conffile = fopen(configfile, "w");
514         } else {
515                 conffile = stdout;
516         }
517
518 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
519         fprintf(conffile, s "=%s\n", c); \
520 }
521 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
522
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");
532
533         fprintf(conffile, "[verification]\n");
534         WRITE_BOOL(config.clean_policies & ONAK_CLEAN_CHECK_SIGHASH,
535                         "check_sighash");
536         fprintf(conffile, "\n");
537
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");
544
545         cur = config.syncsites;
546         while (cur != NULL) {
547                 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
548                 cur = cur->next;
549         }
550
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");
561                 cur = cur->next;
562         }
563
564         if (configfile) {
565                 fclose(conffile);
566         }
567 }
568
569 void cleanupdbconfig(void *object)
570 {
571         struct onak_db_config *dbconfig = (struct onak_db_config *) object;
572
573         if (dbconfig->name != NULL) {
574                 free(dbconfig->name);
575                 dbconfig->name = NULL;
576         }
577         if (dbconfig->type != NULL) {
578                 free(dbconfig->type);
579                 dbconfig->type = NULL;
580         }
581         if (dbconfig->location != NULL) {
582                 free(dbconfig->location);
583                 dbconfig->location = NULL;
584         }
585         if (dbconfig->hostname != NULL) {
586                 free(dbconfig->hostname);
587                 dbconfig->hostname = NULL;
588         }
589         if (dbconfig->username != NULL) {
590                 free(dbconfig->username);
591                 dbconfig->username = NULL;
592         }
593         if (dbconfig->password != NULL) {
594                 free(dbconfig->password);
595                 dbconfig->password = NULL;
596         }
597
598         free(dbconfig);
599 }
600
601 void cleanupconfig(void) {
602         /* Free any defined DB backend configuration first */
603         llfree(config.backends, cleanupdbconfig);
604         config.backends = NULL;
605
606         if (config.thissite != NULL) {
607                 free(config.thissite);
608                 config.thissite = NULL;
609         }
610         if (config.adminemail != NULL) {
611                 free(config.adminemail);
612                 config.adminemail = NULL;
613         }
614         if (config.mta != NULL) {
615                 free(config.mta);
616                 config.mta = NULL;
617         }
618         if (config.syncsites != NULL) {
619                 llfree(config.syncsites, free);
620                 config.syncsites = NULL;
621         }
622         if (config.logfile != NULL) {
623                 free(config.logfile);
624                 config.logfile = NULL;
625         }
626         if (config.db_backend != NULL) {
627                 free(config.db_backend);
628                 config.db_backend = NULL;
629         }
630         if (config.backends_dir != NULL) {
631                 free(config.backends_dir);
632                 config.backends_dir = NULL;
633         }
634         if (config.sock_dir != NULL) {
635                 free(config.sock_dir);
636                 config.sock_dir = NULL;
637         }
638         if (config.bin_dir != NULL) {
639                 free(config.bin_dir);
640                 config.bin_dir = NULL;
641         }
642         if (config.mail_dir != NULL) {
643                 free(config.mail_dir);
644                 config.mail_dir = NULL;
645         }
646         if (config.blacklist.count != 0) {
647                 array_free(&config.blacklist);
648         }
649 }