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