chiark / gitweb /
By default, disable user management over TCP (since it tends to have
[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 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19  * USA
20  */
21 /** @file lib/configuration.c
22  * @brief Configuration file support
23  */
24
25 #include <config.h>
26 #include "types.h"
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <unistd.h>
35 #include <ctype.h>
36 #include <stddef.h>
37 #include <pwd.h>
38 #include <langinfo.h>
39 #include <pcre.h>
40 #include <signal.h>
41
42 #include "rights.h"
43 #include "configuration.h"
44 #include "mem.h"
45 #include "log.h"
46 #include "split.h"
47 #include "syscalls.h"
48 #include "table.h"
49 #include "inputline.h"
50 #include "charset.h"
51 #include "defs.h"
52 #include "mixer.h"
53 #include "printf.h"
54 #include "regsub.h"
55 #include "signame.h"
56 #include "authhash.h"
57 #include "vector.h"
58
59 /** @brief Path to config file 
60  *
61  * set_configfile() sets the deafult if it is null.
62  */
63 char *configfile;
64
65 /** @brief Read user configuration
66  *
67  * If clear, the user-specific configuration is not read.
68  */
69 int config_per_user = 1;
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, struct restriction, 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_backend(const struct config_state *cs,
476                        const struct conf *whoami,
477                        int nvec, char **vec) {
478   int *const valuep = ADDRESS(cs->config, int);
479   
480   if(nvec != 1) {
481     error(0, "%s:%d: '%s' requires one argument",
482           cs->path, cs->line, whoami->name);
483     return -1;
484   }
485   if(!strcmp(vec[0], "alsa")) {
486 #if HAVE_ALSA_ASOUNDLIB_H
487     *valuep = BACKEND_ALSA;
488 #else
489     error(0, "%s:%d: ALSA is not available on this platform",
490           cs->path, cs->line);
491     return -1;
492 #endif
493   } else if(!strcmp(vec[0], "command"))
494     *valuep = BACKEND_COMMAND;
495   else if(!strcmp(vec[0], "network"))
496     *valuep = BACKEND_NETWORK;
497   else if(!strcmp(vec[0], "coreaudio")) {
498 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
499     *valuep = BACKEND_COREAUDIO;
500 #else
501     error(0, "%s:%d: Core Audio is not available on this platform",
502           cs->path, cs->line);
503     return -1;
504 #endif
505   } else if(!strcmp(vec[0], "oss")) {
506 #if HAVE_SYS_SOUNDCARD_H
507     *valuep = BACKEND_OSS;
508 #else
509     error(0, "%s:%d: OSS is not available on this platform",
510           cs->path, cs->line);
511     return -1;
512 #endif
513   } else {
514     error(0, "%s:%d: invalid '%s' value '%s'",
515           cs->path, cs->line, whoami->name, vec[0]);
516     return -1;
517   }
518   return 0;
519 }
520
521 static int set_rights(const struct config_state *cs,
522                       const struct conf *whoami,
523                       int nvec, char **vec) {
524   if(nvec != 1) {
525     error(0, "%s:%d: '%s' requires one argument",
526           cs->path, cs->line, whoami->name);
527     return -1;
528   }
529   if(parse_rights(vec[0], 0, 1)) {
530     error(0, "%s:%d: invalid rights string '%s'",
531           cs->path, cs->line, vec[0]);
532     return -1;
533   }
534   *ADDRESS(cs->config, char *) = vec[0];
535   return 0;
536 }
537
538 /* free functions */
539
540 static void free_none(struct config attribute((unused)) *c,
541                       const struct conf attribute((unused)) *whoami) {
542 }
543
544 static void free_string(struct config *c,
545                         const struct conf *whoami) {
546   xfree(VALUE(c, char *));
547 }
548
549 static void free_stringlist(struct config *c,
550                             const struct conf *whoami) {
551   int n;
552   struct stringlist *sl = ADDRESS(c, struct stringlist);
553
554   for(n = 0; n < sl->n; ++n)
555     xfree(sl->s[n]);
556   xfree(sl->s);
557 }
558
559 static void free_stringlistlist(struct config *c,
560                                 const struct conf *whoami) {
561   int n, m;
562   struct stringlistlist *sll = ADDRESS(c, struct stringlistlist);
563   struct stringlist *sl;
564
565   for(n = 0; n < sll->n; ++n) {
566     sl = &sll->s[n];
567     for(m = 0; m < sl->n; ++m)
568       xfree(sl->s[m]);
569     xfree(sl->s);
570   }
571   xfree(sll->s);
572 }
573
574 static void free_collectionlist(struct config *c,
575                                 const struct conf *whoami) {
576   struct collectionlist *cll = ADDRESS(c, struct collectionlist);
577   struct collection *cl;
578   int n;
579
580   for(n = 0; n < cll->n; ++n) {
581     cl = &cll->s[n];
582     xfree(cl->module);
583     xfree(cl->encoding);
584     xfree(cl->root);
585   }
586   xfree(cll->s);
587 }
588
589 static void free_namepartlist(struct config *c,
590                               const struct conf *whoami) {
591   struct namepartlist *npl = ADDRESS(c, struct namepartlist);
592   struct namepart *np;
593   int n;
594
595   for(n = 0; n < npl->n; ++n) {
596     np = &npl->s[n];
597     xfree(np->part);
598     pcre_free(np->re);                  /* ...whatever pcre_free is set to. */
599     xfree(np->replace);
600     xfree(np->context);
601   }
602   xfree(npl->s);
603 }
604
605 static void free_transformlist(struct config *c,
606                                const struct conf *whoami) {
607   struct transformlist *tl = ADDRESS(c, struct transformlist);
608   struct transform *t;
609   int n;
610
611   for(n = 0; n < tl->n; ++n) {
612     t = &tl->t[n];
613     xfree(t->type);
614     pcre_free(t->re);                   /* ...whatever pcre_free is set to. */
615     xfree(t->replace);
616     xfree(t->context);
617   }
618   xfree(tl->t);
619 }
620
621 /* configuration types */
622
623 static const struct conftype
624   type_signal = { set_signal, free_none },
625   type_collections = { set_collections, free_collectionlist },
626   type_boolean = { set_boolean, free_none },
627   type_string = { set_string, free_string },
628   type_stringlist = { set_stringlist, free_stringlist },
629   type_integer = { set_integer, free_none },
630   type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
631   type_string_accum = { set_string_accum, free_stringlist },
632   type_sample_format = { set_sample_format, free_none },
633   type_restrict = { set_restrict, free_none },
634   type_namepart = { set_namepart, free_namepartlist },
635   type_transform = { set_transform, free_transformlist },
636   type_rights = { set_rights, free_none },
637   type_backend = { set_backend, free_none };
638
639 /* specific validation routine */
640
641 #define VALIDATE_FILE(test, what) do {                          \
642   struct stat sb;                                               \
643   int n;                                                        \
644                                                                 \
645   for(n = 0; n < nvec; ++n) {                                   \
646     if(stat(vec[n], &sb) < 0) {                                 \
647       error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]);    \
648       return -1;                                                \
649     }                                                           \
650     if(!test(sb.st_mode)) {                                     \
651       error(0, "%s:%d: %s is not a %s",                         \
652             cs->path, cs->line, vec[n], what);                  \
653       return -1;                                                \
654     }                                                           \
655   }                                                             \
656 } while(0)
657
658 static int validate_isabspath(const struct config_state *cs,
659                               int nvec, char **vec) {
660   int n;
661
662   for(n = 0; n < nvec; ++n)
663     if(vec[n][0] != '/') {
664       error(errno, "%s:%d: %s: not an absolute path", 
665             cs->path, cs->line, vec[n]);
666       return -1;
667     }
668   return 0;
669 }
670
671 static int validate_isdir(const struct config_state *cs,
672                           int nvec, char **vec) {
673   VALIDATE_FILE(S_ISDIR, "directory");
674   return 0;
675 }
676
677 static int validate_isreg(const struct config_state *cs,
678                           int nvec, char **vec) {
679   VALIDATE_FILE(S_ISREG, "regular file");
680   return 0;
681 }
682
683 static int validate_player(const struct config_state *cs,
684                            int nvec,
685                            char attribute((unused)) **vec) {
686   if(nvec < 2) {
687     error(0, "%s:%d: should be at least 'player PATTERN MODULE'",
688           cs->path, cs->line);
689     return -1;
690   }
691   return 0;
692 }
693
694 static int validate_tracklength(const struct config_state *cs,
695                                 int nvec,
696                                 char attribute((unused)) **vec) {
697   if(nvec < 2) {
698     error(0, "%s:%d: should be at least 'tracklength PATTERN MODULE'",
699           cs->path, cs->line);
700     return -1;
701   }
702   return 0;
703 }
704
705 static int validate_allow(const struct config_state *cs,
706                           int nvec,
707                           char attribute((unused)) **vec) {
708   if(nvec != 2) {
709     error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line);
710     return -1;
711   }
712   return 0;
713 }
714
715 static int validate_non_negative(const struct config_state *cs,
716                                  int nvec, char **vec) {
717   long n;
718
719   if(nvec < 1) {
720     error(0, "%s:%d: missing argument", cs->path, cs->line);
721     return -1;
722   }
723   if(nvec > 1) {
724     error(0, "%s:%d: too many arguments", cs->path, cs->line);
725     return -1;
726   }
727   if(xstrtol(&n, vec[0], 0, 0)) {
728     error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
729     return -1;
730   }
731   if(n < 0) {
732     error(0, "%s:%d: must not be negative", cs->path, cs->line);
733     return -1;
734   }
735   return 0;
736 }
737
738 static int validate_positive(const struct config_state *cs,
739                           int nvec, char **vec) {
740   long n;
741
742   if(nvec < 1) {
743     error(0, "%s:%d: missing argument", cs->path, cs->line);
744     return -1;
745   }
746   if(nvec > 1) {
747     error(0, "%s:%d: too many arguments", cs->path, cs->line);
748     return -1;
749   }
750   if(xstrtol(&n, vec[0], 0, 0)) {
751     error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno));
752     return -1;
753   }
754   if(n <= 0) {
755     error(0, "%s:%d: must be positive", cs->path, cs->line);
756     return -1;
757   }
758   return 0;
759 }
760
761 static int validate_isauser(const struct config_state *cs,
762                             int attribute((unused)) nvec,
763                             char **vec) {
764   struct passwd *pw;
765
766   if(!(pw = getpwnam(vec[0]))) {
767     error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]);
768     return -1;
769   }
770   return 0;
771 }
772
773 static int validate_sample_format(const struct config_state *cs,
774                                   int attribute((unused)) nvec,
775                                   char **vec) {
776   return parse_sample_format(cs, 0, nvec, vec);
777 }
778
779 static int validate_any(const struct config_state attribute((unused)) *cs,
780                         int attribute((unused)) nvec,
781                         char attribute((unused)) **vec) {
782   return 0;
783 }
784
785 static int validate_url(const struct config_state attribute((unused)) *cs,
786                         int attribute((unused)) nvec,
787                         char **vec) {
788   const char *s;
789   int n;
790   /* absoluteURI   = scheme ":" ( hier_part | opaque_part )
791      scheme        = alpha *( alpha | digit | "+" | "-" | "." ) */
792   s = vec[0];
793   n = strspn(s, ("abcdefghijklmnopqrstuvwxyz"
794                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
795                  "0123456789"));
796   if(s[n] != ':') {
797     error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
798     return -1;
799   }
800   if(!strncmp(s, "http:", 5)
801      || !strncmp(s, "https:", 6)) {
802     s += n + 1;
803     /* we only do a rather cursory check */
804     if(strncmp(s, "//", 2)) {
805       error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]);
806       return -1;
807     }
808   }
809   return 0;
810 }
811
812 static int validate_alias(const struct config_state *cs,
813                           int nvec,
814                           char **vec) {
815   const char *s;
816   int in_brackets = 0, c;
817
818   if(nvec < 1) {
819     error(0, "%s:%d: missing argument", cs->path, cs->line);
820     return -1;
821   }
822   if(nvec > 1) {
823     error(0, "%s:%d: too many arguments", cs->path, cs->line);
824     return -1;
825   }
826   s = vec[0];
827   while((c = (unsigned char)*s++)) {
828     if(in_brackets) {
829       if(c == '}')
830         in_brackets = 0;
831       else if(!isalnum(c)) {
832         error(0, "%s:%d: invalid part name in alias expansion in '%s'",
833               cs->path, cs->line, vec[0]);
834           return -1;
835       }
836     } else {
837       if(c == '{') {
838         in_brackets = 1;
839         if(*s == '/')
840           ++s;
841       } else if(c == '\\') {
842         if(!(c = (unsigned char)*s++)) {
843           error(0, "%s:%d: unterminated escape in alias expansion in '%s'",
844                 cs->path, cs->line, vec[0]);
845           return -1;
846         } else if(c != '\\' && c != '{') {
847           error(0, "%s:%d: invalid escape in alias expansion in '%s'",
848                 cs->path, cs->line, vec[0]);
849           return -1;
850         }
851       }
852     }
853     ++s;
854   }
855   if(in_brackets) {
856     error(0, "%s:%d: unterminated part name in alias expansion in '%s'",
857           cs->path, cs->line, vec[0]);
858     return -1;
859   }
860   return 0;
861 }
862
863 static int validate_addrport(const struct config_state attribute((unused)) *cs,
864                              int nvec,
865                              char attribute((unused)) **vec) {
866   switch(nvec) {
867   case 0:
868     error(0, "%s:%d: missing address",
869           cs->path, cs->line);
870     return -1;
871   case 1:
872     error(0, "%s:%d: missing port name/number",
873           cs->path, cs->line);
874     return -1;
875   case 2:
876     return 0;
877   default:
878     error(0, "%s:%d: expected ADDRESS PORT",
879           cs->path, cs->line);
880     return -1;
881   }
882 }
883
884 static int validate_port(const struct config_state attribute((unused)) *cs,
885                          int nvec,
886                          char attribute((unused)) **vec) {
887   switch(nvec) {
888   case 0:
889     error(0, "%s:%d: missing address",
890           cs->path, cs->line);
891     return -1;
892   case 1:
893   case 2:
894     return 0;
895   default:
896     error(0, "%s:%d: expected [ADDRESS] PORT",
897           cs->path, cs->line);
898     return -1;
899   }
900 }
901
902 static int validate_algo(const struct config_state attribute((unused)) *cs,
903                          int nvec,
904                          char **vec) {
905   if(nvec != 1) {
906     error(0, "%s:%d: invalid algorithm specification", cs->path, cs->line);
907     return -1;
908   }
909   if(!valid_authhash(vec[0])) {
910     error(0, "%s:%d: unsuported algorithm '%s'", cs->path, cs->line, vec[0]);
911     return -1;
912   }
913   return 0;
914 }
915
916 /** @brief Item name and and offset */
917 #define C(x) #x, offsetof(struct config, x)
918 /** @brief Item name and and offset */
919 #define C2(x,y) #x, offsetof(struct config, y)
920
921 /** @brief All configuration items */
922 static const struct conf conf[] = {
923   { C(alias),            &type_string,           validate_alias },
924   { C(allow),            &type_stringlist_accum, validate_allow },
925   { C(api),              &type_backend,          validate_any },
926   { C(authorization_algorithm), &type_string,    validate_algo },
927   { C(broadcast),        &type_stringlist,       validate_addrport },
928   { C(broadcast_from),   &type_stringlist,       validate_addrport },
929   { C(channel),          &type_string,           validate_any },
930   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
931   { C(checkpoint_min),   &type_integer,          validate_non_negative },
932   { C(collection),       &type_collections,      validate_any },
933   { C(connect),          &type_stringlist,       validate_addrport },
934   { C(cookie_login_lifetime),  &type_integer,    validate_positive },
935   { C(cookie_key_lifetime),  &type_integer,      validate_positive },
936   { C(dbversion),        &type_integer,          validate_positive },
937   { C(default_rights),   &type_rights,           validate_any },
938   { C(device),           &type_string,           validate_any },
939   { C(gap),              &type_integer,          validate_non_negative },
940   { C(history),          &type_integer,          validate_positive },
941   { C(home),             &type_string,           validate_isabspath },
942   { C(listen),           &type_stringlist,       validate_port },
943   { C(lock),             &type_boolean,          validate_any },
944   { C(mail_sender),      &type_string,           validate_any },
945   { C(mixer),            &type_string,           validate_any },
946   { C(multicast_loop),   &type_boolean,          validate_any },
947   { C(multicast_ttl),    &type_integer,          validate_non_negative },
948   { C(namepart),         &type_namepart,         validate_any },
949   { C(new_max),          &type_integer,          validate_positive },
950   { C2(nice, nice_rescan), &type_integer,        validate_non_negative },
951   { C(nice_rescan),      &type_integer,          validate_non_negative },
952   { C(nice_server),      &type_integer,          validate_any },
953   { C(nice_speaker),     &type_integer,          validate_any },
954   { C(noticed_history),  &type_integer,          validate_positive },
955   { C(password),         &type_string,           validate_any },
956   { C(player),           &type_stringlist_accum, validate_player },
957   { C(plugins),          &type_string_accum,     validate_isdir },
958   { C(prefsync),         &type_integer,          validate_positive },
959   { C(queue_pad),        &type_integer,          validate_positive },
960   { C(replay_min),       &type_integer,          validate_non_negative },
961   { C(refresh),          &type_integer,          validate_positive },
962   { C(reminder_interval), &type_integer,         validate_positive },
963   { C(remote_userman),   &type_boolean,          validate_any },
964   { C2(restrict, restrictions),         &type_restrict,         validate_any },
965   { C(sample_format),    &type_sample_format,    validate_sample_format },
966   { C(scratch),          &type_string_accum,     validate_isreg },
967   { C(short_display),    &type_integer,          validate_positive },
968   { C(signal),           &type_signal,           validate_any },
969   { C(smtp_server),      &type_string,           validate_any },
970   { C(sox_generation),   &type_integer,          validate_non_negative },
971   { C2(speaker_backend, api),  &type_backend,          validate_any },
972   { C(speaker_command),  &type_string,           validate_any },
973   { C(stopword),         &type_string_accum,     validate_any },
974   { C(templates),        &type_string_accum,     validate_isdir },
975   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
976   { C(transform),        &type_transform,        validate_any },
977   { C(trust),            &type_string_accum,     validate_any },
978   { C(url),              &type_string,           validate_url },
979   { C(user),             &type_string,           validate_isauser },
980   { C(username),         &type_string,           validate_any },
981 };
982
983 /** @brief Find a configuration item's definition by key */
984 static const struct conf *find(const char *key) {
985   int n;
986
987   if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
988     return 0;
989   return &conf[n];
990 }
991
992 /** @brief Set a new configuration value */
993 static int config_set(const struct config_state *cs,
994                       int nvec, char **vec) {
995   const struct conf *which;
996
997   D(("config_set %s", vec[0]));
998   if(!(which = find(vec[0]))) {
999     error(0, "%s:%d: unknown configuration key '%s'",
1000           cs->path, cs->line, vec[0]);
1001     return -1;
1002   }
1003   return (which->validate(cs, nvec - 1, vec + 1)
1004           || which->type->set(cs, which, nvec - 1, vec + 1));
1005 }
1006
1007 static int config_set_args(const struct config_state *cs,
1008                            const char *which, ...) {
1009   va_list ap;
1010   struct vector v[1];
1011   char *s;
1012
1013   vector_init(v);
1014   vector_append(v, (char *)which);
1015   va_start(ap, which);
1016   while((s = va_arg(ap, char *)))
1017     vector_append(v, s);
1018   va_end(ap);
1019   vector_terminate(v);
1020   return config_set(cs, v->nvec, v->vec);
1021 }
1022
1023 /** @brief Error callback used by config_include() */
1024 static void config_error(const char *msg, void *u) {
1025   const struct config_state *cs = u;
1026
1027   error(0, "%s:%d: %s", cs->path, cs->line, msg);
1028 }
1029
1030 /** @brief Include a file by name */
1031 static int config_include(struct config *c, const char *path) {
1032   FILE *fp;
1033   char *buffer, *inputbuffer, **vec;
1034   int n, ret = 0;
1035   struct config_state cs;
1036
1037   cs.path = path;
1038   cs.line = 0;
1039   cs.config = c;
1040   D(("%s: reading configuration", path));
1041   if(!(fp = fopen(path, "r"))) {
1042     error(errno, "error opening %s", path);
1043     return -1;
1044   }
1045   while(!inputline(path, fp, &inputbuffer, '\n')) {
1046     ++cs.line;
1047     if(!(buffer = mb2utf8(inputbuffer))) {
1048       error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1049       ret = -1;
1050       xfree(inputbuffer);
1051       continue;
1052     }
1053     xfree(inputbuffer);
1054     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1055                      config_error, &cs))) {
1056       ret = -1;
1057       xfree(buffer);
1058       continue;
1059     }
1060     if(n) {
1061       if(!strcmp(vec[0], "include")) {
1062         if(n != 2) {
1063           error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1064           ret = -1;
1065         } else
1066           config_include(c, vec[1]);
1067       } else
1068         ret |= config_set(&cs, n, vec);
1069     }
1070     for(n = 0; vec[n]; ++n) xfree(vec[n]);
1071     xfree(vec);
1072     xfree(buffer);
1073   }
1074   if(ferror(fp)) {
1075     error(errno, "error reading %s", path);
1076     ret = -1;
1077   }
1078   fclose(fp);
1079   return ret;
1080 }
1081
1082 static const char *const default_stopwords[] = {
1083   "stopword",
1084
1085   "01",
1086   "02",
1087   "03",
1088   "04",
1089   "05",
1090   "06",
1091   "07",
1092   "08",
1093   "09",
1094   "1",
1095   "10",
1096   "11",
1097   "12",
1098   "13",
1099   "14",
1100   "15",
1101   "16",
1102   "17",
1103   "18",
1104   "19",
1105   "2",
1106   "20",
1107   "21",
1108   "22",
1109   "23",
1110   "24",
1111   "25",
1112   "26",
1113   "27",
1114   "28",
1115   "29",
1116   "3",
1117   "30",
1118   "4",
1119   "5",
1120   "6",
1121   "7",
1122   "8",
1123   "9",
1124   "a",
1125   "am",
1126   "an",
1127   "and",
1128   "as",
1129   "for",
1130   "i",
1131   "im",
1132   "in",
1133   "is",
1134   "of",
1135   "on",
1136   "the",
1137   "to",
1138   "too",
1139   "we",
1140 };
1141 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1142
1143 static const char *const default_players[] = {
1144   "*.ogg",
1145   "*.flac",
1146   "*.mp3",
1147   "*.wav",
1148 };
1149 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1150
1151 /** @brief Make a new default configuration */
1152 static struct config *config_default(void) {
1153   struct config *c = xmalloc(sizeof *c);
1154   const char *logname;
1155   struct passwd *pw;
1156   struct config_state cs;
1157   size_t n;
1158
1159   cs.path = "<internal>";
1160   cs.line = 0;
1161   cs.config = c;
1162   /* Strings had better be xstrdup'd as they will get freed at some point. */
1163   c->gap = 0;
1164   c->history = 60;
1165   c->home = xstrdup(pkgstatedir);
1166   if(!(pw = getpwuid(getuid())))
1167     fatal(0, "cannot determine our username");
1168   logname = pw->pw_name;
1169   c->username = xstrdup(logname);
1170   c->refresh = 15;
1171   c->prefsync = 3600;
1172   c->signal = SIGKILL;
1173   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1174   c->lock = 1;
1175   c->device = xstrdup("default");
1176   c->nice_rescan = 10;
1177   c->speaker_command = 0;
1178   c->sample_format.bits = 16;
1179   c->sample_format.rate = 44100;
1180   c->sample_format.channels = 2;
1181   c->sample_format.endian = ENDIAN_NATIVE;
1182   c->queue_pad = 10;
1183   c->replay_min = 8 * 3600;
1184   c->api = -1;
1185   c->multicast_ttl = 1;
1186   c->multicast_loop = 1;
1187   c->authorization_algorithm = xstrdup("sha1");
1188   c->noticed_history = 31;
1189   c->short_display = 32;
1190   c->mixer = 0;
1191   c->channel = 0;
1192   c->dbversion = 2;
1193   c->cookie_login_lifetime = 86400;
1194   c->cookie_key_lifetime = 86400 * 7;
1195   c->smtp_server = xstrdup("127.0.0.1");
1196   c->new_max = 100;
1197   c->reminder_interval = 600;           /* 10m */
1198   /* Default stopwords */
1199   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1200     exit(1);
1201   /* Default player configuration */
1202   for(n = 0; n < NDEFAULT_PLAYERS; ++n) {
1203     if(config_set_args(&cs, "player",
1204                        default_players[n], "execraw", "disorder-decode", (char *)0))
1205       exit(1);
1206     if(config_set_args(&cs, "tracklength",
1207                        default_players[n], "disorder-tracklength", (char *)0))
1208       exit(1);
1209   }
1210   return c;
1211 }
1212
1213 static char *get_file(struct config *c, const char *name) {
1214   char *s;
1215
1216   byte_xasprintf(&s, "%s/%s", c->home, name);
1217   return s;
1218 }
1219
1220 /** @brief Set the default configuration file */
1221 static void set_configfile(void) {
1222   if(!configfile)
1223     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1224 }
1225
1226 /** @brief Free a configuration object */
1227 static void config_free(struct config *c) {
1228   int n;
1229
1230   if(c) {
1231     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1232       conf[n].type->free(c, &conf[n]);
1233     for(n = 0; n < c->nparts; ++n)
1234       xfree(c->parts[n]);
1235     xfree(c->parts);
1236     xfree(c);
1237   }
1238 }
1239
1240 /** @brief Set post-parse defaults */
1241 static void config_postdefaults(struct config *c,
1242                                 int server) {
1243   struct config_state cs;
1244   const struct conf *whoami;
1245   int n;
1246
1247   static const char *namepart[][4] = {
1248     { "title",  "/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1249     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
1250     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
1251     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
1252     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
1253   };
1254 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1255
1256   static const char *transform[][5] = {
1257     { "track", "^.*/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1258     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
1259     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
1260     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
1261     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
1262   };
1263 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1264
1265   cs.path = "<internal>";
1266   cs.line = 0;
1267   cs.config = c;
1268   if(!c->namepart.n) {
1269     whoami = find("namepart");
1270     for(n = 0; n < NNAMEPART; ++n)
1271       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1272   }
1273   if(!c->transform.n) {
1274     whoami = find("transform");
1275     for(n = 0; n < NTRANSFORM; ++n)
1276       set_transform(&cs, whoami, 5, (char **)transform[n]);
1277   }
1278   if(c->api == -1) {
1279     if(c->speaker_command)
1280       c->api = BACKEND_COMMAND;
1281     else if(c->broadcast.n)
1282       c->api = BACKEND_NETWORK;
1283     else {
1284 #if HAVE_ALSA_ASOUNDLIB_H
1285       c->api = BACKEND_ALSA;
1286 #elif HAVE_SYS_SOUNDCARD_H
1287       c->api = BACKEND_OSS;
1288 #elif HAVE_COREAUDIO_AUDIOHARDWARE_H
1289       c->api = BACKEND_COREAUDIO;
1290 #else
1291       c->api = BACKEND_COMMAND;
1292 #endif
1293     }
1294   }
1295   if(server) {
1296     if(c->api == BACKEND_COMMAND && !c->speaker_command)
1297       fatal(0, "'api command' but speaker_command is not set");
1298     if(c->api == BACKEND_NETWORK && !c->broadcast.n)
1299       fatal(0, "'api network' but broadcast is not set");
1300   }
1301   /* Override sample format */
1302   switch(c->api) {
1303   case BACKEND_NETWORK:
1304     c->sample_format.rate = 44100;
1305     c->sample_format.channels = 2;
1306     c->sample_format.bits = 16;
1307     c->sample_format.endian = ENDIAN_BIG;
1308     break;
1309   case BACKEND_COREAUDIO:
1310     c->sample_format.rate = 44100;
1311     c->sample_format.channels = 2;
1312     c->sample_format.bits = 16;
1313     c->sample_format.endian = ENDIAN_NATIVE;
1314     break; 
1315   }
1316   if(!c->default_rights) {
1317     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1318                                      |RIGHT_MOVE__MASK
1319                                      |RIGHT_SCRATCH__MASK
1320                                      |RIGHT_REMOVE__MASK);
1321     /* The idea is to approximate the meaning of the old 'restrict' directive
1322      * in the default rights if they are not overridden. */
1323     if(c->restrictions & RESTRICT_SCRATCH)
1324       r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1325     else
1326       r |= RIGHT_SCRATCH_ANY;
1327     if(!(c->restrictions & RESTRICT_MOVE))
1328       r |= RIGHT_MOVE_ANY;
1329     if(c->restrictions & RESTRICT_REMOVE)
1330       r |= RIGHT_REMOVE_MINE;
1331     else
1332       r |= RIGHT_REMOVE_ANY;
1333     c->default_rights = rights_string(r);
1334   }
1335 }
1336
1337 /** @brief (Re-)read the config file
1338  * @param server If set, do extra checking
1339  */
1340 int config_read(int server) {
1341   struct config *c;
1342   char *privconf;
1343   struct passwd *pw;
1344
1345   set_configfile();
1346   c = config_default();
1347   /* standalone Disobedience installs might not have a global config file */
1348   if(access(configfile, F_OK) == 0)
1349     if(config_include(c, configfile))
1350       return -1;
1351   /* if we can read the private config file, do */
1352   if((privconf = config_private())
1353      && access(privconf, R_OK) == 0
1354      && config_include(c, privconf))
1355     return -1;
1356   xfree(privconf);
1357   /* if there's a per-user system config file for this user, read it */
1358   if(config_per_user) {
1359     if(!(pw = getpwuid(getuid())))
1360       fatal(0, "cannot determine our username");
1361     if((privconf = config_usersysconf(pw))
1362        && access(privconf, F_OK) == 0
1363        && config_include(c, privconf))
1364       return -1;
1365     xfree(privconf);
1366     /* if we have a password file, read it */
1367     if((privconf = config_userconf(0, pw))
1368        && access(privconf, F_OK) == 0
1369        && config_include(c, privconf))
1370       return -1;
1371     xfree(privconf);
1372   }
1373   /* install default namepart and transform settings */
1374   config_postdefaults(c, server);
1375   /* everything is good so we shall use the new config */
1376   config_free(config);
1377   /* warn about obsolete directives */
1378   if(c->restrictions)
1379     error(0, "'restrict' will be removed in a future version");
1380   if(c->allow.n)
1381     error(0, "'allow' will be removed in a future version");
1382   if(c->trust.n)
1383     error(0, "'trust' will be removed in a future version");
1384   config = c;
1385   return 0;
1386 }
1387
1388 /** @brief Return the path to the private configuration file */
1389 char *config_private(void) {
1390   char *s;
1391
1392   set_configfile();
1393   byte_xasprintf(&s, "%s.private", configfile);
1394   return s;
1395 }
1396
1397 /** @brief Return the path to user's personal configuration file */
1398 char *config_userconf(const char *home, const struct passwd *pw) {
1399   char *s;
1400
1401   if(!home && !pw && !(pw = getpwuid(getuid())))
1402     fatal(0, "cannot determine our username");
1403   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1404   return s;
1405 }
1406
1407 /** @brief Return the path to user-specific system configuration */
1408 char *config_usersysconf(const struct passwd *pw) {
1409   char *s;
1410
1411   set_configfile();
1412   if(!strchr(pw->pw_name, '/')) {
1413     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1414     return s;
1415   } else
1416     return 0;
1417 }
1418
1419 char *config_get_file(const char *name) {
1420   return get_file(config, name);
1421 }
1422
1423 /*
1424 Local Variables:
1425 c-basic-offset:2
1426 comment-column:40
1427 fill-column:79
1428 End:
1429 */