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