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