chiark / gitweb /
Merge more 3.0 branch changes
[disorder] / lib / configuration.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder.
964e027d 3 * Copyright (C) 2004-2008 Richard Kettlewell
313acc77 4 * Portions copyright (C) 2007 Mark Wooding
460b9539 5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20 */
0e4472a0 21/** @file lib/configuration.c
22 * @brief Configuration file support
23 */
460b9539 24
25#include <config.h>
26#include "types.h"
27
28#include <stdio.h>
29#include <string.h>
30#include <stdlib.h>
31#include <errno.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <unistd.h>
35#include <ctype.h>
36#include <stddef.h>
37#include <pwd.h>
38#include <langinfo.h>
39#include <pcre.h>
40#include <signal.h>
41
04e1fa7c 42#include "rights.h"
460b9539 43#include "configuration.h"
44#include "mem.h"
45#include "log.h"
46#include "split.h"
47#include "syscalls.h"
48#include "table.h"
49#include "inputline.h"
50#include "charset.h"
51#include "defs.h"
52#include "mixer.h"
53#include "printf.h"
54#include "regsub.h"
55#include "signame.h"
637fdea3 56#include "authhash.h"
e6a35d1c 57#include "vector.h"
460b9539 58
3f3bb97b
RK
59/** @brief Path to config file
60 *
61 * set_configfile() sets the deafult if it is null.
62 */
460b9539 63char *configfile;
64
63ad732f
RK
65/** @brief Read user configuration
66 *
67 * If clear, the user-specific configuration is not read.
68 */
69int config_per_user = 1;
70
3f3bb97b 71/** @brief Config file parser state */
460b9539 72struct config_state {
3f3bb97b 73 /** @brief Filename */
460b9539 74 const char *path;
3f3bb97b 75 /** @brief Line number */
460b9539 76 int line;
3f3bb97b 77 /** @brief Configuration object under construction */
460b9539 78 struct config *config;
79};
80
3f3bb97b 81/** @brief Current configuration */
460b9539 82struct config *config;
83
3f3bb97b 84/** @brief One configuration item */
460b9539 85struct conf {
3f3bb97b 86 /** @brief Name as it appears in the config file */
460b9539 87 const char *name;
3f3bb97b 88 /** @brief Offset in @ref config structure */
460b9539 89 size_t offset;
3f3bb97b 90 /** @brief Pointer to item type */
460b9539 91 const struct conftype *type;
3f3bb97b 92 /** @brief Pointer to item-specific validation routine */
460b9539 93 int (*validate)(const struct config_state *cs,
94 int nvec, char **vec);
95};
96
3f3bb97b 97/** @brief Type of a configuration item */
460b9539 98struct conftype {
3f3bb97b 99 /** @brief Pointer to function to set item */
460b9539 100 int (*set)(const struct config_state *cs,
101 const struct conf *whoami,
102 int nvec, char **vec);
3f3bb97b 103 /** @brief Pointer to function to free item */
460b9539 104 void (*free)(struct config *c, const struct conf *whoami);
105};
106
3f3bb97b 107/** @brief Compute the address of an item */
460b9539 108#define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
3f3bb97b 109/** @brief Return the value of an item */
460b9539 110#define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
111
112static int set_signal(const struct config_state *cs,
113 const struct conf *whoami,
114 int nvec, char **vec) {
115 int n;
116
117 if(nvec != 1) {
118 error(0, "%s:%d: '%s' requires one argument",
119 cs->path, cs->line, whoami->name);
120 return -1;
121 }
122 if((n = find_signal(vec[0])) == -1) {
123 error(0, "%s:%d: unknown signal '%s'",
124 cs->path, cs->line, vec[0]);
125 return -1;
126 }
127 VALUE(cs->config, int) = n;
128 return 0;
129}
130
131static int set_collections(const struct config_state *cs,
132 const struct conf *whoami,
133 int nvec, char **vec) {
134 struct collectionlist *cl;
01cef138 135 const char *root, *encoding, *module;
460b9539 136
01cef138
RK
137 switch(nvec) {
138 case 1:
139 module = 0;
140 encoding = 0;
141 root = vec[0];
142 break;
143 case 2:
144 module = vec[0];
145 encoding = 0;
146 root = vec[1];
147 break;
148 case 3:
149 module = vec[0];
150 encoding = vec[1];
151 root = vec[2];
152 break;
153 case 0:
154 error(0, "%s:%d: '%s' requires at least one argument",
155 cs->path, cs->line, whoami->name);
156 return -1;
157 default:
158 error(0, "%s:%d: '%s' requires at most three arguments",
460b9539 159 cs->path, cs->line, whoami->name);
160 return -1;
161 }
01cef138
RK
162 /* Sanity check root */
163 if(root[0] != '/') {
460b9539 164 error(0, "%s:%d: collection root must start with '/'",
165 cs->path, cs->line);
166 return -1;
167 }
01cef138 168 if(root[1] && root[strlen(root)-1] == '/') {
460b9539 169 error(0, "%s:%d: collection root must not end with '/'",
170 cs->path, cs->line);
171 return -1;
172 }
01cef138
RK
173 /* Defaults */
174 if(!module)
175 module = "fs";
176 if(!encoding)
177 encoding = nl_langinfo(CODESET);
460b9539 178 cl = ADDRESS(cs->config, struct collectionlist);
179 ++cl->n;
180 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
01cef138
RK
181 cl->s[cl->n - 1].module = xstrdup(module);
182 cl->s[cl->n - 1].encoding = xstrdup(encoding);
183 cl->s[cl->n - 1].root = xstrdup(root);
460b9539 184 return 0;
185}
186
187static int set_boolean(const struct config_state *cs,
188 const struct conf *whoami,
189 int nvec, char **vec) {
190 int state;
191
192 if(nvec != 1) {
193 error(0, "%s:%d: '%s' takes only one argument",
194 cs->path, cs->line, whoami->name);
195 return -1;
196 }
197 if(!strcmp(vec[0], "yes")) state = 1;
198 else if(!strcmp(vec[0], "no")) state = 0;
199 else {
200 error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
201 cs->path, cs->line, whoami->name);
202 return -1;
203 }
204 VALUE(cs->config, int) = state;
205 return 0;
206}
207
208static int set_string(const struct config_state *cs,
209 const struct conf *whoami,
210 int nvec, char **vec) {
211 if(nvec != 1) {
212 error(0, "%s:%d: '%s' takes only one argument",
213 cs->path, cs->line, whoami->name);
214 return -1;
215 }
216 VALUE(cs->config, char *) = xstrdup(vec[0]);
217 return 0;
218}
219
220static int set_stringlist(const struct config_state *cs,
221 const struct conf *whoami,
222 int nvec, char **vec) {
223 int n;
224 struct stringlist *sl;
225
226 sl = ADDRESS(cs->config, struct stringlist);
227 sl->n = 0;
228 for(n = 0; n < nvec; ++n) {
229 sl->n++;
230 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
231 sl->s[sl->n - 1] = xstrdup(vec[n]);
232 }
233 return 0;
234}
235
236static int set_integer(const struct config_state *cs,
237 const struct conf *whoami,
238 int nvec, char **vec) {
239 char *e;
240
241 if(nvec != 1) {
242 error(0, "%s:%d: '%s' takes only one argument",
243 cs->path, cs->line, whoami->name);
244 return -1;
245 }
246 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
247 error(errno, "%s:%d: converting integer", cs->path, cs->line);
248 return -1;
249 }
250 if(*e) {
251 error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
252 return -1;
253 }
254 return 0;
255}
256
257static int set_stringlist_accum(const struct config_state *cs,
258 const struct conf *whoami,
259 int nvec, char **vec) {
260 int n;
261 struct stringlist *s;
262 struct stringlistlist *sll;
263
264 sll = ADDRESS(cs->config, struct stringlistlist);
40c30921
RK
265 if(nvec == 0) {
266 sll->n = 0;
267 return 0;
268 }
460b9539 269 sll->n++;
270 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
271 s = &sll->s[sll->n - 1];
272 s->n = nvec;
273 s->s = xmalloc((nvec + 1) * sizeof (char *));
274 for(n = 0; n < nvec; ++n)
275 s->s[n] = xstrdup(vec[n]);
276 return 0;
277}
278
279static int set_string_accum(const struct config_state *cs,
280 const struct conf *whoami,
281 int nvec, char **vec) {
282 int n;
283 struct stringlist *sl;
284
285 sl = ADDRESS(cs->config, struct stringlist);
40c30921
RK
286 if(nvec == 0) {
287 sl->n = 0;
288 return 0;
289 }
460b9539 290 for(n = 0; n < nvec; ++n) {
291 sl->n++;
292 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
293 sl->s[sl->n - 1] = xstrdup(vec[n]);
294 }
295 return 0;
296}
297
298static int set_restrict(const struct config_state *cs,
299 const struct conf *whoami,
300 int nvec, char **vec) {
301 unsigned r = 0;
302 int n, i;
303
304 static const struct restriction {
305 const char *name;
306 unsigned bit;
307 } restrictions[] = {
308 { "remove", RESTRICT_REMOVE },
309 { "scratch", RESTRICT_SCRATCH },
310 { "move", RESTRICT_MOVE },
311 };
312
313 for(n = 0; n < nvec; ++n) {
314 if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) {
315 error(0, "%s:%d: invalid restriction '%s'",
316 cs->path, cs->line, vec[n]);
317 return -1;
318 }
319 r |= restrictions[i].bit;
320 }
321 VALUE(cs->config, unsigned) = r;
322 return 0;
323}
324
9d5da576 325static int parse_sample_format(const struct config_state *cs,
6d2d327c 326 struct stream_header *format,
9d5da576 327 int nvec, char **vec) {
328 char *p = vec[0];
329 long t;
330
6d2d327c 331 if(nvec != 1) {
9d5da576 332 error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
333 return -1;
334 }
6d2d327c 335 if(xstrtol(&t, p, &p, 0)) {
9d5da576 336 error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line);
337 return -1;
338 }
6d2d327c 339 if(t != 8 && t != 16) {
9d5da576 340 error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t);
341 return -1;
342 }
6d2d327c 343 if(format) format->bits = t;
9d5da576 344 switch (*p) {
6d2d327c
RK
345 case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
346 case 'b': case 'B': t = ENDIAN_BIG; p++; break;
347 default: t = ENDIAN_NATIVE; break;
9d5da576 348 }
6d2d327c
RK
349 if(format) format->endian = t;
350 if(*p != '/') {
9d5da576 351 error(errno, "%s:%d: expected `/' after bits-per-sample",
352 cs->path, cs->line);
353 return -1;
354 }
355 p++;
6d2d327c 356 if(xstrtol(&t, p, &p, 0)) {
9d5da576 357 error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
358 return -1;
359 }
6d2d327c 360 if(t < 1 || t > INT_MAX) {
9d5da576 361 error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
362 return -1;
363 }
6d2d327c
RK
364 if(format) format->rate = t;
365 if(*p != '/') {
9d5da576 366 error(0, "%s:%d: expected `/' after sample-rate",
367 cs->path, cs->line);
368 return -1;
369 }
370 p++;
6d2d327c 371 if(xstrtol(&t, p, &p, 0)) {
9d5da576 372 error(errno, "%s:%d: converting channels", cs->path, cs->line);
373 return -1;
374 }
6d2d327c 375 if(t < 1 || t > 8) {
9d5da576 376 error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t);
377 return -1;
378 }
6d2d327c
RK
379 if(format) format->channels = t;
380 if(*p) {
9d5da576 381 error(0, "%s:%d: junk after channels", cs->path, cs->line);
382 return -1;
383 }
384 return 0;
385}
386
387static int set_sample_format(const struct config_state *cs,
388 const struct conf *whoami,
389 int nvec, char **vec) {
6d2d327c 390 return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
9d5da576 391 nvec, vec);
392}
393
460b9539 394static int set_namepart(const struct config_state *cs,
395 const struct conf *whoami,
396 int nvec, char **vec) {
397 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
398 unsigned reflags;
399 const char *errstr;
400 int erroffset, n;
401 pcre *re;
402
403 if(nvec < 3) {
404 error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line);
405 return -1;
406 }
407 if(nvec > 5) {
408 error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line);
409 return -1;
410 }
411 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
412 if(!(re = pcre_compile(vec[1],
413 PCRE_UTF8
414 |regsub_compile_options(reflags),
415 &errstr, &erroffset, 0))) {
416 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
417 cs->path, cs->line, vec[1], errstr, erroffset);
418 return -1;
419 }
420 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
421 npl->s[npl->n].part = xstrdup(vec[0]);
422 npl->s[npl->n].re = re;
423 npl->s[npl->n].replace = xstrdup(vec[2]);
424 npl->s[npl->n].context = xstrdup(vec[3]);
425 npl->s[npl->n].reflags = reflags;
426 ++npl->n;
427 /* XXX a bit of a bodge; relies on there being very few parts. */
428 for(n = 0; (n < cs->config->nparts
429 && strcmp(cs->config->parts[n], vec[0])); ++n)
430 ;
431 if(n >= cs->config->nparts) {
432 cs->config->parts = xrealloc(cs->config->parts,
433 (cs->config->nparts + 1) * sizeof (char *));
434 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
435 }
436 return 0;
437}
438
439static int set_transform(const struct config_state *cs,
440 const struct conf *whoami,
441 int nvec, char **vec) {
442 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
443 pcre *re;
444 unsigned reflags;
445 const char *errstr;
446 int erroffset;
447
448 if(nvec < 3) {
449 error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line);
450 return -1;
451 }
452 if(nvec > 5) {
453 error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line);
454 return -1;
455 }
456 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
457 if(!(re = pcre_compile(vec[1],
458 PCRE_UTF8
459 |regsub_compile_options(reflags),
460 &errstr, &erroffset, 0))) {
461 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
462 cs->path, cs->line, vec[1], errstr, erroffset);
463 return -1;
464 }
465 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
466 tl->t[tl->n].type = xstrdup(vec[0]);
467 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
468 tl->t[tl->n].re = re;
469 tl->t[tl->n].replace = xstrdup(vec[2]);
470 tl->t[tl->n].flags = reflags;
471 ++tl->n;
472 return 0;
473}
474
e83d0967
RK
475static int set_backend(const struct config_state *cs,
476 const struct conf *whoami,
477 int nvec, char **vec) {
478 int *const valuep = ADDRESS(cs->config, int);
479
480 if(nvec != 1) {
481 error(0, "%s:%d: '%s' requires one argument",
482 cs->path, cs->line, whoami->name);
483 return -1;
484 }
485 if(!strcmp(vec[0], "alsa")) {
146e86fb 486#if HAVE_ALSA_ASOUNDLIB_H
e83d0967
RK
487 *valuep = BACKEND_ALSA;
488#else
489 error(0, "%s:%d: ALSA is not available on this platform",
490 cs->path, cs->line);
491 return -1;
492#endif
493 } else if(!strcmp(vec[0], "command"))
494 *valuep = BACKEND_COMMAND;
495 else if(!strcmp(vec[0], "network"))
496 *valuep = BACKEND_NETWORK;
937be4c0
RK
497 else if(!strcmp(vec[0], "coreaudio")) {
498#if HAVE_COREAUDIO_AUDIOHARDWARE_H
499 *valuep = BACKEND_COREAUDIO;
500#else
501 error(0, "%s:%d: Core Audio is not available on this platform",
502 cs->path, cs->line);
503 return -1;
e99d42b1 504#endif
505 } else if(!strcmp(vec[0], "oss")) {
506#if HAVE_SYS_SOUNDCARD_H
507 *valuep = BACKEND_OSS;
508#else
509 error(0, "%s:%d: OSS is not available on this platform",
510 cs->path, cs->line);
511 return -1;
937be4c0
RK
512#endif
513 } else {
e83d0967
RK
514 error(0, "%s:%d: invalid '%s' value '%s'",
515 cs->path, cs->line, whoami->name, vec[0]);
516 return -1;
517 }
518 return 0;
519}
520
04e1fa7c
RK
521static int set_rights(const struct config_state *cs,
522 const struct conf *whoami,
523 int nvec, char **vec) {
04e1fa7c
RK
524 if(nvec != 1) {
525 error(0, "%s:%d: '%s' requires one argument",
526 cs->path, cs->line, whoami->name);
527 return -1;
528 }
0f55e905 529 if(parse_rights(vec[0], 0, 1)) {
04e1fa7c
RK
530 error(0, "%s:%d: invalid rights string '%s'",
531 cs->path, cs->line, vec[0]);
532 return -1;
533 }
0f55e905 534 *ADDRESS(cs->config, char *) = vec[0];
04e1fa7c
RK
535 return 0;
536}
537
460b9539 538/* free functions */
539
540static void free_none(struct config attribute((unused)) *c,
541 const struct conf attribute((unused)) *whoami) {
542}
543
544static void free_string(struct config *c,
545 const struct conf *whoami) {
546 xfree(VALUE(c, char *));
547}
548
549static void free_stringlist(struct config *c,
550 const struct conf *whoami) {
551 int n;
552 struct stringlist *sl = ADDRESS(c, struct stringlist);
553
554 for(n = 0; n < sl->n; ++n)
555 xfree(sl->s[n]);
556 xfree(sl->s);
557}
558
559static void free_stringlistlist(struct config *c,
560 const struct conf *whoami) {
561 int n, m;
562 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
563 struct stringlist *sl;
564
565 for(n = 0; n < sll->n; ++n) {
566 sl = &sll->s[n];
567 for(m = 0; m < sl->n; ++m)
568 xfree(sl->s[m]);
569 xfree(sl->s);
570 }
571 xfree(sll->s);
572}
573
574static void free_collectionlist(struct config *c,
575 const struct conf *whoami) {
576 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
577 struct collection *cl;
578 int n;
579
580 for(n = 0; n < cll->n; ++n) {
581 cl = &cll->s[n];
582 xfree(cl->module);
583 xfree(cl->encoding);
584 xfree(cl->root);
585 }
586 xfree(cll->s);
587}
588
589static void free_namepartlist(struct config *c,
590 const struct conf *whoami) {
591 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
592 struct namepart *np;
593 int n;
594
595 for(n = 0; n < npl->n; ++n) {
596 np = &npl->s[n];
597 xfree(np->part);
598 pcre_free(np->re); /* ...whatever pcre_free is set to. */
599 xfree(np->replace);
600 xfree(np->context);
601 }
602 xfree(npl->s);
603}
604
605static void free_transformlist(struct config *c,
606 const struct conf *whoami) {
607 struct transformlist *tl = ADDRESS(c, struct transformlist);
608 struct transform *t;
609 int n;
610
611 for(n = 0; n < tl->n; ++n) {
612 t = &tl->t[n];
613 xfree(t->type);
614 pcre_free(t->re); /* ...whatever pcre_free is set to. */
615 xfree(t->replace);
616 xfree(t->context);
617 }
618 xfree(tl->t);
619}
620
621/* configuration types */
622
623static const struct conftype
624 type_signal = { set_signal, free_none },
625 type_collections = { set_collections, free_collectionlist },
626 type_boolean = { set_boolean, free_none },
627 type_string = { set_string, free_string },
628 type_stringlist = { set_stringlist, free_stringlist },
629 type_integer = { set_integer, free_none },
630 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
631 type_string_accum = { set_string_accum, free_stringlist },
9d5da576 632 type_sample_format = { set_sample_format, free_none },
460b9539 633 type_restrict = { set_restrict, free_none },
634 type_namepart = { set_namepart, free_namepartlist },
e83d0967 635 type_transform = { set_transform, free_transformlist },
04e1fa7c 636 type_rights = { set_rights, free_none },
e83d0967 637 type_backend = { set_backend, free_none };
460b9539 638
639/* specific validation routine */
640
641#define VALIDATE_FILE(test, what) do { \
642 struct stat sb; \
643 int n; \
644 \
645 for(n = 0; n < nvec; ++n) { \
646 if(stat(vec[n], &sb) < 0) { \
647 error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \
648 return -1; \
649 } \
650 if(!test(sb.st_mode)) { \
651 error(0, "%s:%d: %s is not a %s", \
652 cs->path, cs->line, vec[n], what); \
653 return -1; \
654 } \
655 } \
656} while(0)
657
659d87e8
RK
658static int validate_isabspath(const struct config_state *cs,
659 int nvec, char **vec) {
660 int n;
661
662 for(n = 0; n < nvec; ++n)
663 if(vec[n][0] != '/') {
664 error(errno, "%s:%d: %s: not an absolute path",
665 cs->path, cs->line, vec[n]);
666 return -1;
667 }
668 return 0;
669}
670
460b9539 671static int validate_isdir(const struct config_state *cs,
672 int nvec, char **vec) {
673 VALIDATE_FILE(S_ISDIR, "directory");
674 return 0;
675}
676
677static int validate_isreg(const struct config_state *cs,
678 int nvec, char **vec) {
679 VALIDATE_FILE(S_ISREG, "regular file");
680 return 0;
681}
682
460b9539 683static int validate_player(const struct config_state *cs,
684 int nvec,
685 char attribute((unused)) **vec) {
686 if(nvec < 2) {
687 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
688 cs->path, cs->line);
689 return -1;
690 }
691 return 0;
692}
693
62dc3748
RK
694static int validate_tracklength(const struct config_state *cs,
695 int nvec,
696 char attribute((unused)) **vec) {
697 if(nvec < 2) {
698 error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
699 cs->path, cs->line);
700 return -1;
701 }
702 return 0;
703}
704
460b9539 705static int validate_allow(const struct config_state *cs,
706 int nvec,
707 char attribute((unused)) **vec) {
708 if(nvec != 2) {
709 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
710 return -1;
711 }
712 return 0;
713}
714
715static int validate_non_negative(const struct config_state *cs,
716 int nvec, char **vec) {
717 long n;
718
719 if(nvec < 1) {
720 error(0, "%s:%d: missing argument", cs->path, cs->line);
721 return -1;
722 }
723 if(nvec > 1) {
724 error(0, "%s:%d: too many arguments", cs->path, cs->line);
725 return -1;
726 }
727 if(xstrtol(&n, vec[0], 0, 0)) {
728 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
729 return -1;
730 }
731 if(n < 0) {
732 error(0, "%s:%d: must not be negative", cs->path, cs->line);
733 return -1;
734 }
735 return 0;
736}
737
738static int validate_positive(const struct config_state *cs,
739 int nvec, char **vec) {
740 long n;
741
742 if(nvec < 1) {
743 error(0, "%s:%d: missing argument", cs->path, cs->line);
744 return -1;
745 }
746 if(nvec > 1) {
747 error(0, "%s:%d: too many arguments", cs->path, cs->line);
748 return -1;
749 }
750 if(xstrtol(&n, vec[0], 0, 0)) {
751 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
752 return -1;
753 }
754 if(n <= 0) {
755 error(0, "%s:%d: must be positive", cs->path, cs->line);
756 return -1;
757 }
758 return 0;
759}
760
761static int validate_isauser(const struct config_state *cs,
762 int attribute((unused)) nvec,
763 char **vec) {
764 struct passwd *pw;
765
766 if(!(pw = getpwnam(vec[0]))) {
767 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
768 return -1;
769 }
770 return 0;
771}
772
9d5da576 773static int validate_sample_format(const struct config_state *cs,
774 int attribute((unused)) nvec,
775 char **vec) {
776 return parse_sample_format(cs, 0, nvec, vec);
777}
778
460b9539 779static int validate_any(const struct config_state attribute((unused)) *cs,
780 int attribute((unused)) nvec,
781 char attribute((unused)) **vec) {
782 return 0;
783}
784
785static int validate_url(const struct config_state attribute((unused)) *cs,
786 int attribute((unused)) nvec,
787 char **vec) {
788 const char *s;
789 int n;
790 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
791 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
792 s = vec[0];
793 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
794 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
795 "0123456789"));
796 if(s[n] != ':') {
797 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
798 return -1;
799 }
800 if(!strncmp(s, "http:", 5)
801 || !strncmp(s, "https:", 6)) {
802 s += n + 1;
803 /* we only do a rather cursory check */
804 if(strncmp(s, "//", 2)) {
805 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
806 return -1;
807 }
808 }
809 return 0;
810}
811
812static int validate_alias(const struct config_state *cs,
813 int nvec,
814 char **vec) {
815 const char *s;
816 int in_brackets = 0, c;
817
818 if(nvec < 1) {
819 error(0, "%s:%d: missing argument", cs->path, cs->line);
820 return -1;
821 }
822 if(nvec > 1) {
823 error(0, "%s:%d: too many arguments", cs->path, cs->line);
824 return -1;
825 }
826 s = vec[0];
827 while((c = (unsigned char)*s++)) {
828 if(in_brackets) {
829 if(c == '}')
830 in_brackets = 0;
831 else if(!isalnum(c)) {
832 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
833 cs->path, cs->line, vec[0]);
834 return -1;
835 }
836 } else {
837 if(c == '{') {
838 in_brackets = 1;
839 if(*s == '/')
840 ++s;
841 } else if(c == '\\') {
842 if(!(c = (unsigned char)*s++)) {
843 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
844 cs->path, cs->line, vec[0]);
845 return -1;
846 } else if(c != '\\' && c != '{') {
847 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
848 cs->path, cs->line, vec[0]);
849 return -1;
850 }
851 }
852 }
853 ++s;
854 }
855 if(in_brackets) {
856 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
857 cs->path, cs->line, vec[0]);
858 return -1;
859 }
860 return 0;
861}
862
e83d0967
RK
863static int validate_addrport(const struct config_state attribute((unused)) *cs,
864 int nvec,
865 char attribute((unused)) **vec) {
866 switch(nvec) {
867 case 0:
868 error(0, "%s:%d: missing address",
869 cs->path, cs->line);
870 return -1;
871 case 1:
872 error(0, "%s:%d: missing port name/number",
873 cs->path, cs->line);
874 return -1;
875 case 2:
876 return 0;
877 default:
878 error(0, "%s:%d: expected ADDRESS PORT",
879 cs->path, cs->line);
880 return -1;
881 }
882}
883
ccf0aafa 884static int validate_port(const struct config_state attribute((unused)) *cs,
e83d0967
RK
885 int nvec,
886 char attribute((unused)) **vec) {
887 switch(nvec) {
888 case 0:
889 error(0, "%s:%d: missing address",
890 cs->path, cs->line);
891 return -1;
892 case 1:
893 case 2:
894 return 0;
895 default:
ccf0aafa 896 error(0, "%s:%d: expected [ADDRESS] PORT",
e83d0967
RK
897 cs->path, cs->line);
898 return -1;
899 }
900}
901
637fdea3
RK
902static int validate_algo(const struct config_state attribute((unused)) *cs,
903 int nvec,
904 char **vec) {
905 if(nvec != 1) {
906 error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
907 return -1;
908 }
909 if(!valid_authhash(vec[0])) {
910 error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
911 return -1;
912 }
913 return 0;
914}
915
3f3bb97b 916/** @brief Item name and and offset */
460b9539 917#define C(x) #x, offsetof(struct config, x)
3f3bb97b 918/** @brief Item name and and offset */
460b9539 919#define C2(x,y) #x, offsetof(struct config, y)
920
3f3bb97b 921/** @brief All configuration items */
460b9539 922static const struct conf conf[] = {
923 { C(alias), &type_string, validate_alias },
924 { C(allow), &type_stringlist_accum, validate_allow },
bd8895a8 925 { C(api), &type_backend, validate_any },
637fdea3 926 { C(authorization_algorithm), &type_string, validate_algo },
e83d0967 927 { C(broadcast), &type_stringlist, validate_addrport },
ccf0aafa 928 { C(broadcast_from), &type_stringlist, validate_addrport },
bd8895a8 929 { C(channel), &type_string, validate_any },
460b9539 930 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
931 { C(checkpoint_min), &type_integer, validate_non_negative },
932 { C(collection), &type_collections, validate_any },
e83d0967 933 { C(connect), &type_stringlist, validate_addrport },
b12be54a
RK
934 { C(cookie_login_lifetime), &type_integer, validate_positive },
935 { C(cookie_key_lifetime), &type_integer, validate_positive },
8818b7fc 936 { C(dbversion), &type_integer, validate_positive },
04e1fa7c 937 { C(default_rights), &type_rights, validate_any },
460b9539 938 { C(device), &type_string, validate_any },
939 { C(gap), &type_integer, validate_non_negative },
940 { C(history), &type_integer, validate_positive },
659d87e8 941 { C(home), &type_string, validate_isabspath },
ccf0aafa 942 { C(listen), &type_stringlist, validate_port },
460b9539 943 { C(lock), &type_boolean, validate_any },
bb6ae3fb 944 { C(mail_sender), &type_string, validate_any },
bd8895a8 945 { C(mixer), &type_string, validate_any },
61941295 946 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 947 { C(multicast_ttl), &type_integer, validate_non_negative },
460b9539 948 { C(namepart), &type_namepart, validate_any },
d742bb47 949 { C(new_max), &type_integer, validate_positive },
460b9539 950 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
951 { C(nice_rescan), &type_integer, validate_non_negative },
952 { C(nice_server), &type_integer, validate_any },
953 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 954 { C(noticed_history), &type_integer, validate_positive },
460b9539 955 { C(password), &type_string, validate_any },
956 { C(player), &type_stringlist_accum, validate_player },
957 { C(plugins), &type_string_accum, validate_isdir },
958 { C(prefsync), &type_integer, validate_positive },
459d4402 959 { C(queue_pad), &type_integer, validate_positive },
cebe3127 960 { C(replay_min), &type_integer, validate_non_negative },
460b9539 961 { C(refresh), &type_integer, validate_positive },
6207d2f3 962 { C(reminder_interval), &type_integer, validate_positive },
460b9539 963 { C2(restrict, restrictions), &type_restrict, validate_any },
9d5da576 964 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 965 { C(scratch), &type_string_accum, validate_isreg },
61507e3c 966 { C(short_display), &type_integer, validate_positive },
460b9539 967 { C(signal), &type_signal, validate_any },
bb6ae3fb 968 { C(smtp_server), &type_string, validate_any },
5330d674 969 { C(sox_generation), &type_integer, validate_non_negative },
bd8895a8 970 { C2(speaker_backend, api), &type_backend, validate_any },
9d5da576 971 { C(speaker_command), &type_string, validate_any },
460b9539 972 { C(stopword), &type_string_accum, validate_any },
973 { C(templates), &type_string_accum, validate_isdir },
62dc3748 974 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 975 { C(transform), &type_transform, validate_any },
976 { C(trust), &type_string_accum, validate_any },
977 { C(url), &type_string, validate_url },
978 { C(user), &type_string, validate_isauser },
979 { C(username), &type_string, validate_any },
980};
981
3f3bb97b 982/** @brief Find a configuration item's definition by key */
460b9539 983static const struct conf *find(const char *key) {
984 int n;
985
986 if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
987 return 0;
988 return &conf[n];
989}
990
3f3bb97b 991/** @brief Set a new configuration value */
460b9539 992static int config_set(const struct config_state *cs,
993 int nvec, char **vec) {
994 const struct conf *which;
995
996 D(("config_set %s", vec[0]));
997 if(!(which = find(vec[0]))) {
998 error(0, "%s:%d: unknown configuration key '%s'",
999 cs->path, cs->line, vec[0]);
1000 return -1;
1001 }
1002 return (which->validate(cs, nvec - 1, vec + 1)
1003 || which->type->set(cs, which, nvec - 1, vec + 1));
1004}
1005
e6a35d1c
RK
1006static int config_set_args(const struct config_state *cs,
1007 const char *which, ...) {
1008 va_list ap;
1009 struct vector v[1];
1010 char *s;
1011
1012 vector_init(v);
1013 vector_append(v, (char *)which);
1014 va_start(ap, which);
1015 while((s = va_arg(ap, char *)))
1016 vector_append(v, s);
1017 va_end(ap);
1018 vector_terminate(v);
1019 return config_set(cs, v->nvec, v->vec);
1020}
1021
0e4472a0 1022/** @brief Error callback used by config_include() */
460b9539 1023static void config_error(const char *msg, void *u) {
1024 const struct config_state *cs = u;
1025
1026 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1027}
1028
3f3bb97b 1029/** @brief Include a file by name */
460b9539 1030static int config_include(struct config *c, const char *path) {
1031 FILE *fp;
1032 char *buffer, *inputbuffer, **vec;
1033 int n, ret = 0;
1034 struct config_state cs;
1035
1036 cs.path = path;
1037 cs.line = 0;
1038 cs.config = c;
1039 D(("%s: reading configuration", path));
1040 if(!(fp = fopen(path, "r"))) {
1041 error(errno, "error opening %s", path);
1042 return -1;
1043 }
1044 while(!inputline(path, fp, &inputbuffer, '\n')) {
1045 ++cs.line;
1046 if(!(buffer = mb2utf8(inputbuffer))) {
1047 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1048 ret = -1;
1049 xfree(inputbuffer);
1050 continue;
1051 }
1052 xfree(inputbuffer);
1053 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1054 config_error, &cs))) {
1055 ret = -1;
1056 xfree(buffer);
1057 continue;
1058 }
1059 if(n) {
1060 if(!strcmp(vec[0], "include")) {
1061 if(n != 2) {
1062 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1063 ret = -1;
1064 } else
1065 config_include(c, vec[1]);
1066 } else
1067 ret |= config_set(&cs, n, vec);
1068 }
1069 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1070 xfree(vec);
1071 xfree(buffer);
1072 }
1073 if(ferror(fp)) {
1074 error(errno, "error reading %s", path);
1075 ret = -1;
1076 }
1077 fclose(fp);
1078 return ret;
1079}
1080
86be0c30 1081static const char *const default_stopwords[] = {
1082 "stopword",
1083
1084 "01",
1085 "02",
1086 "03",
1087 "04",
1088 "05",
1089 "06",
1090 "07",
1091 "08",
1092 "09",
1093 "1",
1094 "10",
1095 "11",
1096 "12",
1097 "13",
1098 "14",
1099 "15",
1100 "16",
1101 "17",
1102 "18",
1103 "19",
1104 "2",
1105 "20",
1106 "21",
1107 "22",
1108 "23",
1109 "24",
1110 "25",
1111 "26",
1112 "27",
1113 "28",
1114 "29",
1115 "3",
1116 "30",
1117 "4",
1118 "5",
1119 "6",
1120 "7",
1121 "8",
1122 "9",
1123 "a",
1124 "am",
1125 "an",
1126 "and",
1127 "as",
1128 "for",
1129 "i",
1130 "im",
1131 "in",
1132 "is",
1133 "of",
1134 "on",
1135 "the",
1136 "to",
1137 "too",
1138 "we",
1139};
1140#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1141
e6a35d1c
RK
1142static const char *const default_players[] = {
1143 "*.ogg",
1144 "*.flac",
1145 "*.mp3",
1146 "*.wav",
1147};
1148#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1149
3f3bb97b 1150/** @brief Make a new default configuration */
460b9539 1151static struct config *config_default(void) {
1152 struct config *c = xmalloc(sizeof *c);
1153 const char *logname;
1154 struct passwd *pw;
86be0c30 1155 struct config_state cs;
e6a35d1c 1156 size_t n;
460b9539 1157
86be0c30 1158 cs.path = "<internal>";
1159 cs.line = 0;
1160 cs.config = c;
460b9539 1161 /* Strings had better be xstrdup'd as they will get freed at some point. */
07bc035e 1162 c->gap = 0;
460b9539 1163 c->history = 60;
1164 c->home = xstrdup(pkgstatedir);
1165 if(!(pw = getpwuid(getuid())))
1166 fatal(0, "cannot determine our username");
1167 logname = pw->pw_name;
1168 c->username = xstrdup(logname);
1169 c->refresh = 15;
1170 c->prefsync = 3600;
1171 c->signal = SIGKILL;
1172 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1173 c->lock = 1;
1174 c->device = xstrdup("default");
1175 c->nice_rescan = 10;
9d5da576 1176 c->speaker_command = 0;
1177 c->sample_format.bits = 16;
1178 c->sample_format.rate = 44100;
1179 c->sample_format.channels = 2;
6d2d327c 1180 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1181 c->queue_pad = 10;
cebe3127 1182 c->replay_min = 8 * 3600;
bd8895a8 1183 c->api = -1;
23205f9c 1184 c->multicast_ttl = 1;
61941295 1185 c->multicast_loop = 1;
637fdea3 1186 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1187 c->noticed_history = 31;
61507e3c 1188 c->short_display = 32;
bd8895a8 1189 c->mixer = 0;
1190 c->channel = 0;
8818b7fc 1191 c->dbversion = 2;
b12be54a
RK
1192 c->cookie_login_lifetime = 86400;
1193 c->cookie_key_lifetime = 86400 * 7;
bb6ae3fb 1194 c->smtp_server = xstrdup("127.0.0.1");
d742bb47 1195 c->new_max = 100;
6207d2f3 1196 c->reminder_interval = 600; /* 10m */
e6a35d1c 1197 /* Default stopwords */
86be0c30 1198 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1199 exit(1);
e6a35d1c
RK
1200 /* Default player configuration */
1201 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1202 if(config_set_args(&cs, "player",
1203 default_players[n], "execraw", "disorder-decode", (char *)0))
1204 exit(1);
1205 if(config_set_args(&cs, "tracklength",
1206 default_players[n], "disorder-tracklength", (char *)0))
1207 exit(1);
1208 }
460b9539 1209 return c;
1210}
1211
1212static char *get_file(struct config *c, const char *name) {
1213 char *s;
1214
1215 byte_xasprintf(&s, "%s/%s", c->home, name);
1216 return s;
1217}
1218
3f3bb97b 1219/** @brief Set the default configuration file */
460b9539 1220static void set_configfile(void) {
1221 if(!configfile)
1222 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1223}
1224
3f3bb97b 1225/** @brief Free a configuration object */
460b9539 1226static void config_free(struct config *c) {
1227 int n;
1228
1229 if(c) {
1230 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1231 conf[n].type->free(c, &conf[n]);
1232 for(n = 0; n < c->nparts; ++n)
1233 xfree(c->parts[n]);
1234 xfree(c->parts);
1235 xfree(c);
1236 }
1237}
1238
3f3bb97b 1239/** @brief Set post-parse defaults */
c00fce3a
RK
1240static void config_postdefaults(struct config *c,
1241 int server) {
460b9539 1242 struct config_state cs;
1243 const struct conf *whoami;
1244 int n;
1245
1246 static const char *namepart[][4] = {
88608992 1247 { "title", "/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1248 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1249 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1250 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1251 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1252 };
1253#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1254
1255 static const char *transform[][5] = {
88608992 1256 { "track", "^.*/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1257 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1258 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1259 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1260 { "dir", "[[:punct:]]", "", "sort", "g", }
1261 };
1262#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1263
1264 cs.path = "<internal>";
1265 cs.line = 0;
1266 cs.config = c;
1267 if(!c->namepart.n) {
1268 whoami = find("namepart");
1269 for(n = 0; n < NNAMEPART; ++n)
1270 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1271 }
1272 if(!c->transform.n) {
1273 whoami = find("transform");
1274 for(n = 0; n < NTRANSFORM; ++n)
1275 set_transform(&cs, whoami, 5, (char **)transform[n]);
1276 }
bd8895a8 1277 if(c->api == -1) {
e83d0967 1278 if(c->speaker_command)
bd8895a8 1279 c->api = BACKEND_COMMAND;
e83d0967 1280 else if(c->broadcast.n)
bd8895a8 1281 c->api = BACKEND_NETWORK;
e83d0967 1282 else {
146e86fb 1283#if HAVE_ALSA_ASOUNDLIB_H
bd8895a8 1284 c->api = BACKEND_ALSA;
146e86fb 1285#elif HAVE_SYS_SOUNDCARD_H
bd8895a8 1286 c->api = BACKEND_OSS;
937be4c0 1287#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
bd8895a8 1288 c->api = BACKEND_COREAUDIO;
e83d0967 1289#else
bd8895a8 1290 c->api = BACKEND_COMMAND;
e83d0967
RK
1291#endif
1292 }
1293 }
c00fce3a 1294 if(server) {
bd8895a8 1295 if(c->api == BACKEND_COMMAND && !c->speaker_command)
1296 fatal(0, "'api command' but speaker_command is not set");
1297 if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1298 fatal(0, "'api network' but broadcast is not set");
c00fce3a 1299 }
e99d42b1 1300 /* Override sample format */
bd8895a8 1301 switch(c->api) {
e99d42b1 1302 case BACKEND_NETWORK:
6d2d327c
RK
1303 c->sample_format.rate = 44100;
1304 c->sample_format.channels = 2;
1305 c->sample_format.bits = 16;
1306 c->sample_format.endian = ENDIAN_BIG;
e99d42b1 1307 break;
1308 case BACKEND_COREAUDIO:
937be4c0
RK
1309 c->sample_format.rate = 44100;
1310 c->sample_format.channels = 2;
1311 c->sample_format.bits = 16;
1312 c->sample_format.endian = ENDIAN_NATIVE;
e99d42b1 1313 break;
04e1fa7c
RK
1314 }
1315 if(!c->default_rights) {
1316 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1317 |RIGHT_MOVE__MASK
1318 |RIGHT_SCRATCH__MASK
1319 |RIGHT_REMOVE__MASK);
1320 /* The idea is to approximate the meaning of the old 'restrict' directive
1321 * in the default rights if they are not overridden. */
1322 if(c->restrictions & RESTRICT_SCRATCH)
1323 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1324 else
1325 r |= RIGHT_SCRATCH_ANY;
1326 if(!(c->restrictions & RESTRICT_MOVE))
1327 r |= RIGHT_MOVE_ANY;
1328 if(c->restrictions & RESTRICT_REMOVE)
1329 r |= RIGHT_REMOVE_MINE;
1330 else
1331 r |= RIGHT_REMOVE_ANY;
0f55e905 1332 c->default_rights = rights_string(r);
04e1fa7c 1333 }
460b9539 1334}
1335
c00fce3a
RK
1336/** @brief (Re-)read the config file
1337 * @param server If set, do extra checking
1338 */
1339int config_read(int server) {
460b9539 1340 struct config *c;
1341 char *privconf;
1342 struct passwd *pw;
1343
1344 set_configfile();
1345 c = config_default();
9ade2319 1346 /* standalone Disobedience installs might not have a global config file */
1347 if(access(configfile, F_OK) == 0)
1348 if(config_include(c, configfile))
1349 return -1;
460b9539 1350 /* if we can read the private config file, do */
1351 if((privconf = config_private())
1352 && access(privconf, R_OK) == 0
1353 && config_include(c, privconf))
1354 return -1;
1355 xfree(privconf);
1356 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1357 if(config_per_user) {
1358 if(!(pw = getpwuid(getuid())))
1359 fatal(0, "cannot determine our username");
1360 if((privconf = config_usersysconf(pw))
1361 && access(privconf, F_OK) == 0
1362 && config_include(c, privconf))
460b9539 1363 return -1;
63ad732f
RK
1364 xfree(privconf);
1365 /* if we have a password file, read it */
5b14453f 1366 if((privconf = config_userconf(0, pw))
63ad732f
RK
1367 && access(privconf, F_OK) == 0
1368 && config_include(c, privconf))
1369 return -1;
1370 xfree(privconf);
1371 }
460b9539 1372 /* install default namepart and transform settings */
c00fce3a 1373 config_postdefaults(c, server);
460b9539 1374 /* everything is good so we shall use the new config */
1375 config_free(config);
04e1fa7c
RK
1376 /* warn about obsolete directives */
1377 if(c->restrictions)
1378 error(0, "'restrict' will be removed in a future version");
1379 if(c->allow.n)
1380 error(0, "'allow' will be removed in a future version");
1381 if(c->trust.n)
1382 error(0, "'trust' will be removed in a future version");
460b9539 1383 config = c;
1384 return 0;
1385}
1386
3f3bb97b 1387/** @brief Return the path to the private configuration file */
460b9539 1388char *config_private(void) {
1389 char *s;
1390
1391 set_configfile();
1392 byte_xasprintf(&s, "%s.private", configfile);
1393 return s;
1394}
1395
3f3bb97b 1396/** @brief Return the path to user's personal configuration file */
460b9539 1397char *config_userconf(const char *home, const struct passwd *pw) {
1398 char *s;
1399
73f1b9f3
RK
1400 if(!home && !pw && !(pw = getpwuid(getuid())))
1401 fatal(0, "cannot determine our username");
460b9539 1402 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1403 return s;
1404}
1405
3f3bb97b
RK
1406/** @brief Return the path to user-specific system configuration */
1407char *config_usersysconf(const struct passwd *pw) {
460b9539 1408 char *s;
1409
1410 set_configfile();
1411 if(!strchr(pw->pw_name, '/')) {
1412 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1413 return s;
1414 } else
1415 return 0;
1416}
1417
1418char *config_get_file(const char *name) {
1419 return get_file(config, name);
1420}
1421
1422/*
1423Local Variables:
1424c-basic-offset:2
1425comment-column:40
1426fill-column:79
1427End:
1428*/