]> the.earth.li Git - onak.git/blob - onak-conf.c
e4f0bc75312f1b5f4c65cbfbbf1ab27c7868f25d
[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 "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 writeconfig(const char *configfile)
425 {
426         FILE *conffile;
427         struct ll *cur;
428
429         if (configfile) {
430                 conffile = fopen(configfile, "w");
431         } else {
432                 conffile = stdout;
433         }
434
435 #define WRITE_IF_NOT_NULL(c, s) if (c != NULL) { \
436         fprintf(conffile, s "=%s\n", c); \
437 }
438 #define WRITE_BOOL(c, s) fprintf(conffile, s "=%s\n", s ? "true" : "false");
439
440         fprintf(conffile, "[main]\n");
441         WRITE_IF_NOT_NULL(config.backend->name, "backend");
442         WRITE_IF_NOT_NULL(config.backends_dir, "backends_dir");
443         WRITE_IF_NOT_NULL(config.logfile, "logfile");
444         fprintf(conffile, "loglevel=%d\n", getlogthreshold());
445         WRITE_BOOL(config.use_keyd, "use_keyd");
446         WRITE_IF_NOT_NULL(config.sock_dir, "sock_dir");
447         fprintf(conffile, "max_reply_keys=%d\n", config.maxkeys);
448         fprintf(conffile, "\n");
449
450         fprintf(conffile, "[verification]\n");
451         WRITE_BOOL(config.check_sighash, "check_sighash");
452         fprintf(conffile, "\n");
453
454         fprintf(conffile, "[mail]\n");
455         WRITE_IF_NOT_NULL(config.adminemail, "maintainer_email");
456         WRITE_IF_NOT_NULL(config.mail_dir, "mail_dir");
457         WRITE_IF_NOT_NULL(config.mta, "mta");
458         WRITE_IF_NOT_NULL(config.bin_dir, "bin_dir");
459         WRITE_IF_NOT_NULL(config.thissite, "this_site");
460
461         cur = config.syncsites;
462         while (cur != NULL) {
463                 fprintf(conffile, "syncsite=%s\n", (char *) cur->object);
464                 cur = cur->next;
465         }
466
467         cur = config.backends;
468         while (cur != NULL) {
469                 struct onak_db_config *backend =
470                         (struct onak_db_config *) cur->object;
471                 fprintf(conffile, "\n[backend:%s]\n", backend->name);
472                 WRITE_IF_NOT_NULL(backend->type, "type");
473                 WRITE_IF_NOT_NULL(backend->location, "location");
474                 WRITE_IF_NOT_NULL(backend->hostname, "hostname");
475                 WRITE_IF_NOT_NULL(backend->username, "username");
476                 WRITE_IF_NOT_NULL(backend->password, "password");
477                 cur = cur->next;
478         }
479
480         if (configfile) {
481                 fclose(conffile);
482         }
483 }
484
485 void cleanupdbconfig(void *object)
486 {
487         struct onak_db_config *dbconfig = (struct onak_db_config *) object;
488
489         if (dbconfig->name != NULL) {
490                 free(dbconfig->name);
491                 dbconfig->name = NULL;
492         }
493         if (dbconfig->type != NULL) {
494                 free(dbconfig->type);
495                 dbconfig->type = NULL;
496         }
497         if (dbconfig->location != NULL) {
498                 free(dbconfig->location);
499                 dbconfig->location = NULL;
500         }
501         if (dbconfig->hostname != NULL) {
502                 free(dbconfig->hostname);
503                 dbconfig->hostname = NULL;
504         }
505         if (dbconfig->username != NULL) {
506                 free(dbconfig->username);
507                 dbconfig->username = NULL;
508         }
509         if (dbconfig->password != NULL) {
510                 free(dbconfig->password);
511                 dbconfig->password = NULL;
512         }
513
514         free(dbconfig);
515 }
516
517 void cleanupconfig(void) {
518         /* Free any defined DB backend configuration first */
519         llfree(config.backends, cleanupdbconfig);
520         config.backends = NULL;
521
522         if (config.thissite != NULL) {
523                 free(config.thissite);
524                 config.thissite = NULL;
525         }
526         if (config.adminemail != NULL) {
527                 free(config.adminemail);
528                 config.adminemail = NULL;
529         }
530         if (config.mta != NULL) {
531                 free(config.mta);
532                 config.mta = NULL;
533         }
534         if (config.syncsites != NULL) {
535                 llfree(config.syncsites, free);
536                 config.syncsites = NULL;
537         }
538         if (config.logfile != NULL) {
539                 free(config.logfile);
540                 config.logfile = NULL;
541         }
542         if (config.db_backend != NULL) {
543                 free(config.db_backend);
544                 config.db_backend = NULL;
545         }
546         if (config.backends_dir != NULL) {
547                 free(config.backends_dir);
548                 config.backends_dir = NULL;
549         }
550         if (config.sock_dir != NULL) {
551                 free(config.sock_dir);
552                 config.sock_dir = NULL;
553         }
554         if (config.bin_dir != NULL) {
555                 free(config.bin_dir);
556                 config.bin_dir = NULL;
557         }
558         if (config.mail_dir != NULL) {
559                 free(config.mail_dir);
560                 config.mail_dir = NULL;
561         }
562 }