460b9539 |
1 | /* |
2 | * This file is part of DisOrder. |
3 | * Copyright (C) 2004, 2005, 2006 Richard Kettlewell |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, but |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License |
16 | * along with this program; if not, write to the Free Software |
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
18 | * USA |
19 | */ |
20 | |
21 | #include <config.h> |
22 | #include "types.h" |
23 | |
24 | #include <stdio.h> |
25 | #include <string.h> |
26 | #include <stdlib.h> |
27 | #include <errno.h> |
28 | #include <sys/types.h> |
29 | #include <sys/stat.h> |
30 | #include <unistd.h> |
31 | #include <ctype.h> |
32 | #include <stddef.h> |
33 | #include <pwd.h> |
34 | #include <langinfo.h> |
35 | #include <pcre.h> |
36 | #include <signal.h> |
37 | |
38 | #include "configuration.h" |
39 | #include "mem.h" |
40 | #include "log.h" |
41 | #include "split.h" |
42 | #include "syscalls.h" |
43 | #include "table.h" |
44 | #include "inputline.h" |
45 | #include "charset.h" |
46 | #include "defs.h" |
47 | #include "mixer.h" |
48 | #include "printf.h" |
49 | #include "regsub.h" |
50 | #include "signame.h" |
51 | |
52 | char *configfile; |
53 | |
54 | struct config_state { |
55 | const char *path; |
56 | int line; |
57 | struct config *config; |
58 | }; |
59 | |
60 | struct config *config; |
61 | |
62 | struct conf { |
63 | const char *name; |
64 | size_t offset; |
65 | const struct conftype *type; |
66 | int (*validate)(const struct config_state *cs, |
67 | int nvec, char **vec); |
68 | }; |
69 | |
70 | struct conftype { |
71 | int (*set)(const struct config_state *cs, |
72 | const struct conf *whoami, |
73 | int nvec, char **vec); |
74 | void (*free)(struct config *c, const struct conf *whoami); |
75 | }; |
76 | |
77 | #define ADDRESS(C, TYPE) ((TYPE *)((char *)(C) + whoami->offset)) |
78 | #define VALUE(C, TYPE) (*ADDRESS(C, TYPE)) |
79 | |
80 | static int set_signal(const struct config_state *cs, |
81 | const struct conf *whoami, |
82 | int nvec, char **vec) { |
83 | int n; |
84 | |
85 | if(nvec != 1) { |
86 | error(0, "%s:%d: '%s' requires one argument", |
87 | cs->path, cs->line, whoami->name); |
88 | return -1; |
89 | } |
90 | if((n = find_signal(vec[0])) == -1) { |
91 | error(0, "%s:%d: unknown signal '%s'", |
92 | cs->path, cs->line, vec[0]); |
93 | return -1; |
94 | } |
95 | VALUE(cs->config, int) = n; |
96 | return 0; |
97 | } |
98 | |
99 | static int set_collections(const struct config_state *cs, |
100 | const struct conf *whoami, |
101 | int nvec, char **vec) { |
102 | struct collectionlist *cl; |
103 | |
104 | if(nvec != 3) { |
105 | error(0, "%s:%d: '%s' requires three arguments", |
106 | cs->path, cs->line, whoami->name); |
107 | return -1; |
108 | } |
109 | if(vec[2][0] != '/') { |
110 | error(0, "%s:%d: collection root must start with '/'", |
111 | cs->path, cs->line); |
112 | return -1; |
113 | } |
114 | if(vec[2][1] && vec[2][strlen(vec[2])-1] == '/') { |
115 | error(0, "%s:%d: collection root must not end with '/'", |
116 | cs->path, cs->line); |
117 | return -1; |
118 | } |
119 | cl = ADDRESS(cs->config, struct collectionlist); |
120 | ++cl->n; |
121 | cl->s = xrealloc(cl->s, cl->n * sizeof (struct collection)); |
122 | cl->s[cl->n - 1].module = xstrdup(vec[0]); |
123 | cl->s[cl->n - 1].encoding = xstrdup(vec[1]); |
124 | cl->s[cl->n - 1].root = xstrdup(vec[2]); |
125 | return 0; |
126 | } |
127 | |
128 | static int set_boolean(const struct config_state *cs, |
129 | const struct conf *whoami, |
130 | int nvec, char **vec) { |
131 | int state; |
132 | |
133 | if(nvec != 1) { |
134 | error(0, "%s:%d: '%s' takes only one argument", |
135 | cs->path, cs->line, whoami->name); |
136 | return -1; |
137 | } |
138 | if(!strcmp(vec[0], "yes")) state = 1; |
139 | else if(!strcmp(vec[0], "no")) state = 0; |
140 | else { |
141 | error(0, "%s:%d: argument to '%s' must be 'yes' or 'no'", |
142 | cs->path, cs->line, whoami->name); |
143 | return -1; |
144 | } |
145 | VALUE(cs->config, int) = state; |
146 | return 0; |
147 | } |
148 | |
149 | static int set_string(const struct config_state *cs, |
150 | const struct conf *whoami, |
151 | int nvec, char **vec) { |
152 | if(nvec != 1) { |
153 | error(0, "%s:%d: '%s' takes only one argument", |
154 | cs->path, cs->line, whoami->name); |
155 | return -1; |
156 | } |
157 | VALUE(cs->config, char *) = xstrdup(vec[0]); |
158 | return 0; |
159 | } |
160 | |
161 | static int set_stringlist(const struct config_state *cs, |
162 | const struct conf *whoami, |
163 | int nvec, char **vec) { |
164 | int n; |
165 | struct stringlist *sl; |
166 | |
167 | sl = ADDRESS(cs->config, struct stringlist); |
168 | sl->n = 0; |
169 | for(n = 0; n < nvec; ++n) { |
170 | sl->n++; |
171 | sl->s = xrealloc(sl->s, (sl->n * sizeof (char *))); |
172 | sl->s[sl->n - 1] = xstrdup(vec[n]); |
173 | } |
174 | return 0; |
175 | } |
176 | |
177 | static int set_integer(const struct config_state *cs, |
178 | const struct conf *whoami, |
179 | int nvec, char **vec) { |
180 | char *e; |
181 | |
182 | if(nvec != 1) { |
183 | error(0, "%s:%d: '%s' takes only one argument", |
184 | cs->path, cs->line, whoami->name); |
185 | return -1; |
186 | } |
187 | if(xstrtol(ADDRESS(cs->config, long), vec[0], &e, 0)) { |
188 | error(errno, "%s:%d: converting integer", cs->path, cs->line); |
189 | return -1; |
190 | } |
191 | if(*e) { |
192 | error(0, "%s:%d: invalid integer syntax", cs->path, cs->line); |
193 | return -1; |
194 | } |
195 | return 0; |
196 | } |
197 | |
198 | static int set_stringlist_accum(const struct config_state *cs, |
199 | const struct conf *whoami, |
200 | int nvec, char **vec) { |
201 | int n; |
202 | struct stringlist *s; |
203 | struct stringlistlist *sll; |
204 | |
205 | sll = ADDRESS(cs->config, struct stringlistlist); |
206 | sll->n++; |
207 | sll->s = xrealloc(sll->s, (sll->n * sizeof (struct stringlist))); |
208 | s = &sll->s[sll->n - 1]; |
209 | s->n = nvec; |
210 | s->s = xmalloc((nvec + 1) * sizeof (char *)); |
211 | for(n = 0; n < nvec; ++n) |
212 | s->s[n] = xstrdup(vec[n]); |
213 | return 0; |
214 | } |
215 | |
216 | static int set_string_accum(const struct config_state *cs, |
217 | const struct conf *whoami, |
218 | int nvec, char **vec) { |
219 | int n; |
220 | struct stringlist *sl; |
221 | |
222 | sl = ADDRESS(cs->config, struct stringlist); |
223 | for(n = 0; n < nvec; ++n) { |
224 | sl->n++; |
225 | sl->s = xrealloc(sl->s, (sl->n * sizeof (char *))); |
226 | sl->s[sl->n - 1] = xstrdup(vec[n]); |
227 | } |
228 | return 0; |
229 | } |
230 | |
231 | static int set_restrict(const struct config_state *cs, |
232 | const struct conf *whoami, |
233 | int nvec, char **vec) { |
234 | unsigned r = 0; |
235 | int n, i; |
236 | |
237 | static const struct restriction { |
238 | const char *name; |
239 | unsigned bit; |
240 | } restrictions[] = { |
241 | { "remove", RESTRICT_REMOVE }, |
242 | { "scratch", RESTRICT_SCRATCH }, |
243 | { "move", RESTRICT_MOVE }, |
244 | }; |
245 | |
246 | for(n = 0; n < nvec; ++n) { |
247 | if((i = TABLE_FIND(restrictions, struct restriction, name, vec[n])) < 0) { |
248 | error(0, "%s:%d: invalid restriction '%s'", |
249 | cs->path, cs->line, vec[n]); |
250 | return -1; |
251 | } |
252 | r |= restrictions[i].bit; |
253 | } |
254 | VALUE(cs->config, unsigned) = r; |
255 | return 0; |
256 | } |
257 | |
258 | static int set_namepart(const struct config_state *cs, |
259 | const struct conf *whoami, |
260 | int nvec, char **vec) { |
261 | struct namepartlist *npl = ADDRESS(cs->config, struct namepartlist); |
262 | unsigned reflags; |
263 | const char *errstr; |
264 | int erroffset, n; |
265 | pcre *re; |
266 | |
267 | if(nvec < 3) { |
268 | error(0, "%s:%d: namepart needs at least 3 arguments", cs->path, cs->line); |
269 | return -1; |
270 | } |
271 | if(nvec > 5) { |
272 | error(0, "%s:%d: namepart needs at most 5 arguments", cs->path, cs->line); |
273 | return -1; |
274 | } |
275 | reflags = nvec >= 5 ? regsub_flags(vec[4]) : 0; |
276 | if(!(re = pcre_compile(vec[1], |
277 | PCRE_UTF8 |
278 | |regsub_compile_options(reflags), |
279 | &errstr, &erroffset, 0))) { |
280 | error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)", |
281 | cs->path, cs->line, vec[1], errstr, erroffset); |
282 | return -1; |
283 | } |
284 | npl->s = xrealloc(npl->s, (npl->n + 1) * sizeof (struct namepart)); |
285 | npl->s[npl->n].part = xstrdup(vec[0]); |
286 | npl->s[npl->n].re = re; |
287 | npl->s[npl->n].replace = xstrdup(vec[2]); |
288 | npl->s[npl->n].context = xstrdup(vec[3]); |
289 | npl->s[npl->n].reflags = reflags; |
290 | ++npl->n; |
291 | /* XXX a bit of a bodge; relies on there being very few parts. */ |
292 | for(n = 0; (n < cs->config->nparts |
293 | && strcmp(cs->config->parts[n], vec[0])); ++n) |
294 | ; |
295 | if(n >= cs->config->nparts) { |
296 | cs->config->parts = xrealloc(cs->config->parts, |
297 | (cs->config->nparts + 1) * sizeof (char *)); |
298 | cs->config->parts[cs->config->nparts++] = xstrdup(vec[0]); |
299 | } |
300 | return 0; |
301 | } |
302 | |
303 | static int set_transform(const struct config_state *cs, |
304 | const struct conf *whoami, |
305 | int nvec, char **vec) { |
306 | struct transformlist *tl = ADDRESS(cs->config, struct transformlist); |
307 | pcre *re; |
308 | unsigned reflags; |
309 | const char *errstr; |
310 | int erroffset; |
311 | |
312 | if(nvec < 3) { |
313 | error(0, "%s:%d: transform needs at least 3 arguments", cs->path, cs->line); |
314 | return -1; |
315 | } |
316 | if(nvec > 5) { |
317 | error(0, "%s:%d: transform needs at most 5 arguments", cs->path, cs->line); |
318 | return -1; |
319 | } |
320 | reflags = (nvec >= 5 ? regsub_flags(vec[4]) : 0); |
321 | if(!(re = pcre_compile(vec[1], |
322 | PCRE_UTF8 |
323 | |regsub_compile_options(reflags), |
324 | &errstr, &erroffset, 0))) { |
325 | error(0, "%s:%d: error compiling regexp /%s/: %s (offset %d)", |
326 | cs->path, cs->line, vec[1], errstr, erroffset); |
327 | return -1; |
328 | } |
329 | tl->t = xrealloc(tl->t, (tl->n + 1) * sizeof (struct namepart)); |
330 | tl->t[tl->n].type = xstrdup(vec[0]); |
331 | tl->t[tl->n].context = xstrdup(vec[3] ? vec[3] : "*"); |
332 | tl->t[tl->n].re = re; |
333 | tl->t[tl->n].replace = xstrdup(vec[2]); |
334 | tl->t[tl->n].flags = reflags; |
335 | ++tl->n; |
336 | return 0; |
337 | } |
338 | |
339 | /* free functions */ |
340 | |
341 | static void free_none(struct config attribute((unused)) *c, |
342 | const struct conf attribute((unused)) *whoami) { |
343 | } |
344 | |
345 | static void free_string(struct config *c, |
346 | const struct conf *whoami) { |
347 | xfree(VALUE(c, char *)); |
348 | } |
349 | |
350 | static void free_stringlist(struct config *c, |
351 | const struct conf *whoami) { |
352 | int n; |
353 | struct stringlist *sl = ADDRESS(c, struct stringlist); |
354 | |
355 | for(n = 0; n < sl->n; ++n) |
356 | xfree(sl->s[n]); |
357 | xfree(sl->s); |
358 | } |
359 | |
360 | static void free_stringlistlist(struct config *c, |
361 | const struct conf *whoami) { |
362 | int n, m; |
363 | struct stringlistlist *sll = ADDRESS(c, struct stringlistlist); |
364 | struct stringlist *sl; |
365 | |
366 | for(n = 0; n < sll->n; ++n) { |
367 | sl = &sll->s[n]; |
368 | for(m = 0; m < sl->n; ++m) |
369 | xfree(sl->s[m]); |
370 | xfree(sl->s); |
371 | } |
372 | xfree(sll->s); |
373 | } |
374 | |
375 | static void free_collectionlist(struct config *c, |
376 | const struct conf *whoami) { |
377 | struct collectionlist *cll = ADDRESS(c, struct collectionlist); |
378 | struct collection *cl; |
379 | int n; |
380 | |
381 | for(n = 0; n < cll->n; ++n) { |
382 | cl = &cll->s[n]; |
383 | xfree(cl->module); |
384 | xfree(cl->encoding); |
385 | xfree(cl->root); |
386 | } |
387 | xfree(cll->s); |
388 | } |
389 | |
390 | static void free_namepartlist(struct config *c, |
391 | const struct conf *whoami) { |
392 | struct namepartlist *npl = ADDRESS(c, struct namepartlist); |
393 | struct namepart *np; |
394 | int n; |
395 | |
396 | for(n = 0; n < npl->n; ++n) { |
397 | np = &npl->s[n]; |
398 | xfree(np->part); |
399 | pcre_free(np->re); /* ...whatever pcre_free is set to. */ |
400 | xfree(np->replace); |
401 | xfree(np->context); |
402 | } |
403 | xfree(npl->s); |
404 | } |
405 | |
406 | static void free_transformlist(struct config *c, |
407 | const struct conf *whoami) { |
408 | struct transformlist *tl = ADDRESS(c, struct transformlist); |
409 | struct transform *t; |
410 | int n; |
411 | |
412 | for(n = 0; n < tl->n; ++n) { |
413 | t = &tl->t[n]; |
414 | xfree(t->type); |
415 | pcre_free(t->re); /* ...whatever pcre_free is set to. */ |
416 | xfree(t->replace); |
417 | xfree(t->context); |
418 | } |
419 | xfree(tl->t); |
420 | } |
421 | |
422 | /* configuration types */ |
423 | |
424 | static const struct conftype |
425 | type_signal = { set_signal, free_none }, |
426 | type_collections = { set_collections, free_collectionlist }, |
427 | type_boolean = { set_boolean, free_none }, |
428 | type_string = { set_string, free_string }, |
429 | type_stringlist = { set_stringlist, free_stringlist }, |
430 | type_integer = { set_integer, free_none }, |
431 | type_stringlist_accum = { set_stringlist_accum, free_stringlistlist }, |
432 | type_string_accum = { set_string_accum, free_stringlist }, |
433 | type_restrict = { set_restrict, free_none }, |
434 | type_namepart = { set_namepart, free_namepartlist }, |
435 | type_transform = { set_transform, free_transformlist }; |
436 | |
437 | /* specific validation routine */ |
438 | |
439 | #define VALIDATE_FILE(test, what) do { \ |
440 | struct stat sb; \ |
441 | int n; \ |
442 | \ |
443 | for(n = 0; n < nvec; ++n) { \ |
444 | if(stat(vec[n], &sb) < 0) { \ |
445 | error(errno, "%s:%d: %s", cs->path, cs->line, vec[n]); \ |
446 | return -1; \ |
447 | } \ |
448 | if(!test(sb.st_mode)) { \ |
449 | error(0, "%s:%d: %s is not a %s", \ |
450 | cs->path, cs->line, vec[n], what); \ |
451 | return -1; \ |
452 | } \ |
453 | } \ |
454 | } while(0) |
455 | |
456 | static int validate_isdir(const struct config_state *cs, |
457 | int nvec, char **vec) { |
458 | VALIDATE_FILE(S_ISDIR, "directory"); |
459 | return 0; |
460 | } |
461 | |
462 | static int validate_isreg(const struct config_state *cs, |
463 | int nvec, char **vec) { |
464 | VALIDATE_FILE(S_ISREG, "regular file"); |
465 | return 0; |
466 | } |
467 | |
468 | static int validate_ischr(const struct config_state *cs, |
469 | int nvec, char **vec) { |
470 | VALIDATE_FILE(S_ISCHR, "character device"); |
471 | return 0; |
472 | } |
473 | |
474 | static int validate_player(const struct config_state *cs, |
475 | int nvec, |
476 | char attribute((unused)) **vec) { |
477 | if(nvec < 2) { |
478 | error(0, "%s:%d: should be at least 'player PATTERN MODULE'", |
479 | cs->path, cs->line); |
480 | return -1; |
481 | } |
482 | return 0; |
483 | } |
484 | |
485 | static int validate_allow(const struct config_state *cs, |
486 | int nvec, |
487 | char attribute((unused)) **vec) { |
488 | if(nvec != 2) { |
489 | error(0, "%s:%d: must be 'allow NAME PASS'", cs->path, cs->line); |
490 | return -1; |
491 | } |
492 | return 0; |
493 | } |
494 | |
495 | static int validate_non_negative(const struct config_state *cs, |
496 | int nvec, char **vec) { |
497 | long n; |
498 | |
499 | if(nvec < 1) { |
500 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
501 | return -1; |
502 | } |
503 | if(nvec > 1) { |
504 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
505 | return -1; |
506 | } |
507 | if(xstrtol(&n, vec[0], 0, 0)) { |
508 | error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno)); |
509 | return -1; |
510 | } |
511 | if(n < 0) { |
512 | error(0, "%s:%d: must not be negative", cs->path, cs->line); |
513 | return -1; |
514 | } |
515 | return 0; |
516 | } |
517 | |
518 | static int validate_positive(const struct config_state *cs, |
519 | int nvec, char **vec) { |
520 | long n; |
521 | |
522 | if(nvec < 1) { |
523 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
524 | return -1; |
525 | } |
526 | if(nvec > 1) { |
527 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
528 | return -1; |
529 | } |
530 | if(xstrtol(&n, vec[0], 0, 0)) { |
531 | error(0, "%s:%d: %s", cs->path, cs->line, strerror(errno)); |
532 | return -1; |
533 | } |
534 | if(n <= 0) { |
535 | error(0, "%s:%d: must be positive", cs->path, cs->line); |
536 | return -1; |
537 | } |
538 | return 0; |
539 | } |
540 | |
541 | static int validate_isauser(const struct config_state *cs, |
542 | int attribute((unused)) nvec, |
543 | char **vec) { |
544 | struct passwd *pw; |
545 | |
546 | if(!(pw = getpwnam(vec[0]))) { |
547 | error(0, "%s:%d: no such user as '%s'", cs->path, cs->line, vec[0]); |
548 | return -1; |
549 | } |
550 | return 0; |
551 | } |
552 | |
553 | static int validate_channel(const struct config_state *cs, |
554 | int attribute((unused)) nvec, |
555 | char **vec) { |
556 | if(mixer_channel(vec[0]) == -1) { |
557 | error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]); |
558 | return -1; |
559 | } |
560 | return 0; |
561 | } |
562 | |
563 | static int validate_any(const struct config_state attribute((unused)) *cs, |
564 | int attribute((unused)) nvec, |
565 | char attribute((unused)) **vec) { |
566 | return 0; |
567 | } |
568 | |
569 | static int validate_url(const struct config_state attribute((unused)) *cs, |
570 | int attribute((unused)) nvec, |
571 | char **vec) { |
572 | const char *s; |
573 | int n; |
574 | /* absoluteURI = scheme ":" ( hier_part | opaque_part ) |
575 | scheme = alpha *( alpha | digit | "+" | "-" | "." ) */ |
576 | s = vec[0]; |
577 | n = strspn(s, ("abcdefghijklmnopqrstuvwxyz" |
578 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
579 | "0123456789")); |
580 | if(s[n] != ':') { |
581 | error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]); |
582 | return -1; |
583 | } |
584 | if(!strncmp(s, "http:", 5) |
585 | || !strncmp(s, "https:", 6)) { |
586 | s += n + 1; |
587 | /* we only do a rather cursory check */ |
588 | if(strncmp(s, "//", 2)) { |
589 | error(0, "%s:%d: invalid url '%s'", cs->path, cs->line, vec[0]); |
590 | return -1; |
591 | } |
592 | } |
593 | return 0; |
594 | } |
595 | |
596 | static int validate_alias(const struct config_state *cs, |
597 | int nvec, |
598 | char **vec) { |
599 | const char *s; |
600 | int in_brackets = 0, c; |
601 | |
602 | if(nvec < 1) { |
603 | error(0, "%s:%d: missing argument", cs->path, cs->line); |
604 | return -1; |
605 | } |
606 | if(nvec > 1) { |
607 | error(0, "%s:%d: too many arguments", cs->path, cs->line); |
608 | return -1; |
609 | } |
610 | s = vec[0]; |
611 | while((c = (unsigned char)*s++)) { |
612 | if(in_brackets) { |
613 | if(c == '}') |
614 | in_brackets = 0; |
615 | else if(!isalnum(c)) { |
616 | error(0, "%s:%d: invalid part name in alias expansion in '%s'", |
617 | cs->path, cs->line, vec[0]); |
618 | return -1; |
619 | } |
620 | } else { |
621 | if(c == '{') { |
622 | in_brackets = 1; |
623 | if(*s == '/') |
624 | ++s; |
625 | } else if(c == '\\') { |
626 | if(!(c = (unsigned char)*s++)) { |
627 | error(0, "%s:%d: unterminated escape in alias expansion in '%s'", |
628 | cs->path, cs->line, vec[0]); |
629 | return -1; |
630 | } else if(c != '\\' && c != '{') { |
631 | error(0, "%s:%d: invalid escape in alias expansion in '%s'", |
632 | cs->path, cs->line, vec[0]); |
633 | return -1; |
634 | } |
635 | } |
636 | } |
637 | ++s; |
638 | } |
639 | if(in_brackets) { |
640 | error(0, "%s:%d: unterminated part name in alias expansion in '%s'", |
641 | cs->path, cs->line, vec[0]); |
642 | return -1; |
643 | } |
644 | return 0; |
645 | } |
646 | |
647 | /* configuration table */ |
648 | |
649 | #define C(x) #x, offsetof(struct config, x) |
650 | #define C2(x,y) #x, offsetof(struct config, y) |
651 | |
652 | static const struct conf conf[] = { |
653 | { C(alias), &type_string, validate_alias }, |
654 | { C(allow), &type_stringlist_accum, validate_allow }, |
655 | { C(channel), &type_string, validate_channel }, |
656 | { C(checkpoint_kbyte), &type_integer, validate_non_negative }, |
657 | { C(checkpoint_min), &type_integer, validate_non_negative }, |
658 | { C(collection), &type_collections, validate_any }, |
659 | { C(connect), &type_stringlist, validate_any }, |
660 | { C(device), &type_string, validate_any }, |
661 | { C(gap), &type_integer, validate_non_negative }, |
662 | { C(history), &type_integer, validate_positive }, |
663 | { C(home), &type_string, validate_isdir }, |
664 | { C(listen), &type_stringlist, validate_any }, |
665 | { C(lock), &type_boolean, validate_any }, |
666 | { C(mixer), &type_string, validate_ischr }, |
667 | { C(namepart), &type_namepart, validate_any }, |
668 | { C2(nice, nice_rescan), &type_integer, validate_non_negative }, |
669 | { C(nice_rescan), &type_integer, validate_non_negative }, |
670 | { C(nice_server), &type_integer, validate_any }, |
671 | { C(nice_speaker), &type_integer, validate_any }, |
672 | { C(password), &type_string, validate_any }, |
673 | { C(player), &type_stringlist_accum, validate_player }, |
674 | { C(plugins), &type_string_accum, validate_isdir }, |
675 | { C(prefsync), &type_integer, validate_positive }, |
676 | { C(refresh), &type_integer, validate_positive }, |
677 | { C2(restrict, restrictions), &type_restrict, validate_any }, |
678 | { C(scratch), &type_string_accum, validate_isreg }, |
679 | { C(signal), &type_signal, validate_any }, |
680 | { C(stopword), &type_string_accum, validate_any }, |
681 | { C(templates), &type_string_accum, validate_isdir }, |
682 | { C(transform), &type_transform, validate_any }, |
683 | { C(trust), &type_string_accum, validate_any }, |
684 | { C(url), &type_string, validate_url }, |
685 | { C(user), &type_string, validate_isauser }, |
686 | { C(username), &type_string, validate_any }, |
687 | }; |
688 | |
689 | /* find a configuration item's definition by key */ |
690 | static const struct conf *find(const char *key) { |
691 | int n; |
692 | |
693 | if((n = TABLE_FIND(conf, struct conf, name, key)) < 0) |
694 | return 0; |
695 | return &conf[n]; |
696 | } |
697 | |
698 | /* set a new configuration value */ |
699 | static int config_set(const struct config_state *cs, |
700 | int nvec, char **vec) { |
701 | const struct conf *which; |
702 | |
703 | D(("config_set %s", vec[0])); |
704 | if(!(which = find(vec[0]))) { |
705 | error(0, "%s:%d: unknown configuration key '%s'", |
706 | cs->path, cs->line, vec[0]); |
707 | return -1; |
708 | } |
709 | return (which->validate(cs, nvec - 1, vec + 1) |
710 | || which->type->set(cs, which, nvec - 1, vec + 1)); |
711 | } |
712 | |
713 | static void config_error(const char *msg, void *u) { |
714 | const struct config_state *cs = u; |
715 | |
716 | error(0, "%s:%d: %s", cs->path, cs->line, msg); |
717 | } |
718 | |
719 | /* include a file by name */ |
720 | static int config_include(struct config *c, const char *path) { |
721 | FILE *fp; |
722 | char *buffer, *inputbuffer, **vec; |
723 | int n, ret = 0; |
724 | struct config_state cs; |
725 | |
726 | cs.path = path; |
727 | cs.line = 0; |
728 | cs.config = c; |
729 | D(("%s: reading configuration", path)); |
730 | if(!(fp = fopen(path, "r"))) { |
731 | error(errno, "error opening %s", path); |
732 | return -1; |
733 | } |
734 | while(!inputline(path, fp, &inputbuffer, '\n')) { |
735 | ++cs.line; |
736 | if(!(buffer = mb2utf8(inputbuffer))) { |
737 | error(errno, "%s:%d: cannot convert to UTF-8", cs.path, cs.line); |
738 | ret = -1; |
739 | xfree(inputbuffer); |
740 | continue; |
741 | } |
742 | xfree(inputbuffer); |
743 | if(!(vec = split(buffer, &n, SPLIT_COMMENTS|SPLIT_QUOTES, |
744 | config_error, &cs))) { |
745 | ret = -1; |
746 | xfree(buffer); |
747 | continue; |
748 | } |
749 | if(n) { |
750 | if(!strcmp(vec[0], "include")) { |
751 | if(n != 2) { |
752 | error(0, "%s:%d: must be 'include PATH'", cs.path, cs.line); |
753 | ret = -1; |
754 | } else |
755 | config_include(c, vec[1]); |
756 | } else |
757 | ret |= config_set(&cs, n, vec); |
758 | } |
759 | for(n = 0; vec[n]; ++n) xfree(vec[n]); |
760 | xfree(vec); |
761 | xfree(buffer); |
762 | } |
763 | if(ferror(fp)) { |
764 | error(errno, "error reading %s", path); |
765 | ret = -1; |
766 | } |
767 | fclose(fp); |
768 | return ret; |
769 | } |
770 | |
771 | /* make a new default config */ |
772 | static struct config *config_default(void) { |
773 | struct config *c = xmalloc(sizeof *c); |
774 | const char *logname; |
775 | struct passwd *pw; |
776 | |
777 | /* Strings had better be xstrdup'd as they will get freed at some point. */ |
778 | c->gap = 2; |
779 | c->history = 60; |
780 | c->home = xstrdup(pkgstatedir); |
781 | if(!(pw = getpwuid(getuid()))) |
782 | fatal(0, "cannot determine our username"); |
783 | logname = pw->pw_name; |
784 | c->username = xstrdup(logname); |
785 | c->refresh = 15; |
786 | c->prefsync = 3600; |
787 | c->signal = SIGKILL; |
788 | c->alias = xstrdup("{/artist}{/album}{/title}{ext}"); |
789 | c->lock = 1; |
790 | c->device = xstrdup("default"); |
791 | c->nice_rescan = 10; |
792 | return c; |
793 | } |
794 | |
795 | static char *get_file(struct config *c, const char *name) { |
796 | char *s; |
797 | |
798 | byte_xasprintf(&s, "%s/%s", c->home, name); |
799 | return s; |
800 | } |
801 | |
802 | static void set_configfile(void) { |
803 | if(!configfile) |
804 | byte_xasprintf(&configfile, "%s/config", pkgconfdir); |
805 | } |
806 | |
807 | /* free the config file */ |
808 | static void config_free(struct config *c) { |
809 | int n; |
810 | |
811 | if(c) { |
812 | for(n = 0; n < (int)(sizeof conf / sizeof *conf); ++n) |
813 | conf[n].type->free(c, &conf[n]); |
814 | for(n = 0; n < c->nparts; ++n) |
815 | xfree(c->parts[n]); |
816 | xfree(c->parts); |
817 | xfree(c); |
818 | } |
819 | } |
820 | |
821 | static void config_postdefaults(struct config *c) { |
822 | struct config_state cs; |
823 | const struct conf *whoami; |
824 | int n; |
825 | |
826 | static const char *namepart[][4] = { |
827 | { "title", "/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display" }, |
828 | { "title", "/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort" }, |
829 | { "album", "/([^/]+)/[^/]+$", "$1", "*" }, |
830 | { "artist", "/([^/]+)/[^/]+/[^/]+$", "$1", "*" }, |
831 | { "ext", "(\\.[a-zA-Z0-9]+)$", "$1", "*" }, |
832 | }; |
833 | #define NNAMEPART (int)(sizeof namepart / sizeof *namepart) |
834 | |
835 | static const char *transform[][5] = { |
836 | { "track", "^.*/([0-9]+:)?([^/]+)\\.[a-zA-Z0-9]+$", "$2", "display", "" }, |
837 | { "track", "^.*/([^/]+)\\.[a-zA-Z0-9]+$", "$1", "sort", "" }, |
838 | { "dir", "^.*/([^/]+)$", "$1", "*", "" }, |
839 | { "dir", "^(the) ([^/]*)", "$2, $1", "sort", "i", }, |
840 | { "dir", "[[:punct:]]", "", "sort", "g", } |
841 | }; |
842 | #define NTRANSFORM (int)(sizeof transform / sizeof *transform) |
843 | |
844 | cs.path = "<internal>"; |
845 | cs.line = 0; |
846 | cs.config = c; |
847 | if(!c->namepart.n) { |
848 | whoami = find("namepart"); |
849 | for(n = 0; n < NNAMEPART; ++n) |
850 | set_namepart(&cs, whoami, 4, (char **)namepart[n]); |
851 | } |
852 | if(!c->transform.n) { |
853 | whoami = find("transform"); |
854 | for(n = 0; n < NTRANSFORM; ++n) |
855 | set_transform(&cs, whoami, 5, (char **)transform[n]); |
856 | } |
857 | } |
858 | |
859 | /* re-read the config file */ |
860 | int config_read() { |
861 | struct config *c; |
862 | char *privconf; |
863 | struct passwd *pw; |
864 | |
865 | set_configfile(); |
866 | c = config_default(); |
867 | if(config_include(c, configfile)) |
868 | return -1; |
869 | /* if we can read the private config file, do */ |
870 | if((privconf = config_private()) |
871 | && access(privconf, R_OK) == 0 |
872 | && config_include(c, privconf)) |
873 | return -1; |
874 | xfree(privconf); |
875 | /* if there's a per-user system config file for this user, read it */ |
876 | if(!(pw = getpwuid(getuid()))) |
877 | fatal(0, "cannot determine our username"); |
878 | if((privconf = config_usersysconf(pw)) |
879 | && access(privconf, F_OK) == 0 |
880 | && config_include(c, privconf)) |
881 | return -1; |
882 | xfree(privconf); |
883 | /* if we have a password file, read it */ |
884 | if((privconf = config_userconf(getenv("HOME"), pw)) |
885 | && access(privconf, F_OK) == 0 |
886 | && config_include(c, privconf)) |
887 | return -1; |
888 | xfree(privconf); |
889 | /* install default namepart and transform settings */ |
890 | config_postdefaults(c); |
891 | /* everything is good so we shall use the new config */ |
892 | config_free(config); |
893 | config = c; |
894 | return 0; |
895 | } |
896 | |
897 | char *config_private(void) { |
898 | char *s; |
899 | |
900 | set_configfile(); |
901 | byte_xasprintf(&s, "%s.private", configfile); |
902 | return s; |
903 | } |
904 | |
905 | char *config_userconf(const char *home, const struct passwd *pw) { |
906 | char *s; |
907 | |
908 | byte_xasprintf(&s, "%s/.disorder/passwd", home ? home : pw->pw_dir); |
909 | return s; |
910 | } |
911 | |
912 | char *config_usersysconf(const struct passwd *pw ) { |
913 | char *s; |
914 | |
915 | set_configfile(); |
916 | if(!strchr(pw->pw_name, '/')) { |
917 | byte_xasprintf(&s, "%s.%s", configfile, pw->pw_name); |
918 | return s; |
919 | } else |
920 | return 0; |
921 | } |
922 | |
923 | char *config_get_file(const char *name) { |
924 | return get_file(config, name); |
925 | } |
926 | |
927 | /* |
928 | Local Variables: |
929 | c-basic-offset:2 |
930 | comment-column:40 |
931 | fill-column:79 |
932 | End: |
933 | */ |