]> the.earth.li Git - onak.git/blob - onak-conf.c
Cleanup various includes
[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, write to the Free Software Foundation, Inc., 51
17  * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include <ctype.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <strings.h>
27
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         .check_sighash = true,
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                 config.check_sighash = parsebool(&line[9],
174                                         config.check_sighash);
175         } else {
176                 return false;
177         }
178
179         return true;
180 }
181
182 /*
183  * Parse a new style .ini config line, supporting [sections] and name=value
184  * format.
185  */
186 static bool parseconfigline(char *line)
187 {
188         /* Yes, this means we're not re-entrant. */
189         static char section[32] = "";
190         struct onak_db_config *backend;
191         size_t len;
192         char *name, *value;
193
194         if (line[0] == '#' || line[0] == ';' ||
195                         line[0] == 0) {
196                 /*
197                  * Comment line, ignore.
198                  */
199         } else if (line[0] == '[') {
200                 /* Section name */
201                 len = strlen(line);
202                 if (line[len - 1] != ']') {
203                         logthing(LOGTHING_CRITICAL,
204                                 "Malformed section header '%s' in "
205                                 "config file.", line);
206                         return false;
207                 }
208                 if (len > sizeof(section)) {
209                         logthing(LOGTHING_CRITICAL,
210                                 "Section header '%s' is too long in "
211                                 "config file.", line);
212                         return false;
213                 }
214                 line[len - 1] = 0;
215                 strncpy(section, &line[1], len);
216         } else if ((value = strchr(line, '=')) != NULL) {
217                 name = line;
218                 *value++ = 0;
219
220                 /* We can have multiple backend: sections */
221                 if (!strncmp(section, "backend:", 8)) {
222                         backend = find_db_backend_config(
223                                 config.backends, &section[8]);
224                         if (backend == NULL) {
225                                 backend = calloc(1,
226                                         sizeof(struct onak_db_config));
227                                 backend->name = strdup(&section[8]);
228                                 config.backends = lladd(config.backends,
229                                                         backend);
230                         }
231
232                         if (!strcmp(name, "type")) {
233                                 backend->type = strdup(value);
234                         } else if (!strcmp(name, "location")) {
235                                 backend->location = strdup(value);
236                         } else if (!strcmp(name, "hostname")) {
237                                 backend->location = strdup(value);
238                         } else if (!strcmp(name, "username")) {
239                                 backend->location = strdup(value);
240                         } else if (!strcmp(name, "password")) {
241                                 backend->location = strdup(value);
242                         }
243
244 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
245                 /* [main] section */
246                 } else if (MATCH("main", "backend")) {
247                         config.db_backend = strdup(value);
248                 } else if (MATCH("main", "backends_dir")) {
249                         config.backends_dir = strdup(value);
250                 } else if (MATCH("main", "logfile")) {
251                         config.logfile = strdup(value);
252                 } else if (MATCH("main", "loglevel")) {
253                         setlogthreshold(atoi(value));
254                 } else if (MATCH("main", "use_keyd")) {
255                         config.use_keyd = parsebool(value,
256                                         config.use_keyd);
257                 } else if (MATCH("main", "sock_dir")) {
258                         config.sock_dir = strdup(value);
259                 } else if (MATCH("main", "max_reply_keys")) {
260                         config.maxkeys = atoi(value);
261                 /* [mail] section */
262                 } else if (MATCH("mail", "maintainer_email")) {
263                         config.adminemail = strdup(value);
264                 } else if (MATCH("mail", "mail_dir")) {
265                 config.mail_dir = strdup(value);
266                 } else if (MATCH("mail", "mta")) {
267                         config.mta = strdup(value);
268                 } else if (MATCH("mail", "bin_dir")) {
269                         config.bin_dir = strdup(value);
270                 } else if (MATCH("mail", "this_site")) {
271                         config.thissite = strdup(value);
272                 } else if (MATCH("mail", "syncsite")) {
273                         config.syncsites = lladd(config.syncsites,
274                                 strdup(value));
275                 /* [verification] section */
276                 } else if (MATCH("verification", "check_sighash")) {
277                         config.check_sighash = parsebool(value,
278                                         config.check_sighash);
279                 } else {
280                         return false;
281                 }
282         } else {
283                 return false;
284         }
285
286         return true;
287 }
288
289 void readconfig(const char *configfile) {
290         FILE *conffile;
291         char  curline[1024];
292         int   i;
293         char *dir, *conf;
294         size_t len;
295         struct onak_db_config *backend;
296         bool oldstyle = false;
297         bool res;
298
299         curline[1023] = 0;
300
301         /*
302          * Try to find a config file to use. If one is explicitly provided,
303          * use that. Otherwise look in $XDG_CONFIG_HOME, $HOME and finally
304          * the build in configuration directory. We try an old style onak.conf
305          * first and then the new style onak.ini if that wasn't found - this
306          * is to prevent breaking existing installs on upgrade.
307          */
308         if (configfile == NULL) {
309                 conffile = NULL;
310                 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
311                         /* dir + / + onak.conf + NUL */
312                         len = strlen(dir) + 1 + 9 + 1;
313                         conf = malloc(len);
314                         snprintf(conf, len, "%s/onak.conf", dir);
315                         conffile = fopen(conf, "r");
316                         if (conffile == NULL) {
317                                 /* Conveniently .ini is shorter than .conf */
318                                 snprintf(conf, len, "%s/onak.ini", dir);
319                                 conffile = fopen(conf, "r");
320                         } else {
321                                 oldstyle = true;
322                         }
323                         free(conf);
324                 } else if ((dir = getenv("HOME")) != NULL) {
325                         /* dir + /.config/onak.conf + NUL */
326                         len = strlen(dir) + 18 + 1;
327                         conf = malloc(len);
328                         snprintf(conf, len, "%s/.config/onak.conf", dir);
329                         conffile = fopen(conf, "r");
330                         if (conffile == NULL) {
331                                 /* Conveniently .ini is shorter than .conf */
332                                 snprintf(conf, len, "%s/onak.ini", dir);
333                                 conffile = fopen(conf, "r");
334                         } else {
335                                 oldstyle = true;
336                         }
337                         free(conf);
338                 }
339                 if (conffile == NULL) {
340                         conffile = fopen(CONFIGDIR "/onak.conf", "r");
341                         if (conffile == NULL) {
342                                 conffile = fopen(CONFIGDIR "/onak.ini", "r");
343                         } else {
344                                 oldstyle = true;
345                         }
346                 }
347         } else {
348                 /*
349                  * Explicitly provided config file; if the filename ends .conf
350                  * assume it's old style.
351                  */
352                 len = strlen(configfile);
353                 if (!strcmp(&configfile[len - 5], ".conf")) {
354                         oldstyle = true;
355                 }
356                 conffile = fopen(configfile, "r");
357         }
358
359         if (conffile != NULL) {
360                 if (!fgets(curline, 1023, conffile)) {
361                         logthing(LOGTHING_CRITICAL,
362                                 "Problem reading configuration file.");
363                         fclose(conffile);
364                         return;
365                 }
366
367                 if (oldstyle) {
368                         /* Add a single DB configuration */
369                         backend = calloc(1, sizeof(*backend));
370                         config.backend = backend;
371                         config.backends = lladd(NULL, backend);
372                 }
373
374                 while (!feof(conffile)) {
375                         /* Strip any trailing white space */
376                         for (i = strlen(curline) - 1;
377                                         i >= 0 && isspace(curline[i]);
378                                         i--) {
379                                 curline[i] = 0;
380                         }
381
382                         /* Strip any leading white space */
383                         i = 0;
384                         while (curline[i] != 0 && isspace(curline[i])) {
385                                 i++;
386                         }
387
388                         if (oldstyle) {
389                                 res = parseoldconfigline(&curline[i]);
390                         } else {
391                                 res = parseconfigline(&curline[i]);
392                         }
393                         if (!res) {
394                                 logthing(LOGTHING_ERROR,
395                                         "Unknown config line: %s", curline);
396                         }
397
398                         if (!fgets(curline, 1023, conffile) &&
399                                         !feof(conffile)) {
400                                 logthing(LOGTHING_CRITICAL,
401                                         "Problem reading configuration file.");
402                                 break;
403                         }
404                 }
405                 fclose(conffile);
406
407                 if (config.db_backend == NULL) {
408                         logthing(LOGTHING_CRITICAL,
409                                 "No database backend configured.");
410                 } else if (!oldstyle) {
411                         config.backend = find_db_backend_config(
412                                 config.backends, config.db_backend);
413                         if (config.backend == NULL) {
414                                 logthing(LOGTHING_NOTICE,
415                                         "Couldn't find configuration for %s "
416                                         "backend.", config.db_backend);
417                         }
418                 }
419         } else {
420                 logthing(LOGTHING_NOTICE,
421                                 "Couldn't open config file; using defaults.");
422         }
423 }
424
425 void writeconfig(const char *configfile)
426 {
427         FILE *conffile;
428         struct ll *cur;
429
430         if (configfile) {
431                 conffile = fopen(configfile, "w");
432         } else {
433                 conffile = stdout;
434         }
435
436 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
437         fprintf(conffile, s "=%s\n", c); \
438 }
439 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
440
441         fprintf(conffile, "[main]\n");
442         WRITE_IF_NOT_NULL(config.backend->name, "backend");
443         WRITE_IF_NOT_NULL(config.backends_dir, "backends_dir");
444         WRITE_IF_NOT_NULL(config.logfile, "logfile");
445         fprintf(conffile, "loglevel=%d\n", getlogthreshold());
446         WRITE_BOOL(config.use_keyd, "use_keyd");
447         WRITE_IF_NOT_NULL(config.sock_dir, "sock_dir");
448         fprintf(conffile, "max_reply_keys=%d\n", config.maxkeys);
449         fprintf(conffile, "\n");
450
451         fprintf(conffile, "[verification]\n");
452         WRITE_BOOL(config.check_sighash, "check_sighash");
453         fprintf(conffile, "\n");
454
455         fprintf(conffile, "[mail]\n");
456         WRITE_IF_NOT_NULL(config.adminemail, "maintainer_email");
457         WRITE_IF_NOT_NULL(config.mail_dir, "mail_dir");
458         WRITE_IF_NOT_NULL(config.mta, "mta");
459         WRITE_IF_NOT_NULL(config.bin_dir, "bin_dir");
460         WRITE_IF_NOT_NULL(config.thissite, "this_site");
461
462         cur = config.syncsites;
463         while (cur != NULL) {
464                 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
465                 cur = cur->next;
466         }
467
468         cur = config.backends;
469         while (cur != NULL) {
470                 struct onak_db_config *backend =
471                         (struct onak_db_config *) cur->object;
472                 fprintf(conffile, "\n[backend:%s]\n", backend->name);
473                 WRITE_IF_NOT_NULL(backend->type, "type");
474                 WRITE_IF_NOT_NULL(backend->location, "location");
475                 WRITE_IF_NOT_NULL(backend->hostname, "hostname");
476                 WRITE_IF_NOT_NULL(backend->username, "username");
477                 WRITE_IF_NOT_NULL(backend->password, "password");
478                 cur = cur->next;
479         }
480
481         if (configfile) {
482                 fclose(conffile);
483         }
484 }
485
486 void cleanupdbconfig(void *object)
487 {
488         struct onak_db_config *dbconfig = (struct onak_db_config *) object;
489
490         if (dbconfig->name != NULL) {
491                 free(dbconfig->name);
492                 dbconfig->name = NULL;
493         }
494         if (dbconfig->type != NULL) {
495                 free(dbconfig->type);
496                 dbconfig->type = NULL;
497         }
498         if (dbconfig->location != NULL) {
499                 free(dbconfig->location);
500                 dbconfig->location = NULL;
501         }
502         if (dbconfig->hostname != NULL) {
503                 free(dbconfig->hostname);
504                 dbconfig->hostname = NULL;
505         }
506         if (dbconfig->username != NULL) {
507                 free(dbconfig->username);
508                 dbconfig->username = NULL;
509         }
510         if (dbconfig->password != NULL) {
511                 free(dbconfig->password);
512                 dbconfig->password = NULL;
513         }
514
515         free(dbconfig);
516 }
517
518 void cleanupconfig(void) {
519         /* Free any defined DB backend configuration first */
520         llfree(config.backends, cleanupdbconfig);
521         config.backends = NULL;
522
523         if (config.thissite != NULL) {
524                 free(config.thissite);
525                 config.thissite = NULL;
526         }
527         if (config.adminemail != NULL) {
528                 free(config.adminemail);
529                 config.adminemail = NULL;
530         }
531         if (config.mta != NULL) {
532                 free(config.mta);
533                 config.mta = NULL;
534         }
535         if (config.syncsites != NULL) {
536                 llfree(config.syncsites, free);
537                 config.syncsites = NULL;
538         }
539         if (config.logfile != NULL) {
540                 free(config.logfile);
541                 config.logfile = NULL;
542         }
543         if (config.db_backend != NULL) {
544                 free(config.db_backend);
545                 config.db_backend = NULL;
546         }
547         if (config.backends_dir != NULL) {
548                 free(config.backends_dir);
549                 config.backends_dir = NULL;
550         }
551         if (config.sock_dir != NULL) {
552                 free(config.sock_dir);
553                 config.sock_dir = NULL;
554         }
555         if (config.bin_dir != NULL) {
556                 free(config.bin_dir);
557                 config.bin_dir = NULL;
558         }
559         if (config.mail_dir != NULL) {
560                 free(config.mail_dir);
561                 config.mail_dir = NULL;
562         }
563 }