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