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