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