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