chiark / gitweb /
More clang-inspired fixes
[disorder] / lib / configuration.c
... / ...
CommitLineData
1/*
2 * This file is part of DisOrder.
3 * Copyright (C) 2004-2010 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 default 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
76 /** @brief Line number */
77 int line;
78
79 /** @brief Configuration object under construction */
80 struct config *config;
81};
82
83/** @brief Current configuration */
84struct config *config;
85
86/** @brief One configuration item */
87struct conf {
88 /** @brief Name as it appears in the config file */
89 const char *name;
90
91 /** @brief Offset in @ref config structure */
92 size_t offset;
93
94 /** @brief Pointer to item type */
95 const struct conftype *type;
96
97 /** @brief Pointer to item-specific validation routine
98 * @param cs Configuration state
99 * @param nvec Length of (proposed) new value
100 * @param vec Elements of new value
101 * @return 0 on success, non-0 on error
102 *
103 * The validate function should report any error it detects.
104 */
105 int (*validate)(const struct config_state *cs,
106 int nvec, char **vec);
107};
108
109/** @brief Type of a configuration item */
110struct conftype {
111 /** @brief Pointer to function to set item
112 * @param cs Configuration state
113 * @param whoami Configuration item to set
114 * @param nvec Length of new value
115 * @param vec New value
116 * @return 0 on success, non-0 on error
117 */
118 int (*set)(const struct config_state *cs,
119 const struct conf *whoami,
120 int nvec, char **vec);
121
122 /** @brief Pointer to function to free item
123 * @param c Configuration structure to free an item of
124 * @param whoami Configuration item to free
125 */
126 void (*free)(struct config *c, const struct conf *whoami);
127};
128
129/** @brief Compute the address of an item */
130#define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset))
131/** @brief Return the value of an item */
132#define VALUE(C, TYPE) (*ADDRESS(C, TYPE))
133
134static int stringlist_compare(const struct stringlist *a,
135 const struct stringlist *b);
136static int namepartlist_compare(const struct namepartlist *a,
137 const struct namepartlist *b);
138
139static int set_signal(const struct config_state *cs,
140 const struct conf *whoami,
141 int nvec, char **vec) {
142 int n;
143
144 if(nvec != 1) {
145 disorder_error(0, "%s:%d: '%s' requires one argument",
146 cs->path, cs->line, whoami->name);
147 return -1;
148 }
149 if((n = find_signal(vec[0])) == -1) {
150 disorder_error(0, "%s:%d: unknown signal '%s'",
151 cs->path, cs->line, vec[0]);
152 return -1;
153 }
154 VALUE(cs->config, int) = n;
155 return 0;
156}
157
158static int set_collections(const struct config_state *cs,
159 const struct conf *whoami,
160 int nvec, char **vec) {
161 struct collectionlist *cl;
162 const char *root, *encoding, *module;
163
164 switch(nvec) {
165 case 1:
166 module = 0;
167 encoding = 0;
168 root = vec[0];
169 break;
170 case 2:
171 module = vec[0];
172 encoding = 0;
173 root = vec[1];
174 break;
175 case 3:
176 module = vec[0];
177 encoding = vec[1];
178 root = vec[2];
179 break;
180 case 0:
181 disorder_error(0, "%s:%d: '%s' requires at least one argument",
182 cs->path, cs->line, whoami->name);
183 return -1;
184 default:
185 disorder_error(0, "%s:%d: '%s' requires at most three arguments",
186 cs->path, cs->line, whoami->name);
187 return -1;
188 }
189 /* Sanity check root */
190 if(root[0] != '/') {
191 disorder_error(0, "%s:%d: collection root must start with '/'",
192 cs->path, cs->line);
193 return -1;
194 }
195 if(root[1] && root[strlen(root)-1] == '/') {
196 disorder_error(0, "%s:%d: collection root must not end with '/'",
197 cs->path, cs->line);
198 return -1;
199 }
200 /* Defaults */
201 if(!module)
202 module = "fs";
203 if(!encoding)
204 encoding = nl_langinfo(CODESET);
205 cl = ADDRESS(cs->config, struct collectionlist);
206 ++cl->n;
207 cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection));
208 cl->s[cl->n - 1].module = xstrdup(module);
209 cl->s[cl->n - 1].encoding = xstrdup(encoding);
210 cl->s[cl->n - 1].root = xstrdup(root);
211 return 0;
212}
213
214static int set_boolean(const struct config_state *cs,
215 const struct conf *whoami,
216 int nvec, char **vec) {
217 int state;
218
219 if(nvec != 1) {
220 disorder_error(0, "%s:%d: '%s' takes only one argument",
221 cs->path, cs->line, whoami->name);
222 return -1;
223 }
224 if(!strcmp(vec[0], "yes")) state = 1;
225 else if(!strcmp(vec[0], "no")) state = 0;
226 else {
227 disorder_error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'",
228 cs->path, cs->line, whoami->name);
229 return -1;
230 }
231 VALUE(cs->config, int) = state;
232 return 0;
233}
234
235static int set_string(const struct config_state *cs,
236 const struct conf *whoami,
237 int nvec, char **vec) {
238 if(nvec != 1) {
239 disorder_error(0, "%s:%d: '%s' takes only one argument",
240 cs->path, cs->line, whoami->name);
241 return -1;
242 }
243 VALUE(cs->config, char *) = xstrdup(vec[0]);
244 return 0;
245}
246
247static int set_stringlist(const struct config_state *cs,
248 const struct conf *whoami,
249 int nvec, char **vec) {
250 int n;
251 struct stringlist *sl;
252
253 sl = ADDRESS(cs->config, struct stringlist);
254 sl->n = 0;
255 for(n = 0; n < nvec; ++n) {
256 sl->n++;
257 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
258 sl->s[sl->n - 1] = xstrdup(vec[n]);
259 }
260 return 0;
261}
262
263static int set_integer(const struct config_state *cs,
264 const struct conf *whoami,
265 int nvec, char **vec) {
266 char *e;
267
268 if(nvec != 1) {
269 disorder_error(0, "%s:%d: '%s' takes only one argument",
270 cs->path, cs->line, whoami->name);
271 return -1;
272 }
273 if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) {
274 disorder_error(errno, "%s:%d: converting integer", cs->path, cs->line);
275 return -1;
276 }
277 if(*e) {
278 disorder_error(0, "%s:%d: invalid integer syntax", cs->path, cs->line);
279 return -1;
280 }
281 return 0;
282}
283
284static int set_stringlist_accum(const struct config_state *cs,
285 const struct conf *whoami,
286 int nvec, char **vec) {
287 int n;
288 struct stringlist *s;
289 struct stringlistlist *sll;
290
291 sll = ADDRESS(cs->config, struct stringlistlist);
292 if(nvec == 0) {
293 sll->n = 0;
294 return 0;
295 }
296 sll->n++;
297 sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist)));
298 s = &sll->s[sll->n - 1];
299 s->n = nvec;
300 s->s = xmalloc((nvec + 1) * sizeof (char *));
301 for(n = 0; n < nvec; ++n)
302 s->s[n] = xstrdup(vec[n]);
303 return 0;
304}
305
306static int set_string_accum(const struct config_state *cs,
307 const struct conf *whoami,
308 int nvec, char **vec) {
309 int n;
310 struct stringlist *sl;
311
312 sl = ADDRESS(cs->config, struct stringlist);
313 if(nvec == 0) {
314 sl->n = 0;
315 return 0;
316 }
317 for(n = 0; n < nvec; ++n) {
318 sl->n++;
319 sl->s = xrealloc(sl->s, (sl->n * sizeof (char *)));
320 sl->s[sl->n - 1] = xstrdup(vec[n]);
321 }
322 return 0;
323}
324
325static int set_restrict(const struct config_state *cs,
326 const struct conf *whoami,
327 int nvec, char **vec) {
328 unsigned r = 0;
329 int n, i;
330
331 static const struct restriction {
332 const char *name;
333 unsigned bit;
334 } restrictions[] = {
335 { "remove", RESTRICT_REMOVE },
336 { "scratch", RESTRICT_SCRATCH },
337 { "move", RESTRICT_MOVE },
338 };
339
340 for(n = 0; n < nvec; ++n) {
341 if((i = TABLE_FIND(restrictions, name, vec[n])) < 0) {
342 disorder_error(0, "%s:%d: invalid restriction '%s'",
343 cs->path, cs->line, vec[n]);
344 return -1;
345 }
346 r |= restrictions[i].bit;
347 }
348 VALUE(cs->config, unsigned) = r;
349 return 0;
350}
351
352static int parse_sample_format(const struct config_state *cs,
353 struct stream_header *format,
354 int nvec, char **vec) {
355 char *p = vec[0];
356 long t;
357
358 if(nvec != 1) {
359 disorder_error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
360 return -1;
361 }
362 if(xstrtol(&t, p, &p, 0)) {
363 disorder_error(errno, "%s:%d: converting bits-per-sample",
364 cs->path, cs->line);
365 return -1;
366 }
367 if(t != 8 && t != 16) {
368 disorder_error(0, "%s:%d: bad bits-per-sample (%ld)",
369 cs->path, cs->line, t);
370 return -1;
371 }
372 if(format) format->bits = t;
373 switch (*p) {
374 case 'l': case 'L': t = ENDIAN_LITTLE; p++; break;
375 case 'b': case 'B': t = ENDIAN_BIG; p++; break;
376 default: t = ENDIAN_NATIVE; break;
377 }
378 if(format) format->endian = t;
379 if(*p != '/') {
380 disorder_error(errno, "%s:%d: expected `/' after bits-per-sample",
381 cs->path, cs->line);
382 return -1;
383 }
384 p++;
385 if(xstrtol(&t, p, &p, 0)) {
386 disorder_error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
387 return -1;
388 }
389 if(t < 1 || t > INT_MAX) {
390 disorder_error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
391 return -1;
392 }
393 if(format) format->rate = t;
394 if(*p != '/') {
395 disorder_error(0, "%s:%d: expected `/' after sample-rate",
396 cs->path, cs->line);
397 return -1;
398 }
399 p++;
400 if(xstrtol(&t, p, &p, 0)) {
401 disorder_error(errno, "%s:%d: converting channels", cs->path, cs->line);
402 return -1;
403 }
404 if(t < 1 || t > 8) {
405 disorder_error(0, "%s:%d: silly number (%ld) of channels",
406 cs->path, cs->line, t);
407 return -1;
408 }
409 if(format) format->channels = t;
410 if(*p) {
411 disorder_error(0, "%s:%d: junk after channels", cs->path, cs->line);
412 return -1;
413 }
414 return 0;
415}
416
417static int set_sample_format(const struct config_state *cs,
418 const struct conf *whoami,
419 int nvec, char **vec) {
420 return parse_sample_format(cs, ADDRESS(cs->config, struct stream_header),
421 nvec, vec);
422}
423
424static int set_namepart(const struct config_state *cs,
425 const struct conf *whoami,
426 int nvec, char **vec) {
427 struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist);
428 unsigned reflags;
429 const char *errstr;
430 int erroffset, n;
431 pcre *re;
432
433 if(nvec < 3) {
434 disorder_error(0, "%s:%d: namepart needs at least 3 arguments",
435 cs->path, cs->line);
436 return -1;
437 }
438 if(nvec > 5) {
439 disorder_error(0, "%s:%d: namepart needs at most 5 arguments",
440 cs->path, cs->line);
441 return -1;
442 }
443 reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0;
444 if(!(re = pcre_compile(vec[1],
445 PCRE_UTF8
446 |regsub_compile_options(reflags),
447 &errstr, &erroffset, 0))) {
448 disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
449 cs->path, cs->line, vec[1], errstr, erroffset);
450 return -1;
451 }
452 npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart));
453 npl->s[npl->n].part = xstrdup(vec[0]);
454 npl->s[npl->n].re = re;
455 npl->s[npl->n].res = xstrdup(vec[1]);
456 npl->s[npl->n].replace = xstrdup(vec[2]);
457 npl->s[npl->n].context = xstrdup(vec[3]);
458 npl->s[npl->n].reflags = reflags;
459 ++npl->n;
460 /* XXX a bit of a bodge; relies on there being very few parts. */
461 for(n = 0; (n < cs->config->nparts
462 && strcmp(cs->config->parts[n], vec[0])); ++n)
463 ;
464 if(n >= cs->config->nparts) {
465 cs->config->parts = xrealloc(cs->config->parts,
466 (cs->config->nparts + 1) * sizeof (char *));
467 cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]);
468 }
469 return 0;
470}
471
472static int set_transform(const struct config_state *cs,
473 const struct conf *whoami,
474 int nvec, char **vec) {
475 struct transformlist *tl = ADDRESS(cs->config, struct transformlist);
476 pcre *re;
477 unsigned reflags;
478 const char *errstr;
479 int erroffset;
480
481 if(nvec < 3) {
482 disorder_error(0, "%s:%d: transform needs at least 3 arguments",
483 cs->path, cs->line);
484 return -1;
485 }
486 if(nvec > 5) {
487 disorder_error(0, "%s:%d: transform needs at most 5 arguments",
488 cs->path, cs->line);
489 return -1;
490 }
491 reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0);
492 if(!(re = pcre_compile(vec[1],
493 PCRE_UTF8
494 |regsub_compile_options(reflags),
495 &errstr, &erroffset, 0))) {
496 disorder_error(0, "%s:%d: compiling regexp /%s/: %s (offset %d)",
497 cs->path, cs->line, vec[1], errstr, erroffset);
498 return -1;
499 }
500 tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart));
501 tl->t[tl->n].type = xstrdup(vec[0]);
502 tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*");
503 tl->t[tl->n].re = re;
504 tl->t[tl->n].replace = xstrdup(vec[2]);
505 tl->t[tl->n].flags = reflags;
506 ++tl->n;
507 return 0;
508}
509
510static int set_rights(const struct config_state *cs,
511 const struct conf *whoami,
512 int nvec, char **vec) {
513 if(nvec != 1) {
514 disorder_error(0, "%s:%d: '%s' requires one argument",
515 cs->path, cs->line, whoami->name);
516 return -1;
517 }
518 if(parse_rights(vec[0], 0, 1)) {
519 disorder_error(0, "%s:%d: invalid rights string '%s'",
520 cs->path, cs->line, vec[0]);
521 return -1;
522 }
523 *ADDRESS(cs->config, char *) = vec[0];
524 return 0;
525}
526
527static int set_netaddress(const struct config_state *cs,
528 const struct conf *whoami,
529 int nvec, char **vec) {
530 struct netaddress *na = ADDRESS(cs->config, struct netaddress);
531
532 if(netaddress_parse(na, nvec, vec)) {
533 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
534 return -1;
535 }
536 return 0;
537}
538
539/* free functions */
540
541static void free_none(struct config attribute((unused)) *c,
542 const struct conf attribute((unused)) *whoami) {
543}
544
545static void free_string(struct config *c,
546 const struct conf *whoami) {
547 xfree(VALUE(c, char *));
548 VALUE(c, char *) = 0;
549}
550
551static void free_stringlist(struct config *c,
552 const struct conf *whoami) {
553 int n;
554 struct stringlist *sl = ADDRESS(c, struct stringlist);
555
556 for(n = 0; n < sl->n; ++n)
557 xfree(sl->s[n]);
558 xfree(sl->s);
559}
560
561static void free_stringlistlist(struct config *c,
562 const struct conf *whoami) {
563 int n, m;
564 struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
565 struct stringlist *sl;
566
567 for(n = 0; n < sll->n; ++n) {
568 sl = &sll->s[n];
569 for(m = 0; m < sl->n; ++m)
570 xfree(sl->s[m]);
571 xfree(sl->s);
572 }
573 xfree(sll->s);
574}
575
576static void free_collectionlist(struct config *c,
577 const struct conf *whoami) {
578 struct collectionlist *cll = ADDRESS(c, struct collectionlist);
579 struct collection *cl;
580 int n;
581
582 for(n = 0; n < cll->n; ++n) {
583 cl = &cll->s[n];
584 xfree(cl->module);
585 xfree(cl->encoding);
586 xfree(cl->root);
587 }
588 xfree(cll->s);
589}
590
591static void free_namepartlist(struct config *c,
592 const struct conf *whoami) {
593 struct namepartlist *npl = ADDRESS(c, struct namepartlist);
594 struct namepart *np;
595 int n;
596
597 for(n = 0; n < npl->n; ++n) {
598 np = &npl->s[n];
599 xfree(np->part);
600 pcre_free(np->re); /* ...whatever pcre_free is set to. */
601 xfree(np->res);
602 xfree(np->replace);
603 xfree(np->context);
604 }
605 xfree(npl->s);
606}
607
608static void free_transformlist(struct config *c,
609 const struct conf *whoami) {
610 struct transformlist *tl = ADDRESS(c, struct transformlist);
611 struct transform *t;
612 int n;
613
614 for(n = 0; n < tl->n; ++n) {
615 t = &tl->t[n];
616 xfree(t->type);
617 pcre_free(t->re); /* ...whatever pcre_free is set to. */
618 xfree(t->replace);
619 xfree(t->context);
620 }
621 xfree(tl->t);
622}
623
624static void free_netaddress(struct config *c,
625 const struct conf *whoami) {
626 struct netaddress *na = ADDRESS(c, struct netaddress);
627
628 xfree(na->address);
629}
630
631/* configuration types */
632
633static const struct conftype
634 type_signal = { set_signal, free_none },
635 type_collections = { set_collections, free_collectionlist },
636 type_boolean = { set_boolean, free_none },
637 type_string = { set_string, free_string },
638 type_stringlist = { set_stringlist, free_stringlist },
639 type_integer = { set_integer, free_none },
640 type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
641 type_string_accum = { set_string_accum, free_stringlist },
642 type_sample_format = { set_sample_format, free_none },
643 type_restrict = { set_restrict, free_none },
644 type_namepart = { set_namepart, free_namepartlist },
645 type_transform = { set_transform, free_transformlist },
646 type_netaddress = { set_netaddress, free_netaddress },
647 type_rights = { set_rights, free_none };
648
649/* specific validation routine */
650
651/** @brief Perform a test on a filename
652 * @param test Test function to call on mode bits
653 * @param what Type of file sought
654 *
655 * If @p test returns 0 then the file is not a @p what and an error
656 * is reported and -1 is returned.
657 */
658#define VALIDATE_FILE(test, what) do { \
659 struct stat sb; \
660 int n; \
661 \
662 for(n = 0; n < nvec; ++n) { \
663 if(stat(vec[n], &sb) < 0) { \
664 disorder_error(errno, "%s:%d: %s", \
665 cs->path, cs->line, vec[n]); \
666 return -1; \
667 } \
668 if(!test(sb.st_mode)) { \
669 disorder_error(0, "%s:%d: %s is not a %s", \
670 cs->path, cs->line, vec[n], what); \
671 return -1; \
672 } \
673 } \
674} while(0)
675
676/** @brief Validate an absolute path
677 * @param cs Configuration state
678 * @param nvec Length of (proposed) new value
679 * @param vec Elements of new value
680 * @return 0 on success, non-0 on error
681 */
682static int validate_isabspath(const struct config_state *cs,
683 int nvec, char **vec) {
684 int n;
685
686 for(n = 0; n < nvec; ++n)
687 if(vec[n][0] != '/') {
688 disorder_error(errno, "%s:%d: %s: not an absolute path",
689 cs->path, cs->line, vec[n]);
690 return -1;
691 }
692 return 0;
693}
694
695/** @brief Validate an existing directory
696 * @param cs Configuration state
697 * @param nvec Length of (proposed) new value
698 * @param vec Elements of new value
699 * @return 0 on success, non-0 on error
700 */
701static int validate_isdir(const struct config_state *cs,
702 int nvec, char **vec) {
703 VALIDATE_FILE(S_ISDIR, "directory");
704 return 0;
705}
706
707/** @brief Validate an existing regular file
708 * @param cs Configuration state
709 * @param nvec Length of (proposed) new value
710 * @param vec Elements of new value
711 * @return 0 on success, non-0 on error
712 */
713static int validate_isreg(const struct config_state *cs,
714 int nvec, char **vec) {
715 VALIDATE_FILE(S_ISREG, "regular file");
716 return 0;
717}
718
719/** @brief Validate a player pattern
720 * @param cs Configuration state
721 * @param nvec Length of (proposed) new value
722 * @param vec Elements of new value
723 * @return 0 on success, non-0 on error
724 */
725static int validate_player(const struct config_state *cs,
726 int nvec,
727 char attribute((unused)) **vec) {
728 if(nvec < 2) {
729 disorder_error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
730 cs->path, cs->line);
731 return -1;
732 }
733 return 0;
734}
735
736/** @brief Validate a track length pattern
737 * @param cs Configuration state
738 * @param nvec Length of (proposed) new value
739 * @param vec Elements of new value
740 * @return 0 on success, non-0 on error
741 */
742static int validate_tracklength(const struct config_state *cs,
743 int nvec,
744 char attribute((unused)) **vec) {
745 if(nvec < 2) {
746 disorder_error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
747 cs->path, cs->line);
748 return -1;
749 }
750 return 0;
751}
752
753/** @brief Validate an allow directive
754 * @param cs Configuration state
755 * @param nvec Length of (proposed) new value
756 * @param vec Elements of new value
757 * @return 0 on success, non-0 on error
758 *
759 * Obsolete - only used for parsing legacy configuration.
760 */
761static int validate_allow(const struct config_state *cs,
762 int nvec,
763 char attribute((unused)) **vec) {
764 if(nvec != 2) {
765 disorder_error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
766 return -1;
767 }
768 return 0;
769}
770
771/** @brief Validate a non-negative (@c long) integer
772 * @param cs Configuration state
773 * @param nvec Length of (proposed) new value
774 * @param vec Elements of new value
775 * @return 0 on success, non-0 on error
776 */
777static int validate_non_negative(const struct config_state *cs,
778 int nvec, char **vec) {
779 long n;
780
781 if(nvec < 1) {
782 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
783 return -1;
784 }
785 if(nvec > 1) {
786 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
787 return -1;
788 }
789 if(xstrtol(&n, vec[0], 0, 0)) {
790 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
791 return -1;
792 }
793 if(n < 0) {
794 disorder_error(0, "%s:%d: must not be negative", cs->path, cs->line);
795 return -1;
796 }
797 return 0;
798}
799
800/** @brief Validate a positive (@c long) integer
801 * @param cs Configuration state
802 * @param nvec Length of (proposed) new value
803 * @param vec Elements of new value
804 * @return 0 on success, non-0 on error
805 */
806static int validate_positive(const struct config_state *cs,
807 int nvec, char **vec) {
808 long n;
809
810 if(nvec < 1) {
811 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
812 return -1;
813 }
814 if(nvec > 1) {
815 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
816 return -1;
817 }
818 if(xstrtol(&n, vec[0], 0, 0)) {
819 disorder_error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
820 return -1;
821 }
822 if(n <= 0) {
823 disorder_error(0, "%s:%d: must be positive", cs->path, cs->line);
824 return -1;
825 }
826 return 0;
827}
828
829/** @brief Validate a system username
830 * @param cs Configuration state
831 * @param nvec Length of (proposed) new value
832 * @param vec Elements of new value
833 * @return 0 on success, non-0 on error
834 */
835static int validate_isauser(const struct config_state *cs,
836 int attribute((unused)) nvec,
837 char **vec) {
838 struct passwd *pw;
839
840 if(!(pw = getpwnam(vec[0]))) {
841 disorder_error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
842 return -1;
843 }
844 return 0;
845}
846
847/** @brief Validate a sample format string
848 * @param cs Configuration state
849 * @param nvec Length of (proposed) new value
850 * @param vec Elements of new value
851 * @return 0 on success, non-0 on error
852 */
853static int validate_sample_format(const struct config_state *cs,
854 int attribute((unused)) nvec,
855 char **vec) {
856 return parse_sample_format(cs, 0, nvec, vec);
857}
858
859/** @brief Validate anything
860 * @param cs Configuration state
861 * @param nvec Length of (proposed) new value
862 * @param vec Elements of new value
863 * @return 0
864 */
865static int validate_any(const struct config_state attribute((unused)) *cs,
866 int attribute((unused)) nvec,
867 char attribute((unused)) **vec) {
868 return 0;
869}
870
871/** @brief Validate a URL
872 * @param cs Configuration state
873 * @param nvec Length of (proposed) new value
874 * @param vec Elements of new value
875 * @return 0 on success, non-0 on error
876 *
877 * Rather cursory.
878 */
879static int validate_url(const struct config_state attribute((unused)) *cs,
880 int attribute((unused)) nvec,
881 char **vec) {
882 const char *s;
883 int n;
884 /* absoluteURI = scheme ":" ( hier_part | opaque_part )
885 scheme = alpha *( alpha | digit | "+" | "-" | "." ) */
886 s = vec[0];
887 n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
888 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
889 "0123456789"));
890 if(s[n] != ':') {
891 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
892 return -1;
893 }
894 if(!strncmp(s, "http:", 5)
895 || !strncmp(s, "https:", 6)) {
896 s += n + 1;
897 /* we only do a rather cursory check */
898 if(strncmp(s, "//", 2)) {
899 disorder_error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
900 return -1;
901 }
902 }
903 return 0;
904}
905
906/** @brief Validate an alias pattern
907 * @param cs Configuration state
908 * @param nvec Length of (proposed) new value
909 * @param vec Elements of new value
910 * @return 0 on success, non-0 on error
911 */
912static int validate_alias(const struct config_state *cs,
913 int nvec,
914 char **vec) {
915 const char *s;
916 int in_brackets = 0, c;
917
918 if(nvec < 1) {
919 disorder_error(0, "%s:%d: missing argument", cs->path, cs->line);
920 return -1;
921 }
922 if(nvec > 1) {
923 disorder_error(0, "%s:%d: too many arguments", cs->path, cs->line);
924 return -1;
925 }
926 s = vec[0];
927 while((c = (unsigned char)*s++)) {
928 if(in_brackets) {
929 if(c == '}')
930 in_brackets = 0;
931 else if(!isalnum(c)) {
932 disorder_error(0, "%s:%d: invalid part name in alias expansion in '%s'",
933 cs->path, cs->line, vec[0]);
934 return -1;
935 }
936 } else {
937 if(c == '{') {
938 in_brackets = 1;
939 if(*s == '/')
940 ++s;
941 } else if(c == '\\') {
942 if(!(c = (unsigned char)*s++)) {
943 disorder_error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
944 cs->path, cs->line, vec[0]);
945 return -1;
946 } else if(c != '\\' && c != '{') {
947 disorder_error(0, "%s:%d: invalid escape in alias expansion in '%s'",
948 cs->path, cs->line, vec[0]);
949 return -1;
950 }
951 }
952 }
953 ++s;
954 }
955 if(in_brackets) {
956 disorder_error(0,
957 "%s:%d: unterminated part name in alias expansion in '%s'",
958 cs->path, cs->line, vec[0]);
959 return -1;
960 }
961 return 0;
962}
963
964/** @brief Validate a hash algorithm name
965 * @param cs Configuration state
966 * @param nvec Length of (proposed) new value
967 * @param vec Elements of new value
968 * @return 0 on success, non-0 on error
969 */
970static int validate_algo(const struct config_state attribute((unused)) *cs,
971 int nvec,
972 char **vec) {
973 if(nvec != 1) {
974 disorder_error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
975 return -1;
976 }
977 if(!valid_authhash(vec[0])) {
978 disorder_error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
979 return -1;
980 }
981 return 0;
982}
983
984/** @brief Validate a playback backend name
985 * @param cs Configuration state
986 * @param nvec Length of (proposed) new value
987 * @param vec Elements of new value
988 * @return 0 on success, non-0 on error
989 */
990static int validate_backend(const struct config_state attribute((unused)) *cs,
991 int nvec,
992 char **vec) {
993 int n;
994 if(nvec != 1) {
995 disorder_error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
996 return -1;
997 }
998 if(!strcmp(vec[0], "network")) {
999 disorder_error(0, "'api network' is deprecated; use 'api rtp'");
1000 return 0;
1001 }
1002 if(config_uaudio_apis) {
1003 for(n = 0; config_uaudio_apis[n]; ++n)
1004 if(!strcmp(vec[0], config_uaudio_apis[n]->name))
1005 return 0;
1006 disorder_error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
1007 return -1;
1008 }
1009 /* In non-server processes we have no idea what's valid */
1010 return 0;
1011}
1012
1013/** @brief Validate a pause mode string
1014 * @param cs Configuration state
1015 * @param nvec Length of (proposed) new value
1016 * @param vec Elements of new value
1017 * @return 0 on success, non-0 on error
1018 */
1019static int validate_pausemode(const struct config_state attribute((unused)) *cs,
1020 int nvec,
1021 char **vec) {
1022 if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
1023 return 0;
1024 disorder_error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
1025 return -1;
1026}
1027
1028/** @brief Validate a destination network address
1029 * @param cs Configuration state
1030 * @param nvec Length of (proposed) new value
1031 * @param vec Elements of new value
1032 * @return 0 on success, non-0 on error
1033 *
1034 * By a destination address, it is meant that it must not be a wildcard
1035 * address.
1036 */
1037static int validate_destaddr(const struct config_state attribute((unused)) *cs,
1038 int nvec,
1039 char **vec) {
1040 struct netaddress na[1];
1041
1042 if(netaddress_parse(na, nvec, vec)) {
1043 disorder_error(0, "%s:%d: invalid network address", cs->path, cs->line);
1044 return -1;
1045 }
1046 if(!na->address) {
1047 disorder_error(0, "%s:%d: destination address required", cs->path, cs->line);
1048 return -1;
1049 }
1050 return 0;
1051}
1052
1053/** @brief Item name and and offset */
1054#define C(x) #x, offsetof(struct config, x)
1055/** @brief Item name and and offset */
1056#define C2(x,y) #x, offsetof(struct config, y)
1057
1058/** @brief All configuration items */
1059static const struct conf conf[] = {
1060 { C(alias), &type_string, validate_alias },
1061 { C(allow), &type_stringlist_accum, validate_allow },
1062 { C(api), &type_string, validate_backend },
1063 { C(authorization_algorithm), &type_string, validate_algo },
1064 { C(broadcast), &type_netaddress, validate_destaddr },
1065 { C(broadcast_from), &type_netaddress, validate_any },
1066 { C(channel), &type_string, validate_any },
1067 { C(checkpoint_kbyte), &type_integer, validate_non_negative },
1068 { C(checkpoint_min), &type_integer, validate_non_negative },
1069 { C(collection), &type_collections, validate_any },
1070 { C(connect), &type_netaddress, validate_destaddr },
1071 { C(cookie_key_lifetime), &type_integer, validate_positive },
1072 { C(cookie_login_lifetime), &type_integer, validate_positive },
1073 { C(dbversion), &type_integer, validate_positive },
1074 { C(default_rights), &type_rights, validate_any },
1075 { C(device), &type_string, validate_any },
1076 { C(gap), &type_integer, validate_non_negative },
1077 { C(history), &type_integer, validate_positive },
1078 { C(home), &type_string, validate_isabspath },
1079 { C(listen), &type_netaddress, validate_any },
1080 { C(lock), &type_boolean, validate_any },
1081 { C(mail_sender), &type_string, validate_any },
1082 { C(mixer), &type_string, validate_any },
1083 { C(mount_rescan), &type_boolean, validate_any },
1084 { C(multicast_loop), &type_boolean, validate_any },
1085 { C(multicast_ttl), &type_integer, validate_non_negative },
1086 { C(namepart), &type_namepart, validate_any },
1087 { C(new_bias), &type_integer, validate_positive },
1088 { C(new_bias_age), &type_integer, validate_positive },
1089 { C(new_max), &type_integer, validate_positive },
1090 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
1091 { C(nice_rescan), &type_integer, validate_non_negative },
1092 { C(nice_server), &type_integer, validate_any },
1093 { C(nice_speaker), &type_integer, validate_any },
1094 { C(noticed_history), &type_integer, validate_positive },
1095 { C(password), &type_string, validate_any },
1096 { C(pause_mode), &type_string, validate_pausemode },
1097 { C(player), &type_stringlist_accum, validate_player },
1098 { C(playlist_lock_timeout), &type_integer, validate_positive },
1099 { C(playlist_max) , &type_integer, validate_positive },
1100 { C(plugins), &type_string_accum, validate_isdir },
1101 { C(prefsync), &type_integer, validate_positive },
1102 { C(queue_pad), &type_integer, validate_positive },
1103 { C(refresh), &type_integer, validate_positive },
1104 { C(refresh_min), &type_integer, validate_non_negative },
1105 { C(reminder_interval), &type_integer, validate_positive },
1106 { C(remote_userman), &type_boolean, validate_any },
1107 { C(replay_min), &type_integer, validate_non_negative },
1108 { C2(restrict, restrictions), &type_restrict, validate_any },
1109 { C(rtp_delay_threshold), &type_integer, validate_positive },
1110 { C(sample_format), &type_sample_format, validate_sample_format },
1111 { C(scratch), &type_string_accum, validate_isreg },
1112 { C(sendmail), &type_string, validate_isabspath },
1113 { C(short_display), &type_integer, validate_positive },
1114 { C(signal), &type_signal, validate_any },
1115 { C(smtp_server), &type_string, validate_any },
1116 { C(sox_generation), &type_integer, validate_non_negative },
1117 { C2(speaker_backend, api), &type_string, validate_backend },
1118 { C(speaker_command), &type_string, validate_any },
1119 { C(stopword), &type_string_accum, validate_any },
1120 { C(templates), &type_string_accum, validate_isdir },
1121 { C(tracklength), &type_stringlist_accum, validate_tracklength },
1122 { C(transform), &type_transform, validate_any },
1123 { C(trust), &type_string_accum, validate_any },
1124 { C(url), &type_string, validate_url },
1125 { C(user), &type_string, validate_isauser },
1126 { C(username), &type_string, validate_any },
1127};
1128
1129/** @brief Find a configuration item's definition by key */
1130static const struct conf *find(const char *key) {
1131 int n;
1132
1133 if((n = TABLE_FIND(conf, name, key)) < 0)
1134 return 0;
1135 return &conf[n];
1136}
1137
1138/** @brief Set a new configuration value
1139 * @param cs Configuration state
1140 * @param nvec Length of @p vec
1141 * @param vec Name and new value
1142 * @return 0 on success, non-0 on error.
1143 *
1144 * @c vec[0] is the name, the rest is the value.
1145 */
1146static int config_set(const struct config_state *cs,
1147 int nvec, char **vec) {
1148 const struct conf *which;
1149
1150 D(("config_set %s", vec[0]));
1151 if(!(which = find(vec[0]))) {
1152 disorder_error(0, "%s:%d: unknown configuration key '%s'",
1153 cs->path, cs->line, vec[0]);
1154 return -1;
1155 }
1156 return (which->validate(cs, nvec - 1, vec + 1)
1157 || which->type->set(cs, which, nvec - 1, vec + 1));
1158}
1159
1160/** @brief Set a configuration item from parameters
1161 * @param cs Configuration state
1162 * @param which Item to set
1163 * @param ... Value as strings, terminated by (char *)NULL
1164 * @return 0 on success, non-0 on error
1165 */
1166static int config_set_args(const struct config_state *cs,
1167 const char *which, ...) {
1168 va_list ap;
1169 struct vector v[1];
1170 char *s;
1171
1172 vector_init(v);
1173 vector_append(v, (char *)which);
1174 va_start(ap, which);
1175 while((s = va_arg(ap, char *)))
1176 vector_append(v, s);
1177 va_end(ap);
1178 vector_terminate(v);
1179 return config_set(cs, v->nvec, v->vec);
1180}
1181
1182/** @brief Error callback used by config_include()
1183 * @param msg Error message
1184 * @param u User data (@ref config_state)
1185 */
1186static void config_error(const char *msg, void *u) {
1187 const struct config_state *cs = u;
1188
1189 disorder_error(0, "%s:%d: %s", cs->path, cs->line, msg);
1190}
1191
1192/** @brief Include a file by name
1193 * @param c Configuration to update
1194 * @param path Path to read
1195 * @return 0 on success, non-0 on error
1196 */
1197static int config_include(struct config *c, const char *path) {
1198 FILE *fp;
1199 char *buffer, *inputbuffer, **vec;
1200 int n, ret = 0;
1201 struct config_state cs;
1202
1203 cs.path = path;
1204 cs.line = 0;
1205 cs.config = c;
1206 D(("%s: reading configuration", path));
1207 if(!(fp = fopen(path, "r"))) {
1208 disorder_error(errno, "error opening %s", path);
1209 return -1;
1210 }
1211 while(!inputline(path, fp, &inputbuffer, '\n')) {
1212 ++cs.line;
1213 if(!(buffer = mb2utf8(inputbuffer))) {
1214 disorder_error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1215 ret = -1;
1216 xfree(inputbuffer);
1217 continue;
1218 }
1219 xfree(inputbuffer);
1220 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1221 config_error, &cs))) {
1222 ret = -1;
1223 xfree(buffer);
1224 continue;
1225 }
1226 if(n) {
1227 /* 'include' is special-cased */
1228 if(!strcmp(vec[0], "include")) {
1229 if(n != 2) {
1230 disorder_error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1231 ret = -1;
1232 } else
1233 config_include(c, vec[1]);
1234 } else
1235 ret |= config_set(&cs, n, vec);
1236 }
1237 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1238 xfree(vec);
1239 xfree(buffer);
1240 }
1241 if(ferror(fp)) {
1242 disorder_error(errno, "error reading %s", path);
1243 ret = -1;
1244 }
1245 fclose(fp);
1246 return ret;
1247}
1248
1249/** @brief Default stopword setting */
1250static const char *const default_stopwords[] = {
1251 "stopword",
1252
1253 "01",
1254 "02",
1255 "03",
1256 "04",
1257 "05",
1258 "06",
1259 "07",
1260 "08",
1261 "09",
1262 "1",
1263 "10",
1264 "11",
1265 "12",
1266 "13",
1267 "14",
1268 "15",
1269 "16",
1270 "17",
1271 "18",
1272 "19",
1273 "2",
1274 "20",
1275 "21",
1276 "22",
1277 "23",
1278 "24",
1279 "25",
1280 "26",
1281 "27",
1282 "28",
1283 "29",
1284 "3",
1285 "30",
1286 "4",
1287 "5",
1288 "6",
1289 "7",
1290 "8",
1291 "9",
1292 "a",
1293 "am",
1294 "an",
1295 "and",
1296 "as",
1297 "for",
1298 "i",
1299 "im",
1300 "in",
1301 "is",
1302 "of",
1303 "on",
1304 "the",
1305 "to",
1306 "too",
1307 "we",
1308};
1309#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1310
1311/** @brief Default player patterns */
1312static const char *const default_players[] = {
1313 "*.ogg",
1314 "*.flac",
1315 "*.mp3",
1316 "*.wav",
1317};
1318#define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1319
1320/** @brief Make a new default configuration
1321 * @return New configuration
1322 */
1323static struct config *config_default(void) {
1324 struct config *c = xmalloc(sizeof *c);
1325 const char *logname;
1326 struct passwd *pw;
1327 struct config_state cs;
1328 size_t n;
1329
1330 cs.path = "<internal>";
1331 cs.line = 0;
1332 cs.config = c;
1333 /* Strings had better be xstrdup'd as they will get freed at some point. */
1334 c->gap = 0;
1335 c->history = 60;
1336 c->home = xstrdup(pkgstatedir);
1337 if(!(pw = getpwuid(getuid())))
1338 disorder_fatal(0, "cannot determine our username");
1339 logname = pw->pw_name;
1340 c->username = xstrdup(logname);
1341 c->refresh = 15;
1342 c->refresh_min = 1;
1343 c->prefsync = 0;
1344 c->signal = SIGKILL;
1345 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1346 c->lock = 1;
1347 c->device = xstrdup("default");
1348 c->nice_rescan = 10;
1349 c->speaker_command = 0;
1350 c->sample_format.bits = 16;
1351 c->sample_format.rate = 44100;
1352 c->sample_format.channels = 2;
1353 c->sample_format.endian = ENDIAN_NATIVE;
1354 c->queue_pad = 10;
1355 c->replay_min = 8 * 3600;
1356 c->api = NULL;
1357 c->multicast_ttl = 1;
1358 c->multicast_loop = 1;
1359 c->authorization_algorithm = xstrdup("sha1");
1360 c->noticed_history = 31;
1361 c->short_display = 32;
1362 c->mixer = 0;
1363 c->channel = 0;
1364 c->dbversion = 2;
1365 c->cookie_login_lifetime = 86400;
1366 c->cookie_key_lifetime = 86400 * 7;
1367 if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1368 c->sendmail = xstrdup(sendmail_binary);
1369 c->smtp_server = xstrdup("127.0.0.1");
1370 c->new_max = 100;
1371 c->reminder_interval = 600; /* 10m */
1372 c->new_bias_age = 7 * 86400; /* 1 week */
1373 c->new_bias = 4500000; /* 50 times the base weight */
1374 c->sox_generation = DEFAULT_SOX_GENERATION;
1375 c->playlist_max = INT_MAX; /* effectively no limit */
1376 c->playlist_lock_timeout = 10; /* 10s */
1377 c->mount_rescan = 1;
1378 /* Default stopwords */
1379 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1380 exit(1);
1381 /* Default player configuration */
1382 for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1383 if(config_set_args(&cs, "player",
1384 default_players[n], "execraw", "disorder-decode", (char *)0))
1385 exit(1);
1386 if(config_set_args(&cs, "tracklength",
1387 default_players[n], "disorder-tracklength", (char *)0))
1388 exit(1);
1389 }
1390 c->broadcast.af = -1;
1391 c->broadcast_from.af = -1;
1392 c->listen.af = -1;
1393 c->connect.af = -1;
1394 return c;
1395}
1396
1397/** @brief Construct a filename
1398 * @param c Configuration
1399 * @param name Base filename
1400 * @return Full filename
1401 *
1402 * Usually use config_get_file() instead.
1403 */
1404char *config_get_file2(struct config *c, const char *name) {
1405 char *s;
1406
1407 byte_xasprintf(&s, "%s/%s", c->home, name);
1408 return s;
1409}
1410
1411/** @brief Set the default configuration file */
1412static void set_configfile(void) {
1413 if(!configfile)
1414 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1415}
1416
1417/** @brief Free a configuration object
1418 * @param c Configuration to free
1419 *
1420 * @p c is indeterminate after this function is called.
1421 */
1422static void config_free(struct config *c) {
1423 int n;
1424
1425 if(c) {
1426 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1427 conf[n].type->free(c, &conf[n]);
1428 for(n = 0; n < c->nparts; ++n)
1429 xfree(c->parts[n]);
1430 xfree(c->parts);
1431 xfree(c);
1432 }
1433}
1434
1435/** @brief Set post-parse defaults
1436 * @param c Configuration to update
1437 * @param server True when running in the server
1438 *
1439 * If @p server is set then certain parts of the configuration are more
1440 * strictly validated.
1441 */
1442static void config_postdefaults(struct config *c,
1443 int server) {
1444 struct config_state cs;
1445 const struct conf *whoami;
1446 int n;
1447
1448 static const char *namepart[][4] = {
1449 { "title", "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1450 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1451 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1452 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1453 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1454 };
1455#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1456
1457 static const char *transform[][5] = {
1458 { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1459 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1460 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1461 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1462 { "dir", "[[:punct:]]", "", "sort", "g", }
1463 };
1464#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1465
1466 cs.path = "<internal>";
1467 cs.line = 0;
1468 cs.config = c;
1469 if(!c->namepart.n) {
1470 whoami = find("namepart");
1471 for(n = 0; n < NNAMEPART; ++n)
1472 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1473 }
1474 if(!c->transform.n) {
1475 whoami = find("transform");
1476 for(n = 0; n < NTRANSFORM; ++n)
1477 set_transform(&cs, whoami, 5, (char **)transform[n]);
1478 }
1479 if(!c->api) {
1480 if(c->speaker_command)
1481 c->api = xstrdup("command");
1482 else if(c->broadcast.af != -1)
1483 c->api = xstrdup("rtp");
1484 else if(config_uaudio_apis)
1485 c->api = xstrdup(config_uaudio_apis[0]->name);
1486 else
1487 c->api = xstrdup("<none>");
1488 }
1489 if(!strcmp(c->api, "network"))
1490 c->api = xstrdup("rtp");
1491 if(server) {
1492 if(!strcmp(c->api, "command") && !c->speaker_command)
1493 disorder_fatal(0, "'api command' but speaker_command is not set");
1494 if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1495 disorder_fatal(0, "'api rtp' but broadcast is not set");
1496 }
1497 /* Override sample format */
1498 if(!strcmp(c->api, "rtp")) {
1499 c->sample_format.rate = 44100;
1500 c->sample_format.channels = 2;
1501 c->sample_format.bits = 16;
1502 c->sample_format.endian = ENDIAN_NATIVE;
1503 }
1504 if(!strcmp(c->api, "coreaudio")) {
1505 c->sample_format.rate = 44100;
1506 c->sample_format.channels = 2;
1507 c->sample_format.bits = 16;
1508 c->sample_format.endian = ENDIAN_NATIVE;
1509 }
1510 if(!c->default_rights) {
1511 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1512 |RIGHT_MOVE__MASK
1513 |RIGHT_SCRATCH__MASK
1514 |RIGHT_REMOVE__MASK);
1515 /* The idea is to approximate the meaning of the old 'restrict' directive
1516 * in the default rights if they are not overridden. */
1517 if(c->restrictions & RESTRICT_SCRATCH)
1518 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1519 else
1520 r |= RIGHT_SCRATCH_ANY;
1521 if(!(c->restrictions & RESTRICT_MOVE))
1522 r |= RIGHT_MOVE_ANY;
1523 if(c->restrictions & RESTRICT_REMOVE)
1524 r |= RIGHT_REMOVE_MINE;
1525 else
1526 r |= RIGHT_REMOVE_ANY;
1527 c->default_rights = rights_string(r);
1528 }
1529}
1530
1531/** @brief (Re-)read the config file
1532 * @param server If set, do extra checking
1533 * @param oldconfig Old configuration for compatibility check
1534 * @return 0 on success, non-0 on error
1535 *
1536 * If @p oldconfig is set, then certain compatibility checks are done between
1537 * the old and new configurations.
1538 */
1539int config_read(int server,
1540 const struct config *oldconfig) {
1541 struct config *c;
1542 char *privconf;
1543 struct passwd *pw;
1544
1545 set_configfile();
1546 c = config_default();
1547 /* standalone Disobedience installs might not have a global config file */
1548 if(access(configfile, F_OK) == 0)
1549 if(config_include(c, configfile))
1550 return -1;
1551 /* if we can read the private config file, do */
1552 if((privconf = config_private())
1553 && access(privconf, R_OK) == 0
1554 && config_include(c, privconf))
1555 return -1;
1556 xfree(privconf);
1557 /* if there's a per-user system config file for this user, read it */
1558 if(config_per_user) {
1559 if(!(pw = getpwuid(getuid())))
1560 disorder_fatal(0, "cannot determine our username");
1561 if((privconf = config_usersysconf(pw))
1562 && access(privconf, F_OK) == 0
1563 && config_include(c, privconf))
1564 return -1;
1565 xfree(privconf);
1566 /* if we have a password file, read it */
1567 if((privconf = config_userconf(0, pw))
1568 && access(privconf, F_OK) == 0
1569 && config_include(c, privconf))
1570 return -1;
1571 xfree(privconf);
1572 }
1573 /* install default namepart and transform settings */
1574 config_postdefaults(c, server);
1575 if(oldconfig) {
1576 int failed = 0;
1577 if(strcmp(c->home, oldconfig->home)) {
1578 disorder_error(0, "'home' cannot be changed without a restart");
1579 failed = 1;
1580 }
1581 if(strcmp(c->alias, oldconfig->alias)) {
1582 disorder_error(0, "'alias' cannot be changed without a restart");
1583 failed = 1;
1584 }
1585 if(strcmp(c->user, oldconfig->user)) {
1586 disorder_error(0, "'user' cannot be changed without a restart");
1587 failed = 1;
1588 }
1589 if(c->nice_speaker != oldconfig->nice_speaker) {
1590 disorder_error(0, "'nice_speaker' cannot be changed without a restart");
1591 /* ...but we accept the new config anyway */
1592 }
1593 if(c->nice_server != oldconfig->nice_server) {
1594 disorder_error(0, "'nice_server' cannot be changed without a restart");
1595 /* ...but we accept the new config anyway */
1596 }
1597 if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1598 disorder_error(0, "'namepart' settings cannot be changed without a restart");
1599 failed = 1;
1600 }
1601 if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1602 disorder_error(0, "'stopword' settings cannot be changed without a restart");
1603 failed = 1;
1604 }
1605 if(failed) {
1606 disorder_error(0, "not installing incompatible new configuration");
1607 return -1;
1608 }
1609 }
1610 /* everything is good so we shall use the new config */
1611 config_free(config);
1612 /* warn about obsolete directives */
1613 if(c->restrictions)
1614 disorder_error(0, "'restrict' will be removed in a future version");
1615 if(c->allow.n)
1616 disorder_error(0, "'allow' will be removed in a future version");
1617 if(c->trust.n)
1618 disorder_error(0, "'trust' will be removed in a future version");
1619 if(!c->lock)
1620 disorder_error(0, "'lock' will be removed in a future version");
1621 if(c->gap)
1622 disorder_error(0, "'gap' will be removed in a future version");
1623 if(c->prefsync)
1624 disorder_error(0, "'prefsync' will be removed in a future version");
1625 config = c;
1626 return 0;
1627}
1628
1629/** @brief Return the path to the private configuration file */
1630char *config_private(void) {
1631 char *s;
1632
1633 set_configfile();
1634 byte_xasprintf(&s, "%s.private", configfile);
1635 return s;
1636}
1637
1638/** @brief Return the path to user's personal configuration file */
1639char *config_userconf(const char *home, const struct passwd *pw) {
1640 char *s;
1641
1642 if(!home && !pw && !(pw = getpwuid(getuid())))
1643 disorder_fatal(0, "cannot determine our username");
1644 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1645 return s;
1646}
1647
1648/** @brief Return the path to user-specific system configuration */
1649char *config_usersysconf(const struct passwd *pw) {
1650 char *s;
1651
1652 set_configfile();
1653 if(!strchr(pw->pw_name, '/')) {
1654 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1655 return s;
1656 } else
1657 return 0;
1658}
1659
1660/** @brief Get a filename within the home directory
1661 * @param name Relative name
1662 * @return Full path
1663 */
1664char *config_get_file(const char *name) {
1665 return config_get_file2(config, name);
1666}
1667
1668/** @brief Order two stringlists
1669 * @param a First stringlist
1670 * @param b Second stringlist
1671 * @return <0, 0 or >0 if a<b, a=b or a>b
1672 */
1673static int stringlist_compare(const struct stringlist *a,
1674 const struct stringlist *b) {
1675 int n = 0, c;
1676
1677 while(n < a->n && n < b->n) {
1678 if((c = strcmp(a->s[n], b->s[n])))
1679 return c;
1680 ++n;
1681 }
1682 if(a->n < b->n)
1683 return -1;
1684 else if(a->n > b->n)
1685 return 1;
1686 else
1687 return 0;
1688}
1689
1690/** @brief Order two namepart definitions
1691 * @param a First namepart definition
1692 * @param b Second namepart definition
1693 * @return <0, 0 or >0 if a<b, a=b or a>b
1694 */
1695static int namepart_compare(const struct namepart *a,
1696 const struct namepart *b) {
1697 int c;
1698
1699 if((c = strcmp(a->part, b->part)))
1700 return c;
1701 if((c = strcmp(a->res, b->res)))
1702 return c;
1703 if((c = strcmp(a->replace, b->replace)))
1704 return c;
1705 if((c = strcmp(a->context, b->context)))
1706 return c;
1707 if(a->reflags > b->reflags)
1708 return 1;
1709 if(a->reflags < b->reflags)
1710 return -1;
1711 return 0;
1712}
1713
1714/** @brief Order two lists of namepart definitions
1715 * @param a First list of namepart definitions
1716 * @param b Second list of namepart definitions
1717 * @return <0, 0 or >0 if a<b, a=b or a>b
1718 */
1719static int namepartlist_compare(const struct namepartlist *a,
1720 const struct namepartlist *b) {
1721 int n = 0, c;
1722
1723 while(n < a->n && n < b->n) {
1724 if((c = namepart_compare(&a->s[n], &b->s[n])))
1725 return c;
1726 ++n;
1727 }
1728 if(a->n > b->n)
1729 return 1;
1730 else if(a->n < b->n)
1731 return -1;
1732 else
1733 return 0;
1734}
1735
1736/** @brief Verify configuration table.
1737 * @return The number of problems found
1738*/
1739int config_verify(void) {
1740 int fails = 0;
1741 for(size_t n = 1; n < sizeof conf / sizeof *conf; ++n)
1742 if(strcmp(conf[n-1].name, conf[n].name) >= 0) {
1743 fprintf(stderr, "%s >= %s\n", conf[n-1].name, conf[n].name);
1744 ++fails;
1745 }
1746 return fails;
1747}
1748
1749/*
1750Local Variables:
1751c-basic-offset:2
1752comment-column:40
1753fill-column:79
1754End:
1755*/