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