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