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