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