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