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