chiark / gitweb /
Provide for a network initialization step
[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 },
86ed4f0e 635#if HAVE_PCRE_H
460b9539 636 type_namepart = { set_namepart, free_namepartlist },
e83d0967 637 type_transform = { set_transform, free_transformlist },
86ed4f0e 638#endif
76e72f65 639 type_netaddress = { set_netaddress, free_netaddress },
47854c5f 640 type_rights = { set_rights, free_string };
460b9539 641
642/* specific validation routine */
643
34d37b3e
RK
644/** @brief Perform a test on a filename
645 * @param test Test function to call on mode bits
646 * @param what Type of file sought
647 *
648 * If @p test returns 0 then the file is not a @p what and an error
649 * is reported and -1 is returned.
650 */
2e9ba080
RK
651#define VALIDATE_FILE(test, what) do { \
652 struct stat sb; \
653 int n; \
654 \
655 for(n = 0; n < nvec; ++n) { \
656 if(stat(vec[n], &sb) < 0) { \
657 disorder_error(errno, "%s:%d: %s", \
658 cs->path, cs->line, vec[n]); \
659 return -1; \
660 } \
661 if(!test(sb.st_mode)) { \
662 disorder_error(0, "%s:%d: %s is not a %s", \
663 cs->path, cs->line, vec[n], what); \
664 return -1; \
665 } \
666 } \
460b9539 667} while(0)
668
34d37b3e
RK
669/** @brief Validate an absolute path
670 * @param cs Configuration state
671 * @param nvec Length of (proposed) new value
672 * @param vec Elements of new value
673 * @return 0 on success, non-0 on error
674 */
659d87e8
RK
675static int validate_isabspath(const struct config_state *cs,
676 int nvec, char **vec) {
677 int n;
678
679 for(n = 0; n < nvec; ++n)
680 if(vec[n][0] != '/') {
70adc86b 681 disorder_error(0, "%s:%d: %s: not an absolute path",
2e9ba080 682 cs->path, cs->line, vec[n]);
659d87e8
RK
683 return -1;
684 }
685 return 0;
686}
687
34d37b3e
RK
688/** @brief Validate an existing directory
689 * @param cs Configuration state
690 * @param nvec Length of (proposed) new value
691 * @param vec Elements of new value
692 * @return 0 on success, non-0 on error
693 */
460b9539 694static int validate_isdir(const struct config_state *cs,
695 int nvec, char **vec) {
696 VALIDATE_FILE(S_ISDIR, "directory");
697 return 0;
698}
699
34d37b3e
RK
700/** @brief Validate an existing regular file
701 * @param cs Configuration state
702 * @param nvec Length of (proposed) new value
703 * @param vec Elements of new value
704 * @return 0 on success, non-0 on error
705 */
460b9539 706static int validate_isreg(const struct config_state *cs,
707 int nvec, char **vec) {
708 VALIDATE_FILE(S_ISREG, "regular file");
709 return 0;
710}
711
34d37b3e
RK
712/** @brief Validate a player pattern
713 * @param cs Configuration state
714 * @param nvec Length of (proposed) new value
715 * @param vec Elements of new value
716 * @return 0 on success, non-0 on error
717 */
460b9539 718static int validate_player(const struct config_state *cs,
719 int nvec,
720 char attribute((unused)) **vec) {
ec554a3d 721 if(nvec && nvec < 2) {
2e9ba080
RK
722 disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
723 cs->path, cs->line);
460b9539 724 return -1;
725 }
726 return 0;
727}
728
34d37b3e
RK
729/** @brief Validate a track length pattern
730 * @param cs Configuration state
731 * @param nvec Length of (proposed) new value
732 * @param vec Elements of new value
733 * @return 0 on success, non-0 on error
734 */
62dc3748
RK
735static int validate_tracklength(const struct config_state *cs,
736 int nvec,
737 char attribute((unused)) **vec) {
ec554a3d 738 if(nvec && nvec < 2) {
2e9ba080
RK
739 disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
740 cs->path, cs->line);
62dc3748
RK
741 return -1;
742 }
743 return 0;
744}
745
34d37b3e
RK
746/** @brief Validate a non-negative (@c long) integer
747 * @param cs Configuration state
748 * @param nvec Length of (proposed) new value
749 * @param vec Elements of new value
750 * @return 0 on success, non-0 on error
751 */
460b9539 752static int validate_non_negative(const struct config_state *cs,
753 int nvec, char **vec) {
754 long n;
b94e827f 755 char errbuf[1024];
460b9539 756
757 if(nvec < 1) {
2e9ba080 758 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 759 return -1;
760 }
761 if(nvec > 1) {
2e9ba080 762 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 763 return -1;
764 }
765 if(xstrtol(&n, vec[0], 0, 0)) {
b94e827f
RK
766 disorder_error(0, "%s:%d: %s", cs->path, cs->line,
767 format_error(ec_errno, errno, errbuf, sizeof errbuf));
460b9539 768 return -1;
769 }
770 if(n < 0) {
2e9ba080 771 disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
460b9539 772 return -1;
773 }
774 return 0;
775}
776
34d37b3e
RK
777/** @brief Validate a positive (@c long) integer
778 * @param cs Configuration state
779 * @param nvec Length of (proposed) new value
780 * @param vec Elements of new value
781 * @return 0 on success, non-0 on error
782 */
460b9539 783static int validate_positive(const struct config_state *cs,
784 int nvec, char **vec) {
785 long n;
b94e827f 786 char errbuf[1024];
460b9539 787
788 if(nvec < 1) {
2e9ba080 789 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 790 return -1;
791 }
792 if(nvec > 1) {
2e9ba080 793 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 794 return -1;
795 }
796 if(xstrtol(&n, vec[0], 0, 0)) {
b94e827f
RK
797 disorder_error(0, "%s:%d: %s", cs->path, cs->line,
798 format_error(ec_errno, errno, errbuf, sizeof errbuf));
460b9539 799 return -1;
800 }
801 if(n <= 0) {
2e9ba080 802 disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
460b9539 803 return -1;
804 }
805 return 0;
806}
807
34d37b3e
RK
808/** @brief Validate a system username
809 * @param cs Configuration state
810 * @param nvec Length of (proposed) new value
811 * @param vec Elements of new value
812 * @return 0 on success, non-0 on error
813 */
460b9539 814static int validate_isauser(const struct config_state *cs,
815 int attribute((unused)) nvec,
816 char **vec) {
b3756e27 817 if(!getpwnam(vec[0])) {
2e9ba080 818 disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
460b9539 819 return -1;
820 }
821 return 0;
822}
823
34d37b3e
RK
824/** @brief Validate a sample format string
825 * @param cs Configuration state
826 * @param nvec Length of (proposed) new value
827 * @param vec Elements of new value
828 * @return 0 on success, non-0 on error
829 */
9d5da576 830static int validate_sample_format(const struct config_state *cs,
831 int attribute((unused)) nvec,
832 char **vec) {
833 return parse_sample_format(cs, 0, nvec, vec);
834}
835
34d37b3e
RK
836/** @brief Validate anything
837 * @param cs Configuration state
838 * @param nvec Length of (proposed) new value
839 * @param vec Elements of new value
840 * @return 0
841 */
460b9539 842static int validate_any(const struct config_state attribute((unused)) *cs,
843 int attribute((unused)) nvec,
844 char attribute((unused)) **vec) {
845 return 0;
846}
847
34d37b3e
RK
848/** @brief Validate a URL
849 * @param cs Configuration state
850 * @param nvec Length of (proposed) new value
851 * @param vec Elements of new value
852 * @return 0 on success, non-0 on error
853 *
854 * Rather cursory.
855 */
460b9539 856static int validate_url(const struct config_state attribute((unused)) *cs,
857 int attribute((unused)) nvec,
858 char **vec) {
859 const char *s;
860 int n;
861 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
862 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
863 s = vec[0];
864 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
865 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
866 "0123456789"));
867 if(s[n] != ':') {
2e9ba080 868 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
460b9539 869 return -1;
870 }
871 if(!strncmp(s, "http:", 5)
872 || !strncmp(s, "https:", 6)) {
873 s += n + 1;
874 /* we only do a rather cursory check */
875 if(strncmp(s, "//", 2)) {
2e9ba080 876 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
460b9539 877 return -1;
878 }
879 }
880 return 0;
881}
882
34d37b3e
RK
883/** @brief Validate an alias pattern
884 * @param cs Configuration state
885 * @param nvec Length of (proposed) new value
886 * @param vec Elements of new value
887 * @return 0 on success, non-0 on error
888 */
460b9539 889static int validate_alias(const struct config_state *cs,
890 int nvec,
891 char **vec) {
892 const char *s;
893 int in_brackets = 0, c;
894
895 if(nvec < 1) {
2e9ba080 896 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
460b9539 897 return -1;
898 }
899 if(nvec > 1) {
2e9ba080 900 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
460b9539 901 return -1;
902 }
903 s = vec[0];
904 while((c = (unsigned char)*s++)) {
905 if(in_brackets) {
906 if(c == '}')
907 in_brackets = 0;
908 else if(!isalnum(c)) {
2e9ba080
RK
909 disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
910 cs->path, cs->line, vec[0]);
460b9539 911 return -1;
912 }
913 } else {
914 if(c == '{') {
915 in_brackets = 1;
916 if(*s == '/')
917 ++s;
918 } else if(c == '\\') {
919 if(!(c = (unsigned char)*s++)) {
2e9ba080
RK
920 disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
921 cs->path, cs->line, vec[0]);
460b9539 922 return -1;
923 } else if(c != '\\' && c != '{') {
2e9ba080
RK
924 disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
925 cs->path, cs->line, vec[0]);
460b9539 926 return -1;
927 }
928 }
929 }
930 ++s;
931 }
932 if(in_brackets) {
2e9ba080
RK
933 disorder_error(0,
934 "%s:%d: unterminated part name in alias expansion in '%s'",
935 cs->path, cs->line, vec[0]);
460b9539 936 return -1;
937 }
938 return 0;
939}
940
34d37b3e
RK
941/** @brief Validate a hash algorithm name
942 * @param cs Configuration state
943 * @param nvec Length of (proposed) new value
944 * @param vec Elements of new value
945 * @return 0 on success, non-0 on error
946 */
637fdea3
RK
947static int validate_algo(const struct config_state attribute((unused)) *cs,
948 int nvec,
949 char **vec) {
950 if(nvec != 1) {
2e9ba080 951 disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
637fdea3
RK
952 return -1;
953 }
954 if(!valid_authhash(vec[0])) {
2e9ba080 955 disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
637fdea3
RK
956 return -1;
957 }
958 return 0;
959}
960
34d37b3e
RK
961/** @brief Validate a playback backend name
962 * @param cs Configuration state
963 * @param nvec Length of (proposed) new value
964 * @param vec Elements of new value
965 * @return 0 on success, non-0 on error
966 */
b50cfb8a
RK
967static int validate_backend(const struct config_state attribute((unused)) *cs,
968 int nvec,
969 char **vec) {
970 int n;
971 if(nvec != 1) {
2e9ba080 972 disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
b50cfb8a
RK
973 return -1;
974 }
975 if(!strcmp(vec[0], "network")) {
2e9ba080 976 disorder_error(0, "'api network' is deprecated; use 'api rtp'");
b50cfb8a
RK
977 return 0;
978 }
979 if(config_uaudio_apis) {
980 for(n = 0; config_uaudio_apis[n]; ++n)
981 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
982 return 0;
2e9ba080 983 disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
b50cfb8a
RK
984 return -1;
985 }
986 /* In non-server processes we have no idea what's valid */
987 return 0;
988}
989
34d37b3e
RK
990/** @brief Validate a pause mode string
991 * @param cs Configuration state
992 * @param nvec Length of (proposed) new value
993 * @param vec Elements of new value
994 * @return 0 on success, non-0 on error
995 */
f75ab9d3
RK
996static int validate_pausemode(const struct config_state attribute((unused)) *cs,
997 int nvec,
998 char **vec) {
999 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
1000 return 0;
2e9ba080 1001 disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
f75ab9d3
RK
1002 return -1;
1003}
1004
34d37b3e
RK
1005/** @brief Validate a destination network address
1006 * @param cs Configuration state
1007 * @param nvec Length of (proposed) new value
1008 * @param vec Elements of new value
1009 * @return 0 on success, non-0 on error
1010 *
1011 * By a destination address, it is meant that it must not be a wildcard
1012 * address.
1013 */
76e72f65
RK
1014static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1015 int nvec,
1016 char **vec) {
1017 struct netaddress na[1];
1018
1019 if(netaddress_parse(na, nvec, vec)) {
2e9ba080 1020 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
76e72f65
RK
1021 return -1;
1022 }
1023 if(!na->address) {
2e9ba080 1024 disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
76e72f65
RK
1025 return -1;
1026 }
47854c5f 1027 xfree(na->address);
76e72f65
RK
1028 return 0;
1029}
1030
3f3bb97b 1031/** @brief Item name and and offset */
460b9539 1032#define C(x) #x, offsetof(struct config, x)
3f3bb97b 1033/** @brief Item name and and offset */
460b9539 1034#define C2(x,y) #x, offsetof(struct config, y)
1035
3f3bb97b 1036/** @brief All configuration items */
460b9539 1037static const struct conf conf[] = {
1038 { C(alias), &type_string, validate_alias },
b50cfb8a 1039 { C(api), &type_string, validate_backend },
637fdea3 1040 { C(authorization_algorithm), &type_string, validate_algo },
76e72f65
RK
1041 { C(broadcast), &type_netaddress, validate_destaddr },
1042 { C(broadcast_from), &type_netaddress, validate_any },
bd8895a8 1043 { C(channel), &type_string, validate_any },
460b9539 1044 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
1045 { C(checkpoint_min), &type_integer, validate_non_negative },
1046 { C(collection), &type_collections, validate_any },
e41a9999 1047 { C(connect), &type_netaddress, validate_destaddr },
b12be54a 1048 { C(cookie_key_lifetime), &type_integer, validate_positive },
2a1c84fb 1049 { C(cookie_login_lifetime), &type_integer, validate_positive },
8818b7fc 1050 { C(dbversion), &type_integer, validate_positive },
04e1fa7c 1051 { C(default_rights), &type_rights, validate_any },
460b9539 1052 { C(device), &type_string, validate_any },
460b9539 1053 { C(history), &type_integer, validate_positive },
659d87e8 1054 { C(home), &type_string, validate_isabspath },
80dc2c5f 1055 { C(listen), &type_netaddress, validate_any },
bb6ae3fb 1056 { C(mail_sender), &type_string, validate_any },
bd8895a8 1057 { C(mixer), &type_string, validate_any },
8488cf7d 1058 { C(mount_rescan), &type_boolean, validate_any },
61941295 1059 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 1060 { C(multicast_ttl), &type_integer, validate_non_negative },
cca89d7c 1061#if HAVE_PCRE_H
460b9539 1062 { C(namepart), &type_namepart, validate_any },
cca89d7c 1063#endif
05dcfac6
RK
1064 { C(new_bias), &type_integer, validate_positive },
1065 { C(new_bias_age), &type_integer, validate_positive },
d742bb47 1066 { C(new_max), &type_integer, validate_positive },
460b9539 1067 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
1068 { C(nice_rescan), &type_integer, validate_non_negative },
1069 { C(nice_server), &type_integer, validate_any },
1070 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 1071 { C(noticed_history), &type_integer, validate_positive },
460b9539 1072 { C(password), &type_string, validate_any },
f75ab9d3 1073 { C(pause_mode), &type_string, validate_pausemode },
460b9539 1074 { C(player), &type_stringlist_accum, validate_player },
ddbf05c8 1075 { C(playlist_lock_timeout), &type_integer, validate_positive },
2563dc1f 1076 { C(playlist_max) , &type_integer, validate_positive },
460b9539 1077 { C(plugins), &type_string_accum, validate_isdir },
459d4402 1078 { C(queue_pad), &type_integer, validate_positive },
460b9539 1079 { C(refresh), &type_integer, validate_positive },
533272be 1080 { C(refresh_min), &type_integer, validate_non_negative },
6207d2f3 1081 { C(reminder_interval), &type_integer, validate_positive },
810b8083 1082 { C(remote_userman), &type_boolean, validate_any },
2a1c84fb 1083 { C(replay_min), &type_integer, validate_non_negative },
ba70caca 1084 { C(rtp_delay_threshold), &type_integer, validate_positive },
b0116b5c 1085 { C(rtp_mode), &type_string, validate_any },
87864f77 1086 { C(rtp_verbose), &type_boolean, validate_any },
9d5da576 1087 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 1088 { C(scratch), &type_string_accum, validate_isreg },
2eee4b0c 1089 { C(sendmail), &type_string, validate_isabspath },
61507e3c 1090 { C(short_display), &type_integer, validate_positive },
460b9539 1091 { C(signal), &type_signal, validate_any },
bb6ae3fb 1092 { C(smtp_server), &type_string, validate_any },
5330d674 1093 { C(sox_generation), &type_integer, validate_non_negative },
b50cfb8a 1094 { C2(speaker_backend, api), &type_string, validate_backend },
9d5da576 1095 { C(speaker_command), &type_string, validate_any },
460b9539 1096 { C(stopword), &type_string_accum, validate_any },
1097 { C(templates), &type_string_accum, validate_isdir },
62dc3748 1098 { C(tracklength), &type_stringlist_accum, validate_tracklength },
cca89d7c 1099#if HAVE_PCRE_H
460b9539 1100 { C(transform), &type_transform, validate_any },
cca89d7c 1101#endif
460b9539 1102 { C(url), &type_string, validate_url },
1103 { C(user), &type_string, validate_isauser },
1104 { C(username), &type_string, validate_any },
1105};
1106
3f3bb97b 1107/** @brief Find a configuration item's definition by key */
460b9539 1108static const struct conf *find(const char *key) {
1109 int n;
1110
ba937f01 1111 if((n = TABLE_FIND(conf, name, key)) < 0)
460b9539 1112 return 0;
1113 return &conf[n];
1114}
1115
34d37b3e
RK
1116/** @brief Set a new configuration value
1117 * @param cs Configuration state
1118 * @param nvec Length of @p vec
1119 * @param vec Name and new value
1120 * @return 0 on success, non-0 on error.
1121 *
1122 * @c vec[0] is the name, the rest is the value.
1123 */
460b9539 1124static int config_set(const struct config_state *cs,
1125 int nvec, char **vec) {
1126 const struct conf *which;
1127
1128 D(("config_set %s", vec[0]));
1129 if(!(which = find(vec[0]))) {
2e9ba080 1130 disorder_error(0, "%s:%d: unknown configuration key '%s'",
460b9539 1131 cs->path, cs->line, vec[0]);
1132 return -1;
1133 }
1134 return (which->validate(cs, nvec - 1, vec + 1)
1135 || which->type->set(cs, which, nvec - 1, vec + 1));
1136}
1137
34d37b3e
RK
1138/** @brief Set a configuration item from parameters
1139 * @param cs Configuration state
1140 * @param which Item to set
1141 * @param ... Value as strings, terminated by (char *)NULL
1142 * @return 0 on success, non-0 on error
1143 */
e6a35d1c
RK
1144static int config_set_args(const struct config_state *cs,
1145 const char *which, ...) {
1146 va_list ap;
1147 struct vector v[1];
1148 char *s;
05b3f1f6 1149 int rc;
e6a35d1c
RK
1150
1151 vector_init(v);
1152 vector_append(v, (char *)which);
1153 va_start(ap, which);
1154 while((s = va_arg(ap, char *)))
1155 vector_append(v, s);
1156 va_end(ap);
1157 vector_terminate(v);
05b3f1f6 1158 rc = config_set(cs, v->nvec, v->vec);
47854c5f
RK
1159 xfree(v->vec);
1160 return rc;
e6a35d1c
RK
1161}
1162
34d37b3e
RK
1163/** @brief Error callback used by config_include()
1164 * @param msg Error message
1165 * @param u User data (@ref config_state)
1166 */
460b9539 1167static void config_error(const char *msg, void *u) {
1168 const struct config_state *cs = u;
1169
2e9ba080 1170 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
460b9539 1171}
1172
34d37b3e
RK
1173/** @brief Include a file by name
1174 * @param c Configuration to update
1175 * @param path Path to read
1176 * @return 0 on success, non-0 on error
1177 */
460b9539 1178static int config_include(struct config *c, const char *path) {
1179 FILE *fp;
1180 char *buffer, *inputbuffer, **vec;
1181 int n, ret = 0;
1182 struct config_state cs;
1183
1184 cs.path = path;
1185 cs.line = 0;
1186 cs.config = c;
1187 D(("%s: reading configuration", path));
1188 if(!(fp = fopen(path, "r"))) {
2e9ba080 1189 disorder_error(errno, "error opening %s", path);
460b9539 1190 return -1;
1191 }
1192 while(!inputline(path, fp, &inputbuffer, '\n')) {
1193 ++cs.line;
1194 if(!(buffer = mb2utf8(inputbuffer))) {
2e9ba080 1195 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
460b9539 1196 ret = -1;
1197 xfree(inputbuffer);
1198 continue;
1199 }
1200 xfree(inputbuffer);
1201 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1202 config_error, &cs))) {
1203 ret = -1;
1204 xfree(buffer);
1205 continue;
1206 }
1207 if(n) {
34d37b3e 1208 /* 'include' is special-cased */
460b9539 1209 if(!strcmp(vec[0], "include")) {
1210 if(n != 2) {
2e9ba080 1211 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
460b9539 1212 ret = -1;
1213 } else
1214 config_include(c, vec[1]);
1215 } else
1216 ret |= config_set(&cs, n, vec);
1217 }
1218 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1219 xfree(vec);
1220 xfree(buffer);
1221 }
1222 if(ferror(fp)) {
2e9ba080 1223 disorder_error(errno, "error reading %s", path);
460b9539 1224 ret = -1;
1225 }
1226 fclose(fp);
1227 return ret;
1228}
1229
34d37b3e 1230/** @brief Default stopword setting */
86be0c30 1231static const char *const default_stopwords[] = {
1232 "stopword",
1233
1234 "01",
1235 "02",
1236 "03",
1237 "04",
1238 "05",
1239 "06",
1240 "07",
1241 "08",
1242 "09",
1243 "1",
1244 "10",
1245 "11",
1246 "12",
1247 "13",
1248 "14",
1249 "15",
1250 "16",
1251 "17",
1252 "18",
1253 "19",
1254 "2",
1255 "20",
1256 "21",
1257 "22",
1258 "23",
1259 "24",
1260 "25",
1261 "26",
1262 "27",
1263 "28",
1264 "29",
1265 "3",
1266 "30",
1267 "4",
1268 "5",
1269 "6",
1270 "7",
1271 "8",
1272 "9",
1273 "a",
1274 "am",
1275 "an",
1276 "and",
1277 "as",
1278 "for",
1279 "i",
1280 "im",
1281 "in",
1282 "is",
1283 "of",
1284 "on",
1285 "the",
1286 "to",
1287 "too",
1288 "we",
1289};
1290#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1291
34d37b3e 1292/** @brief Default player patterns */
e6a35d1c
RK
1293static const char *const default_players[] = {
1294 "*.ogg",
1295 "*.flac",
1296 "*.mp3",
1297 "*.wav",
1298};
1299#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1300
34d37b3e
RK
1301/** @brief Make a new default configuration
1302 * @return New configuration
1303 */
460b9539 1304static struct config *config_default(void) {
1305 struct config *c = xmalloc(sizeof *c);
1306 const char *logname;
1307 struct passwd *pw;
86be0c30 1308 struct config_state cs;
e6a35d1c 1309 size_t n;
460b9539 1310
86be0c30 1311 cs.path = "<internal>";
1312 cs.line = 0;
1313 cs.config = c;
460b9539 1314 /* Strings had better be xstrdup'd as they will get freed at some point. */
460b9539 1315 c->history = 60;
1316 c->home = xstrdup(pkgstatedir);
1317 if(!(pw = getpwuid(getuid())))
2e9ba080 1318 disorder_fatal(0, "cannot determine our username");
460b9539 1319 logname = pw->pw_name;
1320 c->username = xstrdup(logname);
1321 c->refresh = 15;
533272be 1322 c->refresh_min = 1;
460b9539 1323 c->signal = SIGKILL;
1324 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
460b9539 1325 c->device = xstrdup("default");
1326 c->nice_rescan = 10;
9d5da576 1327 c->speaker_command = 0;
1328 c->sample_format.bits = 16;
1329 c->sample_format.rate = 44100;
1330 c->sample_format.channels = 2;
6d2d327c 1331 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1332 c->queue_pad = 10;
cebe3127 1333 c->replay_min = 8 * 3600;
b50cfb8a 1334 c->api = NULL;
23205f9c 1335 c->multicast_ttl = 1;
61941295 1336 c->multicast_loop = 1;
637fdea3 1337 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1338 c->noticed_history = 31;
61507e3c 1339 c->short_display = 32;
bd8895a8 1340 c->mixer = 0;
1341 c->channel = 0;
8818b7fc 1342 c->dbversion = 2;
b12be54a
RK
1343 c->cookie_login_lifetime = 86400;
1344 c->cookie_key_lifetime = 86400 * 7;
2eee4b0c
RK
1345 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1346 c->sendmail = xstrdup(sendmail_binary);
bb6ae3fb 1347 c->smtp_server = xstrdup("127.0.0.1");
d742bb47 1348 c->new_max = 100;
6207d2f3 1349 c->reminder_interval = 600; /* 10m */
05dcfac6 1350 c->new_bias_age = 7 * 86400; /* 1 week */
6151ae7e 1351 c->new_bias = 4500000; /* 50 times the base weight */
419893d7 1352 c->sox_generation = DEFAULT_SOX_GENERATION;
2563dc1f 1353 c->playlist_max = INT_MAX; /* effectively no limit */
ddbf05c8 1354 c->playlist_lock_timeout = 10; /* 10s */
8488cf7d 1355 c->mount_rescan = 1;
e6a35d1c 1356 /* Default stopwords */
86be0c30 1357 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1358 exit(1);
e6a35d1c
RK
1359 /* Default player configuration */
1360 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1361 if(config_set_args(&cs, "player",
1362 default_players[n], "execraw", "disorder-decode", (char *)0))
1363 exit(1);
1364 if(config_set_args(&cs, "tracklength",
1365 default_players[n], "disorder-tracklength", (char *)0))
1366 exit(1);
1367 }
76e72f65
RK
1368 c->broadcast.af = -1;
1369 c->broadcast_from.af = -1;
80dc2c5f 1370 c->listen.af = -1;
e41a9999 1371 c->connect.af = -1;
b0116b5c 1372 c->rtp_mode = xstrdup("auto");
460b9539 1373 return c;
1374}
1375
34d37b3e
RK
1376/** @brief Construct a filename
1377 * @param c Configuration
1378 * @param name Base filename
1379 * @return Full filename
1380 *
1381 * Usually use config_get_file() instead.
1382 */
319d7107 1383char *config_get_file2(struct config *c, const char *name) {
460b9539 1384 char *s;
1385
1386 byte_xasprintf(&s, "%s/%s", c->home, name);
1387 return s;
1388}
1389
3f3bb97b 1390/** @brief Set the default configuration file */
460b9539 1391static void set_configfile(void) {
1392 if(!configfile)
1393 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1394}
1395
34d37b3e
RK
1396/** @brief Free a configuration object
1397 * @param c Configuration to free
1398 *
1399 * @p c is indeterminate after this function is called.
1400 */
47854c5f 1401void config_free(struct config *c) {
460b9539 1402 int n;
1403
1404 if(c) {
1405 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1406 conf[n].type->free(c, &conf[n]);
1407 for(n = 0; n < c->nparts; ++n)
1408 xfree(c->parts[n]);
1409 xfree(c->parts);
1410 xfree(c);
1411 }
1412}
1413
34d37b3e
RK
1414/** @brief Set post-parse defaults
1415 * @param c Configuration to update
1416 * @param server True when running in the server
1417 *
1418 * If @p server is set then certain parts of the configuration are more
1419 * strictly validated.
1420 */
c00fce3a
RK
1421static void config_postdefaults(struct config *c,
1422 int server) {
460b9539 1423 struct config_state cs;
cca89d7c 1424#if HAVE_PCRE_H
460b9539 1425 const struct conf *whoami;
1426 int n;
cca89d7c 1427#endif
460b9539 1428
cca89d7c 1429#if HAVE_PCRE_H
460b9539 1430 static const char *namepart[][4] = {
bcf50f5c 1431 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1432 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1433 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1434 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1435 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1436 };
1437#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1438
1439 static const char *transform[][5] = {
bcf50f5c 1440 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1441 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1442 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1443 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1444 { "dir", "[[:punct:]]", "", "sort", "g", }
1445 };
1446#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
cca89d7c 1447#endif
460b9539 1448
1449 cs.path = "<internal>";
1450 cs.line = 0;
1451 cs.config = c;
cca89d7c 1452#if HAVE_PCRE_H
460b9539 1453 if(!c->namepart.n) {
1454 whoami = find("namepart");
1455 for(n = 0; n < NNAMEPART; ++n)
1456 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1457 }
1458 if(!c->transform.n) {
1459 whoami = find("transform");
1460 for(n = 0; n < NTRANSFORM; ++n)
1461 set_transform(&cs, whoami, 5, (char **)transform[n]);
1462 }
cca89d7c 1463#endif
b50cfb8a 1464 if(!c->api) {
e83d0967 1465 if(c->speaker_command)
b50cfb8a 1466 c->api = xstrdup("command");
76e72f65 1467 else if(c->broadcast.af != -1)
b50cfb8a
RK
1468 c->api = xstrdup("rtp");
1469 else if(config_uaudio_apis)
06385470
RK
1470 c->api = xstrdup(uaudio_default(config_uaudio_apis,
1471 UAUDIO_API_SERVER)->name);
8aae240b 1472 else
b50cfb8a 1473 c->api = xstrdup("<none>");
e83d0967 1474 }
b50cfb8a
RK
1475 if(!strcmp(c->api, "network"))
1476 c->api = xstrdup("rtp");
c00fce3a 1477 if(server) {
b50cfb8a 1478 if(!strcmp(c->api, "command") && !c->speaker_command)
2e9ba080 1479 disorder_fatal(0, "'api command' but speaker_command is not set");
76e72f65 1480 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
2e9ba080 1481 disorder_fatal(0, "'api rtp' but broadcast is not set");
c00fce3a 1482 }
e99d42b1 1483 /* Override sample format */
b50cfb8a 1484 if(!strcmp(c->api, "rtp")) {
6d2d327c
RK
1485 c->sample_format.rate = 44100;
1486 c->sample_format.channels = 2;
1487 c->sample_format.bits = 16;
b50cfb8a
RK
1488 c->sample_format.endian = ENDIAN_NATIVE;
1489 }
1490 if(!strcmp(c->api, "coreaudio")) {
937be4c0
RK
1491 c->sample_format.rate = 44100;
1492 c->sample_format.channels = 2;
1493 c->sample_format.bits = 16;
1494 c->sample_format.endian = ENDIAN_NATIVE;
04e1fa7c
RK
1495 }
1496 if(!c->default_rights) {
1497 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1498 |RIGHT_MOVE__MASK
1499 |RIGHT_SCRATCH__MASK
1500 |RIGHT_REMOVE__MASK);
657fdb79 1501 r |= RIGHT_SCRATCH_ANY|RIGHT_MOVE_ANY|RIGHT_REMOVE_ANY;
0f55e905 1502 c->default_rights = rights_string(r);
04e1fa7c 1503 }
460b9539 1504}
1505
c00fce3a
RK
1506/** @brief (Re-)read the config file
1507 * @param server If set, do extra checking
02ba7921
RK
1508 * @param oldconfig Old configuration for compatibility check
1509 * @return 0 on success, non-0 on error
1510 *
1511 * If @p oldconfig is set, then certain compatibility checks are done between
1512 * the old and new configurations.
c00fce3a 1513 */
02ba7921
RK
1514int config_read(int server,
1515 const struct config *oldconfig) {
460b9539 1516 struct config *c;
1517 char *privconf;
1518 struct passwd *pw;
1519
1520 set_configfile();
1521 c = config_default();
9ade2319 1522 /* standalone Disobedience installs might not have a global config file */
1523 if(access(configfile, F_OK) == 0)
1524 if(config_include(c, configfile))
1525 return -1;
460b9539 1526 /* if we can read the private config file, do */
1527 if((privconf = config_private())
1528 && access(privconf, R_OK) == 0
1529 && config_include(c, privconf))
1530 return -1;
1531 xfree(privconf);
1532 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1533 if(config_per_user) {
1534 if(!(pw = getpwuid(getuid())))
2e9ba080 1535 disorder_fatal(0, "cannot determine our username");
63ad732f
RK
1536 if((privconf = config_usersysconf(pw))
1537 && access(privconf, F_OK) == 0
1538 && config_include(c, privconf))
460b9539 1539 return -1;
63ad732f
RK
1540 xfree(privconf);
1541 /* if we have a password file, read it */
5b14453f 1542 if((privconf = config_userconf(0, pw))
63ad732f
RK
1543 && access(privconf, F_OK) == 0
1544 && config_include(c, privconf))
1545 return -1;
1546 xfree(privconf);
1547 }
460b9539 1548 /* install default namepart and transform settings */
c00fce3a 1549 config_postdefaults(c, server);
02ba7921
RK
1550 if(oldconfig) {
1551 int failed = 0;
1552 if(strcmp(c->home, oldconfig->home)) {
2e9ba080 1553 disorder_error(0, "'home' cannot be changed without a restart");
02ba7921
RK
1554 failed = 1;
1555 }
1556 if(strcmp(c->alias, oldconfig->alias)) {
2e9ba080 1557 disorder_error(0, "'alias' cannot be changed without a restart");
02ba7921
RK
1558 failed = 1;
1559 }
1560 if(strcmp(c->user, oldconfig->user)) {
2e9ba080 1561 disorder_error(0, "'user' cannot be changed without a restart");
02ba7921
RK
1562 failed = 1;
1563 }
1564 if(c->nice_speaker != oldconfig->nice_speaker) {
2e9ba080 1565 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
02ba7921
RK
1566 /* ...but we accept the new config anyway */
1567 }
553d8115 1568 if(c->nice_server != oldconfig->nice_server) {
2e9ba080 1569 disorder_error(0, "'nice_server' cannot be changed without a restart");
553d8115
RK
1570 /* ...but we accept the new config anyway */
1571 }
cca89d7c 1572#if HAVE_PCRE_H
9417e1a3 1573 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
2e9ba080 1574 disorder_error(0, "'namepart' settings cannot be changed without a restart");
9417e1a3
RK
1575 failed = 1;
1576 }
cca89d7c 1577#endif
9417e1a3 1578 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
2e9ba080 1579 disorder_error(0, "'stopword' settings cannot be changed without a restart");
9417e1a3
RK
1580 failed = 1;
1581 }
02ba7921 1582 if(failed) {
2e9ba080 1583 disorder_error(0, "not installing incompatible new configuration");
02ba7921
RK
1584 return -1;
1585 }
1586 }
460b9539 1587 /* everything is good so we shall use the new config */
1588 config_free(config);
04e1fa7c 1589 /* warn about obsolete directives */
460b9539 1590 config = c;
1591 return 0;
1592}
1593
3f3bb97b 1594/** @brief Return the path to the private configuration file */
460b9539 1595char *config_private(void) {
1596 char *s;
1597
1598 set_configfile();
1599 byte_xasprintf(&s, "%s.private", configfile);
1600 return s;
1601}
1602
3f3bb97b 1603/** @brief Return the path to user's personal configuration file */
460b9539 1604char *config_userconf(const char *home, const struct passwd *pw) {
1605 char *s;
1606
73f1b9f3 1607 if(!home && !pw && !(pw = getpwuid(getuid())))
2e9ba080 1608 disorder_fatal(0, "cannot determine our username");
460b9539 1609 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1610 return s;
1611}
1612
3f3bb97b
RK
1613/** @brief Return the path to user-specific system configuration */
1614char *config_usersysconf(const struct passwd *pw) {
460b9539 1615 char *s;
1616
1617 set_configfile();
1618 if(!strchr(pw->pw_name, '/')) {
1619 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1620 return s;
1621 } else
1622 return 0;
1623}
1624
34d37b3e
RK
1625/** @brief Get a filename within the home directory
1626 * @param name Relative name
1627 * @return Full path
1628 */
460b9539 1629char *config_get_file(const char *name) {
319d7107 1630 return config_get_file2(config, name);
460b9539 1631}
1632
34d37b3e
RK
1633/** @brief Order two stringlists
1634 * @param a First stringlist
1635 * @param b Second stringlist
1636 * @return <0, 0 or >0 if a<b, a=b or a>b
1637 */
9417e1a3
RK
1638static int stringlist_compare(const struct stringlist *a,
1639 const struct stringlist *b) {
e4132fd0 1640 int n = 0, c;
9417e1a3
RK
1641
1642 while(n < a->n && n < b->n) {
1643 if((c = strcmp(a->s[n], b->s[n])))
1644 return c;
1645 ++n;
1646 }
1647 if(a->n < b->n)
1648 return -1;
1649 else if(a->n > b->n)
1650 return 1;
1651 else
1652 return 0;
1653}
1654
cca89d7c 1655#if HAVE_PCRE_H
34d37b3e
RK
1656/** @brief Order two namepart definitions
1657 * @param a First namepart definition
1658 * @param b Second namepart definition
1659 * @return <0, 0 or >0 if a<b, a=b or a>b
1660 */
9417e1a3
RK
1661static int namepart_compare(const struct namepart *a,
1662 const struct namepart *b) {
1663 int c;
1664
1665 if((c = strcmp(a->part, b->part)))
1666 return c;
1667 if((c = strcmp(a->res, b->res)))
1668 return c;
1669 if((c = strcmp(a->replace, b->replace)))
1670 return c;
1671 if((c = strcmp(a->context, b->context)))
1672 return c;
1673 if(a->reflags > b->reflags)
1674 return 1;
1675 if(a->reflags < b->reflags)
1676 return -1;
1677 return 0;
1678}
1679
34d37b3e
RK
1680/** @brief Order two lists of namepart definitions
1681 * @param a First list of namepart definitions
1682 * @param b Second list of namepart definitions
1683 * @return <0, 0 or >0 if a<b, a=b or a>b
1684 */
9417e1a3
RK
1685static int namepartlist_compare(const struct namepartlist *a,
1686 const struct namepartlist *b) {
e4132fd0 1687 int n = 0, c;
9417e1a3
RK
1688
1689 while(n < a->n && n < b->n) {
1690 if((c = namepart_compare(&a->s[n], &b->s[n])))
1691 return c;
1692 ++n;
1693 }
1694 if(a->n > b->n)
1695 return 1;
1696 else if(a->n < b->n)
1697 return -1;
1698 else
1699 return 0;
1700}
cca89d7c 1701#endif
9417e1a3 1702
2a1c84fb
RK
1703/** @brief Verify configuration table.
1704 * @return The number of problems found
1705*/
1706int config_verify(void) {
1707 int fails = 0;
05b3f1f6
RK
1708 size_t n;
1709 for(n = 1; n < sizeof conf / sizeof *conf; ++n)
2a1c84fb
RK
1710 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1711 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1712 ++fails;
1713 }
1714 return fails;
1715}
1716
460b9539 1717/*
1718Local Variables:
1719c-basic-offset:2
1720comment-column:40
1721fill-column:79
1722End:
1723*/