chiark / gitweb /
ALSA mixer support.
[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;
e99d42b1 503#endif
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;
937be4c0
RK
511#endif
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
460b9539 682static int validate_player(const struct config_state *cs,
683 int nvec,
684 char attribute((unused)) **vec) {
685 if(nvec < 2) {
686 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
687 cs->path, cs->line);
688 return -1;
689 }
690 return 0;
691}
692
62dc3748
RK
693static int validate_tracklength(const struct config_state *cs,
694 int nvec,
695 char attribute((unused)) **vec) {
696 if(nvec < 2) {
697 error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
698 cs->path, cs->line);
699 return -1;
700 }
701 return 0;
702}
703
460b9539 704static int validate_allow(const struct config_state *cs,
705 int nvec,
706 char attribute((unused)) **vec) {
707 if(nvec != 2) {
708 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
709 return -1;
710 }
711 return 0;
712}
713
714static int validate_non_negative(const struct config_state *cs,
715 int nvec, char **vec) {
716 long n;
717
718 if(nvec < 1) {
719 error(0, "%s:%d: missing argument", cs->path, cs->line);
720 return -1;
721 }
722 if(nvec > 1) {
723 error(0, "%s:%d: too many arguments", cs->path, cs->line);
724 return -1;
725 }
726 if(xstrtol(&n, vec[0], 0, 0)) {
727 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
728 return -1;
729 }
730 if(n < 0) {
731 error(0, "%s:%d: must not be negative", cs->path, cs->line);
732 return -1;
733 }
734 return 0;
735}
736
737static int validate_positive(const struct config_state *cs,
738 int nvec, char **vec) {
739 long n;
740
741 if(nvec < 1) {
742 error(0, "%s:%d: missing argument", cs->path, cs->line);
743 return -1;
744 }
745 if(nvec > 1) {
746 error(0, "%s:%d: too many arguments", cs->path, cs->line);
747 return -1;
748 }
749 if(xstrtol(&n, vec[0], 0, 0)) {
750 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
751 return -1;
752 }
753 if(n <= 0) {
754 error(0, "%s:%d: must be positive", cs->path, cs->line);
755 return -1;
756 }
757 return 0;
758}
759
760static int validate_isauser(const struct config_state *cs,
761 int attribute((unused)) nvec,
762 char **vec) {
763 struct passwd *pw;
764
765 if(!(pw = getpwnam(vec[0]))) {
766 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
767 return -1;
768 }
769 return 0;
770}
771
9d5da576 772static int validate_sample_format(const struct config_state *cs,
773 int attribute((unused)) nvec,
774 char **vec) {
775 return parse_sample_format(cs, 0, nvec, vec);
776}
777
460b9539 778static int validate_any(const struct config_state attribute((unused)) *cs,
779 int attribute((unused)) nvec,
780 char attribute((unused)) **vec) {
781 return 0;
782}
783
784static int validate_url(const struct config_state attribute((unused)) *cs,
785 int attribute((unused)) nvec,
786 char **vec) {
787 const char *s;
788 int n;
789 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
790 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
791 s = vec[0];
792 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
793 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
794 "0123456789"));
795 if(s[n] != ':') {
796 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
797 return -1;
798 }
799 if(!strncmp(s, "http:", 5)
800 || !strncmp(s, "https:", 6)) {
801 s += n + 1;
802 /* we only do a rather cursory check */
803 if(strncmp(s, "//", 2)) {
804 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
805 return -1;
806 }
807 }
808 return 0;
809}
810
811static int validate_alias(const struct config_state *cs,
812 int nvec,
813 char **vec) {
814 const char *s;
815 int in_brackets = 0, c;
816
817 if(nvec < 1) {
818 error(0, "%s:%d: missing argument", cs->path, cs->line);
819 return -1;
820 }
821 if(nvec > 1) {
822 error(0, "%s:%d: too many arguments", cs->path, cs->line);
823 return -1;
824 }
825 s = vec[0];
826 while((c = (unsigned char)*s++)) {
827 if(in_brackets) {
828 if(c == '}')
829 in_brackets = 0;
830 else if(!isalnum(c)) {
831 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
832 cs->path, cs->line, vec[0]);
833 return -1;
834 }
835 } else {
836 if(c == '{') {
837 in_brackets = 1;
838 if(*s == '/')
839 ++s;
840 } else if(c == '\\') {
841 if(!(c = (unsigned char)*s++)) {
842 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
843 cs->path, cs->line, vec[0]);
844 return -1;
845 } else if(c != '\\' && c != '{') {
846 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
847 cs->path, cs->line, vec[0]);
848 return -1;
849 }
850 }
851 }
852 ++s;
853 }
854 if(in_brackets) {
855 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
856 cs->path, cs->line, vec[0]);
857 return -1;
858 }
859 return 0;
860}
861
e83d0967
RK
862static int validate_addrport(const struct config_state attribute((unused)) *cs,
863 int nvec,
864 char attribute((unused)) **vec) {
865 switch(nvec) {
866 case 0:
867 error(0, "%s:%d: missing address",
868 cs->path, cs->line);
869 return -1;
870 case 1:
871 error(0, "%s:%d: missing port name/number",
872 cs->path, cs->line);
873 return -1;
874 case 2:
875 return 0;
876 default:
877 error(0, "%s:%d: expected ADDRESS PORT",
878 cs->path, cs->line);
879 return -1;
880 }
881}
882
ccf0aafa 883static int validate_port(const struct config_state attribute((unused)) *cs,
e83d0967
RK
884 int nvec,
885 char attribute((unused)) **vec) {
886 switch(nvec) {
887 case 0:
888 error(0, "%s:%d: missing address",
889 cs->path, cs->line);
890 return -1;
891 case 1:
892 case 2:
893 return 0;
894 default:
ccf0aafa 895 error(0, "%s:%d: expected [ADDRESS] PORT",
e83d0967
RK
896 cs->path, cs->line);
897 return -1;
898 }
899}
900
637fdea3
RK
901static int validate_algo(const struct config_state attribute((unused)) *cs,
902 int nvec,
903 char **vec) {
904 if(nvec != 1) {
905 error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
906 return -1;
907 }
908 if(!valid_authhash(vec[0])) {
909 error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
910 return -1;
911 }
912 return 0;
913}
914
3f3bb97b 915/** @brief Item name and and offset */
460b9539 916#define C(x) #x, offsetof(struct config, x)
3f3bb97b 917/** @brief Item name and and offset */
460b9539 918#define C2(x,y) #x, offsetof(struct config, y)
919
3f3bb97b 920/** @brief All configuration items */
460b9539 921static const struct conf conf[] = {
922 { C(alias), &type_string, validate_alias },
923 { C(allow), &type_stringlist_accum, validate_allow },
bd8895a8 924 { C(api), &type_backend, validate_any },
637fdea3 925 { C(authorization_algorithm), &type_string, validate_algo },
e83d0967 926 { C(broadcast), &type_stringlist, validate_addrport },
ccf0aafa 927 { C(broadcast_from), &type_stringlist, validate_addrport },
bd8895a8 928 { C(channel), &type_string, validate_any },
460b9539 929 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
930 { C(checkpoint_min), &type_integer, validate_non_negative },
931 { C(collection), &type_collections, validate_any },
e83d0967 932 { C(connect), &type_stringlist, validate_addrport },
b12be54a
RK
933 { C(cookie_login_lifetime), &type_integer, validate_positive },
934 { C(cookie_key_lifetime), &type_integer, validate_positive },
8818b7fc 935 { C(dbversion), &type_integer, validate_positive },
04e1fa7c 936 { C(default_rights), &type_rights, validate_any },
460b9539 937 { C(device), &type_string, validate_any },
938 { C(gap), &type_integer, validate_non_negative },
939 { C(history), &type_integer, validate_positive },
659d87e8 940 { C(home), &type_string, validate_isabspath },
ccf0aafa 941 { C(listen), &type_stringlist, validate_port },
460b9539 942 { C(lock), &type_boolean, validate_any },
bb6ae3fb 943 { C(mail_sender), &type_string, validate_any },
bd8895a8 944 { C(mixer), &type_string, validate_any },
61941295 945 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 946 { C(multicast_ttl), &type_integer, validate_non_negative },
460b9539 947 { C(namepart), &type_namepart, validate_any },
948 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
949 { C(nice_rescan), &type_integer, validate_non_negative },
950 { C(nice_server), &type_integer, validate_any },
951 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 952 { C(noticed_history), &type_integer, validate_positive },
460b9539 953 { C(password), &type_string, validate_any },
954 { C(player), &type_stringlist_accum, validate_player },
955 { C(plugins), &type_string_accum, validate_isdir },
956 { C(prefsync), &type_integer, validate_positive },
459d4402 957 { C(queue_pad), &type_integer, validate_positive },
460b9539 958 { C(refresh), &type_integer, validate_positive },
959 { C2(restrict, restrictions), &type_restrict, validate_any },
9d5da576 960 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 961 { C(scratch), &type_string_accum, validate_isreg },
61507e3c 962 { C(short_display), &type_integer, validate_positive },
460b9539 963 { C(signal), &type_signal, validate_any },
bb6ae3fb 964 { C(smtp_server), &type_string, validate_any },
5330d674 965 { C(sox_generation), &type_integer, validate_non_negative },
bd8895a8 966 { C2(speaker_backend, api), &type_backend, validate_any },
9d5da576 967 { C(speaker_command), &type_string, validate_any },
460b9539 968 { C(stopword), &type_string_accum, validate_any },
969 { C(templates), &type_string_accum, validate_isdir },
62dc3748 970 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 971 { C(transform), &type_transform, validate_any },
972 { C(trust), &type_string_accum, validate_any },
973 { C(url), &type_string, validate_url },
974 { C(user), &type_string, validate_isauser },
975 { C(username), &type_string, validate_any },
976};
977
3f3bb97b 978/** @brief Find a configuration item's definition by key */
460b9539 979static const struct conf *find(const char *key) {
980 int n;
981
982 if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
983 return 0;
984 return &conf[n];
985}
986
3f3bb97b 987/** @brief Set a new configuration value */
460b9539 988static int config_set(const struct config_state *cs,
989 int nvec, char **vec) {
990 const struct conf *which;
991
992 D(("config_set %s", vec[0]));
993 if(!(which = find(vec[0]))) {
994 error(0, "%s:%d: unknown configuration key '%s'",
995 cs->path, cs->line, vec[0]);
996 return -1;
997 }
998 return (which->validate(cs, nvec - 1, vec + 1)
999 || which->type->set(cs, which, nvec - 1, vec + 1));
1000}
1001
0e4472a0 1002/** @brief Error callback used by config_include() */
460b9539 1003static void config_error(const char *msg, void *u) {
1004 const struct config_state *cs = u;
1005
1006 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1007}
1008
3f3bb97b 1009/** @brief Include a file by name */
460b9539 1010static int config_include(struct config *c, const char *path) {
1011 FILE *fp;
1012 char *buffer, *inputbuffer, **vec;
1013 int n, ret = 0;
1014 struct config_state cs;
1015
1016 cs.path = path;
1017 cs.line = 0;
1018 cs.config = c;
1019 D(("%s: reading configuration", path));
1020 if(!(fp = fopen(path, "r"))) {
1021 error(errno, "error opening %s", path);
1022 return -1;
1023 }
1024 while(!inputline(path, fp, &inputbuffer, '\n')) {
1025 ++cs.line;
1026 if(!(buffer = mb2utf8(inputbuffer))) {
1027 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1028 ret = -1;
1029 xfree(inputbuffer);
1030 continue;
1031 }
1032 xfree(inputbuffer);
1033 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1034 config_error, &cs))) {
1035 ret = -1;
1036 xfree(buffer);
1037 continue;
1038 }
1039 if(n) {
1040 if(!strcmp(vec[0], "include")) {
1041 if(n != 2) {
1042 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1043 ret = -1;
1044 } else
1045 config_include(c, vec[1]);
1046 } else
1047 ret |= config_set(&cs, n, vec);
1048 }
1049 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1050 xfree(vec);
1051 xfree(buffer);
1052 }
1053 if(ferror(fp)) {
1054 error(errno, "error reading %s", path);
1055 ret = -1;
1056 }
1057 fclose(fp);
1058 return ret;
1059}
1060
86be0c30 1061static const char *const default_stopwords[] = {
1062 "stopword",
1063
1064 "01",
1065 "02",
1066 "03",
1067 "04",
1068 "05",
1069 "06",
1070 "07",
1071 "08",
1072 "09",
1073 "1",
1074 "10",
1075 "11",
1076 "12",
1077 "13",
1078 "14",
1079 "15",
1080 "16",
1081 "17",
1082 "18",
1083 "19",
1084 "2",
1085 "20",
1086 "21",
1087 "22",
1088 "23",
1089 "24",
1090 "25",
1091 "26",
1092 "27",
1093 "28",
1094 "29",
1095 "3",
1096 "30",
1097 "4",
1098 "5",
1099 "6",
1100 "7",
1101 "8",
1102 "9",
1103 "a",
1104 "am",
1105 "an",
1106 "and",
1107 "as",
1108 "for",
1109 "i",
1110 "im",
1111 "in",
1112 "is",
1113 "of",
1114 "on",
1115 "the",
1116 "to",
1117 "too",
1118 "we",
1119};
1120#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1121
3f3bb97b 1122/** @brief Make a new default configuration */
460b9539 1123static struct config *config_default(void) {
1124 struct config *c = xmalloc(sizeof *c);
1125 const char *logname;
1126 struct passwd *pw;
86be0c30 1127 struct config_state cs;
460b9539 1128
86be0c30 1129 cs.path = "<internal>";
1130 cs.line = 0;
1131 cs.config = c;
460b9539 1132 /* Strings had better be xstrdup'd as they will get freed at some point. */
07bc035e 1133 c->gap = 0;
460b9539 1134 c->history = 60;
1135 c->home = xstrdup(pkgstatedir);
1136 if(!(pw = getpwuid(getuid())))
1137 fatal(0, "cannot determine our username");
1138 logname = pw->pw_name;
1139 c->username = xstrdup(logname);
1140 c->refresh = 15;
1141 c->prefsync = 3600;
1142 c->signal = SIGKILL;
1143 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1144 c->lock = 1;
1145 c->device = xstrdup("default");
1146 c->nice_rescan = 10;
9d5da576 1147 c->speaker_command = 0;
1148 c->sample_format.bits = 16;
1149 c->sample_format.rate = 44100;
1150 c->sample_format.channels = 2;
6d2d327c 1151 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1152 c->queue_pad = 10;
bd8895a8 1153 c->api = -1;
23205f9c 1154 c->multicast_ttl = 1;
61941295 1155 c->multicast_loop = 1;
637fdea3 1156 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1157 c->noticed_history = 31;
61507e3c 1158 c->short_display = 32;
bd8895a8 1159 c->mixer = 0;
1160 c->channel = 0;
8818b7fc 1161 c->dbversion = 2;
b12be54a
RK
1162 c->cookie_login_lifetime = 86400;
1163 c->cookie_key_lifetime = 86400 * 7;
bb6ae3fb 1164 c->smtp_server = xstrdup("127.0.0.1");
86be0c30 1165 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1166 exit(1);
460b9539 1167 return c;
1168}
1169
1170static char *get_file(struct config *c, const char *name) {
1171 char *s;
1172
1173 byte_xasprintf(&s, "%s/%s", c->home, name);
1174 return s;
1175}
1176
3f3bb97b 1177/** @brief Set the default configuration file */
460b9539 1178static void set_configfile(void) {
1179 if(!configfile)
1180 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1181}
1182
3f3bb97b 1183/** @brief Free a configuration object */
460b9539 1184static void config_free(struct config *c) {
1185 int n;
1186
1187 if(c) {
1188 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1189 conf[n].type->free(c, &conf[n]);
1190 for(n = 0; n < c->nparts; ++n)
1191 xfree(c->parts[n]);
1192 xfree(c->parts);
1193 xfree(c);
1194 }
1195}
1196
3f3bb97b 1197/** @brief Set post-parse defaults */
c00fce3a
RK
1198static void config_postdefaults(struct config *c,
1199 int server) {
460b9539 1200 struct config_state cs;
1201 const struct conf *whoami;
1202 int n;
1203
1204 static const char *namepart[][4] = {
88608992 1205 { "title", "/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1206 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1207 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1208 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1209 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1210 };
1211#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1212
1213 static const char *transform[][5] = {
88608992 1214 { "track", "^.*/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1215 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1216 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1217 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1218 { "dir", "[[:punct:]]", "", "sort", "g", }
1219 };
1220#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1221
1222 cs.path = "<internal>";
1223 cs.line = 0;
1224 cs.config = c;
1225 if(!c->namepart.n) {
1226 whoami = find("namepart");
1227 for(n = 0; n < NNAMEPART; ++n)
1228 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1229 }
1230 if(!c->transform.n) {
1231 whoami = find("transform");
1232 for(n = 0; n < NTRANSFORM; ++n)
1233 set_transform(&cs, whoami, 5, (char **)transform[n]);
1234 }
bd8895a8 1235 if(c->api == -1) {
e83d0967 1236 if(c->speaker_command)
bd8895a8 1237 c->api = BACKEND_COMMAND;
e83d0967 1238 else if(c->broadcast.n)
bd8895a8 1239 c->api = BACKEND_NETWORK;
e83d0967 1240 else {
146e86fb 1241#if HAVE_ALSA_ASOUNDLIB_H
bd8895a8 1242 c->api = BACKEND_ALSA;
146e86fb 1243#elif HAVE_SYS_SOUNDCARD_H
bd8895a8 1244 c->api = BACKEND_OSS;
937be4c0 1245#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
bd8895a8 1246 c->api = BACKEND_COREAUDIO;
e83d0967 1247#else
bd8895a8 1248 c->api = BACKEND_COMMAND;
e83d0967
RK
1249#endif
1250 }
1251 }
c00fce3a 1252 if(server) {
bd8895a8 1253 if(c->api == BACKEND_COMMAND && !c->speaker_command)
1254 fatal(0, "'api command' but speaker_command is not set");
1255 if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1256 fatal(0, "'api network' but broadcast is not set");
c00fce3a 1257 }
e99d42b1 1258 /* Override sample format */
bd8895a8 1259 switch(c->api) {
e99d42b1 1260 case BACKEND_NETWORK:
6d2d327c
RK
1261 c->sample_format.rate = 44100;
1262 c->sample_format.channels = 2;
1263 c->sample_format.bits = 16;
1264 c->sample_format.endian = ENDIAN_BIG;
e99d42b1 1265 break;
1266 case BACKEND_COREAUDIO:
937be4c0
RK
1267 c->sample_format.rate = 44100;
1268 c->sample_format.channels = 2;
1269 c->sample_format.bits = 16;
1270 c->sample_format.endian = ENDIAN_NATIVE;
e99d42b1 1271 break;
04e1fa7c
RK
1272 }
1273 if(!c->default_rights) {
1274 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1275 |RIGHT_MOVE__MASK
1276 |RIGHT_SCRATCH__MASK
1277 |RIGHT_REMOVE__MASK);
1278 /* The idea is to approximate the meaning of the old 'restrict' directive
1279 * in the default rights if they are not overridden. */
1280 if(c->restrictions & RESTRICT_SCRATCH)
1281 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1282 else
1283 r |= RIGHT_SCRATCH_ANY;
1284 if(!(c->restrictions & RESTRICT_MOVE))
1285 r |= RIGHT_MOVE_ANY;
1286 if(c->restrictions & RESTRICT_REMOVE)
1287 r |= RIGHT_REMOVE_MINE;
1288 else
1289 r |= RIGHT_REMOVE_ANY;
0f55e905 1290 c->default_rights = rights_string(r);
04e1fa7c 1291 }
460b9539 1292}
1293
c00fce3a
RK
1294/** @brief (Re-)read the config file
1295 * @param server If set, do extra checking
1296 */
1297int config_read(int server) {
460b9539 1298 struct config *c;
1299 char *privconf;
1300 struct passwd *pw;
1301
1302 set_configfile();
1303 c = config_default();
9ade2319 1304 /* standalone Disobedience installs might not have a global config file */
1305 if(access(configfile, F_OK) == 0)
1306 if(config_include(c, configfile))
1307 return -1;
460b9539 1308 /* if we can read the private config file, do */
1309 if((privconf = config_private())
1310 && access(privconf, R_OK) == 0
1311 && config_include(c, privconf))
1312 return -1;
1313 xfree(privconf);
1314 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1315 if(config_per_user) {
1316 if(!(pw = getpwuid(getuid())))
1317 fatal(0, "cannot determine our username");
1318 if((privconf = config_usersysconf(pw))
1319 && access(privconf, F_OK) == 0
1320 && config_include(c, privconf))
460b9539 1321 return -1;
63ad732f
RK
1322 xfree(privconf);
1323 /* if we have a password file, read it */
1324 if((privconf = config_userconf(getenv("HOME"), pw))
1325 && access(privconf, F_OK) == 0
1326 && config_include(c, privconf))
1327 return -1;
1328 xfree(privconf);
1329 }
460b9539 1330 /* install default namepart and transform settings */
c00fce3a 1331 config_postdefaults(c, server);
460b9539 1332 /* everything is good so we shall use the new config */
1333 config_free(config);
04e1fa7c
RK
1334 /* warn about obsolete directives */
1335 if(c->restrictions)
1336 error(0, "'restrict' will be removed in a future version");
1337 if(c->allow.n)
1338 error(0, "'allow' will be removed in a future version");
1339 if(c->trust.n)
1340 error(0, "'trust' will be removed in a future version");
460b9539 1341 config = c;
1342 return 0;
1343}
1344
3f3bb97b 1345/** @brief Return the path to the private configuration file */
460b9539 1346char *config_private(void) {
1347 char *s;
1348
1349 set_configfile();
1350 byte_xasprintf(&s, "%s.private", configfile);
1351 return s;
1352}
1353
3f3bb97b 1354/** @brief Return the path to user's personal configuration file */
460b9539 1355char *config_userconf(const char *home, const struct passwd *pw) {
1356 char *s;
1357
73f1b9f3
RK
1358 if(!home && !pw && !(pw = getpwuid(getuid())))
1359 fatal(0, "cannot determine our username");
460b9539 1360 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1361 return s;
1362}
1363
3f3bb97b
RK
1364/** @brief Return the path to user-specific system configuration */
1365char *config_usersysconf(const struct passwd *pw) {
460b9539 1366 char *s;
1367
1368 set_configfile();
1369 if(!strchr(pw->pw_name, '/')) {
1370 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1371 return s;
1372 } else
1373 return 0;
1374}
1375
1376char *config_get_file(const char *name) {
1377 return get_file(config, name);
1378}
1379
1380/*
1381Local Variables:
1382c-basic-offset:2
1383comment-column:40
1384fill-column:79
1385End:
1386*/