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