chiark / gitweb /
Quieten compiler.
[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(plugins),          &type_string_accum,     validate_isdir },
951   { C(prefsync),         &type_integer,          validate_positive },
952   { C(queue_pad),        &type_integer,          validate_positive },
953   { C(replay_min),       &type_integer,          validate_non_negative },
954   { C(refresh),          &type_integer,          validate_positive },
955   { C(reminder_interval), &type_integer,         validate_positive },
956   { C(remote_userman),   &type_boolean,          validate_any },
957   { C2(restrict, restrictions),         &type_restrict,         validate_any },
958   { C(rtp_delay_threshold), &type_integer,       validate_positive },
959   { C(sample_format),    &type_sample_format,    validate_sample_format },
960   { C(scratch),          &type_string_accum,     validate_isreg },
961   { C(sendmail),         &type_string,           validate_isabspath },
962   { C(short_display),    &type_integer,          validate_positive },
963   { C(signal),           &type_signal,           validate_any },
964   { C(smtp_server),      &type_string,           validate_any },
965   { C(sox_generation),   &type_integer,          validate_non_negative },
966   { C2(speaker_backend, api),  &type_string,     validate_backend },
967   { C(speaker_command),  &type_string,           validate_any },
968   { C(stopword),         &type_string_accum,     validate_any },
969   { C(templates),        &type_string_accum,     validate_isdir },
970   { C(tracklength),      &type_stringlist_accum, validate_tracklength },
971   { C(transform),        &type_transform,        validate_any },
972   { C(trust),            &type_string_accum,     validate_any },
973   { C(url),              &type_string,           validate_url },
974   { C(user),             &type_string,           validate_isauser },
975   { C(username),         &type_string,           validate_any },
976 };
977
978 /** @brief Find a configuration item's definition by key */
979 static const struct conf *find(const char *key) {
980   int n;
981
982   if((n = TABLE_FIND(conf, name, key)) < 0)
983     return 0;
984   return &conf[n];
985 }
986
987 /** @brief Set a new configuration value */
988 static int config_set(const struct config_state *cs,
989                       int nvec, char **vec) {
990   const struct conf *which;
991
992   D(("config_set %s", vec[0]));
993   if(!(which = find(vec[0]))) {
994     error(0, "%s:%d: unknown configuration key '%s'",
995           cs->path, cs->line, vec[0]);
996     return -1;
997   }
998   return (which->validate(cs, nvec - 1, vec + 1)
999           || which->type->set(cs, which, nvec - 1, vec + 1));
1000 }
1001
1002 static int config_set_args(const struct config_state *cs,
1003                            const char *which, ...) {
1004   va_list ap;
1005   struct vector v[1];
1006   char *s;
1007
1008   vector_init(v);
1009   vector_append(v, (char *)which);
1010   va_start(ap, which);
1011   while((s = va_arg(ap, char *)))
1012     vector_append(v, s);
1013   va_end(ap);
1014   vector_terminate(v);
1015   return config_set(cs, v->nvec, v->vec);
1016 }
1017
1018 /** @brief Error callback used by config_include() */
1019 static void config_error(const char *msg, void *u) {
1020   const struct config_state *cs = u;
1021
1022   error(0, "%s:%d: %s", cs->path, cs->line, msg);
1023 }
1024
1025 /** @brief Include a file by name */
1026 static int config_include(struct config *c, const char *path) {
1027   FILE *fp;
1028   char *buffer, *inputbuffer, **vec;
1029   int n, ret = 0;
1030   struct config_state cs;
1031
1032   cs.path = path;
1033   cs.line = 0;
1034   cs.config = c;
1035   D(("%s: reading configuration", path));
1036   if(!(fp = fopen(path, "r"))) {
1037     error(errno, "error opening %s", path);
1038     return -1;
1039   }
1040   while(!inputline(path, fp, &inputbuffer, '\n')) {
1041     ++cs.line;
1042     if(!(buffer = mb2utf8(inputbuffer))) {
1043       error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line);
1044       ret = -1;
1045       xfree(inputbuffer);
1046       continue;
1047     }
1048     xfree(inputbuffer);
1049     if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES,
1050                      config_error, &cs))) {
1051       ret = -1;
1052       xfree(buffer);
1053       continue;
1054     }
1055     if(n) {
1056       if(!strcmp(vec[0], "include")) {
1057         if(n != 2) {
1058           error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line);
1059           ret = -1;
1060         } else
1061           config_include(c, vec[1]);
1062       } else
1063         ret |= config_set(&cs, n, vec);
1064     }
1065     for(n = 0; vec[n]; ++n) xfree(vec[n]);
1066     xfree(vec);
1067     xfree(buffer);
1068   }
1069   if(ferror(fp)) {
1070     error(errno, "error reading %s", path);
1071     ret = -1;
1072   }
1073   fclose(fp);
1074   return ret;
1075 }
1076
1077 static const char *const default_stopwords[] = {
1078   "stopword",
1079
1080   "01",
1081   "02",
1082   "03",
1083   "04",
1084   "05",
1085   "06",
1086   "07",
1087   "08",
1088   "09",
1089   "1",
1090   "10",
1091   "11",
1092   "12",
1093   "13",
1094   "14",
1095   "15",
1096   "16",
1097   "17",
1098   "18",
1099   "19",
1100   "2",
1101   "20",
1102   "21",
1103   "22",
1104   "23",
1105   "24",
1106   "25",
1107   "26",
1108   "27",
1109   "28",
1110   "29",
1111   "3",
1112   "30",
1113   "4",
1114   "5",
1115   "6",
1116   "7",
1117   "8",
1118   "9",
1119   "a",
1120   "am",
1121   "an",
1122   "and",
1123   "as",
1124   "for",
1125   "i",
1126   "im",
1127   "in",
1128   "is",
1129   "of",
1130   "on",
1131   "the",
1132   "to",
1133   "too",
1134   "we",
1135 };
1136 #define NDEFAULT_STOPWORDS (sizeof default_stopwords / sizeof *default_stopwords)
1137
1138 static const char *const default_players[] = {
1139   "*.ogg",
1140   "*.flac",
1141   "*.mp3",
1142   "*.wav",
1143 };
1144 #define NDEFAULT_PLAYERS (sizeof default_players / sizeof *default_players)
1145
1146 /** @brief Make a new default configuration */
1147 static struct config *config_default(void) {
1148   struct config *c = xmalloc(sizeof *c);
1149   const char *logname;
1150   struct passwd *pw;
1151   struct config_state cs;
1152   size_t n;
1153
1154   cs.path = "<internal>";
1155   cs.line = 0;
1156   cs.config = c;
1157   /* Strings had better be xstrdup'd as they will get freed at some point. */
1158   c->gap = 0;
1159   c->history = 60;
1160   c->home = xstrdup(pkgstatedir);
1161   if(!(pw = getpwuid(getuid())))
1162     fatal(0, "cannot determine our username");
1163   logname = pw->pw_name;
1164   c->username = xstrdup(logname);
1165   c->refresh = 15;
1166   c->prefsync = 0;
1167   c->signal = SIGKILL;
1168   c->alias = xstrdup("{/artist}{/album}{/title}{ext}");
1169   c->lock = 1;
1170   c->device = xstrdup("default");
1171   c->nice_rescan = 10;
1172   c->speaker_command = 0;
1173   c->sample_format.bits = 16;
1174   c->sample_format.rate = 44100;
1175   c->sample_format.channels = 2;
1176   c->sample_format.endian = ENDIAN_NATIVE;
1177   c->queue_pad = 10;
1178   c->replay_min = 8 * 3600;
1179   c->api = NULL;
1180   c->multicast_ttl = 1;
1181   c->multicast_loop = 1;
1182   c->authorization_algorithm = xstrdup("sha1");
1183   c->noticed_history = 31;
1184   c->short_display = 32;
1185   c->mixer = 0;
1186   c->channel = 0;
1187   c->dbversion = 2;
1188   c->cookie_login_lifetime = 86400;
1189   c->cookie_key_lifetime = 86400 * 7;
1190   if(sendmail_binary[0] && strcmp(sendmail_binary, "none"))
1191     c->sendmail = xstrdup(sendmail_binary);
1192   c->smtp_server = xstrdup("127.0.0.1");
1193   c->new_max = 100;
1194   c->reminder_interval = 600;           /* 10m */
1195   c->new_bias_age = 7 * 86400;          /* 1 week */
1196   c->new_bias = 4500000;                /* 50 times the base weight */
1197   c->sox_generation = DEFAULT_SOX_GENERATION;
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   c->broadcast.af = -1;
1211   c->broadcast_from.af = -1;
1212   c->listen.af = -1;
1213   c->connect.af = -1;
1214   return c;
1215 }
1216
1217 char *config_get_file2(struct config *c, const char *name) {
1218   char *s;
1219
1220   byte_xasprintf(&s, "%s/%s", c->home, name);
1221   return s;
1222 }
1223
1224 /** @brief Set the default configuration file */
1225 static void set_configfile(void) {
1226   if(!configfile)
1227     byte_xasprintf(&configfile, "%s/config", pkgconfdir);
1228 }
1229
1230 /** @brief Free a configuration object */
1231 static void config_free(struct config *c) {
1232   int n;
1233
1234   if(c) {
1235     for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n)
1236       conf[n].type->free(c, &conf[n]);
1237     for(n = 0; n < c->nparts; ++n)
1238       xfree(c->parts[n]);
1239     xfree(c->parts);
1240     xfree(c);
1241   }
1242 }
1243
1244 /** @brief Set post-parse defaults */
1245 static void config_postdefaults(struct config *c,
1246                                 int server) {
1247   struct config_state cs;
1248   const struct conf *whoami;
1249   int n;
1250
1251   static const char *namepart[][4] = {
1252     { "title",  "/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" },
1253     { "title",  "/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort" },
1254     { "album",  "/([^/]+)/[^/]+$",                    "$1", "*" },
1255     { "artist", "/([^/]+)/[^/]+/[^/]+$",              "$1", "*" },
1256     { "ext",    "(\\.[a-zA-Z0-9]+)$",                 "$1", "*" },
1257   };
1258 #define NNAMEPART (int)(sizeof namepart / sizeof *namepart)
1259
1260   static const char *transform[][5] = {
1261     { "track", "^.*/([0-9]+ *[-:]? *)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" },
1262     { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$",           "$1", "sort", "" },
1263     { "dir",   "^.*/([^/]+)$",                          "$1", "*", "" },
1264     { "dir",   "^(the) ([^/]*)",                        "$2, $1", "sort", "i", },
1265     { "dir",   "[[:punct:]]",                           "", "sort", "g", }
1266   };
1267 #define NTRANSFORM (int)(sizeof transform / sizeof *transform)
1268
1269   cs.path = "<internal>";
1270   cs.line = 0;
1271   cs.config = c;
1272   if(!c->namepart.n) {
1273     whoami = find("namepart");
1274     for(n = 0; n < NNAMEPART; ++n)
1275       set_namepart(&cs, whoami, 4, (char **)namepart[n]);
1276   }
1277   if(!c->transform.n) {
1278     whoami = find("transform");
1279     for(n = 0; n < NTRANSFORM; ++n)
1280       set_transform(&cs, whoami, 5, (char **)transform[n]);
1281   }
1282   if(!c->api) {
1283     if(c->speaker_command)
1284       c->api = xstrdup("command");
1285     else if(c->broadcast.af != -1)
1286       c->api = xstrdup("rtp");
1287     else if(config_uaudio_apis)
1288       c->api = xstrdup(config_uaudio_apis[0]->name);
1289     else
1290       c->api = xstrdup("<none>");
1291   }
1292   if(!strcmp(c->api, "network"))
1293     c->api = xstrdup("rtp");
1294   if(server) {
1295     if(!strcmp(c->api, "command") && !c->speaker_command)
1296       fatal(0, "'api command' but speaker_command is not set");
1297     if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
1298       fatal(0, "'api rtp' but broadcast is not set");
1299   }
1300   /* Override sample format */
1301   if(!strcmp(c->api, "rtp")) {
1302     c->sample_format.rate = 44100;
1303     c->sample_format.channels = 2;
1304     c->sample_format.bits = 16;
1305     c->sample_format.endian = ENDIAN_NATIVE;
1306   }
1307   if(!strcmp(c->api, "coreaudio")) {
1308     c->sample_format.rate = 44100;
1309     c->sample_format.channels = 2;
1310     c->sample_format.bits = 16;
1311     c->sample_format.endian = ENDIAN_NATIVE;
1312   }
1313   if(!c->default_rights) {
1314     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
1315                                      |RIGHT_MOVE__MASK
1316                                      |RIGHT_SCRATCH__MASK
1317                                      |RIGHT_REMOVE__MASK);
1318     /* The idea is to approximate the meaning of the old 'restrict' directive
1319      * in the default rights if they are not overridden. */
1320     if(c->restrictions & RESTRICT_SCRATCH)
1321       r |= RIGHT_SCRATCH_MINE|RIGHT_SCRATCH_RANDOM;
1322     else
1323       r |= RIGHT_SCRATCH_ANY;
1324     if(!(c->restrictions & RESTRICT_MOVE))
1325       r |= RIGHT_MOVE_ANY;
1326     if(c->restrictions & RESTRICT_REMOVE)
1327       r |= RIGHT_REMOVE_MINE;
1328     else
1329       r |= RIGHT_REMOVE_ANY;
1330     c->default_rights = rights_string(r);
1331   }
1332 }
1333
1334 /** @brief (Re-)read the config file
1335  * @param server If set, do extra checking
1336  * @param oldconfig Old configuration for compatibility check
1337  * @return 0 on success, non-0 on error
1338  *
1339  * If @p oldconfig is set, then certain compatibility checks are done between
1340  * the old and new configurations.
1341  */
1342 int config_read(int server,
1343                 const struct config *oldconfig) {
1344   struct config *c;
1345   char *privconf;
1346   struct passwd *pw;
1347
1348   set_configfile();
1349   c = config_default();
1350   /* standalone Disobedience installs might not have a global config file */
1351   if(access(configfile, F_OK) == 0)
1352     if(config_include(c, configfile))
1353       return -1;
1354   /* if we can read the private config file, do */
1355   if((privconf = config_private())
1356      && access(privconf, R_OK) == 0
1357      && config_include(c, privconf))
1358     return -1;
1359   xfree(privconf);
1360   /* if there's a per-user system config file for this user, read it */
1361   if(config_per_user) {
1362     if(!(pw = getpwuid(getuid())))
1363       fatal(0, "cannot determine our username");
1364     if((privconf = config_usersysconf(pw))
1365        && access(privconf, F_OK) == 0
1366        && config_include(c, privconf))
1367       return -1;
1368     xfree(privconf);
1369     /* if we have a password file, read it */
1370     if((privconf = config_userconf(0, pw))
1371        && access(privconf, F_OK) == 0
1372        && config_include(c, privconf))
1373       return -1;
1374     xfree(privconf);
1375   }
1376   /* install default namepart and transform settings */
1377   config_postdefaults(c, server);
1378   if(oldconfig)  {
1379     int failed = 0;
1380     if(strcmp(c->home, oldconfig->home)) {
1381       error(0, "'home' cannot be changed without a restart");
1382       failed = 1;
1383     }
1384     if(strcmp(c->alias, oldconfig->alias)) {
1385       error(0, "'alias' cannot be changed without a restart");
1386       failed = 1;
1387     }
1388     if(strcmp(c->user, oldconfig->user)) {
1389       error(0, "'user' cannot be changed without a restart");
1390       failed = 1;
1391     }
1392     if(c->nice_speaker != oldconfig->nice_speaker) {
1393       error(0, "'nice_speaker' cannot be changed without a restart");
1394       /* ...but we accept the new config anyway */
1395     }
1396     if(c->nice_server != oldconfig->nice_server) {
1397       error(0, "'nice_server' cannot be changed without a restart");
1398       /* ...but we accept the new config anyway */
1399     }
1400     if(namepartlist_compare(&c->namepart, &oldconfig->namepart)) {
1401       error(0, "'namepart' settings cannot be changed without a restart");
1402       failed = 1;
1403     }
1404     if(stringlist_compare(&c->stopword, &oldconfig->stopword)) {
1405       error(0, "'stopword' settings cannot be changed without a restart");
1406       failed = 1;
1407     }
1408     if(failed) {
1409       error(0, "not installing incompatible new configuration");
1410       return -1;
1411     }
1412   }
1413   /* everything is good so we shall use the new config */
1414   config_free(config);
1415   /* warn about obsolete directives */
1416   if(c->restrictions)
1417     error(0, "'restrict' will be removed in a future version");
1418   if(c->allow.n)
1419     error(0, "'allow' will be removed in a future version");
1420   if(c->trust.n)
1421     error(0, "'trust' will be removed in a future version");
1422   if(!c->lock)
1423     error(0, "'lock' will be removed in a future version");
1424   if(c->gap)
1425     error(0, "'gap' will be removed in a future version");
1426   if(c->prefsync)
1427     error(0, "'prefsync' will be removed in a future version");
1428   config = c;
1429   return 0;
1430 }
1431
1432 /** @brief Return the path to the private configuration file */
1433 char *config_private(void) {
1434   char *s;
1435
1436   set_configfile();
1437   byte_xasprintf(&s, "%s.private", configfile);
1438   return s;
1439 }
1440
1441 /** @brief Return the path to user's personal configuration file */
1442 char *config_userconf(const char *home, const struct passwd *pw) {
1443   char *s;
1444
1445   if(!home && !pw && !(pw = getpwuid(getuid())))
1446     fatal(0, "cannot determine our username");
1447   byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir);
1448   return s;
1449 }
1450
1451 /** @brief Return the path to user-specific system configuration */
1452 char *config_usersysconf(const struct passwd *pw) {
1453   char *s;
1454
1455   set_configfile();
1456   if(!strchr(pw->pw_name, '/')) {
1457     byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name);
1458     return s;
1459   } else
1460     return 0;
1461 }
1462
1463 char *config_get_file(const char *name) {
1464   return config_get_file2(config, name);
1465 }
1466
1467 static int stringlist_compare(const struct stringlist *a,
1468                               const struct stringlist *b) {
1469   int n = 0, c;
1470
1471   while(n < a->n && n < b->n) {
1472     if((c = strcmp(a->s[n], b->s[n])))
1473       return c;
1474     ++n;
1475   }
1476   if(a->n < b->n)
1477     return -1;
1478   else if(a->n > b->n)
1479     return 1;
1480   else
1481     return 0;
1482 }
1483
1484 static int namepart_compare(const struct namepart *a,
1485                             const struct namepart *b) {
1486   int c;
1487
1488   if((c = strcmp(a->part, b->part)))
1489     return c;
1490   if((c = strcmp(a->res, b->res)))
1491     return c;
1492   if((c = strcmp(a->replace, b->replace)))
1493     return c;
1494   if((c = strcmp(a->context, b->context)))
1495     return c;
1496   if(a->reflags > b->reflags)
1497     return 1;
1498   if(a->reflags < b->reflags)
1499     return -1;
1500   return 0;
1501 }
1502
1503 static int namepartlist_compare(const struct namepartlist *a,
1504                                 const struct namepartlist *b) {
1505   int n = 0, c;
1506
1507   while(n < a->n && n < b->n) {
1508     if((c = namepart_compare(&a->s[n], &b->s[n])))
1509       return c;
1510     ++n;
1511   }
1512   if(a->n > b->n)
1513     return 1;
1514   else if(a->n < b->n)
1515     return -1;
1516   else
1517     return 0;
1518 }
1519
1520 /*
1521 Local Variables:
1522 c-basic-offset:2
1523 comment-column:40
1524 fill-column:79
1525 End:
1526 */