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