chiark / gitweb /
Expose command backend pause mode to server configuration as well.
[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
460b9539 492/* free functions */
493
494static void free_none(struct config attribute((unused)) *c,
495 const struct conf attribute((unused)) *whoami) {
496}
497
498static void free_string(struct config *c,
499 const struct conf *whoami) {
500 xfree(VALUE(c, char *));
501}
502
503static void free_stringlist(struct config *c,
504 const struct conf *whoami) {
505 int n;
506 struct stringlist *sl = ADDRESS(c, struct stringlist);
507
508 for(n = 0; n < sl->n; ++n)
509 xfree(sl->s[n]);
510 xfree(sl->s);
511}
512
513static void free_stringlistlist(struct config *c,
514 const struct conf *whoami) {
515 int n, m;
516 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
517 struct stringlist *sl;
518
519 for(n = 0; n < sll->n; ++n) {
520 sl = &sll->s[n];
521 for(m = 0; m < sl->n; ++m)
522 xfree(sl->s[m]);
523 xfree(sl->s);
524 }
525 xfree(sll->s);
526}
527
528static void free_collectionlist(struct config *c,
529 const struct conf *whoami) {
530 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
531 struct collection *cl;
532 int n;
533
534 for(n = 0; n < cll->n; ++n) {
535 cl = &cll->s[n];
536 xfree(cl->module);
537 xfree(cl->encoding);
538 xfree(cl->root);
539 }
540 xfree(cll->s);
541}
542
543static void free_namepartlist(struct config *c,
544 const struct conf *whoami) {
545 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
546 struct namepart *np;
547 int n;
548
549 for(n = 0; n < npl->n; ++n) {
550 np = &npl->s[n];
551 xfree(np->part);
552 pcre_free(np->re); /* ...whatever pcre_free is set to. */
553 xfree(np->replace);
554 xfree(np->context);
555 }
556 xfree(npl->s);
557}
558
559static void free_transformlist(struct config *c,
560 const struct conf *whoami) {
561 struct transformlist *tl = ADDRESS(c, struct transformlist);
562 struct transform *t;
563 int n;
564
565 for(n = 0; n < tl->n; ++n) {
566 t = &tl->t[n];
567 xfree(t->type);
568 pcre_free(t->re); /* ...whatever pcre_free is set to. */
569 xfree(t->replace);
570 xfree(t->context);
571 }
572 xfree(tl->t);
573}
574
575/* configuration types */
576
577static const struct conftype
578 type_signal = { set_signal, free_none },
579 type_collections = { set_collections, free_collectionlist },
580 type_boolean = { set_boolean, free_none },
581 type_string = { set_string, free_string },
582 type_stringlist = { set_stringlist, free_stringlist },
583 type_integer = { set_integer, free_none },
584 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
585 type_string_accum = { set_string_accum, free_stringlist },
9d5da576 586 type_sample_format = { set_sample_format, free_none },
460b9539 587 type_restrict = { set_restrict, free_none },
588 type_namepart = { set_namepart, free_namepartlist },
e83d0967 589 type_transform = { set_transform, free_transformlist },
b50cfb8a 590 type_rights = { set_rights, free_none };
460b9539 591
592/* specific validation routine */
593
594#define VALIDATE_FILE(test, what) do { \
595 struct stat sb; \
596 int n; \
597 \
598 for(n = 0; n < nvec; ++n) { \
599 if(stat(vec[n], &sb) < 0) { \
600 error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \
601 return -1; \
602 } \
603 if(!test(sb.st_mode)) { \
604 error(0, "%s:%d: %s is not a %s", \
605 cs->path, cs->line, vec[n], what); \
606 return -1; \
607 } \
608 } \
609} while(0)
610
659d87e8
RK
611static int validate_isabspath(const struct config_state *cs,
612 int nvec, char **vec) {
613 int n;
614
615 for(n = 0; n < nvec; ++n)
616 if(vec[n][0] != '/') {
617 error(errno, "%s:%d: %s: not an absolute path",
618 cs->path, cs->line, vec[n]);
619 return -1;
620 }
621 return 0;
622}
623
460b9539 624static int validate_isdir(const struct config_state *cs,
625 int nvec, char **vec) {
626 VALIDATE_FILE(S_ISDIR, "directory");
627 return 0;
628}
629
630static int validate_isreg(const struct config_state *cs,
631 int nvec, char **vec) {
632 VALIDATE_FILE(S_ISREG, "regular file");
633 return 0;
634}
635
460b9539 636static int validate_player(const struct config_state *cs,
637 int nvec,
638 char attribute((unused)) **vec) {
639 if(nvec < 2) {
640 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
641 cs->path, cs->line);
642 return -1;
643 }
644 return 0;
645}
646
62dc3748
RK
647static int validate_tracklength(const struct config_state *cs,
648 int nvec,
649 char attribute((unused)) **vec) {
650 if(nvec < 2) {
651 error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
652 cs->path, cs->line);
653 return -1;
654 }
655 return 0;
656}
657
460b9539 658static int validate_allow(const struct config_state *cs,
659 int nvec,
660 char attribute((unused)) **vec) {
661 if(nvec != 2) {
662 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
663 return -1;
664 }
665 return 0;
666}
667
668static int validate_non_negative(const struct config_state *cs,
669 int nvec, char **vec) {
670 long n;
671
672 if(nvec < 1) {
673 error(0, "%s:%d: missing argument", cs->path, cs->line);
674 return -1;
675 }
676 if(nvec > 1) {
677 error(0, "%s:%d: too many arguments", cs->path, cs->line);
678 return -1;
679 }
680 if(xstrtol(&n, vec[0], 0, 0)) {
681 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
682 return -1;
683 }
684 if(n < 0) {
685 error(0, "%s:%d: must not be negative", cs->path, cs->line);
686 return -1;
687 }
688 return 0;
689}
690
691static int validate_positive(const struct config_state *cs,
692 int nvec, char **vec) {
693 long n;
694
695 if(nvec < 1) {
696 error(0, "%s:%d: missing argument", cs->path, cs->line);
697 return -1;
698 }
699 if(nvec > 1) {
700 error(0, "%s:%d: too many arguments", cs->path, cs->line);
701 return -1;
702 }
703 if(xstrtol(&n, vec[0], 0, 0)) {
704 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
705 return -1;
706 }
707 if(n <= 0) {
708 error(0, "%s:%d: must be positive", cs->path, cs->line);
709 return -1;
710 }
711 return 0;
712}
713
714static int validate_isauser(const struct config_state *cs,
715 int attribute((unused)) nvec,
716 char **vec) {
717 struct passwd *pw;
718
719 if(!(pw = getpwnam(vec[0]))) {
720 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
721 return -1;
722 }
723 return 0;
724}
725
9d5da576 726static int validate_sample_format(const struct config_state *cs,
727 int attribute((unused)) nvec,
728 char **vec) {
729 return parse_sample_format(cs, 0, nvec, vec);
730}
731
460b9539 732static int validate_any(const struct config_state attribute((unused)) *cs,
733 int attribute((unused)) nvec,
734 char attribute((unused)) **vec) {
735 return 0;
736}
737
738static int validate_url(const struct config_state attribute((unused)) *cs,
739 int attribute((unused)) nvec,
740 char **vec) {
741 const char *s;
742 int n;
743 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
744 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
745 s = vec[0];
746 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
747 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
748 "0123456789"));
749 if(s[n] != ':') {
750 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
751 return -1;
752 }
753 if(!strncmp(s, "http:", 5)
754 || !strncmp(s, "https:", 6)) {
755 s += n + 1;
756 /* we only do a rather cursory check */
757 if(strncmp(s, "//", 2)) {
758 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
759 return -1;
760 }
761 }
762 return 0;
763}
764
765static int validate_alias(const struct config_state *cs,
766 int nvec,
767 char **vec) {
768 const char *s;
769 int in_brackets = 0, c;
770
771 if(nvec < 1) {
772 error(0, "%s:%d: missing argument", cs->path, cs->line);
773 return -1;
774 }
775 if(nvec > 1) {
776 error(0, "%s:%d: too many arguments", cs->path, cs->line);
777 return -1;
778 }
779 s = vec[0];
780 while((c = (unsigned char)*s++)) {
781 if(in_brackets) {
782 if(c == '}')
783 in_brackets = 0;
784 else if(!isalnum(c)) {
785 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
786 cs->path, cs->line, vec[0]);
787 return -1;
788 }
789 } else {
790 if(c == '{') {
791 in_brackets = 1;
792 if(*s == '/')
793 ++s;
794 } else if(c == '\\') {
795 if(!(c = (unsigned char)*s++)) {
796 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
797 cs->path, cs->line, vec[0]);
798 return -1;
799 } else if(c != '\\' && c != '{') {
800 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
801 cs->path, cs->line, vec[0]);
802 return -1;
803 }
804 }
805 }
806 ++s;
807 }
808 if(in_brackets) {
809 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
810 cs->path, cs->line, vec[0]);
811 return -1;
812 }
813 return 0;
814}
815
e83d0967
RK
816static int validate_addrport(const struct config_state attribute((unused)) *cs,
817 int nvec,
818 char attribute((unused)) **vec) {
819 switch(nvec) {
820 case 0:
821 error(0, "%s:%d: missing address",
822 cs->path, cs->line);
823 return -1;
824 case 1:
825 error(0, "%s:%d: missing port name/number",
826 cs->path, cs->line);
827 return -1;
828 case 2:
829 return 0;
830 default:
831 error(0, "%s:%d: expected ADDRESS PORT",
832 cs->path, cs->line);
833 return -1;
834 }
835}
836
ccf0aafa 837static int validate_port(const struct config_state attribute((unused)) *cs,
e83d0967
RK
838 int nvec,
839 char attribute((unused)) **vec) {
840 switch(nvec) {
841 case 0:
842 error(0, "%s:%d: missing address",
843 cs->path, cs->line);
844 return -1;
845 case 1:
846 case 2:
847 return 0;
848 default:
ccf0aafa 849 error(0, "%s:%d: expected [ADDRESS] PORT",
e83d0967
RK
850 cs->path, cs->line);
851 return -1;
852 }
853}
854
637fdea3
RK
855static int validate_algo(const struct config_state attribute((unused)) *cs,
856 int nvec,
857 char **vec) {
858 if(nvec != 1) {
859 error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
860 return -1;
861 }
862 if(!valid_authhash(vec[0])) {
863 error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
864 return -1;
865 }
866 return 0;
867}
868
b50cfb8a
RK
869static int validate_backend(const struct config_state attribute((unused)) *cs,
870 int nvec,
871 char **vec) {
872 int n;
873 if(nvec != 1) {
874 error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
875 return -1;
876 }
877 if(!strcmp(vec[0], "network")) {
878 error(0, "'api network' is deprecated; use 'api rtp'");
879 return 0;
880 }
881 if(config_uaudio_apis) {
882 for(n = 0; config_uaudio_apis[n]; ++n)
883 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
884 return 0;
885 error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
886 return -1;
887 }
888 /* In non-server processes we have no idea what's valid */
889 return 0;
890}
891
f75ab9d3
RK
892static int validate_pausemode(const struct config_state attribute((unused)) *cs,
893 int nvec,
894 char **vec) {
895 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
896 return 0;
897 error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
898 return -1;
899}
900
3f3bb97b 901/** @brief Item name and and offset */
460b9539 902#define C(x) #x, offsetof(struct config, x)
3f3bb97b 903/** @brief Item name and and offset */
460b9539 904#define C2(x,y) #x, offsetof(struct config, y)
905
3f3bb97b 906/** @brief All configuration items */
460b9539 907static const struct conf conf[] = {
908 { C(alias), &type_string, validate_alias },
909 { C(allow), &type_stringlist_accum, validate_allow },
b50cfb8a 910 { C(api), &type_string, validate_backend },
637fdea3 911 { C(authorization_algorithm), &type_string, validate_algo },
e83d0967 912 { C(broadcast), &type_stringlist, validate_addrport },
ccf0aafa 913 { C(broadcast_from), &type_stringlist, validate_addrport },
bd8895a8 914 { C(channel), &type_string, validate_any },
460b9539 915 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
916 { C(checkpoint_min), &type_integer, validate_non_negative },
917 { C(collection), &type_collections, validate_any },
e83d0967 918 { C(connect), &type_stringlist, validate_addrport },
b12be54a
RK
919 { C(cookie_login_lifetime), &type_integer, validate_positive },
920 { C(cookie_key_lifetime), &type_integer, validate_positive },
8818b7fc 921 { C(dbversion), &type_integer, validate_positive },
04e1fa7c 922 { C(default_rights), &type_rights, validate_any },
460b9539 923 { C(device), &type_string, validate_any },
924 { C(gap), &type_integer, validate_non_negative },
925 { C(history), &type_integer, validate_positive },
659d87e8 926 { C(home), &type_string, validate_isabspath },
ccf0aafa 927 { C(listen), &type_stringlist, validate_port },
460b9539 928 { C(lock), &type_boolean, validate_any },
bb6ae3fb 929 { C(mail_sender), &type_string, validate_any },
bd8895a8 930 { C(mixer), &type_string, validate_any },
61941295 931 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 932 { C(multicast_ttl), &type_integer, validate_non_negative },
460b9539 933 { C(namepart), &type_namepart, validate_any },
05dcfac6
RK
934 { C(new_bias), &type_integer, validate_positive },
935 { C(new_bias_age), &type_integer, validate_positive },
d742bb47 936 { C(new_max), &type_integer, validate_positive },
460b9539 937 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
938 { C(nice_rescan), &type_integer, validate_non_negative },
939 { C(nice_server), &type_integer, validate_any },
940 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 941 { C(noticed_history), &type_integer, validate_positive },
460b9539 942 { C(password), &type_string, validate_any },
f75ab9d3 943 { C(pause_mode), &type_string, validate_pausemode },
460b9539 944 { C(player), &type_stringlist_accum, validate_player },
945 { C(plugins), &type_string_accum, validate_isdir },
946 { C(prefsync), &type_integer, validate_positive },
459d4402 947 { C(queue_pad), &type_integer, validate_positive },
cebe3127 948 { C(replay_min), &type_integer, validate_non_negative },
460b9539 949 { C(refresh), &type_integer, validate_positive },
6207d2f3 950 { C(reminder_interval), &type_integer, validate_positive },
810b8083 951 { C(remote_userman), &type_boolean, validate_any },
460b9539 952 { C2(restrict, restrictions), &type_restrict, validate_any },
ba70caca 953 { C(rtp_delay_threshold), &type_integer, validate_positive },
9d5da576 954 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 955 { C(scratch), &type_string_accum, validate_isreg },
2eee4b0c 956 { C(sendmail), &type_string, validate_isabspath },
61507e3c 957 { C(short_display), &type_integer, validate_positive },
460b9539 958 { C(signal), &type_signal, validate_any },
bb6ae3fb 959 { C(smtp_server), &type_string, validate_any },
5330d674 960 { C(sox_generation), &type_integer, validate_non_negative },
b50cfb8a 961 { C2(speaker_backend, api), &type_string, validate_backend },
9d5da576 962 { C(speaker_command), &type_string, validate_any },
460b9539 963 { C(stopword), &type_string_accum, validate_any },
964 { C(templates), &type_string_accum, validate_isdir },
62dc3748 965 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 966 { C(transform), &type_transform, validate_any },
967 { C(trust), &type_string_accum, validate_any },
968 { C(url), &type_string, validate_url },
969 { C(user), &type_string, validate_isauser },
970 { C(username), &type_string, validate_any },
971};
972
3f3bb97b 973/** @brief Find a configuration item's definition by key */
460b9539 974static const struct conf *find(const char *key) {
975 int n;
976
ba937f01 977 if((n = TABLE_FIND(conf, name, key)) < 0)
460b9539 978 return 0;
979 return &conf[n];
980}
981
3f3bb97b 982/** @brief Set a new configuration value */
460b9539 983static int config_set(const struct config_state *cs,
984 int nvec, char **vec) {
985 const struct conf *which;
986
987 D(("config_set %s", vec[0]));
988 if(!(which = find(vec[0]))) {
989 error(0, "%s:%d: unknown configuration key '%s'",
990 cs->path, cs->line, vec[0]);
991 return -1;
992 }
993 return (which->validate(cs, nvec - 1, vec + 1)
994 || which->type->set(cs, which, nvec - 1, vec + 1));
995}
996
e6a35d1c
RK
997static int config_set_args(const struct config_state *cs,
998 const char *which, ...) {
999 va_list ap;
1000 struct vector v[1];
1001 char *s;
1002
1003 vector_init(v);
1004 vector_append(v, (char *)which);
1005 va_start(ap, which);
1006 while((s = va_arg(ap, char *)))
1007 vector_append(v, s);
1008 va_end(ap);
1009 vector_terminate(v);
1010 return config_set(cs, v->nvec, v->vec);
1011}
1012
0e4472a0 1013/** @brief Error callback used by config_include() */
460b9539 1014static void config_error(const char *msg, void *u) {
1015 const struct config_state *cs = u;
1016
1017 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1018}
1019
3f3bb97b 1020/** @brief Include a file by name */
460b9539 1021static int config_include(struct config *c, const char *path) {
1022 FILE *fp;
1023 char *buffer, *inputbuffer, **vec;
1024 int n, ret = 0;
1025 struct config_state cs;
1026
1027 cs.path = path;
1028 cs.line = 0;
1029 cs.config = c;
1030 D(("%s: reading configuration", path));
1031 if(!(fp = fopen(path, "r"))) {
1032 error(errno, "error opening %s", path);
1033 return -1;
1034 }
1035 while(!inputline(path, fp, &inputbuffer, '\n')) {
1036 ++cs.line;
1037 if(!(buffer = mb2utf8(inputbuffer))) {
1038 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1039 ret = -1;
1040 xfree(inputbuffer);
1041 continue;
1042 }
1043 xfree(inputbuffer);
1044 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1045 config_error, &cs))) {
1046 ret = -1;
1047 xfree(buffer);
1048 continue;
1049 }
1050 if(n) {
1051 if(!strcmp(vec[0], "include")) {
1052 if(n != 2) {
1053 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1054 ret = -1;
1055 } else
1056 config_include(c, vec[1]);
1057 } else
1058 ret |= config_set(&cs, n, vec);
1059 }
1060 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1061 xfree(vec);
1062 xfree(buffer);
1063 }
1064 if(ferror(fp)) {
1065 error(errno, "error reading %s", path);
1066 ret = -1;
1067 }
1068 fclose(fp);
1069 return ret;
1070}
1071
86be0c30 1072static const char *const default_stopwords[] = {
1073 "stopword",
1074
1075 "01",
1076 "02",
1077 "03",
1078 "04",
1079 "05",
1080 "06",
1081 "07",
1082 "08",
1083 "09",
1084 "1",
1085 "10",
1086 "11",
1087 "12",
1088 "13",
1089 "14",
1090 "15",
1091 "16",
1092 "17",
1093 "18",
1094 "19",
1095 "2",
1096 "20",
1097 "21",
1098 "22",
1099 "23",
1100 "24",
1101 "25",
1102 "26",
1103 "27",
1104 "28",
1105 "29",
1106 "3",
1107 "30",
1108 "4",
1109 "5",
1110 "6",
1111 "7",
1112 "8",
1113 "9",
1114 "a",
1115 "am",
1116 "an",
1117 "and",
1118 "as",
1119 "for",
1120 "i",
1121 "im",
1122 "in",
1123 "is",
1124 "of",
1125 "on",
1126 "the",
1127 "to",
1128 "too",
1129 "we",
1130};
1131#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1132
e6a35d1c
RK
1133static const char *const default_players[] = {
1134 "*.ogg",
1135 "*.flac",
1136 "*.mp3",
1137 "*.wav",
1138};
1139#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1140
3f3bb97b 1141/** @brief Make a new default configuration */
460b9539 1142static struct config *config_default(void) {
1143 struct config *c = xmalloc(sizeof *c);
1144 const char *logname;
1145 struct passwd *pw;
86be0c30 1146 struct config_state cs;
e6a35d1c 1147 size_t n;
460b9539 1148
86be0c30 1149 cs.path = "<internal>";
1150 cs.line = 0;
1151 cs.config = c;
460b9539 1152 /* Strings had better be xstrdup'd as they will get freed at some point. */
07bc035e 1153 c->gap = 0;
460b9539 1154 c->history = 60;
1155 c->home = xstrdup(pkgstatedir);
1156 if(!(pw = getpwuid(getuid())))
1157 fatal(0, "cannot determine our username");
1158 logname = pw->pw_name;
1159 c->username = xstrdup(logname);
1160 c->refresh = 15;
1161 c->prefsync = 3600;
1162 c->signal = SIGKILL;
1163 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1164 c->lock = 1;
1165 c->device = xstrdup("default");
1166 c->nice_rescan = 10;
9d5da576 1167 c->speaker_command = 0;
1168 c->sample_format.bits = 16;
1169 c->sample_format.rate = 44100;
1170 c->sample_format.channels = 2;
6d2d327c 1171 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1172 c->queue_pad = 10;
cebe3127 1173 c->replay_min = 8 * 3600;
b50cfb8a 1174 c->api = NULL;
23205f9c 1175 c->multicast_ttl = 1;
61941295 1176 c->multicast_loop = 1;
637fdea3 1177 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1178 c->noticed_history = 31;
61507e3c 1179 c->short_display = 32;
bd8895a8 1180 c->mixer = 0;
1181 c->channel = 0;
8818b7fc 1182 c->dbversion = 2;
b12be54a
RK
1183 c->cookie_login_lifetime = 86400;
1184 c->cookie_key_lifetime = 86400 * 7;
2eee4b0c
RK
1185 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1186 c->sendmail = xstrdup(sendmail_binary);
bb6ae3fb 1187 c->smtp_server = xstrdup("127.0.0.1");
d742bb47 1188 c->new_max = 100;
6207d2f3 1189 c->reminder_interval = 600; /* 10m */
05dcfac6 1190 c->new_bias_age = 7 * 86400; /* 1 week */
6151ae7e 1191 c->new_bias = 4500000; /* 50 times the base weight */
419893d7 1192 c->sox_generation = DEFAULT_SOX_GENERATION;
e6a35d1c 1193 /* Default stopwords */
86be0c30 1194 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1195 exit(1);
e6a35d1c
RK
1196 /* Default player configuration */
1197 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1198 if(config_set_args(&cs, "player",
1199 default_players[n], "execraw", "disorder-decode", (char *)0))
1200 exit(1);
1201 if(config_set_args(&cs, "tracklength",
1202 default_players[n], "disorder-tracklength", (char *)0))
1203 exit(1);
1204 }
460b9539 1205 return c;
1206}
1207
319d7107 1208char *config_get_file2(struct config *c, const char *name) {
460b9539 1209 char *s;
1210
1211 byte_xasprintf(&s, "%s/%s", c->home, name);
1212 return s;
1213}
1214
3f3bb97b 1215/** @brief Set the default configuration file */
460b9539 1216static void set_configfile(void) {
1217 if(!configfile)
1218 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1219}
1220
3f3bb97b 1221/** @brief Free a configuration object */
460b9539 1222static void config_free(struct config *c) {
1223 int n;
1224
1225 if(c) {
1226 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1227 conf[n].type->free(c, &conf[n]);
1228 for(n = 0; n < c->nparts; ++n)
1229 xfree(c->parts[n]);
1230 xfree(c->parts);
1231 xfree(c);
1232 }
1233}
1234
3f3bb97b 1235/** @brief Set post-parse defaults */
c00fce3a
RK
1236static void config_postdefaults(struct config *c,
1237 int server) {
460b9539 1238 struct config_state cs;
1239 const struct conf *whoami;
1240 int n;
1241
1242 static const char *namepart[][4] = {
bcf50f5c 1243 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1244 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1245 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1246 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1247 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1248 };
1249#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1250
1251 static const char *transform[][5] = {
bcf50f5c 1252 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1253 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1254 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1255 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1256 { "dir", "[[:punct:]]", "", "sort", "g", }
1257 };
1258#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1259
1260 cs.path = "<internal>";
1261 cs.line = 0;
1262 cs.config = c;
1263 if(!c->namepart.n) {
1264 whoami = find("namepart");
1265 for(n = 0; n < NNAMEPART; ++n)
1266 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1267 }
1268 if(!c->transform.n) {
1269 whoami = find("transform");
1270 for(n = 0; n < NTRANSFORM; ++n)
1271 set_transform(&cs, whoami, 5, (char **)transform[n]);
1272 }
b50cfb8a 1273 if(!c->api) {
e83d0967 1274 if(c->speaker_command)
b50cfb8a 1275 c->api = xstrdup("command");
e83d0967 1276 else if(c->broadcast.n)
b50cfb8a
RK
1277 c->api = xstrdup("rtp");
1278 else if(config_uaudio_apis)
1279 c->api = xstrdup(config_uaudio_apis[0]->name);
8aae240b 1280 else
b50cfb8a 1281 c->api = xstrdup("<none>");
e83d0967 1282 }
b50cfb8a
RK
1283 if(!strcmp(c->api, "network"))
1284 c->api = xstrdup("rtp");
c00fce3a 1285 if(server) {
b50cfb8a 1286 if(!strcmp(c->api, "command") && !c->speaker_command)
bd8895a8 1287 fatal(0, "'api command' but speaker_command is not set");
b50cfb8a
RK
1288 if((!strcmp(c->api, "rtp")) && !c->broadcast.n)
1289 fatal(0, "'api rtp' but broadcast is not set");
c00fce3a 1290 }
e99d42b1 1291 /* Override sample format */
b50cfb8a 1292 if(!strcmp(c->api, "rtp")) {
6d2d327c
RK
1293 c->sample_format.rate = 44100;
1294 c->sample_format.channels = 2;
1295 c->sample_format.bits = 16;
b50cfb8a
RK
1296 c->sample_format.endian = ENDIAN_NATIVE;
1297 }
1298 if(!strcmp(c->api, "coreaudio")) {
937be4c0
RK
1299 c->sample_format.rate = 44100;
1300 c->sample_format.channels = 2;
1301 c->sample_format.bits = 16;
1302 c->sample_format.endian = ENDIAN_NATIVE;
04e1fa7c
RK
1303 }
1304 if(!c->default_rights) {
1305 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1306 |RIGHT_MOVE__MASK
1307 |RIGHT_SCRATCH__MASK
1308 |RIGHT_REMOVE__MASK);
1309 /* The idea is to approximate the meaning of the old 'restrict' directive
1310 * in the default rights if they are not overridden. */
1311 if(c->restrictions & RESTRICT_SCRATCH)
1312 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1313 else
1314 r |= RIGHT_SCRATCH_ANY;
1315 if(!(c->restrictions & RESTRICT_MOVE))
1316 r |= RIGHT_MOVE_ANY;
1317 if(c->restrictions & RESTRICT_REMOVE)
1318 r |= RIGHT_REMOVE_MINE;
1319 else
1320 r |= RIGHT_REMOVE_ANY;
0f55e905 1321 c->default_rights = rights_string(r);
04e1fa7c 1322 }
460b9539 1323}
1324
c00fce3a
RK
1325/** @brief (Re-)read the config file
1326 * @param server If set, do extra checking
1327 */
1328int config_read(int server) {
460b9539 1329 struct config *c;
1330 char *privconf;
1331 struct passwd *pw;
1332
1333 set_configfile();
1334 c = config_default();
9ade2319 1335 /* standalone Disobedience installs might not have a global config file */
1336 if(access(configfile, F_OK) == 0)
1337 if(config_include(c, configfile))
1338 return -1;
460b9539 1339 /* if we can read the private config file, do */
1340 if((privconf = config_private())
1341 && access(privconf, R_OK) == 0
1342 && config_include(c, privconf))
1343 return -1;
1344 xfree(privconf);
1345 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1346 if(config_per_user) {
1347 if(!(pw = getpwuid(getuid())))
1348 fatal(0, "cannot determine our username");
1349 if((privconf = config_usersysconf(pw))
1350 && access(privconf, F_OK) == 0
1351 && config_include(c, privconf))
460b9539 1352 return -1;
63ad732f
RK
1353 xfree(privconf);
1354 /* if we have a password file, read it */
5b14453f 1355 if((privconf = config_userconf(0, pw))
63ad732f
RK
1356 && access(privconf, F_OK) == 0
1357 && config_include(c, privconf))
1358 return -1;
1359 xfree(privconf);
1360 }
460b9539 1361 /* install default namepart and transform settings */
c00fce3a 1362 config_postdefaults(c, server);
460b9539 1363 /* everything is good so we shall use the new config */
1364 config_free(config);
04e1fa7c
RK
1365 /* warn about obsolete directives */
1366 if(c->restrictions)
1367 error(0, "'restrict' will be removed in a future version");
1368 if(c->allow.n)
1369 error(0, "'allow' will be removed in a future version");
1370 if(c->trust.n)
1371 error(0, "'trust' will be removed in a future version");
460b9539 1372 config = c;
1373 return 0;
1374}
1375
3f3bb97b 1376/** @brief Return the path to the private configuration file */
460b9539 1377char *config_private(void) {
1378 char *s;
1379
1380 set_configfile();
1381 byte_xasprintf(&s, "%s.private", configfile);
1382 return s;
1383}
1384
3f3bb97b 1385/** @brief Return the path to user's personal configuration file */
460b9539 1386char *config_userconf(const char *home, const struct passwd *pw) {
1387 char *s;
1388
73f1b9f3
RK
1389 if(!home && !pw && !(pw = getpwuid(getuid())))
1390 fatal(0, "cannot determine our username");
460b9539 1391 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1392 return s;
1393}
1394
3f3bb97b
RK
1395/** @brief Return the path to user-specific system configuration */
1396char *config_usersysconf(const struct passwd *pw) {
460b9539 1397 char *s;
1398
1399 set_configfile();
1400 if(!strchr(pw->pw_name, '/')) {
1401 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1402 return s;
1403 } else
1404 return 0;
1405}
1406
1407char *config_get_file(const char *name) {
319d7107 1408 return config_get_file2(config, name);
460b9539 1409}
1410
1411/*
1412Local Variables:
1413c-basic-offset:2
1414comment-column:40
1415fill-column:79
1416End:
1417*/