Merge playlist branch against trunk to date.
[disorder] / lib / configuration.c
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 */
57 char *configfile;
58
59 /** @brief Read user configuration
60 *
61 * If clear, the user-specific configuration is not read.
62 */
63 int config_per_user = 1;
64
65 /** @brief Config file parser state */
66 struct 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 */
76 struct config *config;
77
78 /** @brief One configuration item */
79 struct 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 */
92 struct 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
106 static 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
125 static 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
181 static 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
202 static 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
214 static 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
230 static 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
251 static 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
273 static 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
292 static 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
319 static 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
381 static 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
388 static 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
433 static 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
469 static 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
515 static 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
534 static void free_none(struct config attribute((unused)) *c,
535 const struct conf attribute((unused)) *whoami) {
536 }
537
538 static void free_string(struct config *c,
539 const struct conf *whoami) {
540 xfree(VALUE(c, char *));
541 }
542
543 static 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
553 static 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
568 static 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
583 static 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
599 static 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
617 static 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
652 static 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
665 static int validate_isdir(const struct config_state *cs,
666 int nvec, char **vec) {
667 VALIDATE_FILE(S_ISDIR, "directory");
668 return 0;
669 }
670
671 static 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
677 static 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
688 static 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
699 static 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
709 static 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
732 static 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
755 static 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
767 static 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
773 static 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
779 static 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
806 static 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
857 static 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
878 static 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
896 static 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 */
916 static 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(playlist_lock_timeout), &type_integer, validate_positive },
954 { C(playlist_max) , &type_integer, validate_positive },
955 { C(plugins), &type_string_accum, validate_isdir },
956 { C(prefsync), &type_integer, validate_positive },
957 { C(queue_pad), &type_integer, validate_positive },
958 { C(replay_min), &type_integer, validate_non_negative },
959 { C(refresh), &type_integer, validate_positive },
960 { C(reminder_interval), &type_integer, validate_positive },
961 { C(remote_userman), &type_boolean, validate_any },
962 { C2(restrict, restrictions), &type_restrict, validate_any },
963 { C(sample_format), &type_sample_format, validate_sample_format },
964 { C(scratch), &type_string_accum, validate_isreg },
965 { C(sendmail), &type_string, validate_isabspath },
966 { C(short_display), &type_integer, validate_positive },
967 { C(signal), &type_signal, validate_any },
968 { C(smtp_server), &type_string, validate_any },
969 { C(sox_generation), &type_integer, validate_non_negative },
970 { C2(speaker_backend, api), &type_backend, validate_any },
971 { C(speaker_command), &type_string, validate_any },
972 { C(stopword), &type_string_accum, validate_any },
973 { C(templates), &type_string_accum, validate_isdir },
974 { C(tracklength), &type_stringlist_accum, validate_tracklength },
975 { C(transform), &type_transform, validate_any },
976 { C(trust), &type_string_accum, validate_any },
977 { C(url), &type_string, validate_url },
978 { C(user), &type_string, validate_isauser },
979 { C(username), &type_string, validate_any },
980 };
981
982 /** @brief Find a configuration item's definition by key */
983 static const struct conf *find(const char *key) {
984 int n;
985
986 if((n = TABLE_FIND(conf, name, key)) < 0)
987 return 0;
988 return &conf[n];
989 }
990
991 /** @brief Set a new configuration value */
992 static int config_set(const struct config_state *cs,
993 int nvec, char **vec) {
994 const struct conf *which;
995
996 D(("config_set %s", vec[0]));
997 if(!(which = find(vec[0]))) {
998 error(0, "%s:%d: unknown configuration key '%s'",
999 cs->path, cs->line, vec[0]);
1000 return -1;
1001 }
1002 return (which->validate(cs, nvec - 1, vec + 1)
1003 || which->type->set(cs, which, nvec - 1, vec + 1));
1004 }
1005
1006 static int config_set_args(const struct config_state *cs,
1007 const char *which, ...) {
1008 va_list ap;
1009 struct vector v[1];
1010 char *s;
1011
1012 vector_init(v);
1013 vector_append(v, (char *)which);
1014 va_start(ap, which);
1015 while((s = va_arg(ap, char *)))
1016 vector_append(v, s);
1017 va_end(ap);
1018 vector_terminate(v);
1019 return config_set(cs, v->nvec, v->vec);
1020 }
1021
1022 /** @brief Error callback used by config_include() */
1023 static void config_error(const char *msg, void *u) {
1024 const struct config_state *cs = u;
1025
1026 error(0, "%s:%d: %s", cs->path, cs->line, msg);
1027 }
1028
1029 /** @brief Include a file by name */
1030 static int config_include(struct config *c, const char *path) {
1031 FILE *fp;
1032 char *buffer, *inputbuffer, **vec;
1033 int n, ret = 0;
1034 struct config_state cs;
1035
1036 cs.path = path;
1037 cs.line = 0;
1038 cs.config = c;
1039 D(("%s: reading configuration", path));
1040 if(!(fp = fopen(path, "r"))) {
1041 error(errno, "error opening %s", path);
1042 return -1;
1043 }
1044 while(!inputline(path, fp, &inputbuffer, '\n')) {
1045 ++cs.line;
1046 if(!(buffer = mb2utf8(inputbuffer))) {
1047 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1048 ret = -1;
1049 xfree(inputbuffer);
1050 continue;
1051 }
1052 xfree(inputbuffer);
1053 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1054 config_error, &cs))) {
1055 ret = -1;
1056 xfree(buffer);
1057 continue;
1058 }
1059 if(n) {
1060 if(!strcmp(vec[0], "include")) {
1061 if(n != 2) {
1062 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1063 ret = -1;
1064 } else
1065 config_include(c, vec[1]);
1066 } else
1067 ret |= config_set(&cs, n, vec);
1068 }
1069 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1070 xfree(vec);
1071 xfree(buffer);
1072 }
1073 if(ferror(fp)) {
1074 error(errno, "error reading %s", path);
1075 ret = -1;
1076 }
1077 fclose(fp);
1078 return ret;
1079 }
1080
1081 static const char *const default_stopwords[] = {
1082 "stopword",
1083
1084 "01",
1085 "02",
1086 "03",
1087 "04",
1088 "05",
1089 "06",
1090 "07",
1091 "08",
1092 "09",
1093 "1",
1094 "10",
1095 "11",
1096 "12",
1097 "13",
1098 "14",
1099 "15",
1100 "16",
1101 "17",
1102 "18",
1103 "19",
1104 "2",
1105 "20",
1106 "21",
1107 "22",
1108 "23",
1109 "24",
1110 "25",
1111 "26",
1112 "27",
1113 "28",
1114 "29",
1115 "3",
1116 "30",
1117 "4",
1118 "5",
1119 "6",
1120 "7",
1121 "8",
1122 "9",
1123 "a",
1124 "am",
1125 "an",
1126 "and",
1127 "as",
1128 "for",
1129 "i",
1130 "im",
1131 "in",
1132 "is",
1133 "of",
1134 "on",
1135 "the",
1136 "to",
1137 "too",
1138 "we",
1139 };
1140 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1141
1142 static const char *const default_players[] = {
1143 "*.ogg",
1144 "*.flac",
1145 "*.mp3",
1146 "*.wav",
1147 };
1148 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1149
1150 /** @brief Make a new default configuration */
1151 static struct config *config_default(void) {
1152 struct config *c = xmalloc(sizeof *c);
1153 const char *logname;
1154 struct passwd *pw;
1155 struct config_state cs;
1156 size_t n;
1157
1158 cs.path = "<internal>";
1159 cs.line = 0;
1160 cs.config = c;
1161 /* Strings had better be xstrdup'd as they will get freed at some point. */
1162 c->gap = 0;
1163 c->history = 60;
1164 c->home = xstrdup(pkgstatedir);
1165 if(!(pw = getpwuid(getuid())))
1166 fatal(0, "cannot determine our username");
1167 logname = pw->pw_name;
1168 c->username = xstrdup(logname);
1169 c->refresh = 15;
1170 c->prefsync = 3600;
1171 c->signal = SIGKILL;
1172 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1173 c->lock = 1;
1174 c->device = xstrdup("default");
1175 c->nice_rescan = 10;
1176 c->speaker_command = 0;
1177 c->sample_format.bits = 16;
1178 c->sample_format.rate = 44100;
1179 c->sample_format.channels = 2;
1180 c->sample_format.endian = ENDIAN_NATIVE;
1181 c->queue_pad = 10;
1182 c->replay_min = 8 * 3600;
1183 c->api = -1;
1184 c->multicast_ttl = 1;
1185 c->multicast_loop = 1;
1186 c->authorization_algorithm = xstrdup("sha1");
1187 c->noticed_history = 31;
1188 c->short_display = 32;
1189 c->mixer = 0;
1190 c->channel = 0;
1191 c->dbversion = 2;
1192 c->cookie_login_lifetime = 86400;
1193 c->cookie_key_lifetime = 86400 * 7;
1194 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1195 c->sendmail = xstrdup(sendmail_binary);
1196 c->smtp_server = xstrdup("127.0.0.1");
1197 c->new_max = 100;
1198 c->reminder_interval = 600; /* 10m */
1199 c->new_bias_age = 7 * 86400; /* 1 week */
1200 c->new_bias = 4500000; /* 50 times the base weight */
1201 c->sox_generation = DEFAULT_SOX_GENERATION;
1202 c->playlist_max = INT_MAX; /* effectively no limit */
1203 c->playlist_lock_timeout = 10; /* 10s */
1204 /* Default stopwords */
1205 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1206 exit(1);
1207 /* Default player configuration */
1208 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1209 if(config_set_args(&cs, "player",
1210 default_players[n], "execraw", "disorder-decode", (char *)0))
1211 exit(1);
1212 if(config_set_args(&cs, "tracklength",
1213 default_players[n], "disorder-tracklength", (char *)0))
1214 exit(1);
1215 }
1216 return c;
1217 }
1218
1219 char *config_get_file2(struct config *c, const char *name) {
1220 char *s;
1221
1222 byte_xasprintf(&s, "%s/%s", c->home, name);
1223 return s;
1224 }
1225
1226 /** @brief Set the default configuration file */
1227 static void set_configfile(void) {
1228 if(!configfile)
1229 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1230 }
1231
1232 /** @brief Free a configuration object */
1233 static void config_free(struct config *c) {
1234 int n;
1235
1236 if(c) {
1237 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1238 conf[n].type->free(c, &conf[n]);
1239 for(n = 0; n < c->nparts; ++n)
1240 xfree(c->parts[n]);
1241 xfree(c->parts);
1242 xfree(c);
1243 }
1244 }
1245
1246 /** @brief Set post-parse defaults */
1247 static void config_postdefaults(struct config *c,
1248 int server) {
1249 struct config_state cs;
1250 const struct conf *whoami;
1251 int n;
1252
1253 static const char *namepart[][4] = {
1254 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1255 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1256 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1257 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1258 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1259 };
1260 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1261
1262 static const char *transform[][5] = {
1263 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1264 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1265 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1266 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1267 { "dir", "[[:punct:]]", "", "sort", "g", }
1268 };
1269 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1270
1271 cs.path = "<internal>";
1272 cs.line = 0;
1273 cs.config = c;
1274 if(!c->namepart.n) {
1275 whoami = find("namepart");
1276 for(n = 0; n < NNAMEPART; ++n)
1277 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1278 }
1279 if(!c->transform.n) {
1280 whoami = find("transform");
1281 for(n = 0; n < NTRANSFORM; ++n)
1282 set_transform(&cs, whoami, 5, (char **)transform[n]);
1283 }
1284 if(c->api == -1) {
1285 if(c->speaker_command)
1286 c->api = BACKEND_COMMAND;
1287 else if(c->broadcast.n)
1288 c->api = BACKEND_NETWORK;
1289 else
1290 c->api = DEFAULT_BACKEND;
1291 }
1292 if(server) {
1293 if(c->api == BACKEND_COMMAND && !c->speaker_command)
1294 fatal(0, "'api command' but speaker_command is not set");
1295 if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1296 fatal(0, "'api network' but broadcast is not set");
1297 }
1298 /* Override sample format */
1299 switch(c->api) {
1300 case BACKEND_NETWORK:
1301 c->sample_format.rate = 44100;
1302 c->sample_format.channels = 2;
1303 c->sample_format.bits = 16;
1304 c->sample_format.endian = ENDIAN_BIG;
1305 break;
1306 case BACKEND_COREAUDIO:
1307 c->sample_format.rate = 44100;
1308 c->sample_format.channels = 2;
1309 c->sample_format.bits = 16;
1310 c->sample_format.endian = ENDIAN_NATIVE;
1311 break;
1312 }
1313 if(!c->default_rights) {
1314 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1315 |RIGHT_MOVE__MASK
1316 |RIGHT_SCRATCH__MASK
1317 |RIGHT_REMOVE__MASK);
1318 /* The idea is to approximate the meaning of the old 'restrict' directive
1319 * in the default rights if they are not overridden. */
1320 if(c->restrictions & RESTRICT_SCRATCH)
1321 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1322 else
1323 r |= RIGHT_SCRATCH_ANY;
1324 if(!(c->restrictions & RESTRICT_MOVE))
1325 r |= RIGHT_MOVE_ANY;
1326 if(c->restrictions & RESTRICT_REMOVE)
1327 r |= RIGHT_REMOVE_MINE;
1328 else
1329 r |= RIGHT_REMOVE_ANY;
1330 c->default_rights = rights_string(r);
1331 }
1332 }
1333
1334 /** @brief (Re-)read the config file
1335 * @param server If set, do extra checking
1336 */
1337 int config_read(int server) {
1338 struct config *c;
1339 char *privconf;
1340 struct passwd *pw;
1341
1342 set_configfile();
1343 c = config_default();
1344 /* standalone Disobedience installs might not have a global config file */
1345 if(access(configfile, F_OK) == 0)
1346 if(config_include(c, configfile))
1347 return -1;
1348 /* if we can read the private config file, do */
1349 if((privconf = config_private())
1350 && access(privconf, R_OK) == 0
1351 && config_include(c, privconf))
1352 return -1;
1353 xfree(privconf);
1354 /* if there's a per-user system config file for this user, read it */
1355 if(config_per_user) {
1356 if(!(pw = getpwuid(getuid())))
1357 fatal(0, "cannot determine our username");
1358 if((privconf = config_usersysconf(pw))
1359 && access(privconf, F_OK) == 0
1360 && config_include(c, privconf))
1361 return -1;
1362 xfree(privconf);
1363 /* if we have a password file, read it */
1364 if((privconf = config_userconf(0, pw))
1365 && access(privconf, F_OK) == 0
1366 && config_include(c, privconf))
1367 return -1;
1368 xfree(privconf);
1369 }
1370 /* install default namepart and transform settings */
1371 config_postdefaults(c, server);
1372 /* everything is good so we shall use the new config */
1373 config_free(config);
1374 /* warn about obsolete directives */
1375 if(c->restrictions)
1376 error(0, "'restrict' will be removed in a future version");
1377 if(c->allow.n)
1378 error(0, "'allow' will be removed in a future version");
1379 if(c->trust.n)
1380 error(0, "'trust' will be removed in a future version");
1381 config = c;
1382 return 0;
1383 }
1384
1385 /** @brief Return the path to the private configuration file */
1386 char *config_private(void) {
1387 char *s;
1388
1389 set_configfile();
1390 byte_xasprintf(&s, "%s.private", configfile);
1391 return s;
1392 }
1393
1394 /** @brief Return the path to user's personal configuration file */
1395 char *config_userconf(const char *home, const struct passwd *pw) {
1396 char *s;
1397
1398 if(!home && !pw && !(pw = getpwuid(getuid())))
1399 fatal(0, "cannot determine our username");
1400 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1401 return s;
1402 }
1403
1404 /** @brief Return the path to user-specific system configuration */
1405 char *config_usersysconf(const struct passwd *pw) {
1406 char *s;
1407
1408 set_configfile();
1409 if(!strchr(pw->pw_name, '/')) {
1410 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1411 return s;
1412 } else
1413 return 0;
1414 }
1415
1416 char *config_get_file(const char *name) {
1417 return config_get_file2(config, name);
1418 }
1419
1420 /*
1421 Local Variables:
1422 c-basic-offset:2
1423 comment-column:40
1424 fill-column:79
1425 End:
1426 */