chiark / gitweb /
Online registration now sends email. The confirmation URL doesn't
[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;
e99d42b1 476#endif
477 } else if(!strcmp(vec[0], "oss")) {
478#if HAVE_SYS_SOUNDCARD_H
479 *valuep = BACKEND_OSS;
480#else
481 error(0, "%s:%d: OSS is not available on this platform",
482 cs->path, cs->line);
483 return -1;
937be4c0
RK
484#endif
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 },
bb6ae3fb 931 { C(mail_sender), &type_string, validate_any },
460b9539 932 { C(mixer), &type_string, validate_ischr },
61941295 933 { C(multicast_loop), &type_boolean, validate_any },
23205f9c 934 { C(multicast_ttl), &type_integer, validate_non_negative },
460b9539 935 { C(namepart), &type_namepart, validate_any },
936 { C2(nice, nice_rescan), &type_integer, validate_non_negative },
937 { C(nice_rescan), &type_integer, validate_non_negative },
938 { C(nice_server), &type_integer, validate_any },
939 { C(nice_speaker), &type_integer, validate_any },
2a10b70b 940 { C(noticed_history), &type_integer, validate_positive },
460b9539 941 { C(password), &type_string, validate_any },
942 { C(player), &type_stringlist_accum, validate_player },
943 { C(plugins), &type_string_accum, validate_isdir },
944 { C(prefsync), &type_integer, validate_positive },
459d4402 945 { C(queue_pad), &type_integer, validate_positive },
460b9539 946 { C(refresh), &type_integer, validate_positive },
947 { C2(restrict, restrictions), &type_restrict, validate_any },
9d5da576 948 { C(sample_format), &type_sample_format, validate_sample_format },
460b9539 949 { C(scratch), &type_string_accum, validate_isreg },
61507e3c 950 { C(short_display), &type_integer, validate_positive },
460b9539 951 { C(signal), &type_signal, validate_any },
bb6ae3fb 952 { C(smtp_server), &type_string, validate_any },
5330d674 953 { C(sox_generation), &type_integer, validate_non_negative },
e83d0967 954 { C(speaker_backend), &type_backend, validate_any },
9d5da576 955 { C(speaker_command), &type_string, validate_any },
460b9539 956 { C(stopword), &type_string_accum, validate_any },
957 { C(templates), &type_string_accum, validate_isdir },
62dc3748 958 { C(tracklength), &type_stringlist_accum, validate_tracklength },
460b9539 959 { C(transform), &type_transform, validate_any },
960 { C(trust), &type_string_accum, validate_any },
961 { C(url), &type_string, validate_url },
962 { C(user), &type_string, validate_isauser },
963 { C(username), &type_string, validate_any },
964};
965
3f3bb97b 966/** @brief Find a configuration item's definition by key */
460b9539 967static const struct conf *find(const char *key) {
968 int n;
969
970 if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
971 return 0;
972 return &conf[n];
973}
974
3f3bb97b 975/** @brief Set a new configuration value */
460b9539 976static int config_set(const struct config_state *cs,
977 int nvec, char **vec) {
978 const struct conf *which;
979
980 D(("config_set %s", vec[0]));
981 if(!(which = find(vec[0]))) {
982 error(0, "%s:%d: unknown configuration key '%s'",
983 cs->path, cs->line, vec[0]);
984 return -1;
985 }
986 return (which->validate(cs, nvec - 1, vec + 1)
987 || which->type->set(cs, which, nvec - 1, vec + 1));
988}
989
0e4472a0 990/** @brief Error callback used by config_include() */
460b9539 991static void config_error(const char *msg, void *u) {
992 const struct config_state *cs = u;
993
994 error(0, "%s:%d: %s", cs->path, cs->line, msg);
995}
996
3f3bb97b 997/** @brief Include a file by name */
460b9539 998static int config_include(struct config *c, const char *path) {
999 FILE *fp;
1000 char *buffer, *inputbuffer, **vec;
1001 int n, ret = 0;
1002 struct config_state cs;
1003
1004 cs.path = path;
1005 cs.line = 0;
1006 cs.config = c;
1007 D(("%s: reading configuration", path));
1008 if(!(fp = fopen(path, "r"))) {
1009 error(errno, "error opening %s", path);
1010 return -1;
1011 }
1012 while(!inputline(path, fp, &inputbuffer, '\n')) {
1013 ++cs.line;
1014 if(!(buffer = mb2utf8(inputbuffer))) {
1015 error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1016 ret = -1;
1017 xfree(inputbuffer);
1018 continue;
1019 }
1020 xfree(inputbuffer);
1021 if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1022 config_error, &cs))) {
1023 ret = -1;
1024 xfree(buffer);
1025 continue;
1026 }
1027 if(n) {
1028 if(!strcmp(vec[0], "include")) {
1029 if(n != 2) {
1030 error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1031 ret = -1;
1032 } else
1033 config_include(c, vec[1]);
1034 } else
1035 ret |= config_set(&cs, n, vec);
1036 }
1037 for(n = 0; vec[n]; ++n) xfree(vec[n]);
1038 xfree(vec);
1039 xfree(buffer);
1040 }
1041 if(ferror(fp)) {
1042 error(errno, "error reading %s", path);
1043 ret = -1;
1044 }
1045 fclose(fp);
1046 return ret;
1047}
1048
86be0c30 1049static const char *const default_stopwords[] = {
1050 "stopword",
1051
1052 "01",
1053 "02",
1054 "03",
1055 "04",
1056 "05",
1057 "06",
1058 "07",
1059 "08",
1060 "09",
1061 "1",
1062 "10",
1063 "11",
1064 "12",
1065 "13",
1066 "14",
1067 "15",
1068 "16",
1069 "17",
1070 "18",
1071 "19",
1072 "2",
1073 "20",
1074 "21",
1075 "22",
1076 "23",
1077 "24",
1078 "25",
1079 "26",
1080 "27",
1081 "28",
1082 "29",
1083 "3",
1084 "30",
1085 "4",
1086 "5",
1087 "6",
1088 "7",
1089 "8",
1090 "9",
1091 "a",
1092 "am",
1093 "an",
1094 "and",
1095 "as",
1096 "for",
1097 "i",
1098 "im",
1099 "in",
1100 "is",
1101 "of",
1102 "on",
1103 "the",
1104 "to",
1105 "too",
1106 "we",
1107};
1108#define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1109
3f3bb97b 1110/** @brief Make a new default configuration */
460b9539 1111static struct config *config_default(void) {
1112 struct config *c = xmalloc(sizeof *c);
1113 const char *logname;
1114 struct passwd *pw;
86be0c30 1115 struct config_state cs;
460b9539 1116
86be0c30 1117 cs.path = "<internal>";
1118 cs.line = 0;
1119 cs.config = c;
460b9539 1120 /* Strings had better be xstrdup'd as they will get freed at some point. */
1121 c->gap = 2;
1122 c->history = 60;
1123 c->home = xstrdup(pkgstatedir);
1124 if(!(pw = getpwuid(getuid())))
1125 fatal(0, "cannot determine our username");
1126 logname = pw->pw_name;
1127 c->username = xstrdup(logname);
1128 c->refresh = 15;
1129 c->prefsync = 3600;
1130 c->signal = SIGKILL;
1131 c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1132 c->lock = 1;
1133 c->device = xstrdup("default");
1134 c->nice_rescan = 10;
9d5da576 1135 c->speaker_command = 0;
1136 c->sample_format.bits = 16;
1137 c->sample_format.rate = 44100;
1138 c->sample_format.channels = 2;
6d2d327c 1139 c->sample_format.endian = ENDIAN_NATIVE;
459d4402 1140 c->queue_pad = 10;
e83d0967 1141 c->speaker_backend = -1;
23205f9c 1142 c->multicast_ttl = 1;
61941295 1143 c->multicast_loop = 1;
637fdea3 1144 c->authorization_algorithm = xstrdup("sha1");
2a10b70b 1145 c->noticed_history = 31;
61507e3c 1146 c->short_display = 32;
321f7536
RK
1147 c->mixer = xstrdup("/dev/mixer");
1148 c->channel = xstrdup("pcm");
8818b7fc 1149 c->dbversion = 2;
b12be54a
RK
1150 c->cookie_login_lifetime = 86400;
1151 c->cookie_key_lifetime = 86400 * 7;
bb6ae3fb 1152 c->smtp_server = xstrdup("127.0.0.1");
86be0c30 1153 if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1154 exit(1);
460b9539 1155 return c;
1156}
1157
1158static char *get_file(struct config *c, const char *name) {
1159 char *s;
1160
1161 byte_xasprintf(&s, "%s/%s", c->home, name);
1162 return s;
1163}
1164
3f3bb97b 1165/** @brief Set the default configuration file */
460b9539 1166static void set_configfile(void) {
1167 if(!configfile)
1168 byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1169}
1170
3f3bb97b 1171/** @brief Free a configuration object */
460b9539 1172static void config_free(struct config *c) {
1173 int n;
1174
1175 if(c) {
1176 for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1177 conf[n].type->free(c, &conf[n]);
1178 for(n = 0; n < c->nparts; ++n)
1179 xfree(c->parts[n]);
1180 xfree(c->parts);
1181 xfree(c);
1182 }
1183}
1184
3f3bb97b 1185/** @brief Set post-parse defaults */
c00fce3a
RK
1186static void config_postdefaults(struct config *c,
1187 int server) {
460b9539 1188 struct config_state cs;
1189 const struct conf *whoami;
1190 int n;
1191
1192 static const char *namepart[][4] = {
88608992 1193 { "title", "/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
460b9539 1194 { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" },
1195 { "album", "/([^/]+)/[^/]+$", "$1", "*" },
1196 { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" },
1197 { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" },
1198 };
1199#define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1200
1201 static const char *transform[][5] = {
88608992 1202 { "track", "^.*/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
460b9539 1203 { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" },
1204 { "dir", "^.*/([^/]+)$", "$1", "*", "" },
1205 { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", },
1206 { "dir", "[[:punct:]]", "", "sort", "g", }
1207 };
1208#define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1209
1210 cs.path = "<internal>";
1211 cs.line = 0;
1212 cs.config = c;
1213 if(!c->namepart.n) {
1214 whoami = find("namepart");
1215 for(n = 0; n < NNAMEPART; ++n)
1216 set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1217 }
1218 if(!c->transform.n) {
1219 whoami = find("transform");
1220 for(n = 0; n < NTRANSFORM; ++n)
1221 set_transform(&cs, whoami, 5, (char **)transform[n]);
1222 }
e83d0967
RK
1223 if(c->speaker_backend == -1) {
1224 if(c->speaker_command)
1225 c->speaker_backend = BACKEND_COMMAND;
1226 else if(c->broadcast.n)
1227 c->speaker_backend = BACKEND_NETWORK;
1228 else {
146e86fb 1229#if HAVE_ALSA_ASOUNDLIB_H
e83d0967 1230 c->speaker_backend = BACKEND_ALSA;
146e86fb 1231#elif HAVE_SYS_SOUNDCARD_H
1232 c->speaker_backend = BACKEND_OSS;
937be4c0
RK
1233#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
1234 c->speaker_backend = BACKEND_COREAUDIO;
e83d0967
RK
1235#else
1236 c->speaker_backend = BACKEND_COMMAND;
1237#endif
1238 }
1239 }
c00fce3a
RK
1240 if(server) {
1241 if(c->speaker_backend == BACKEND_COMMAND && !c->speaker_command)
1242 fatal(0, "speaker_backend is command but speaker_command is not set");
1243 if(c->speaker_backend == BACKEND_NETWORK && !c->broadcast.n)
1244 fatal(0, "speaker_backend is network but broadcast is not set");
1245 }
e99d42b1 1246 /* Override sample format */
1247 switch(c->speaker_backend) {
1248 case BACKEND_NETWORK:
6d2d327c
RK
1249 c->sample_format.rate = 44100;
1250 c->sample_format.channels = 2;
1251 c->sample_format.bits = 16;
1252 c->sample_format.endian = ENDIAN_BIG;
e99d42b1 1253 break;
1254 case BACKEND_COREAUDIO:
937be4c0
RK
1255 c->sample_format.rate = 44100;
1256 c->sample_format.channels = 2;
1257 c->sample_format.bits = 16;
1258 c->sample_format.endian = ENDIAN_NATIVE;
e99d42b1 1259 break;
04e1fa7c
RK
1260 }
1261 if(!c->default_rights) {
1262 rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1263 |RIGHT_MOVE__MASK
1264 |RIGHT_SCRATCH__MASK
1265 |RIGHT_REMOVE__MASK);
1266 /* The idea is to approximate the meaning of the old 'restrict' directive
1267 * in the default rights if they are not overridden. */
1268 if(c->restrictions & RESTRICT_SCRATCH)
1269 r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1270 else
1271 r |= RIGHT_SCRATCH_ANY;
1272 if(!(c->restrictions & RESTRICT_MOVE))
1273 r |= RIGHT_MOVE_ANY;
1274 if(c->restrictions & RESTRICT_REMOVE)
1275 r |= RIGHT_REMOVE_MINE;
1276 else
1277 r |= RIGHT_REMOVE_ANY;
0f55e905 1278 c->default_rights = rights_string(r);
04e1fa7c 1279 }
460b9539 1280}
1281
c00fce3a
RK
1282/** @brief (Re-)read the config file
1283 * @param server If set, do extra checking
1284 */
1285int config_read(int server) {
460b9539 1286 struct config *c;
1287 char *privconf;
1288 struct passwd *pw;
1289
1290 set_configfile();
1291 c = config_default();
9ade2319 1292 /* standalone Disobedience installs might not have a global config file */
1293 if(access(configfile, F_OK) == 0)
1294 if(config_include(c, configfile))
1295 return -1;
460b9539 1296 /* if we can read the private config file, do */
1297 if((privconf = config_private())
1298 && access(privconf, R_OK) == 0
1299 && config_include(c, privconf))
1300 return -1;
1301 xfree(privconf);
1302 /* if there's a per-user system config file for this user, read it */
63ad732f
RK
1303 if(config_per_user) {
1304 if(!(pw = getpwuid(getuid())))
1305 fatal(0, "cannot determine our username");
1306 if((privconf = config_usersysconf(pw))
1307 && access(privconf, F_OK) == 0
1308 && config_include(c, privconf))
460b9539 1309 return -1;
63ad732f
RK
1310 xfree(privconf);
1311 /* if we have a password file, read it */
1312 if((privconf = config_userconf(getenv("HOME"), pw))
1313 && access(privconf, F_OK) == 0
1314 && config_include(c, privconf))
1315 return -1;
1316 xfree(privconf);
1317 }
460b9539 1318 /* install default namepart and transform settings */
c00fce3a 1319 config_postdefaults(c, server);
460b9539 1320 /* everything is good so we shall use the new config */
1321 config_free(config);
04e1fa7c
RK
1322 /* warn about obsolete directives */
1323 if(c->restrictions)
1324 error(0, "'restrict' will be removed in a future version");
1325 if(c->allow.n)
1326 error(0, "'allow' will be removed in a future version");
1327 if(c->trust.n)
1328 error(0, "'trust' will be removed in a future version");
460b9539 1329 config = c;
1330 return 0;
1331}
1332
3f3bb97b 1333/** @brief Return the path to the private configuration file */
460b9539 1334char *config_private(void) {
1335 char *s;
1336
1337 set_configfile();
1338 byte_xasprintf(&s, "%s.private", configfile);
1339 return s;
1340}
1341
3f3bb97b 1342/** @brief Return the path to user's personal configuration file */
460b9539 1343char *config_userconf(const char *home, const struct passwd *pw) {
1344 char *s;
1345
73f1b9f3
RK
1346 if(!home && !pw && !(pw = getpwuid(getuid())))
1347 fatal(0, "cannot determine our username");
460b9539 1348 byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1349 return s;
1350}
1351
3f3bb97b
RK
1352/** @brief Return the path to user-specific system configuration */
1353char *config_usersysconf(const struct passwd *pw) {
460b9539 1354 char *s;
1355
1356 set_configfile();
1357 if(!strchr(pw->pw_name, '/')) {
1358 byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1359 return s;
1360 } else
1361 return 0;
1362}
1363
1364char *config_get_file(const char *name) {
1365 return get_file(config, name);
1366}
1367
1368/*
1369Local Variables:
1370c-basic-offset:2
1371comment-column:40
1372fill-column:79
1373End:
1374*/