]> the.earth.li Git - onak.git/blob - onak-conf.c
Change config format to a cleaner .ini style
[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
27 #include "ll.h"
28 #include "log.h"
29 #include "onak-conf.h"
30
31 extern struct onak_dbctx *DBINIT(struct onak_db_config *dbcfg, bool readonly);
32
33 /*
34  *      config - Runtime configuration for onak.
35  *
36  *      This is the default config; normally overridden with values from the
37  *      config file.
38  */
39 struct onak_config config = {
40         .maxkeys = 128,
41         .thissite = NULL,
42         .adminemail = NULL,
43         .mta = NULL,
44         .syncsites = NULL,
45         .logfile = NULL,
46
47         .use_keyd = false,
48         .sock_dir = NULL,
49
50         .backends = NULL,
51         .backends_dir = NULL,
52
53         .dbinit = DBINIT,
54
55         .check_sighash = true,
56
57         .bin_dir = NULL,
58         .mail_dir = NULL,
59 };
60
61 struct onak_db_config *find_db_backend_config(struct ll *backends, char *name)
62 {
63         struct ll *cur;
64
65         cur = backends;
66         while (cur != NULL && strcmp(name,
67                         ((struct onak_db_config *) cur->object)->name)) {
68                 cur = cur->next;
69         }
70
71         return (cur != NULL) ? (struct onak_db_config *) cur->object : NULL;
72 }
73
74 bool parsebool(char *str, bool fallback)
75 {
76         if (!strcasecmp(str, "false") || !strcasecmp(str, "no") ||
77                         !strcasecmp(str, "0")) {
78                 return false;
79         } else if (!strcasecmp(str, "true") || !strcasecmp(str, "yes") ||
80                         !strcasecmp(str, "1")) {
81                 return true;
82         } else {
83                 logthing(LOGTHING_CRITICAL,
84                         "Couldn't parse %s as a boolean config variable, "
85                         "returning fallback of '%s'.",
86                         str,
87                         fallback ? "true" : "false");
88                 return fallback;
89         }
90 }
91
92 /*
93  * Parse an old pksd style config line, as used in onak 0.4.6 and earlier.
94  */
95 static bool parseoldconfigline(char *line)
96 {
97         if (line[0] == '#' || line[0] == 0) {
98                 /*
99                  * Comment line, ignore.
100                  */
101         } else if (!strncmp("db_dir ", line, 7)) {
102                 config.backend->location = strdup(&line[7]);
103         } else if (!strncmp("debug ", line, 6)) {
104                 /*
105                  * Not supported yet; ignore for compatibility with
106                  * pksd.
107                  */
108         } else if (!strncmp("default_language ", line, 17)) {
109                 /*
110                  * Not supported yet; ignore for compatibility with
111                  * pksd.
112                  */
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)) {
118                 /*
119                  * Not supported yet; ignore for compatibility with
120                  * pksd.
121                  */
122         } else if (!strncmp("help_dir ", line, 9)) {
123                 /*
124                  * Not supported yet; ignore for compatibility with
125                  * pksd.
126                  */
127         } else if (!strncmp("max_last ", line, 9)) {
128                 /*
129                  * Not supported yet; ignore for compatibility with
130                  * pksd.
131                  */
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)) {
143                 config.syncsites =
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)) {
153                 /*
154                  * Not applicable; ignored for compatibility with pksd.
155                  */
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],
168                                         config.use_keyd);
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);
174         } else {
175                 return false;
176         }
177
178         return true;
179 }
180
181 /*
182  * Parse a new style .ini config line, supporting [sections] and name=value
183  * format.
184  */
185 static bool parseconfigline(char *line)
186 {
187         /* Yes, this means we're not re-entrant. */
188         static char section[32] = "";
189         struct onak_db_config *backend;
190         size_t len;
191         char *name, *value;
192
193         if (line[0] == '#' || line[0] == ';' ||
194                         line[0] == 0) {
195                 /*
196                  * Comment line, ignore.
197                  */
198         } else if (line[0] == '[') {
199                 /* Section name */
200                 len = strlen(line);
201                 if (line[len - 1] != ']') {
202                         logthing(LOGTHING_CRITICAL,
203                                 "Malformed section header '%s' in "
204                                 "config file.", line);
205                         return false;
206                 }
207                 if (len > sizeof(section)) {
208                         logthing(LOGTHING_CRITICAL,
209                                 "Section header '%s' is too long in "
210                                 "config file.", line);
211                         return false;
212                 }
213                 line[len - 1] = 0;
214                 strncpy(section, &line[1], len);
215         } else if ((value = strchr(line, '=')) != NULL) {
216                 name = line;
217                 *value++ = 0;
218
219                 /* We can have multiple backend: sections */
220                 if (!strncmp(section, "backend:", 8)) {
221                         backend = find_db_backend_config(
222                                 config.backends, &section[8]);
223                         if (backend == NULL) {
224                                 backend = calloc(1,
225                                         sizeof(struct onak_db_config));
226                                 backend->name = strdup(&section[8]);
227                                 config.backends = lladd(config.backends,
228                                                         backend);
229                         }
230
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);
241                         }
242
243 #define MATCH(s, n) !strcmp(section, s) && !strcmp(name, n)
244                 /* [main] section */
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,
255                                         config.use_keyd);
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);
260                 /* [mail] section */
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,
273                                 strdup(value));
274                 /* [verification] section */
275                 } else if (MATCH("verification", "check_sighash")) {
276                         config.check_sighash = parsebool(value,
277                                         config.check_sighash);
278                 } else {
279                         return false;
280                 }
281         } else {
282                 return false;
283         }
284
285         return true;
286 }
287
288 void readconfig(const char *configfile) {
289         FILE *conffile;
290         char  curline[1024];
291         int   i;
292         char *dir, *conf;
293         size_t len;
294         struct onak_db_config *backend;
295         bool oldstyle = false;
296         bool res;
297
298         curline[1023] = 0;
299
300         /*
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.
306          */
307         if (configfile == NULL) {
308                 conffile = NULL;
309                 if ((dir = getenv("XDG_CONFIG_HOME")) != NULL) {
310                         /* dir + / + onak.conf + NUL */
311                         len = strlen(dir) + 1 + 9 + 1;
312                         conf = malloc(len);
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");
319                         } else {
320                                 oldstyle = true;
321                         }
322                         free(conf);
323                 } else if ((dir = getenv("HOME")) != NULL) {
324                         /* dir + /.config/onak.conf + NUL */
325                         len = strlen(dir) + 18 + 1;
326                         conf = malloc(len);
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");
333                         } else {
334                                 oldstyle = true;
335                         }
336                         free(conf);
337                 }
338                 if (conffile == NULL) {
339                         conffile = fopen(CONFIGDIR "/onak.conf", "r");
340                         if (conffile == NULL) {
341                                 conffile = fopen(CONFIGDIR "/onak.ini", "r");
342                         } else {
343                                 oldstyle = true;
344                         }
345                 }
346         } else {
347                 /*
348                  * Explicitly provided config file; if the filename ends .conf
349                  * assume it's old style.
350                  */
351                 len = strlen(configfile);
352                 if (!strcmp(&configfile[len - 5], ".conf")) {
353                         oldstyle = true;
354                 }
355                 conffile = fopen(configfile, "r");
356         }
357
358         if (conffile != NULL) {
359                 if (!fgets(curline, 1023, conffile)) {
360                         logthing(LOGTHING_CRITICAL,
361                                 "Problem reading configuration file.");
362                         fclose(conffile);
363                         return;
364                 }
365
366                 if (oldstyle) {
367                         /* Add a single DB configuration */
368                         backend = calloc(1, sizeof(*backend));
369                         config.backend = backend;
370                         config.backends = lladd(NULL, backend);
371                 }
372
373                 while (!feof(conffile)) {
374                         /* Strip any trailing white space */
375                         for (i = strlen(curline) - 1;
376                                         i >= 0 && isspace(curline[i]);
377                                         i--) {
378                                 curline[i] = 0;
379                         }
380
381                         /* Strip any leading white space */
382                         i = 0;
383                         while (curline[i] != 0 && isspace(curline[i])) {
384                                 i++;
385                         }
386
387                         if (oldstyle) {
388                                 res = parseoldconfigline(&curline[i]);
389                         } else {
390                                 res = parseconfigline(&curline[i]);
391                         }
392                         if (!res) {
393                                 logthing(LOGTHING_ERROR,
394                                         "Unknown config line: %s", curline);
395                         }
396
397                         if (!fgets(curline, 1023, conffile) &&
398                                         !feof(conffile)) {
399                                 logthing(LOGTHING_CRITICAL,
400                                         "Problem reading configuration file.");
401                                 break;
402                         }
403                 }
404                 fclose(conffile);
405
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);
416                         }
417                 }
418         } else {
419                 logthing(LOGTHING_NOTICE,
420                                 "Couldn't open config file; using defaults.");
421         }
422 }
423
424 void cleanupdbconfig(void *object)
425 {
426         struct onak_db_config *dbconfig = (struct onak_db_config *) object;
427
428         if (dbconfig->name != NULL) {
429                 free(dbconfig->name);
430                 dbconfig->name = NULL;
431         }
432         if (dbconfig->type != NULL) {
433                 free(dbconfig->type);
434                 dbconfig->type = NULL;
435         }
436         if (dbconfig->location != NULL) {
437                 free(dbconfig->location);
438                 dbconfig->location = NULL;
439         }
440         if (dbconfig->hostname != NULL) {
441                 free(dbconfig->hostname);
442                 dbconfig->hostname = NULL;
443         }
444         if (dbconfig->username != NULL) {
445                 free(dbconfig->username);
446                 dbconfig->username = NULL;
447         }
448         if (dbconfig->password != NULL) {
449                 free(dbconfig->password);
450                 dbconfig->password = NULL;
451         }
452
453         free(dbconfig);
454 }
455
456 void cleanupconfig(void) {
457         /* Free any defined DB backend configuration first */
458         llfree(config.backends, cleanupdbconfig);
459         config.backends = NULL;
460
461         if (config.thissite != NULL) {
462                 free(config.thissite);
463                 config.thissite = NULL;
464         }
465         if (config.adminemail != NULL) {
466                 free(config.adminemail);
467                 config.adminemail = NULL;
468         }
469         if (config.mta != NULL) {
470                 free(config.mta);
471                 config.mta = NULL;
472         }
473         if (config.syncsites != NULL) {
474                 llfree(config.syncsites, free);
475                 config.syncsites = NULL;
476         }
477         if (config.logfile != NULL) {
478                 free(config.logfile);
479                 config.logfile = NULL;
480         }
481         if (config.db_backend != NULL) {
482                 free(config.db_backend);
483                 config.db_backend = NULL;
484         }
485         if (config.backends_dir != NULL) {
486                 free(config.backends_dir);
487                 config.backends_dir = NULL;
488         }
489         if (config.sock_dir != NULL) {
490                 free(config.sock_dir);
491                 config.sock_dir = NULL;
492         }
493         if (config.bin_dir != NULL) {
494                 free(config.bin_dir);
495                 config.bin_dir = NULL;
496         }
497         if (config.mail_dir != NULL) {
498                 free(config.mail_dir);
499                 config.mail_dir = NULL;
500         }
501 }