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