chiark / gitweb /
Merge playlist branch against trunk to date.
[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 },
ddbf05c8 953 { C(playlist_lock_timeout), &type_integer, validate_positive },
2563dc1f 954 { C(playlist_max) , &type_integer, validate_positive },
460b9539 955 { C(plugins), &type_string_accum, validate_isdir },
956 { C(prefsync), &type_integer, validate_positive },
459d4402 957 { C(queue_pad), &type_integer, validate_positive },
cebe3127 958 { C(replay_min), &type_integer, validate_non_negative },
460b9539 959 { C(refresh), &type_integer, validate_positive },
6207d2f3 960 { C(reminder_interval), &type_integer, validate_positive },
810b8083 961 { C(remote_userman), &type_boolean, validate_any },
460b9539 962 { C2(restrict, restrictions), &type_restrict, validate_any },
9d5da576 963 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 964 { C(scratch), &type_string_accum, validate_isreg },
2eee4b0c 965 { C(sendmail), &type_string, validate_isabspath },
61507e3c 966 { C(short_display), &type_integer, validate_positive },
460b9539 967 { C(signal), &type_signal, validate_any },
bb6ae3fb 968 { C(smtp_server), &type_string, validate_any },
5330d674 969 { C(sox_generation), &type_integer, validate_non_negative },
bd8895a8 970 { C2(speaker_backend, api), &type_backend, validate_any },
9d5da576 971 { C(speaker_command), &type_string, validate_any },
460b9539 972 { C(stopword), &type_string_accum, validate_any },
973 { C(templates), &type_string_accum, validate_isdir },
62dc3748 974 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 975 { C(transform), &type_transform, validate_any },
976 { C(trust), &type_string_accum, validate_any },
977 { C(url), &type_string, validate_url },
978 { C(user), &type_string, validate_isauser },
979 { C(username), &type_string, validate_any },
980};
981
3f3bb97b 982/** @brief Find a configuration item's definition by key */
460b9539 983static const struct conf *find(const char *key) {
984 int n;
985
ba937f01 986 if((n = TABLE_FIND(conf, name, key)) < 0)
460b9539 987 return 0;
988 return &conf[n];
989}
990
3f3bb97b 991/** @brief Set a new configuration value */
460b9539 992static int config_set(const struct config_state *cs,
993 int nvec, char **vec) {
994 const struct conf *which;
995
996 D(("config_set %s", vec[0]));
997 if(!(which = find(vec[0]))) {
998 error(0, "%s:%d: unknown configuration key '%s'",
999 cs->path, cs->line, vec[0]);
1000 return -1;
1001 }
1002 return (which->validate(cs, nvec - 1, vec + 1)
1003 || which->type->set(cs, which, nvec - 1, vec + 1));
1004}
1005
e6a35d1c
RK
1006static int config_set_args(const struct config_state *cs,
1007 const char *which, ...) {
1008 va_list ap;
1009 struct vector v[1];
1010 char *s;
1011
1012 vector_init(v);
1013 vector_append(v, (char *)which);
1014 va_start(ap, which);
1015 while((s = va_arg(ap, char *)))
1016 vector_append(v, s);
1017 va_end(ap);
1018 vector_terminate(v);
1019 return config_set(cs, v->nvec, v->vec);
1020}
1021
0e4472a0 1022/** @brief Error callback used by config_include() */
460b9539 1023static void config_error(const char *msg, void *u) {
1024 const struct config_state *cs = u;
1025
1026 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1027}
1028
3f3bb97b 1029/** @brief Include a file by name */
460b9539 1030static int config_include(struct config *c, const char *path) {
1031 FILE *fp;
1032 char *buffer, *inputbuffer, **vec;
1033 int n, ret = 0;
1034 struct config_state cs;
1035
1036 cs.path = path;
1037 cs.line = 0;
1038 cs.config = c;
1039 D(("%s: reading configuration", path));
1040 if(!(fp = fopen(path, "r"))) {
1041 error(errno, "error opening %s", path);
1042 return -1;
1043 }
1044 while(!inputline(path, fp, &inputbuffer, '\n')) {
1045 ++cs.line;
1046 if(!(buffer = mb2utf8(inputbuffer))) {
1047 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1048 ret = -1;
1049 xfree(inputbuffer);
1050 continue;
1051 }
1052 xfree(inputbuffer);
1053 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1054 config_error, &cs))) {
1055 ret = -1;
1056 xfree(buffer);
1057 continue;
1058 }
1059 if(n) {
1060 if(!strcmp(vec[0], "include")) {
1061 if(n != 2) {
1062 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1063 ret = -1;
1064 } else
1065 config_include(c, vec[1]);
1066 } else
1067 ret |= config_set(&cs, n, vec);
1068 }
1069 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1070 xfree(vec);
1071 xfree(buffer);
1072 }
1073 if(ferror(fp)) {
1074 error(errno, "error reading %s", path);
1075 ret = -1;
1076 }
1077 fclose(fp);
1078 return ret;
1079}
1080
86be0c30 1081static const char *const default_stopwords[] = {
1082 "stopword",
1083
1084 "01",
1085 "02",
1086 "03",
1087 "04",
1088 "05",
1089 "06",
1090 "07",
1091 "08",
1092 "09",
1093 "1",
1094 "10",
1095 "11",
1096 "12",
1097 "13",
1098 "14",
1099 "15",
1100 "16",
1101 "17",
1102 "18",
1103 "19",
1104 "2",
1105 "20",
1106 "21",
1107 "22",
1108 "23",
1109 "24",
1110 "25",
1111 "26",
1112 "27",
1113 "28",
1114 "29",
1115 "3",
1116 "30",
1117 "4",
1118 "5",
1119 "6",
1120 "7",
1121 "8",
1122 "9",
1123 "a",
1124 "am",
1125 "an",
1126 "and",
1127 "as",
1128 "for",
1129 "i",
1130 "im",
1131 "in",
1132 "is",
1133 "of",
1134 "on",
1135 "the",
1136 "to",
1137 "too",
1138 "we",
1139};
1140#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1141
e6a35d1c
RK
1142static const char *const default_players[] = {
1143 "*.ogg",
1144 "*.flac",
1145 "*.mp3",
1146 "*.wav",
1147};
1148#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1149
3f3bb97b 1150/** @brief Make a new default configuration */
460b9539 1151static struct config *config_default(void) {
1152 struct config *c = xmalloc(sizeof *c);
1153 const char *logname;
1154 struct passwd *pw;
86be0c30 1155 struct config_state cs;
e6a35d1c 1156 size_t n;
460b9539 1157
86be0c30 1158 cs.path = "<internal>";
1159 cs.line = 0;
1160 cs.config = c;
460b9539 1161 /* Strings had better be xstrdup'd as they will get freed at some point. */
07bc035e 1162 c->gap = 0;
460b9539 1163 c->history = 60;
1164 c->home = xstrdup(pkgstatedir);
1165 if(!(pw = getpwuid(getuid())))
1166 fatal(0, "cannot determine our username");
1167 logname = pw->pw_name;
1168 c->username = xstrdup(logname);
1169 c->refresh = 15;
1170 c->prefsync = 3600;
1171 c->signal = SIGKILL;
1172 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1173 c->lock = 1;
1174 c->device = xstrdup("default");
1175 c->nice_rescan = 10;
9d5da576 1176 c->speaker_command = 0;
1177 c->sample_format.bits = 16;
1178 c->sample_format.rate = 44100;
1179 c->sample_format.channels = 2;
6d2d327c 1180 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1181 c->queue_pad = 10;
cebe3127 1182 c->replay_min = 8 * 3600;
bd8895a8 1183 c->api = -1;
23205f9c 1184 c->multicast_ttl = 1;
61941295 1185 c->multicast_loop = 1;
637fdea3 1186 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1187 c->noticed_history = 31;
61507e3c 1188 c->short_display = 32;
bd8895a8 1189 c->mixer = 0;
1190 c->channel = 0;
8818b7fc 1191 c->dbversion = 2;
b12be54a
RK
1192 c->cookie_login_lifetime = 86400;
1193 c->cookie_key_lifetime = 86400 * 7;
2eee4b0c
RK
1194 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1195 c->sendmail = xstrdup(sendmail_binary);
bb6ae3fb 1196 c->smtp_server = xstrdup("127.0.0.1");
d742bb47 1197 c->new_max = 100;
6207d2f3 1198 c->reminder_interval = 600; /* 10m */
05dcfac6 1199 c->new_bias_age = 7 * 86400; /* 1 week */
6151ae7e 1200 c->new_bias = 4500000; /* 50 times the base weight */
419893d7 1201 c->sox_generation = DEFAULT_SOX_GENERATION;
2563dc1f 1202 c->playlist_max = INT_MAX; /* effectively no limit */
ddbf05c8 1203 c->playlist_lock_timeout = 10; /* 10s */
e6a35d1c 1204 /* Default stopwords */
86be0c30 1205 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1206 exit(1);
e6a35d1c
RK
1207 /* Default player configuration */
1208 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1209 if(config_set_args(&cs, "player",
1210 default_players[n], "execraw", "disorder-decode", (char *)0))
1211 exit(1);
1212 if(config_set_args(&cs, "tracklength",
1213 default_players[n], "disorder-tracklength", (char *)0))
1214 exit(1);
1215 }
460b9539 1216 return c;
1217}
1218
319d7107 1219char *config_get_file2(struct config *c, const char *name) {
460b9539 1220 char *s;
1221
1222 byte_xasprintf(&s, "%s/%s", c->home, name);
1223 return s;
1224}
1225
3f3bb97b 1226/** @brief Set the default configuration file */
460b9539 1227static void set_configfile(void) {
1228 if(!configfile)
1229 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1230}
1231
3f3bb97b 1232/** @brief Free a configuration object */
460b9539 1233static void config_free(struct config *c) {
1234 int n;
1235
1236 if(c) {
1237 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1238 conf[n].type->free(c, &conf[n]);
1239 for(n = 0; n < c->nparts; ++n)
1240 xfree(c->parts[n]);
1241 xfree(c->parts);
1242 xfree(c);
1243 }
1244}
1245
3f3bb97b 1246/** @brief Set post-parse defaults */
c00fce3a
RK
1247static void config_postdefaults(struct config *c,
1248 int server) {
460b9539 1249 struct config_state cs;
1250 const struct conf *whoami;
1251 int n;
1252
1253 static const char *namepart[][4] = {
bcf50f5c 1254 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1255 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1256 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1257 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1258 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1259 };
1260#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1261
1262 static const char *transform[][5] = {
bcf50f5c 1263 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1264 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1265 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1266 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1267 { "dir", "[[:punct:]]", "", "sort", "g", }
1268 };
1269#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1270
1271 cs.path = "<internal>";
1272 cs.line = 0;
1273 cs.config = c;
1274 if(!c->namepart.n) {
1275 whoami = find("namepart");
1276 for(n = 0; n < NNAMEPART; ++n)
1277 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1278 }
1279 if(!c->transform.n) {
1280 whoami = find("transform");
1281 for(n = 0; n < NTRANSFORM; ++n)
1282 set_transform(&cs, whoami, 5, (char **)transform[n]);
1283 }
bd8895a8 1284 if(c->api == -1) {
e83d0967 1285 if(c->speaker_command)
bd8895a8 1286 c->api = BACKEND_COMMAND;
e83d0967 1287 else if(c->broadcast.n)
bd8895a8 1288 c->api = BACKEND_NETWORK;
8aae240b
RK
1289 else
1290 c->api = DEFAULT_BACKEND;
e83d0967 1291 }
c00fce3a 1292 if(server) {
bd8895a8 1293 if(c->api == BACKEND_COMMAND && !c->speaker_command)
1294 fatal(0, "'api command' but speaker_command is not set");
1295 if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1296 fatal(0, "'api network' but broadcast is not set");
c00fce3a 1297 }
e99d42b1 1298 /* Override sample format */
bd8895a8 1299 switch(c->api) {
e99d42b1 1300 case BACKEND_NETWORK:
6d2d327c
RK
1301 c->sample_format.rate = 44100;
1302 c->sample_format.channels = 2;
1303 c->sample_format.bits = 16;
1304 c->sample_format.endian = ENDIAN_BIG;
e99d42b1 1305 break;
1306 case BACKEND_COREAUDIO:
937be4c0
RK
1307 c->sample_format.rate = 44100;
1308 c->sample_format.channels = 2;
1309 c->sample_format.bits = 16;
1310 c->sample_format.endian = ENDIAN_NATIVE;
e99d42b1 1311 break;
04e1fa7c
RK
1312 }
1313 if(!c->default_rights) {
1314 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1315 |RIGHT_MOVE__MASK
1316 |RIGHT_SCRATCH__MASK
1317 |RIGHT_REMOVE__MASK);
1318 /* The idea is to approximate the meaning of the old 'restrict' directive
1319 * in the default rights if they are not overridden. */
1320 if(c->restrictions & RESTRICT_SCRATCH)
1321 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1322 else
1323 r |= RIGHT_SCRATCH_ANY;
1324 if(!(c->restrictions & RESTRICT_MOVE))
1325 r |= RIGHT_MOVE_ANY;
1326 if(c->restrictions & RESTRICT_REMOVE)
1327 r |= RIGHT_REMOVE_MINE;
1328 else
1329 r |= RIGHT_REMOVE_ANY;
0f55e905 1330 c->default_rights = rights_string(r);
04e1fa7c 1331 }
460b9539 1332}
1333
c00fce3a
RK
1334/** @brief (Re-)read the config file
1335 * @param server If set, do extra checking
1336 */
1337int config_read(int server) {
460b9539 1338 struct config *c;
1339 char *privconf;
1340 struct passwd *pw;
1341
1342 set_configfile();
1343 c = config_default();
9ade2319 1344 /* standalone Disobedience installs might not have a global config file */
1345 if(access(configfile, F_OK) == 0)
1346 if(config_include(c, configfile))
1347 return -1;
460b9539 1348 /* if we can read the private config file, do */
1349 if((privconf = config_private())
1350 && access(privconf, R_OK) == 0
1351 && config_include(c, privconf))
1352 return -1;
1353 xfree(privconf);
1354 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1355 if(config_per_user) {
1356 if(!(pw = getpwuid(getuid())))
1357 fatal(0, "cannot determine our username");
1358 if((privconf = config_usersysconf(pw))
1359 && access(privconf, F_OK) == 0
1360 && config_include(c, privconf))
460b9539 1361 return -1;
63ad732f
RK
1362 xfree(privconf);
1363 /* if we have a password file, read it */
5b14453f 1364 if((privconf = config_userconf(0, pw))
63ad732f
RK
1365 && access(privconf, F_OK) == 0
1366 && config_include(c, privconf))
1367 return -1;
1368 xfree(privconf);
1369 }
460b9539 1370 /* install default namepart and transform settings */
c00fce3a 1371 config_postdefaults(c, server);
460b9539 1372 /* everything is good so we shall use the new config */
1373 config_free(config);
04e1fa7c
RK
1374 /* warn about obsolete directives */
1375 if(c->restrictions)
1376 error(0, "'restrict' will be removed in a future version");
1377 if(c->allow.n)
1378 error(0, "'allow' will be removed in a future version");
1379 if(c->trust.n)
1380 error(0, "'trust' will be removed in a future version");
460b9539 1381 config = c;
1382 return 0;
1383}
1384
3f3bb97b 1385/** @brief Return the path to the private configuration file */
460b9539 1386char *config_private(void) {
1387 char *s;
1388
1389 set_configfile();
1390 byte_xasprintf(&s, "%s.private", configfile);
1391 return s;
1392}
1393
3f3bb97b 1394/** @brief Return the path to user's personal configuration file */
460b9539 1395char *config_userconf(const char *home, const struct passwd *pw) {
1396 char *s;
1397
73f1b9f3
RK
1398 if(!home && !pw && !(pw = getpwuid(getuid())))
1399 fatal(0, "cannot determine our username");
460b9539 1400 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1401 return s;
1402}
1403
3f3bb97b
RK
1404/** @brief Return the path to user-specific system configuration */
1405char *config_usersysconf(const struct passwd *pw) {
460b9539 1406 char *s;
1407
1408 set_configfile();
1409 if(!strchr(pw->pw_name, '/')) {
1410 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1411 return s;
1412 } else
1413 return 0;
1414}
1415
1416char *config_get_file(const char *name) {
319d7107 1417 return config_get_file2(config, name);
460b9539 1418}
1419
1420/*
1421Local Variables:
1422c-basic-offset:2
1423comment-column:40
1424fill-column:79
1425End:
1426*/