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