chiark / gitweb /
Bring CHANGES.html up to date
[disorder] / lib / configuration.c
... / ...
CommitLineData
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2008 Richard Kettlewell
4 * Portions copyright (C) 2007 Mark Wooding
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
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 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19/** @file lib/configuration.c
20 * @brief Configuration file support
21 */
22
23#include "common.h"
24
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
36#include "rights.h"
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"
46#include "mixer.h"
47#include "printf.h"
48#include "regsub.h"
49#include "signame.h"
50#include "authhash.h"
51#include "vector.h"
52
53/** @brief Path to config file
54 *
55 * set_configfile() sets the deafult if it is null.
56 */
57char *configfile;
58
59/** @brief Read user configuration
60 *
61 * If clear, the user-specific configuration is not read.
62 */
63int config_per_user = 1;
64
65/** @brief Config file parser state */
66struct config_state {
67 /** @brief Filename */
68 const char *path;
69 /** @brief Line number */
70 int line;
71 /** @brief Configuration object under construction */
72 struct config *config;
73};
74
75/** @brief Current configuration */
76struct config *config;
77
78/** @brief One configuration item */
79struct conf {
80 /** @brief Name as it appears in the config file */
81 const char *name;
82 /** @brief Offset in @ref config structure */
83 size_t offset;
84 /** @brief Pointer to item type */
85 const struct conftype *type;
86 /** @brief Pointer to item-specific validation routine */
87 int (*validate)(const struct config_state *cs,
88 int nvec, char **vec);
89};
90
91/** @brief Type of a configuration item */
92struct conftype {
93 /** @brief Pointer to function to set item */
94 int (*set)(const struct config_state *cs,
95 const struct conf *whoami,
96 int nvec, char **vec);
97 /** @brief Pointer to function to free item */
98 void (*free)(struct config *c, const struct conf *whoami);
99};
100
101/** @brief Compute the address of an item */
102#define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
103/** @brief Return the value of an item */
104#define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
105
106static int set_signal(const struct config_state *cs,
107 const struct conf *whoami,
108 int nvec, char **vec) {
109 int n;
110
111 if(nvec != 1) {
112 error(0, "%s:%d: '%s' requires one argument",
113 cs->path, cs->line, whoami->name);
114 return -1;
115 }
116 if((n = find_signal(vec[0])) == -1) {
117 error(0, "%s:%d: unknown signal '%s'",
118 cs->path, cs->line, vec[0]);
119 return -1;
120 }
121 VALUE(cs->config, int) = n;
122 return 0;
123}
124
125static int set_collections(const struct config_state *cs,
126 const struct conf *whoami,
127 int nvec, char **vec) {
128 struct collectionlist *cl;
129 const char *root, *encoding, *module;
130
131 switch(nvec) {
132 case 1:
133 module = 0;
134 encoding = 0;
135 root = vec[0];
136 break;
137 case 2:
138 module = vec[0];
139 encoding = 0;
140 root = vec[1];
141 break;
142 case 3:
143 module = vec[0];
144 encoding = vec[1];
145 root = vec[2];
146 break;
147 case 0:
148 error(0, "%s:%d: '%s' requires at least one argument",
149 cs->path, cs->line, whoami->name);
150 return -1;
151 default:
152 error(0, "%s:%d: '%s' requires at most three arguments",
153 cs->path, cs->line, whoami->name);
154 return -1;
155 }
156 /* Sanity check root */
157 if(root[0] != '/') {
158 error(0, "%s:%d: collection root must start with '/'",
159 cs->path, cs->line);
160 return -1;
161 }
162 if(root[1] && root[strlen(root)-1] == '/') {
163 error(0, "%s:%d: collection root must not end with '/'",
164 cs->path, cs->line);
165 return -1;
166 }
167 /* Defaults */
168 if(!module)
169 module = "fs";
170 if(!encoding)
171 encoding = nl_langinfo(CODESET);
172 cl = ADDRESS(cs->config, struct collectionlist);
173 ++cl->n;
174 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
175 cl->s[cl->n - 1].module = xstrdup(module);
176 cl->s[cl->n - 1].encoding = xstrdup(encoding);
177 cl->s[cl->n - 1].root = xstrdup(root);
178 return 0;
179}
180
181static int set_boolean(const struct config_state *cs,
182 const struct conf *whoami,
183 int nvec, char **vec) {
184 int state;
185
186 if(nvec != 1) {
187 error(0, "%s:%d: '%s' takes only one argument",
188 cs->path, cs->line, whoami->name);
189 return -1;
190 }
191 if(!strcmp(vec[0], "yes")) state = 1;
192 else if(!strcmp(vec[0], "no")) state = 0;
193 else {
194 error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
195 cs->path, cs->line, whoami->name);
196 return -1;
197 }
198 VALUE(cs->config, int) = state;
199 return 0;
200}
201
202static int set_string(const struct config_state *cs,
203 const struct conf *whoami,
204 int nvec, char **vec) {
205 if(nvec != 1) {
206 error(0, "%s:%d: '%s' takes only one argument",
207 cs->path, cs->line, whoami->name);
208 return -1;
209 }
210 VALUE(cs->config, char *) = xstrdup(vec[0]);
211 return 0;
212}
213
214static int set_stringlist(const struct config_state *cs,
215 const struct conf *whoami,
216 int nvec, char **vec) {
217 int n;
218 struct stringlist *sl;
219
220 sl = ADDRESS(cs->config, struct stringlist);
221 sl->n = 0;
222 for(n = 0; n < nvec; ++n) {
223 sl->n++;
224 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
225 sl->s[sl->n - 1] = xstrdup(vec[n]);
226 }
227 return 0;
228}
229
230static int set_integer(const struct config_state *cs,
231 const struct conf *whoami,
232 int nvec, char **vec) {
233 char *e;
234
235 if(nvec != 1) {
236 error(0, "%s:%d: '%s' takes only one argument",
237 cs->path, cs->line, whoami->name);
238 return -1;
239 }
240 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
241 error(errno, "%s:%d: converting integer", cs->path, cs->line);
242 return -1;
243 }
244 if(*e) {
245 error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
246 return -1;
247 }
248 return 0;
249}
250
251static int set_stringlist_accum(const struct config_state *cs,
252 const struct conf *whoami,
253 int nvec, char **vec) {
254 int n;
255 struct stringlist *s;
256 struct stringlistlist *sll;
257
258 sll = ADDRESS(cs->config, struct stringlistlist);
259 if(nvec == 0) {
260 sll->n = 0;
261 return 0;
262 }
263 sll->n++;
264 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
265 s = &sll->s[sll->n - 1];
266 s->n = nvec;
267 s->s = xmalloc((nvec + 1) * sizeof (char *));
268 for(n = 0; n < nvec; ++n)
269 s->s[n] = xstrdup(vec[n]);
270 return 0;
271}
272
273static int set_string_accum(const struct config_state *cs,
274 const struct conf *whoami,
275 int nvec, char **vec) {
276 int n;
277 struct stringlist *sl;
278
279 sl = ADDRESS(cs->config, struct stringlist);
280 if(nvec == 0) {
281 sl->n = 0;
282 return 0;
283 }
284 for(n = 0; n < nvec; ++n) {
285 sl->n++;
286 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
287 sl->s[sl->n - 1] = xstrdup(vec[n]);
288 }
289 return 0;
290}
291
292static int set_restrict(const struct config_state *cs,
293 const struct conf *whoami,
294 int nvec, char **vec) {
295 unsigned r = 0;
296 int n, i;
297
298 static const struct restriction {
299 const char *name;
300 unsigned bit;
301 } restrictions[] = {
302 { "remove", RESTRICT_REMOVE },
303 { "scratch", RESTRICT_SCRATCH },
304 { "move", RESTRICT_MOVE },
305 };
306
307 for(n = 0; n < nvec; ++n) {
308 if((i = TABLE_FIND(restrictions, name, vec[n])) < 0) {
309 error(0, "%s:%d: invalid restriction '%s'",
310 cs->path, cs->line, vec[n]);
311 return -1;
312 }
313 r |= restrictions[i].bit;
314 }
315 VALUE(cs->config, unsigned) = r;
316 return 0;
317}
318
319static int parse_sample_format(const struct config_state *cs,
320 struct stream_header *format,
321 int nvec, char **vec) {
322 char *p = vec[0];
323 long t;
324
325 if(nvec != 1) {
326 error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
327 return -1;
328 }
329 if(xstrtol(&t, p, &p, 0)) {
330 error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line);
331 return -1;
332 }
333 if(t != 8 && t != 16) {
334 error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t);
335 return -1;
336 }
337 if(format) format->bits = t;
338 switch (*p) {
339 case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
340 case 'b': case 'B': t = ENDIAN_BIG; p++; break;
341 default: t = ENDIAN_NATIVE; break;
342 }
343 if(format) format->endian = t;
344 if(*p != '/') {
345 error(errno, "%s:%d: expected `/' after bits-per-sample",
346 cs->path, cs->line);
347 return -1;
348 }
349 p++;
350 if(xstrtol(&t, p, &p, 0)) {
351 error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
352 return -1;
353 }
354 if(t < 1 || t > INT_MAX) {
355 error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
356 return -1;
357 }
358 if(format) format->rate = t;
359 if(*p != '/') {
360 error(0, "%s:%d: expected `/' after sample-rate",
361 cs->path, cs->line);
362 return -1;
363 }
364 p++;
365 if(xstrtol(&t, p, &p, 0)) {
366 error(errno, "%s:%d: converting channels", cs->path, cs->line);
367 return -1;
368 }
369 if(t < 1 || t > 8) {
370 error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t);
371 return -1;
372 }
373 if(format) format->channels = t;
374 if(*p) {
375 error(0, "%s:%d: junk after channels", cs->path, cs->line);
376 return -1;
377 }
378 return 0;
379}
380
381static int set_sample_format(const struct config_state *cs,
382 const struct conf *whoami,
383 int nvec, char **vec) {
384 return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
385 nvec, vec);
386}
387
388static int set_namepart(const struct config_state *cs,
389 const struct conf *whoami,
390 int nvec, char **vec) {
391 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
392 unsigned reflags;
393 const char *errstr;
394 int erroffset, n;
395 pcre *re;
396
397 if(nvec < 3) {
398 error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line);
399 return -1;
400 }
401 if(nvec > 5) {
402 error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line);
403 return -1;
404 }
405 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
406 if(!(re = pcre_compile(vec[1],
407 PCRE_UTF8
408 |regsub_compile_options(reflags),
409 &errstr, &erroffset, 0))) {
410 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
411 cs->path, cs->line, vec[1], errstr, erroffset);
412 return -1;
413 }
414 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
415 npl->s[npl->n].part = xstrdup(vec[0]);
416 npl->s[npl->n].re = re;
417 npl->s[npl->n].replace = xstrdup(vec[2]);
418 npl->s[npl->n].context = xstrdup(vec[3]);
419 npl->s[npl->n].reflags = reflags;
420 ++npl->n;
421 /* XXX a bit of a bodge; relies on there being very few parts. */
422 for(n = 0; (n < cs->config->nparts
423 && strcmp(cs->config->parts[n], vec[0])); ++n)
424 ;
425 if(n >= cs->config->nparts) {
426 cs->config->parts = xrealloc(cs->config->parts,
427 (cs->config->nparts + 1) * sizeof (char *));
428 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
429 }
430 return 0;
431}
432
433static int set_transform(const struct config_state *cs,
434 const struct conf *whoami,
435 int nvec, char **vec) {
436 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
437 pcre *re;
438 unsigned reflags;
439 const char *errstr;
440 int erroffset;
441
442 if(nvec < 3) {
443 error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line);
444 return -1;
445 }
446 if(nvec > 5) {
447 error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line);
448 return -1;
449 }
450 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
451 if(!(re = pcre_compile(vec[1],
452 PCRE_UTF8
453 |regsub_compile_options(reflags),
454 &errstr, &erroffset, 0))) {
455 error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)",
456 cs->path, cs->line, vec[1], errstr, erroffset);
457 return -1;
458 }
459 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
460 tl->t[tl->n].type = xstrdup(vec[0]);
461 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
462 tl->t[tl->n].re = re;
463 tl->t[tl->n].replace = xstrdup(vec[2]);
464 tl->t[tl->n].flags = reflags;
465 ++tl->n;
466 return 0;
467}
468
469static int set_backend(const struct config_state *cs,
470 const struct conf *whoami,
471 int nvec, char **vec) {
472 int *const valuep = ADDRESS(cs->config, int);
473
474 if(nvec != 1) {
475 error(0, "%s:%d: '%s' requires one argument",
476 cs->path, cs->line, whoami->name);
477 return -1;
478 }
479 if(!strcmp(vec[0], "alsa")) {
480#if HAVE_ALSA_ASOUNDLIB_H
481 *valuep = BACKEND_ALSA;
482#else
483 error(0, "%s:%d: ALSA is not available on this platform",
484 cs->path, cs->line);
485 return -1;
486#endif
487 } else if(!strcmp(vec[0], "command"))
488 *valuep = BACKEND_COMMAND;
489 else if(!strcmp(vec[0], "network"))
490 *valuep = BACKEND_NETWORK;
491 else if(!strcmp(vec[0], "coreaudio")) {
492#if HAVE_COREAUDIO_AUDIOHARDWARE_H
493 *valuep = BACKEND_COREAUDIO;
494#else
495 error(0, "%s:%d: Core Audio is not available on this platform",
496 cs->path, cs->line);
497 return -1;
498#endif
499 } else if(!strcmp(vec[0], "oss")) {
500#if HAVE_SYS_SOUNDCARD_H
501 *valuep = BACKEND_OSS;
502#else
503 error(0, "%s:%d: OSS is not available on this platform",
504 cs->path, cs->line);
505 return -1;
506#endif
507 } else {
508 error(0, "%s:%d: invalid '%s' value '%s'",
509 cs->path, cs->line, whoami->name, vec[0]);
510 return -1;
511 }
512 return 0;
513}
514
515static int set_rights(const struct config_state *cs,
516 const struct conf *whoami,
517 int nvec, char **vec) {
518 if(nvec != 1) {
519 error(0, "%s:%d: '%s' requires one argument",
520 cs->path, cs->line, whoami->name);
521 return -1;
522 }
523 if(parse_rights(vec[0], 0, 1)) {
524 error(0, "%s:%d: invalid rights string '%s'",
525 cs->path, cs->line, vec[0]);
526 return -1;
527 }
528 *ADDRESS(cs->config, char *) = vec[0];
529 return 0;
530}
531
532/* free functions */
533
534static void free_none(struct config attribute((unused)) *c,
535 const struct conf attribute((unused)) *whoami) {
536}
537
538static void free_string(struct config *c,
539 const struct conf *whoami) {
540 xfree(VALUE(c, char *));
541}
542
543static void free_stringlist(struct config *c,
544 const struct conf *whoami) {
545 int n;
546 struct stringlist *sl = ADDRESS(c, struct stringlist);
547
548 for(n = 0; n < sl->n; ++n)
549 xfree(sl->s[n]);
550 xfree(sl->s);
551}
552
553static void free_stringlistlist(struct config *c,
554 const struct conf *whoami) {
555 int n, m;
556 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
557 struct stringlist *sl;
558
559 for(n = 0; n < sll->n; ++n) {
560 sl = &sll->s[n];
561 for(m = 0; m < sl->n; ++m)
562 xfree(sl->s[m]);
563 xfree(sl->s);
564 }
565 xfree(sll->s);
566}
567
568static void free_collectionlist(struct config *c,
569 const struct conf *whoami) {
570 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
571 struct collection *cl;
572 int n;
573
574 for(n = 0; n < cll->n; ++n) {
575 cl = &cll->s[n];
576 xfree(cl->module);
577 xfree(cl->encoding);
578 xfree(cl->root);
579 }
580 xfree(cll->s);
581}
582
583static void free_namepartlist(struct config *c,
584 const struct conf *whoami) {
585 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
586 struct namepart *np;
587 int n;
588
589 for(n = 0; n < npl->n; ++n) {
590 np = &npl->s[n];
591 xfree(np->part);
592 pcre_free(np->re); /* ...whatever pcre_free is set to. */
593 xfree(np->replace);
594 xfree(np->context);
595 }
596 xfree(npl->s);
597}
598
599static void free_transformlist(struct config *c,
600 const struct conf *whoami) {
601 struct transformlist *tl = ADDRESS(c, struct transformlist);
602 struct transform *t;
603 int n;
604
605 for(n = 0; n < tl->n; ++n) {
606 t = &tl->t[n];
607 xfree(t->type);
608 pcre_free(t->re); /* ...whatever pcre_free is set to. */
609 xfree(t->replace);
610 xfree(t->context);
611 }
612 xfree(tl->t);
613}
614
615/* configuration types */
616
617static const struct conftype
618 type_signal = { set_signal, free_none },
619 type_collections = { set_collections, free_collectionlist },
620 type_boolean = { set_boolean, free_none },
621 type_string = { set_string, free_string },
622 type_stringlist = { set_stringlist, free_stringlist },
623 type_integer = { set_integer, free_none },
624 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
625 type_string_accum = { set_string_accum, free_stringlist },
626 type_sample_format = { set_sample_format, free_none },
627 type_restrict = { set_restrict, free_none },
628 type_namepart = { set_namepart, free_namepartlist },
629 type_transform = { set_transform, free_transformlist },
630 type_rights = { set_rights, free_none },
631 type_backend = { set_backend, free_none };
632
633/* specific validation routine */
634
635#define VALIDATE_FILE(test, what) do { \
636 struct stat sb; \
637 int n; \
638 \
639 for(n = 0; n < nvec; ++n) { \
640 if(stat(vec[n], &sb) < 0) { \
641 error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \
642 return -1; \
643 } \
644 if(!test(sb.st_mode)) { \
645 error(0, "%s:%d: %s is not a %s", \
646 cs->path, cs->line, vec[n], what); \
647 return -1; \
648 } \
649 } \
650} while(0)
651
652static int validate_isabspath(const struct config_state *cs,
653 int nvec, char **vec) {
654 int n;
655
656 for(n = 0; n < nvec; ++n)
657 if(vec[n][0] != '/') {
658 error(errno, "%s:%d: %s: not an absolute path",
659 cs->path, cs->line, vec[n]);
660 return -1;
661 }
662 return 0;
663}
664
665static int validate_isdir(const struct config_state *cs,
666 int nvec, char **vec) {
667 VALIDATE_FILE(S_ISDIR, "directory");
668 return 0;
669}
670
671static int validate_isreg(const struct config_state *cs,
672 int nvec, char **vec) {
673 VALIDATE_FILE(S_ISREG, "regular file");
674 return 0;
675}
676
677static int validate_player(const struct config_state *cs,
678 int nvec,
679 char attribute((unused)) **vec) {
680 if(nvec < 2) {
681 error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
682 cs->path, cs->line);
683 return -1;
684 }
685 return 0;
686}
687
688static int validate_tracklength(const struct config_state *cs,
689 int nvec,
690 char attribute((unused)) **vec) {
691 if(nvec < 2) {
692 error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
693 cs->path, cs->line);
694 return -1;
695 }
696 return 0;
697}
698
699static int validate_allow(const struct config_state *cs,
700 int nvec,
701 char attribute((unused)) **vec) {
702 if(nvec != 2) {
703 error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
704 return -1;
705 }
706 return 0;
707}
708
709static int validate_non_negative(const struct config_state *cs,
710 int nvec, char **vec) {
711 long n;
712
713 if(nvec < 1) {
714 error(0, "%s:%d: missing argument", cs->path, cs->line);
715 return -1;
716 }
717 if(nvec > 1) {
718 error(0, "%s:%d: too many arguments", cs->path, cs->line);
719 return -1;
720 }
721 if(xstrtol(&n, vec[0], 0, 0)) {
722 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
723 return -1;
724 }
725 if(n < 0) {
726 error(0, "%s:%d: must not be negative", cs->path, cs->line);
727 return -1;
728 }
729 return 0;
730}
731
732static int validate_positive(const struct config_state *cs,
733 int nvec, char **vec) {
734 long n;
735
736 if(nvec < 1) {
737 error(0, "%s:%d: missing argument", cs->path, cs->line);
738 return -1;
739 }
740 if(nvec > 1) {
741 error(0, "%s:%d: too many arguments", cs->path, cs->line);
742 return -1;
743 }
744 if(xstrtol(&n, vec[0], 0, 0)) {
745 error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
746 return -1;
747 }
748 if(n <= 0) {
749 error(0, "%s:%d: must be positive", cs->path, cs->line);
750 return -1;
751 }
752 return 0;
753}
754
755static int validate_isauser(const struct config_state *cs,
756 int attribute((unused)) nvec,
757 char **vec) {
758 struct passwd *pw;
759
760 if(!(pw = getpwnam(vec[0]))) {
761 error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
762 return -1;
763 }
764 return 0;
765}
766
767static int validate_sample_format(const struct config_state *cs,
768 int attribute((unused)) nvec,
769 char **vec) {
770 return parse_sample_format(cs, 0, nvec, vec);
771}
772
773static int validate_any(const struct config_state attribute((unused)) *cs,
774 int attribute((unused)) nvec,
775 char attribute((unused)) **vec) {
776 return 0;
777}
778
779static int validate_url(const struct config_state attribute((unused)) *cs,
780 int attribute((unused)) nvec,
781 char **vec) {
782 const char *s;
783 int n;
784 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
785 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
786 s = vec[0];
787 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
788 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
789 "0123456789"));
790 if(s[n] != ':') {
791 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
792 return -1;
793 }
794 if(!strncmp(s, "http:", 5)
795 || !strncmp(s, "https:", 6)) {
796 s += n + 1;
797 /* we only do a rather cursory check */
798 if(strncmp(s, "//", 2)) {
799 error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
800 return -1;
801 }
802 }
803 return 0;
804}
805
806static int validate_alias(const struct config_state *cs,
807 int nvec,
808 char **vec) {
809 const char *s;
810 int in_brackets = 0, c;
811
812 if(nvec < 1) {
813 error(0, "%s:%d: missing argument", cs->path, cs->line);
814 return -1;
815 }
816 if(nvec > 1) {
817 error(0, "%s:%d: too many arguments", cs->path, cs->line);
818 return -1;
819 }
820 s = vec[0];
821 while((c = (unsigned char)*s++)) {
822 if(in_brackets) {
823 if(c == '}')
824 in_brackets = 0;
825 else if(!isalnum(c)) {
826 error(0, "%s:%d: invalid part name in alias expansion in '%s'",
827 cs->path, cs->line, vec[0]);
828 return -1;
829 }
830 } else {
831 if(c == '{') {
832 in_brackets = 1;
833 if(*s == '/')
834 ++s;
835 } else if(c == '\\') {
836 if(!(c = (unsigned char)*s++)) {
837 error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
838 cs->path, cs->line, vec[0]);
839 return -1;
840 } else if(c != '\\' && c != '{') {
841 error(0, "%s:%d: invalid escape in alias expansion in '%s'",
842 cs->path, cs->line, vec[0]);
843 return -1;
844 }
845 }
846 }
847 ++s;
848 }
849 if(in_brackets) {
850 error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
851 cs->path, cs->line, vec[0]);
852 return -1;
853 }
854 return 0;
855}
856
857static int validate_addrport(const struct config_state attribute((unused)) *cs,
858 int nvec,
859 char attribute((unused)) **vec) {
860 switch(nvec) {
861 case 0:
862 error(0, "%s:%d: missing address",
863 cs->path, cs->line);
864 return -1;
865 case 1:
866 error(0, "%s:%d: missing port name/number",
867 cs->path, cs->line);
868 return -1;
869 case 2:
870 return 0;
871 default:
872 error(0, "%s:%d: expected ADDRESS PORT",
873 cs->path, cs->line);
874 return -1;
875 }
876}
877
878static int validate_port(const struct config_state attribute((unused)) *cs,
879 int nvec,
880 char attribute((unused)) **vec) {
881 switch(nvec) {
882 case 0:
883 error(0, "%s:%d: missing address",
884 cs->path, cs->line);
885 return -1;
886 case 1:
887 case 2:
888 return 0;
889 default:
890 error(0, "%s:%d: expected [ADDRESS] PORT",
891 cs->path, cs->line);
892 return -1;
893 }
894}
895
896static int validate_algo(const struct config_state attribute((unused)) *cs,
897 int nvec,
898 char **vec) {
899 if(nvec != 1) {
900 error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
901 return -1;
902 }
903 if(!valid_authhash(vec[0])) {
904 error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
905 return -1;
906 }
907 return 0;
908}
909
910/** @brief Item name and and offset */
911#define C(x) #x, offsetof(struct config, x)
912/** @brief Item name and and offset */
913#define C2(x,y) #x, offsetof(struct config, y)
914
915/** @brief All configuration items */
916static const struct conf conf[] = {
917 { C(alias), &type_string, validate_alias },
918 { C(allow), &type_stringlist_accum, validate_allow },
919 { C(api), &type_backend, validate_any },
920 { C(authorization_algorithm), &type_string, validate_algo },
921 { C(broadcast), &type_stringlist, validate_addrport },
922 { C(broadcast_from), &type_stringlist, validate_addrport },
923 { C(channel), &type_string, validate_any },
924 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
925 { C(checkpoint_min), &type_integer, validate_non_negative },
926 { C(collection), &type_collections, validate_any },
927 { C(connect), &type_stringlist, validate_addrport },
928 { C(cookie_login_lifetime), &type_integer, validate_positive },
929 { C(cookie_key_lifetime), &type_integer, validate_positive },
930 { C(dbversion), &type_integer, validate_positive },
931 { C(default_rights), &type_rights, validate_any },
932 { C(device), &type_string, validate_any },
933 { C(gap), &type_integer, validate_non_negative },
934 { C(history), &type_integer, validate_positive },
935 { C(home), &type_string, validate_isabspath },
936 { C(listen), &type_stringlist, validate_port },
937 { C(lock), &type_boolean, validate_any },
938 { C(mail_sender), &type_string, validate_any },
939 { C(mixer), &type_string, validate_any },
940 { C(multicast_loop), &type_boolean, validate_any },
941 { C(multicast_ttl), &type_integer, validate_non_negative },
942 { C(namepart), &type_namepart, validate_any },
943 { C(new_bias), &type_integer, validate_positive },
944 { C(new_bias_age), &type_integer, validate_positive },
945 { C(new_max), &type_integer, validate_positive },
946 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
947 { C(nice_rescan), &type_integer, validate_non_negative },
948 { C(nice_server), &type_integer, validate_any },
949 { C(nice_speaker), &type_integer, validate_any },
950 { C(noticed_history), &type_integer, validate_positive },
951 { C(password), &type_string, validate_any },
952 { C(player), &type_stringlist_accum, validate_player },
953 { C(plugins), &type_string_accum, validate_isdir },
954 { C(prefsync), &type_integer, validate_positive },
955 { C(queue_pad), &type_integer, validate_positive },
956 { C(replay_min), &type_integer, validate_non_negative },
957 { C(refresh), &type_integer, validate_positive },
958 { C(reminder_interval), &type_integer, validate_positive },
959 { C(remote_userman), &type_boolean, validate_any },
960 { C2(restrict, restrictions), &type_restrict, validate_any },
961 { C(sample_format), &type_sample_format, validate_sample_format },
962 { C(scratch), &type_string_accum, validate_isreg },
963 { C(sendmail), &type_string, validate_isabspath },
964 { C(short_display), &type_integer, validate_positive },
965 { C(signal), &type_signal, validate_any },
966 { C(smtp_server), &type_string, validate_any },
967 { C(sox_generation), &type_integer, validate_non_negative },
968 { C2(speaker_backend, api), &type_backend, validate_any },
969 { C(speaker_command), &type_string, validate_any },
970 { C(stopword), &type_string_accum, validate_any },
971 { C(templates), &type_string_accum, validate_isdir },
972 { C(tracklength), &type_stringlist_accum, validate_tracklength },
973 { C(transform), &type_transform, validate_any },
974 { C(trust), &type_string_accum, validate_any },
975 { C(url), &type_string, validate_url },
976 { C(user), &type_string, validate_isauser },
977 { C(username), &type_string, validate_any },
978};
979
980/** @brief Find a configuration item's definition by key */
981static const struct conf *find(const char *key) {
982 int n;
983
984 if((n = TABLE_FIND(conf, name, key)) < 0)
985 return 0;
986 return &conf[n];
987}
988
989/** @brief Set a new configuration value */
990static int config_set(const struct config_state *cs,
991 int nvec, char **vec) {
992 const struct conf *which;
993
994 D(("config_set %s", vec[0]));
995 if(!(which = find(vec[0]))) {
996 error(0, "%s:%d: unknown configuration key '%s'",
997 cs->path, cs->line, vec[0]);
998 return -1;
999 }
1000 return (which->validate(cs, nvec - 1, vec + 1)
1001 || which->type->set(cs, which, nvec - 1, vec + 1));
1002}
1003
1004static int config_set_args(const struct config_state *cs,
1005 const char *which, ...) {
1006 va_list ap;
1007 struct vector v[1];
1008 char *s;
1009
1010 vector_init(v);
1011 vector_append(v, (char *)which);
1012 va_start(ap, which);
1013 while((s = va_arg(ap, char *)))
1014 vector_append(v, s);
1015 va_end(ap);
1016 vector_terminate(v);
1017 return config_set(cs, v->nvec, v->vec);
1018}
1019
1020/** @brief Error callback used by config_include() */
1021static void config_error(const char *msg, void *u) {
1022 const struct config_state *cs = u;
1023
1024 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1025}
1026
1027/** @brief Include a file by name */
1028static int config_include(struct config *c, const char *path) {
1029 FILE *fp;
1030 char *buffer, *inputbuffer, **vec;
1031 int n, ret = 0;
1032 struct config_state cs;
1033
1034 cs.path = path;
1035 cs.line = 0;
1036 cs.config = c;
1037 D(("%s: reading configuration", path));
1038 if(!(fp = fopen(path, "r"))) {
1039 error(errno, "error opening %s", path);
1040 return -1;
1041 }
1042 while(!inputline(path, fp, &inputbuffer, '\n')) {
1043 ++cs.line;
1044 if(!(buffer = mb2utf8(inputbuffer))) {
1045 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1046 ret = -1;
1047 xfree(inputbuffer);
1048 continue;
1049 }
1050 xfree(inputbuffer);
1051 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1052 config_error, &cs))) {
1053 ret = -1;
1054 xfree(buffer);
1055 continue;
1056 }
1057 if(n) {
1058 if(!strcmp(vec[0], "include")) {
1059 if(n != 2) {
1060 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1061 ret = -1;
1062 } else
1063 config_include(c, vec[1]);
1064 } else
1065 ret |= config_set(&cs, n, vec);
1066 }
1067 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1068 xfree(vec);
1069 xfree(buffer);
1070 }
1071 if(ferror(fp)) {
1072 error(errno, "error reading %s", path);
1073 ret = -1;
1074 }
1075 fclose(fp);
1076 return ret;
1077}
1078
1079static const char *const default_stopwords[] = {
1080 "stopword",
1081
1082 "01",
1083 "02",
1084 "03",
1085 "04",
1086 "05",
1087 "06",
1088 "07",
1089 "08",
1090 "09",
1091 "1",
1092 "10",
1093 "11",
1094 "12",
1095 "13",
1096 "14",
1097 "15",
1098 "16",
1099 "17",
1100 "18",
1101 "19",
1102 "2",
1103 "20",
1104 "21",
1105 "22",
1106 "23",
1107 "24",
1108 "25",
1109 "26",
1110 "27",
1111 "28",
1112 "29",
1113 "3",
1114 "30",
1115 "4",
1116 "5",
1117 "6",
1118 "7",
1119 "8",
1120 "9",
1121 "a",
1122 "am",
1123 "an",
1124 "and",
1125 "as",
1126 "for",
1127 "i",
1128 "im",
1129 "in",
1130 "is",
1131 "of",
1132 "on",
1133 "the",
1134 "to",
1135 "too",
1136 "we",
1137};
1138#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1139
1140static const char *const default_players[] = {
1141 "*.ogg",
1142 "*.flac",
1143 "*.mp3",
1144 "*.wav",
1145};
1146#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1147
1148/** @brief Make a new default configuration */
1149static struct config *config_default(void) {
1150 struct config *c = xmalloc(sizeof *c);
1151 const char *logname;
1152 struct passwd *pw;
1153 struct config_state cs;
1154 size_t n;
1155
1156 cs.path = "<internal>";
1157 cs.line = 0;
1158 cs.config = c;
1159 /* Strings had better be xstrdup'd as they will get freed at some point. */
1160 c->gap = 0;
1161 c->history = 60;
1162 c->home = xstrdup(pkgstatedir);
1163 if(!(pw = getpwuid(getuid())))
1164 fatal(0, "cannot determine our username");
1165 logname = pw->pw_name;
1166 c->username = xstrdup(logname);
1167 c->refresh = 15;
1168 c->prefsync = 3600;
1169 c->signal = SIGKILL;
1170 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1171 c->lock = 1;
1172 c->device = xstrdup("default");
1173 c->nice_rescan = 10;
1174 c->speaker_command = 0;
1175 c->sample_format.bits = 16;
1176 c->sample_format.rate = 44100;
1177 c->sample_format.channels = 2;
1178 c->sample_format.endian = ENDIAN_NATIVE;
1179 c->queue_pad = 10;
1180 c->replay_min = 8 * 3600;
1181 c->api = -1;
1182 c->multicast_ttl = 1;
1183 c->multicast_loop = 1;
1184 c->authorization_algorithm = xstrdup("sha1");
1185 c->noticed_history = 31;
1186 c->short_display = 32;
1187 c->mixer = 0;
1188 c->channel = 0;
1189 c->dbversion = 2;
1190 c->cookie_login_lifetime = 86400;
1191 c->cookie_key_lifetime = 86400 * 7;
1192 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1193 c->sendmail = xstrdup(sendmail_binary);
1194 c->smtp_server = xstrdup("127.0.0.1");
1195 c->new_max = 100;
1196 c->reminder_interval = 600; /* 10m */
1197 c->new_bias_age = 7 * 86400; /* 1 week */
1198 c->new_bias = 4500000; /* 50 times the base weight */
1199 c->sox_generation = DEFAULT_SOX_GENERATION;
1200 /* Default stopwords */
1201 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1202 exit(1);
1203 /* Default player configuration */
1204 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1205 if(config_set_args(&cs, "player",
1206 default_players[n], "execraw", "disorder-decode", (char *)0))
1207 exit(1);
1208 if(config_set_args(&cs, "tracklength",
1209 default_players[n], "disorder-tracklength", (char *)0))
1210 exit(1);
1211 }
1212 return c;
1213}
1214
1215char *config_get_file2(struct config *c, const char *name) {
1216 char *s;
1217
1218 byte_xasprintf(&s, "%s/%s", c->home, name);
1219 return s;
1220}
1221
1222/** @brief Set the default configuration file */
1223static void set_configfile(void) {
1224 if(!configfile)
1225 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1226}
1227
1228/** @brief Free a configuration object */
1229static void config_free(struct config *c) {
1230 int n;
1231
1232 if(c) {
1233 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1234 conf[n].type->free(c, &conf[n]);
1235 for(n = 0; n < c->nparts; ++n)
1236 xfree(c->parts[n]);
1237 xfree(c->parts);
1238 xfree(c);
1239 }
1240}
1241
1242/** @brief Set post-parse defaults */
1243static void config_postdefaults(struct config *c,
1244 int server) {
1245 struct config_state cs;
1246 const struct conf *whoami;
1247 int n;
1248
1249 static const char *namepart[][4] = {
1250 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1251 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1252 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1253 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1254 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1255 };
1256#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1257
1258 static const char *transform[][5] = {
1259 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1260 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1261 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1262 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1263 { "dir", "[[:punct:]]", "", "sort", "g", }
1264 };
1265#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1266
1267 cs.path = "<internal>";
1268 cs.line = 0;
1269 cs.config = c;
1270 if(!c->namepart.n) {
1271 whoami = find("namepart");
1272 for(n = 0; n < NNAMEPART; ++n)
1273 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1274 }
1275 if(!c->transform.n) {
1276 whoami = find("transform");
1277 for(n = 0; n < NTRANSFORM; ++n)
1278 set_transform(&cs, whoami, 5, (char **)transform[n]);
1279 }
1280 if(c->api == -1) {
1281 if(c->speaker_command)
1282 c->api = BACKEND_COMMAND;
1283 else if(c->broadcast.n)
1284 c->api = BACKEND_NETWORK;
1285 else
1286 c->api = DEFAULT_BACKEND;
1287 }
1288 if(server) {
1289 if(c->api == BACKEND_COMMAND && !c->speaker_command)
1290 fatal(0, "'api command' but speaker_command is not set");
1291 if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1292 fatal(0, "'api network' but broadcast is not set");
1293 }
1294 /* Override sample format */
1295 switch(c->api) {
1296 case BACKEND_NETWORK:
1297 c->sample_format.rate = 44100;
1298 c->sample_format.channels = 2;
1299 c->sample_format.bits = 16;
1300 c->sample_format.endian = ENDIAN_BIG;
1301 break;
1302 case BACKEND_COREAUDIO:
1303 c->sample_format.rate = 44100;
1304 c->sample_format.channels = 2;
1305 c->sample_format.bits = 16;
1306 c->sample_format.endian = ENDIAN_NATIVE;
1307 break;
1308 }
1309 if(!c->default_rights) {
1310 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1311 |RIGHT_MOVE__MASK
1312 |RIGHT_SCRATCH__MASK
1313 |RIGHT_REMOVE__MASK);
1314 /* The idea is to approximate the meaning of the old 'restrict' directive
1315 * in the default rights if they are not overridden. */
1316 if(c->restrictions & RESTRICT_SCRATCH)
1317 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1318 else
1319 r |= RIGHT_SCRATCH_ANY;
1320 if(!(c->restrictions & RESTRICT_MOVE))
1321 r |= RIGHT_MOVE_ANY;
1322 if(c->restrictions & RESTRICT_REMOVE)
1323 r |= RIGHT_REMOVE_MINE;
1324 else
1325 r |= RIGHT_REMOVE_ANY;
1326 c->default_rights = rights_string(r);
1327 }
1328}
1329
1330/** @brief (Re-)read the config file
1331 * @param server If set, do extra checking
1332 */
1333int config_read(int server) {
1334 struct config *c;
1335 char *privconf;
1336 struct passwd *pw;
1337
1338 set_configfile();
1339 c = config_default();
1340 /* standalone Disobedience installs might not have a global config file */
1341 if(access(configfile, F_OK) == 0)
1342 if(config_include(c, configfile))
1343 return -1;
1344 /* if we can read the private config file, do */
1345 if((privconf = config_private())
1346 && access(privconf, R_OK) == 0
1347 && config_include(c, privconf))
1348 return -1;
1349 xfree(privconf);
1350 /* if there's a per-user system config file for this user, read it */
1351 if(config_per_user) {
1352 if(!(pw = getpwuid(getuid())))
1353 fatal(0, "cannot determine our username");
1354 if((privconf = config_usersysconf(pw))
1355 && access(privconf, F_OK) == 0
1356 && config_include(c, privconf))
1357 return -1;
1358 xfree(privconf);
1359 /* if we have a password file, read it */
1360 if((privconf = config_userconf(0, pw))
1361 && access(privconf, F_OK) == 0
1362 && config_include(c, privconf))
1363 return -1;
1364 xfree(privconf);
1365 }
1366 /* install default namepart and transform settings */
1367 config_postdefaults(c, server);
1368 /* everything is good so we shall use the new config */
1369 config_free(config);
1370 /* warn about obsolete directives */
1371 if(c->restrictions)
1372 error(0, "'restrict' will be removed in a future version");
1373 if(c->allow.n)
1374 error(0, "'allow' will be removed in a future version");
1375 if(c->trust.n)
1376 error(0, "'trust' will be removed in a future version");
1377 config = c;
1378 return 0;
1379}
1380
1381/** @brief Return the path to the private configuration file */
1382char *config_private(void) {
1383 char *s;
1384
1385 set_configfile();
1386 byte_xasprintf(&s, "%s.private", configfile);
1387 return s;
1388}
1389
1390/** @brief Return the path to user's personal configuration file */
1391char *config_userconf(const char *home, const struct passwd *pw) {
1392 char *s;
1393
1394 if(!home && !pw && !(pw = getpwuid(getuid())))
1395 fatal(0, "cannot determine our username");
1396 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1397 return s;
1398}
1399
1400/** @brief Return the path to user-specific system configuration */
1401char *config_usersysconf(const struct passwd *pw) {
1402 char *s;
1403
1404 set_configfile();
1405 if(!strchr(pw->pw_name, '/')) {
1406 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1407 return s;
1408 } else
1409 return 0;
1410}
1411
1412char *config_get_file(const char *name) {
1413 return config_get_file2(config, name);
1414}
1415
1416/*
1417Local Variables:
1418c-basic-offset:2
1419comment-column:40
1420fill-column:79
1421End:
1422*/