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