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