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