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