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