Merge branch 'master' into timeheap
[ashd.git] / src / dirplex / conf.c
1 /*
2     ashd - A Sane HTTP Daemon
3     Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
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 3 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,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU 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, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <fnmatch.h>
25 #include <sys/stat.h>
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30 #include <utils.h>
31 #include <log.h>
32 #include <cf.h>
33 #include <resp.h>
34
35 #include "dirplex.h"
36
37 static struct config *cflist;
38 struct config *gconfig, *lconfig;
39
40 static void freerule(struct rule *rule)
41 {
42     freeca(rule->patterns);
43     free(rule);
44 }
45
46 static void freepattern(struct pattern *pat)
47 {
48     struct rule **rule;
49     struct headmod *head;
50     
51     for(rule = pat->rules; *rule; rule++)
52         freerule(*rule);
53     while((head = pat->headers) != NULL) {
54         pat->headers = head->next;
55         free(head->name);
56         free(head->value);
57         free(head);
58     }
59     if(pat->childnm != NULL)
60         free(pat->childnm);
61     freeca(pat->fchild);
62     free(pat);
63 }
64
65 static void freeconfig(struct config *cf)
66 {
67     struct child *ch, *nch;
68     struct pattern *pat, *npat;
69     
70     if(cf->prev != NULL)
71         cf->prev->next = cf->next;
72     if(cf->next != NULL)
73         cf->next->prev = cf->prev;
74     if(cf == cflist)
75         cflist = cf->next;
76     if(cf->path != NULL)
77         free(cf->path);
78     for(ch = cf->children; ch != NULL; ch = nch) {
79         nch = ch->next;
80         freechild(ch);
81     }
82     for(pat = cf->patterns; pat != NULL; pat = npat) {
83         npat = pat->next;
84         freepattern(pat);
85     }
86     freeca(cf->index);
87     if(cf->capture != NULL)
88         free(cf->capture);
89     if(cf->reparse != NULL)
90         free(cf->reparse);
91     free(cf);
92 }
93
94 struct child *getchild(struct config *cf, char *name)
95 {
96     struct child *ch;
97     
98     for(ch = cf->children; ch; ch = ch->next) {
99         if(!strcmp(ch->name, name))
100             break;
101     }
102     return(ch);
103 }
104
105 static struct rule *newrule(struct pattern *pat)
106 {
107     int i;
108     struct rule *rule;
109     
110     for(i = 0; pat->rules[i]; i++);
111     pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2));
112     rule = pat->rules[i] = szmalloc(sizeof(*rule));
113     pat->rules[i + 1] = NULL;
114     return(rule);
115 }
116
117 static struct pattern *newpattern(void)
118 {
119     struct pattern *pat;
120     
121     omalloc(pat);
122     pat->rules = szmalloc(sizeof(*pat->rules));
123     return(pat);
124 }
125
126 static char **cadup(char **w)
127 {
128     char **ret;
129     int i, l;
130     
131     l = calen(w);
132     ret = smalloc(sizeof(*ret) * (l + 1));
133     for(i = 0; i < l; i++)
134         ret[i] = sstrdup(w[i]);
135     ret[i] = NULL;
136     return(ret);
137 }
138
139 static struct pattern *parsepattern(struct cfstate *s)
140 {
141     struct pattern *pat;
142     struct rule *rule;
143     struct headmod *head;
144     int sl;
145
146     if(!strcmp(s->argv[0], "match")) {
147         s->expstart = 1;
148         pat = newpattern();
149     } else {
150         return(NULL);
151     }
152     
153     if((s->argc > 1) && !strcmp(s->argv[1], "directory"))
154         pat->type = PT_DIR;
155     else if((s->argc > 1) && !strcmp(s->argv[1], "notfound"))
156         pat->type = PT_NOTFOUND;
157     else
158         pat->type = PT_FILE;
159     sl = s->lno;
160     while(1) {
161         getcfline(s);
162         if(!strcmp(s->argv[0], "filename")) {
163             if(s->argc < 2) {
164                 flog(LOG_WARNING, "%s:%i: missing pattern for `filename' match", s->file, s->lno);
165                 continue;
166             }
167             rule = newrule(pat);
168             rule->type = PAT_BASENAME;
169             rule->patterns = cadup(s->argv + 1);
170         } else if(!strcmp(s->argv[0], "pathname")) {
171             if(s->argc < 2) {
172                 flog(LOG_WARNING, "%s:%i: missing pattern for `pathname' match", s->file, s->lno);
173                 continue;
174             }
175             rule = newrule(pat);
176             rule->type = PAT_PATHNAME;
177             rule->patterns = cadup(s->argv + 1);
178         } else if(!strcmp(s->argv[0], "all")) {
179             newrule(pat)->type = PAT_ALL;
180         } else if(!strcmp(s->argv[0], "default")) {
181             newrule(pat)->type = PAT_DEFAULT;
182         } else if(!strcmp(s->argv[0], "local")) {
183             newrule(pat)->type = PAT_LOCAL;
184         } else if(!strcmp(s->argv[0], "handler")) {
185             if(s->argc < 2) {
186                 flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", s->file, s->lno);
187                 continue;
188             }
189             if(pat->childnm != NULL)
190                 free(pat->childnm);
191             pat->childnm = sstrdup(s->argv[1]);
192         } else if(!strcmp(s->argv[0], "fork")) {
193             pat->fchild = cadup(s->argv + 1);
194         } else if(!strcmp(s->argv[0], "set") || !strcmp(s->argv[0], "xset")) {
195             if(s->argc < 3) {
196                 flog(LOG_WARNING, "%s:%i: missing header name or pattern for `%s' directive", s->file, s->lno, s->argv[0]);
197                 continue;
198             }
199             omalloc(head);
200             if(!strcmp(s->argv[0], "xset"))
201                 head->name = sprintf2("X-Ash-%s", s->argv[1]);
202             else
203                 head->name = sstrdup(s->argv[1]);
204             head->value = sstrdup(s->argv[2]);
205             head->next = pat->headers;
206             pat->headers = head;
207         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
208             break;
209         } else {
210             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in pattern declaration", s->file, s->lno, s->argv[0]);
211         }
212     }
213     
214     if(pat->rules[0] == NULL) {
215         flog(LOG_WARNING, "%s:%i: missing rules in match declaration", s->file, sl);
216         freepattern(pat);
217         return(NULL);
218     }
219     if((pat->childnm == NULL) && (pat->fchild == NULL)) {
220         flog(LOG_WARNING, "%s:%i: missing handler in match declaration", s->file, sl);
221         freepattern(pat);
222         return(NULL);
223     }
224     return(pat);
225 }
226
227 static struct config *emptyconfig(void)
228 {
229     struct config *cf;
230     
231     omalloc(cf);
232     return(cf);
233 }
234
235 struct config *readconfig(char *file)
236 {
237     struct cfstate *s;
238     FILE *in;
239     struct config *cf;
240     struct child *child;
241     struct pattern *pat;
242     
243     if((in = fopen(file, "r")) == NULL) {
244         flog(LOG_WARNING, "%s: %s", file, strerror(errno));
245         return(NULL);
246     }
247     s = mkcfparser(in, file);
248     cf = emptyconfig();
249     
250     while(1) {
251         getcfline(s);
252         if((child = parsechild(s)) != NULL) {
253             child->next = cf->children;
254             cf->children = child;
255         } else if((pat = parsepattern(s)) != NULL) {
256             pat->next = cf->patterns;
257             cf->patterns = pat;
258         } else if(!strcmp(s->argv[0], "index-file")) {
259             freeca(cf->index);
260             cf->index = cadup(s->argv + 1);
261         } else if(!strcmp(s->argv[0], "capture")) {
262             if(s->argc < 2) {
263                 flog(LOG_WARNING, "%s:%i: missing argument to capture declaration", s->file, s->lno);
264                 continue;
265             }
266             if(cf->capture != NULL)
267                 free(cf->capture);
268             cf->capture = sstrdup(s->argv[1]);
269             cf->caproot = 0;
270             if((s->argc > 2) && strchr(s->argv[2], 'D'))
271                 cf->caproot = 1;
272         } else if(!strcmp(s->argv[0], "reparse")) {
273             if(s->argc < 2) {
274                 flog(LOG_WARNING, "%s:%i: missing argument to reparse declaration", s->file, s->lno);
275                 continue;
276             }
277             if(cf->reparse != NULL)
278                 free(cf->reparse);
279             cf->reparse = sstrdup(s->argv[1]);
280             cf->parsecomb = 0;
281             if((s->argc > 2) && strchr(s->argv[2], 'c'))
282                 cf->parsecomb = 1;
283         } else if(!strcmp(s->argv[0], "eof")) {
284             break;
285         } else {
286             flog(LOG_WARNING, "%s:%i: unknown directive `%s'", s->file, s->lno, s->argv[0]);
287         }
288     }
289     
290     freecfparser(s);
291     fclose(in);
292     return(cf);
293 }
294
295 struct config *getconfig(char *path)
296 {
297     struct config *cf, *ocf;
298     struct stat sb;
299     char *fn;
300     time_t mtime;
301     
302     fn = sprintf3("%s/.htrc", path);
303     for(cf = cflist; cf != NULL; cf = cf->next) {
304         if(!strcmp(cf->path, path)) {
305             if(now - cf->lastck > 5) {
306                 cf->lastck = now;
307                 if(stat(fn, &sb) || (sb.st_mtime != cf->mtime))
308                     break;
309             }
310             return(cf);
311         }
312     }
313     ocf = cf;
314     if(access(fn, R_OK) || stat(fn, &sb)) {
315         cf = emptyconfig();
316         mtime = 0;
317     } else {
318         if((cf = readconfig(fn)) == NULL)
319             return(NULL);
320         mtime = sb.st_mtime;
321     }
322     if(ocf != NULL) {
323         mergechildren(cf->children, ocf->children);
324         freeconfig(ocf);
325     }
326     cf->path = sstrdup(path);
327     cf->mtime = mtime;
328     cf->lastck = now;
329     cf->next = cflist;
330     cf->prev = NULL;
331     if(cflist != NULL)
332         cflist->prev = cf;
333     cflist = cf;
334     return(cf);
335 }
336
337 struct config **getconfigs(char *file)
338 {
339     static struct config **ret = NULL;
340     struct {
341         struct config **b;
342         size_t s, d;
343     } buf;
344     struct config *cf;
345     char *tmp, *p;
346     
347     if(ret != NULL)
348         free(ret);
349     bufinit(buf);
350     if(!strncmp(file, "./", 2))
351         file += 2;
352     tmp = sstrdup(file);
353     while(1) {
354         if((p = strrchr(tmp, '/')) == NULL)
355             break;
356         *p = 0;
357         if((cf = getconfig(tmp)) != NULL)
358             bufadd(buf, cf);
359     }
360     free(tmp);
361     if((cf = getconfig(".")) != NULL)
362         bufadd(buf, cf);
363     if(lconfig != NULL)
364         bufadd(buf, lconfig);
365     if(gconfig != NULL)
366         bufadd(buf, gconfig);
367     bufadd(buf, NULL);
368     return(ret = buf.b);
369 }
370
371 struct child *findchild(char *file, char *name, struct config **cf)
372 {
373     int i;
374     struct config **cfs;
375     struct child *ch;
376     
377     if(cf != NULL)
378         *cf = NULL;
379     cfs = getconfigs(file);
380     for(i = 0; cfs[i] != NULL; i++) {
381         if((ch = getchild(cfs[i], name)) != NULL) {
382             if(cf != NULL)
383                 *cf = cfs[i];
384             return(ch);
385         }
386     }
387     if(!strcmp(name, ".notfound"))
388         return(notfound);
389     return(NULL);
390 }
391
392 struct pattern *findmatch(char *file, int trydefault, int type)
393 {
394     int i, o, c;
395     char *bn, *ln;
396     struct config **cfs;
397     struct pattern *pat;
398     struct rule *rule;
399     size_t pl;
400     
401     if((bn = strrchr(file, '/')) != NULL)
402         bn++;
403     else
404         bn = file;
405     cfs = getconfigs(file);
406     for(c = 0; cfs[c] != NULL; c++) {
407         if(cfs[c]->path == NULL) {
408             ln = file;
409         } else {
410             pl = strlen(cfs[c]->path);
411             if((strlen(file) > pl) && !strncmp(file, cfs[c]->path, pl) && (file[pl] == '/'))
412                 ln = file + pl + 1;
413             else
414                 ln = file;      /* This should only happen in the base directory. */
415         }
416         for(pat = cfs[c]->patterns; pat != NULL; pat = pat->next) {
417             if(pat->type != type)
418                 continue;
419             for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
420                 if(rule->type == PAT_BASENAME) {
421                     for(o = 0; rule->patterns[o] != NULL; o++) {
422                         if(!fnmatch(rule->patterns[o], bn, 0))
423                             break;
424                     }
425                     if(rule->patterns[o] == NULL)
426                         break;
427                 } else if(rule->type == PAT_PATHNAME) {
428                     for(o = 0; rule->patterns[o] != NULL; o++) {
429                         if(!fnmatch(rule->patterns[o], ln, FNM_PATHNAME))
430                             break;
431                     }
432                     if(rule->patterns[o] == NULL)
433                         break;
434                 } else if(rule->type == PAT_ALL) {
435                 } else if(rule->type == PAT_DEFAULT) {
436                     if(!trydefault)
437                         break;
438                 } else if(rule->type == PAT_LOCAL) {
439                     if(strchr(ln, '/'))
440                         break;
441                 }
442             }
443             if(!rule)
444                 return(pat);
445         }
446     }
447     if(!trydefault)
448         return(findmatch(file, 1, type));
449     return(NULL);
450 }
451
452 static int donotfound(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
453 {
454     simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
455     return(0);
456 }
457
458 static struct chandler i_notfound = {
459     .handle = donotfound,
460 };
461
462 static struct child s_notfound = {
463     .name = ".notfound",
464     .iface = &i_notfound,
465 };
466 struct child *notfound = &s_notfound;