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