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