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