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