chiark / gitweb /
Online registration now sends email. The confirmation URL doesn't
[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(mail_sender),      &type_string,           validate_any },
932   { C(mixer),            &type_string,           validate_ischr },
933   { C(multicast_loop),   &type_boolean,          validate_any },
934   { C(multicast_ttl),    &type_integer,          validate_non_negative },
935   { C(namepart),         &type_namepart,         validate_any },
936   { C2(nice, nice_rescan), &type_integer,        validate_non_negative },
937   { C(nice_rescan),      &type_integer,          validate_non_negative },
938   { C(nice_server),      &type_integer,          validate_any },
939   { C(nice_speaker),     &type_integer,          validate_any },
940   { C(noticed_history),  &type_integer,          validate_positive },
941   { C(password),         &type_string,           validate_any },
942   { C(player),           &type_stringlist_accum, validate_player },
943   { C(plugins),          &type_string_accum,     validate_isdir },
944   { C(prefsync),         &type_integer,          validate_positive },
945   { C(queue_pad),        &type_integer,          validate_positive },
946   { C(refresh),          &type_integer,          validate_positive },
947   { C2(restrict, restrictions),         &type_restrict,         validate_any },
948   { C(sample_format),    &type_sample_format,    validate_sample_format },
949   { C(scratch),          &type_string_accum,     validate_isreg },
950   { C(short_display),    &type_integer,          validate_positive },
951   { C(signal),           &type_signal,           validate_any },
952   { C(smtp_server),      &type_string,           validate_any },
953   { C(sox_generation),   &type_integer,          validate_non_negative },
954   { C(speaker_backend),  &type_backend,          validate_any },
955   { C(speaker_command),  &type_string,           validate_any },
956   { C(stopword),         &type_string_accum,     validate_any },
957   { C(templates),        &type_string_accum,     validate_isdir },
958   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
959   { C(transform),        &type_transform,        validate_any },
960   { C(trust),            &type_string_accum,     validate_any },
961   { C(url),              &type_string,           validate_url },
962   { C(user),             &type_string,           validate_isauser },
963   { C(username),         &type_string,           validate_any },
964 };
965
966 /** @brief Find a configuration item's definition by key */
967 static const struct conf *find(const char *key) {
968   int n;
969
970   if((n = TABLE_FIND(conf, struct conf, name, key)) < 0)
971     return 0;
972   return &conf[n];
973 }
974
975 /** @brief Set a new configuration value */
976 static int config_set(const struct config_state *cs,
977                       int nvec, char **vec) {
978   const struct conf *which;
979
980   D(("config_set %s", vec[0]));
981   if(!(which = find(vec[0]))) {
982     error(0, "%s:%d: unknown configuration key '%s'",
983           cs->path, cs->line, vec[0]);
984     return -1;
985   }
986   return (which->validate(cs, nvec - 1, vec + 1)
987           || which->type->set(cs, which, nvec - 1, vec + 1));
988 }
989
990 /** @brief Error callback used by config_include() */
991 static void config_error(const char *msg, void *u) {
992   const struct config_state *cs = u;
993
994   error(0, "%s:%d: %s", cs->path, cs->line, msg);
995 }
996
997 /** @brief Include a file by name */
998 static int config_include(struct config *c, const char *path) {
999   FILE *fp;
1000   char *buffer, *inputbuffer, **vec;
1001   int n, ret = 0;
1002   struct config_state cs;
1003
1004   cs.path = path;
1005   cs.line = 0;
1006   cs.config = c;
1007   D(("%s: reading configuration", path));
1008   if(!(fp = fopen(path, "r"))) {
1009     error(errno, "error opening %s", path);
1010     return -1;
1011   }
1012   while(!inputline(path, fp, &inputbuffer, '\n')) {
1013     ++cs.line;
1014     if(!(buffer = mb2utf8(inputbuffer))) {
1015       error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1016       ret = -1;
1017       xfree(inputbuffer);
1018       continue;
1019     }
1020     xfree(inputbuffer);
1021     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1022                      config_error, &cs))) {
1023       ret = -1;
1024       xfree(buffer);
1025       continue;
1026     }
1027     if(n) {
1028       if(!strcmp(vec[0], "include")) {
1029         if(n != 2) {
1030           error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1031           ret = -1;
1032         } else
1033           config_include(c, vec[1]);
1034       } else
1035         ret |= config_set(&cs, n, vec);
1036     }
1037     for(n = 0; vec[n]; ++n) xfree(vec[n]);
1038     xfree(vec);
1039     xfree(buffer);
1040   }
1041   if(ferror(fp)) {
1042     error(errno, "error reading %s", path);
1043     ret = -1;
1044   }
1045   fclose(fp);
1046   return ret;
1047 }
1048
1049 static const char *const default_stopwords[] = {
1050   "stopword",
1051
1052   "01",
1053   "02",
1054   "03",
1055   "04",
1056   "05",
1057   "06",
1058   "07",
1059   "08",
1060   "09",
1061   "1",
1062   "10",
1063   "11",
1064   "12",
1065   "13",
1066   "14",
1067   "15",
1068   "16",
1069   "17",
1070   "18",
1071   "19",
1072   "2",
1073   "20",
1074   "21",
1075   "22",
1076   "23",
1077   "24",
1078   "25",
1079   "26",
1080   "27",
1081   "28",
1082   "29",
1083   "3",
1084   "30",
1085   "4",
1086   "5",
1087   "6",
1088   "7",
1089   "8",
1090   "9",
1091   "a",
1092   "am",
1093   "an",
1094   "and",
1095   "as",
1096   "for",
1097   "i",
1098   "im",
1099   "in",
1100   "is",
1101   "of",
1102   "on",
1103   "the",
1104   "to",
1105   "too",
1106   "we",
1107 };
1108 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1109
1110 /** @brief Make a new default configuration */
1111 static struct config *config_default(void) {
1112   struct config *c = xmalloc(sizeof *c);
1113   const char *logname;
1114   struct passwd *pw;
1115   struct config_state cs;
1116
1117   cs.path = "<internal>";
1118   cs.line = 0;
1119   cs.config = c;
1120   /* Strings had better be xstrdup'd as they will get freed at some point. */
1121   c->gap = 2;
1122   c->history = 60;
1123   c->home = xstrdup(pkgstatedir);
1124   if(!(pw = getpwuid(getuid())))
1125     fatal(0, "cannot determine our username");
1126   logname = pw->pw_name;
1127   c->username = xstrdup(logname);
1128   c->refresh = 15;
1129   c->prefsync = 3600;
1130   c->signal = SIGKILL;
1131   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1132   c->lock = 1;
1133   c->device = xstrdup("default");
1134   c->nice_rescan = 10;
1135   c->speaker_command = 0;
1136   c->sample_format.bits = 16;
1137   c->sample_format.rate = 44100;
1138   c->sample_format.channels = 2;
1139   c->sample_format.endian = ENDIAN_NATIVE;
1140   c->queue_pad = 10;
1141   c->speaker_backend = -1;
1142   c->multicast_ttl = 1;
1143   c->multicast_loop = 1;
1144   c->authorization_algorithm = xstrdup("sha1");
1145   c->noticed_history = 31;
1146   c->short_display = 32;
1147   c->mixer = xstrdup("/dev/mixer");
1148   c->channel = xstrdup("pcm");
1149   c->dbversion = 2;
1150   c->cookie_login_lifetime = 86400;
1151   c->cookie_key_lifetime = 86400 * 7;
1152   c->smtp_server = xstrdup("127.0.0.1");
1153   if(config_set(&cs, (int)NDEFAULT_STOPWORDS, (char **)default_stopwords))
1154     exit(1);
1155   return c;
1156 }
1157
1158 static char *get_file(struct config *c, const char *name) {
1159   char *s;
1160
1161   byte_xasprintf(&s, "%s/%s", c->home, name);
1162   return s;
1163 }
1164
1165 /** @brief Set the default configuration file */
1166 static void set_configfile(void) {
1167   if(!configfile)
1168     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1169 }
1170
1171 /** @brief Free a configuration object */
1172 static void config_free(struct config *c) {
1173   int n;
1174
1175   if(c) {
1176     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1177       conf[n].type->free(c, &conf[n]);
1178     for(n = 0; n < c->nparts; ++n)
1179       xfree(c->parts[n]);
1180     xfree(c->parts);
1181     xfree(c);
1182   }
1183 }
1184
1185 /** @brief Set post-parse defaults */
1186 static void config_postdefaults(struct config *c,
1187                                 int server) {
1188   struct config_state cs;
1189   const struct conf *whoami;
1190   int n;
1191
1192   static const char *namepart[][4] = {
1193     { "title",  "/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1194     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
1195     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
1196     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
1197     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
1198   };
1199 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1200
1201   static const char *transform[][5] = {
1202     { "track", "^.*/([0-9]+ *[-:] *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1203     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
1204     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
1205     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
1206     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
1207   };
1208 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1209
1210   cs.path = "<internal>";
1211   cs.line = 0;
1212   cs.config = c;
1213   if(!c->namepart.n) {
1214     whoami = find("namepart");
1215     for(n = 0; n < NNAMEPART; ++n)
1216       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1217   }
1218   if(!c->transform.n) {
1219     whoami = find("transform");
1220     for(n = 0; n < NTRANSFORM; ++n)
1221       set_transform(&cs, whoami, 5, (char **)transform[n]);
1222   }
1223   if(c->speaker_backend == -1) {
1224     if(c->speaker_command)
1225       c->speaker_backend = BACKEND_COMMAND;
1226     else if(c->broadcast.n)
1227       c->speaker_backend = BACKEND_NETWORK;
1228     else {
1229 #if HAVE_ALSA_ASOUNDLIB_H
1230       c->speaker_backend = BACKEND_ALSA;
1231 #elif HAVE_SYS_SOUNDCARD_H
1232       c->speaker_backend = BACKEND_OSS;
1233 #elif HAVE_COREAUDIO_AUDIOHARDWARE_H
1234       c->speaker_backend = BACKEND_COREAUDIO;
1235 #else
1236       c->speaker_backend = BACKEND_COMMAND;
1237 #endif
1238     }
1239   }
1240   if(server) {
1241     if(c->speaker_backend == BACKEND_COMMAND && !c->speaker_command)
1242       fatal(0, "speaker_backend is command but speaker_command is not set");
1243     if(c->speaker_backend == BACKEND_NETWORK && !c->broadcast.n)
1244       fatal(0, "speaker_backend is network but broadcast is not set");
1245   }
1246   /* Override sample format */
1247   switch(c->speaker_backend) {
1248   case BACKEND_NETWORK:
1249     c->sample_format.rate = 44100;
1250     c->sample_format.channels = 2;
1251     c->sample_format.bits = 16;
1252     c->sample_format.endian = ENDIAN_BIG;
1253     break;
1254   case BACKEND_COREAUDIO:
1255     c->sample_format.rate = 44100;
1256     c->sample_format.channels = 2;
1257     c->sample_format.bits = 16;
1258     c->sample_format.endian = ENDIAN_NATIVE;
1259     break; 
1260   }
1261   if(!c->default_rights) {
1262     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1263                                      |RIGHT_MOVE__MASK
1264                                      |RIGHT_SCRATCH__MASK
1265                                      |RIGHT_REMOVE__MASK);
1266     /* The idea is to approximate the meaning of the old 'restrict' directive
1267      * in the default rights if they are not overridden. */
1268     if(c->restrictions & RESTRICT_SCRATCH)
1269       r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1270     else
1271       r |= RIGHT_SCRATCH_ANY;
1272     if(!(c->restrictions & RESTRICT_MOVE))
1273       r |= RIGHT_MOVE_ANY;
1274     if(c->restrictions & RESTRICT_REMOVE)
1275       r |= RIGHT_REMOVE_MINE;
1276     else
1277       r |= RIGHT_REMOVE_ANY;
1278     c->default_rights = rights_string(r);
1279   }
1280 }
1281
1282 /** @brief (Re-)read the config file
1283  * @param server If set, do extra checking
1284  */
1285 int config_read(int server) {
1286   struct config *c;
1287   char *privconf;
1288   struct passwd *pw;
1289
1290   set_configfile();
1291   c = config_default();
1292   /* standalone Disobedience installs might not have a global config file */
1293   if(access(configfile, F_OK) == 0)
1294     if(config_include(c, configfile))
1295       return -1;
1296   /* if we can read the private config file, do */
1297   if((privconf = config_private())
1298      && access(privconf, R_OK) == 0
1299      && config_include(c, privconf))
1300     return -1;
1301   xfree(privconf);
1302   /* if there's a per-user system config file for this user, read it */
1303   if(config_per_user) {
1304     if(!(pw = getpwuid(getuid())))
1305       fatal(0, "cannot determine our username");
1306     if((privconf = config_usersysconf(pw))
1307        && access(privconf, F_OK) == 0
1308        && config_include(c, privconf))
1309       return -1;
1310     xfree(privconf);
1311     /* if we have a password file, read it */
1312     if((privconf = config_userconf(getenv("HOME"), pw))
1313        && access(privconf, F_OK) == 0
1314        && config_include(c, privconf))
1315       return -1;
1316     xfree(privconf);
1317   }
1318   /* install default namepart and transform settings */
1319   config_postdefaults(c, server);
1320   /* everything is good so we shall use the new config */
1321   config_free(config);
1322   /* warn about obsolete directives */
1323   if(c->restrictions)
1324     error(0, "'restrict' will be removed in a future version");
1325   if(c->allow.n)
1326     error(0, "'allow' will be removed in a future version");
1327   if(c->trust.n)
1328     error(0, "'trust' will be removed in a future version");
1329   config = c;
1330   return 0;
1331 }
1332
1333 /** @brief Return the path to the private configuration file */
1334 char *config_private(void) {
1335   char *s;
1336
1337   set_configfile();
1338   byte_xasprintf(&s, "%s.private", configfile);
1339   return s;
1340 }
1341
1342 /** @brief Return the path to user's personal configuration file */
1343 char *config_userconf(const char *home, const struct passwd *pw) {
1344   char *s;
1345
1346   if(!home && !pw && !(pw = getpwuid(getuid())))
1347     fatal(0, "cannot determine our username");
1348   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1349   return s;
1350 }
1351
1352 /** @brief Return the path to user-specific system configuration */
1353 char *config_usersysconf(const struct passwd *pw) {
1354   char *s;
1355
1356   set_configfile();
1357   if(!strchr(pw->pw_name, '/')) {
1358     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1359     return s;
1360   } else
1361     return 0;
1362 }
1363
1364 char *config_get_file(const char *name) {
1365   return get_file(config, name);
1366 }
1367
1368 /*
1369 Local Variables:
1370 c-basic-offset:2
1371 comment-column:40
1372 fill-column:79
1373 End:
1374 */