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