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