dirplex: Replaced serve with a recursive-descent implementation.
[ashd.git] / src / dirplex.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 <string.h>
22 #include <unistd.h>
23 #include <errno.h>
24 #include <sys/stat.h>
25 #include <ctype.h>
26 #include <dirent.h>
27 #include <fnmatch.h>
28 #include <time.h>
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 #include <utils.h>
34 #include <mt.h>
35 #include <log.h>
36 #include <req.h>
37 #include <proc.h>
38 #include <resp.h>
39 #include <cf.h>
40
41 #define PAT_BASENAME 0
42 #define PAT_PATHNAME 1
43 #define PAT_ALL 2
44 #define PAT_DEFAULT 3
45
46 #define PT_FILE 0
47 #define PT_DIR 1
48
49 struct config {
50     struct config *next, *prev;
51     char *path;
52     time_t mtime, lastck;
53     struct child *children;
54     struct pattern *patterns;
55     char **index;
56 };
57
58 struct rule {
59     int type;
60     char **patterns;
61 };
62
63 struct pattern {
64     struct pattern *next;
65     int type;
66     char *childnm;
67     char **fchild;
68     struct rule **rules;
69 };
70
71 static struct config *cflist;
72 static struct config *gconfig, *lconfig;
73 static time_t now;
74
75 static void freerule(struct rule *rule)
76 {
77     freeca(rule->patterns);
78     free(rule);
79 }
80
81 static void freepattern(struct pattern *pat)
82 {
83     struct rule **rule;
84     
85     for(rule = pat->rules; *rule; rule++)
86         freerule(*rule);
87     if(pat->childnm != NULL)
88         free(pat->childnm);
89     freeca(pat->fchild);
90     free(pat);
91 }
92
93 static void freeconfig(struct config *cf)
94 {
95     struct child *ch, *nch;
96     struct pattern *pat, *npat;
97     
98     if(cf->prev != NULL)
99         cf->prev->next = cf->next;
100     if(cf->next != NULL)
101         cf->next->prev = cf->prev;
102     if(cf == cflist)
103         cflist = cf->next;
104     if(cf->path != NULL)
105         free(cf->path);
106     for(ch = cf->children; ch != NULL; ch = nch) {
107         nch = ch->next;
108         freechild(ch);
109     }
110     for(pat = cf->patterns; pat != NULL; pat = npat) {
111         npat = pat->next;
112         freepattern(pat);
113     }
114     freeca(cf->index);
115     free(cf);
116 }
117
118 static struct child *getchild(struct config *cf, char *name)
119 {
120     struct child *ch;
121     
122     for(ch = cf->children; ch; ch = ch->next) {
123         if(!strcmp(ch->name, name))
124             break;
125     }
126     return(ch);
127 }
128
129 static struct rule *newrule(struct pattern *pat)
130 {
131     int i;
132     struct rule *rule;
133     
134     for(i = 0; pat->rules[i]; i++);
135     pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2));
136     rule = pat->rules[i] = szmalloc(sizeof(*rule));
137     pat->rules[i + 1] = NULL;
138     return(rule);
139 }
140
141 static struct pattern *newpattern(void)
142 {
143     struct pattern *pat;
144     
145     omalloc(pat);
146     pat->rules = szmalloc(sizeof(*pat->rules));
147     return(pat);
148 }
149
150 static char **cadup(char **w)
151 {
152     char **ret;
153     int i, l;
154     
155     l = calen(w);
156     ret = smalloc(sizeof(*ret) * (l + 1));
157     for(i = 0; i < l; i++)
158         ret[i] = sstrdup(w[i]);
159     ret[i] = NULL;
160     return(ret);
161 }
162
163 static struct pattern *parsepattern(struct cfstate *s)
164 {
165     struct pattern *pat;
166     struct rule *rule;
167     int sl;
168
169     if(!strcmp(s->argv[0], "match")) {
170         s->expstart = 1;
171         pat = newpattern();
172     } else {
173         return(NULL);
174     }
175     
176     if((s->argc > 1) && !strcmp(s->argv[1], "directory"))
177         pat->type = PT_DIR;
178     sl = s->lno;
179     while(1) {
180         getcfline(s);
181         if(!strcmp(s->argv[0], "filename")) {
182             if(s->argc < 2) {
183                 flog(LOG_WARNING, "%s:%i: missing pattern for `filename' match", s->file, s->lno);
184                 continue;
185             }
186             rule = newrule(pat);
187             rule->type = PAT_BASENAME;
188             rule->patterns = cadup(s->argv + 1);
189         } else if(!strcmp(s->argv[0], "pathname")) {
190             if(s->argc < 2) {
191                 flog(LOG_WARNING, "%s:%i: missing pattern for `pathname' match", s->file, s->lno);
192                 continue;
193             }
194             rule = newrule(pat);
195             rule->type = PAT_PATHNAME;
196             rule->patterns = cadup(s->argv + 1);
197         } else if(!strcmp(s->argv[0], "all")) {
198             newrule(pat)->type = PAT_ALL;
199         } else if(!strcmp(s->argv[0], "default")) {
200             newrule(pat)->type = PAT_DEFAULT;
201         } else if(!strcmp(s->argv[0], "handler")) {
202             if(s->argc < 2) {
203                 flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", s->file, s->lno);
204                 continue;
205             }
206             if(pat->childnm != NULL)
207                 free(pat->childnm);
208             pat->childnm = sstrdup(s->argv[1]);
209         } else if(!strcmp(s->argv[0], "fork")) {
210             pat->fchild = cadup(s->argv + 1);
211         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
212             break;
213         } else {
214             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in pattern declaration", s->file, s->lno, s->argv[0]);
215         }
216     }
217     
218     if(pat->rules[0] == NULL) {
219         flog(LOG_WARNING, "%s:%i: missing rules in match declaration", s->file, sl);
220         freepattern(pat);
221         return(NULL);
222     }
223     if((pat->childnm == NULL) && (pat->fchild == NULL)) {
224         flog(LOG_WARNING, "%s:%i: missing handler in match declaration", s->file, sl);
225         freepattern(pat);
226         return(NULL);
227     }
228     return(pat);
229 }
230
231 static struct config *emptyconfig(void)
232 {
233     struct config *cf;
234     
235     omalloc(cf);
236     return(cf);
237 }
238
239 static struct config *readconfig(char *file)
240 {
241     struct cfstate *s;
242     FILE *in;
243     struct config *cf;
244     struct child *child;
245     struct pattern *pat;
246     
247     if((in = fopen(file, "r")) == NULL) {
248         flog(LOG_WARNING, "%s: %s", file, strerror(errno));
249         return(NULL);
250     }
251     s = mkcfparser(in, file);
252     cf = emptyconfig();
253     
254     while(1) {
255         getcfline(s);
256         if((child = parsechild(s)) != NULL) {
257             child->next = cf->children;
258             cf->children = child;
259         } else if((pat = parsepattern(s)) != NULL) {
260             pat->next = cf->patterns;
261             cf->patterns = pat;
262         } else if(!strcmp(s->argv[0], "index-file")) {
263             freeca(cf->index);
264             cf->index = NULL;
265             if(s->argc > 1)
266                 cf->index = cadup(s->argv + 1);
267         } else if(!strcmp(s->argv[0], "eof")) {
268             break;
269         } else {
270             flog(LOG_WARNING, "%s:%i: unknown directive `%s'", s->file, s->lno, s->argv[0]);
271         }
272     }
273     
274     freecfparser(s);
275     fclose(in);
276     return(cf);
277 }
278
279 static struct config *getconfig(char *path)
280 {
281     struct config *cf;
282     struct stat sb;
283     char *fn;
284     time_t mtime;
285     
286     fn = sprintf3("%s/.htrc", path);
287     for(cf = cflist; cf != NULL; cf = cf->next) {
288         if(!strcmp(cf->path, path)) {
289             if(now - cf->lastck > 5) {
290                 if(stat(fn, &sb) || (sb.st_mtime != cf->mtime)) {
291                     freeconfig(cf);
292                     break;
293                 }
294             }
295             cf->lastck = now;
296             return(cf);
297         }
298     }
299     if(access(fn, R_OK) || stat(fn, &sb)) {
300         cf = emptyconfig();
301         mtime = 0;
302     } else {
303         if((cf = readconfig(fn)) == NULL)
304             return(NULL);
305         mtime = sb.st_mtime;
306     }
307     cf->path = sstrdup(path);
308     cf->mtime = mtime;
309     cf->lastck = now;
310     cf->next = cflist;
311     cf->prev = NULL;
312     if(cflist != NULL)
313         cflist->prev = cf;
314     cflist = cf;
315     return(cf);
316 }
317
318 static struct config **getconfigs(char *file)
319 {
320     static struct config **ret = NULL;
321     struct {
322         struct config **b;
323         size_t s, d;
324     } buf;
325     struct config *cf;
326     char *tmp, *p;
327     
328     if(ret != NULL)
329         free(ret);
330     bufinit(buf);
331     tmp = sstrdup(file);
332     while(1) {
333         if((p = strrchr(tmp, '/')) == NULL)
334             break;
335         *p = 0;
336         if((cf = getconfig(tmp)) != NULL)
337             bufadd(buf, cf);
338     }
339     free(tmp);
340     if((cf = getconfig(".")) != NULL)
341         bufadd(buf, cf);
342     if(lconfig != NULL)
343         bufadd(buf, lconfig);
344     if(gconfig != NULL)
345         bufadd(buf, gconfig);
346     bufadd(buf, NULL);
347     return(ret = buf.b);
348 }
349
350 static struct child *findchild(char *file, char *name)
351 {
352     int i;
353     struct config **cfs;
354     struct child *ch;
355     
356     cfs = getconfigs(file);
357     for(i = 0; cfs[i] != NULL; i++) {
358         if((ch = getchild(cfs[i], name)) != NULL)
359             break;
360     }
361     return(ch);
362 }
363
364 static struct pattern *findmatch(char *file, int trydefault, int dir)
365 {
366     int i, o, c;
367     char *bn;
368     struct config **cfs;
369     struct pattern *pat;
370     struct rule *rule;
371     
372     if((bn = strrchr(file, '/')) != NULL)
373         bn++;
374     else
375         bn = file;
376     cfs = getconfigs(file);
377     for(c = 0; cfs[c] != NULL; c++) {
378         for(pat = cfs[c]->patterns; pat != NULL; pat = pat->next) {
379             if(!dir && (pat->type == PT_DIR))
380                 continue;
381             if(dir && (pat->type != PT_DIR))
382                 continue;
383             for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
384                 if(rule->type == PAT_BASENAME) {
385                     for(o = 0; rule->patterns[o] != NULL; o++) {
386                         if(!fnmatch(rule->patterns[o], bn, 0))
387                             break;
388                     }
389                     if(rule->patterns[o] == NULL)
390                         break;
391                 } else if(rule->type == PAT_PATHNAME) {
392                     for(o = 0; rule->patterns[o] != NULL; o++) {
393                         if(!fnmatch(rule->patterns[o], file, FNM_PATHNAME))
394                             break;
395                     }
396                     if(rule->patterns[o] == NULL)
397                         break;
398                 } else if(rule->type == PAT_ALL) {
399                 } else if(rule->type == PAT_DEFAULT) {
400                     if(!trydefault)
401                         break;
402                 }
403             }
404             if(!rule)
405                 return(pat);
406         }
407     }
408     if(!trydefault)
409         return(findmatch(file, 1, dir));
410     return(NULL);
411 }
412
413 static void handle(struct hthead *req, int fd, char *path, struct pattern *pat)
414 {
415     struct child *ch;
416
417     headappheader(req, "X-Ash-File", path);
418     if(pat->fchild) {
419         stdforkserve(pat->fchild, req, fd);
420     } else {
421         if((ch = findchild(path, pat->childnm)) == NULL) {
422             flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
423             simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
424             return;
425         }
426         if(childhandle(ch, req, fd))
427             simpleerror(fd, 500, "Server Error", "The request handler crashed.");
428     }
429 }
430
431 static void handlefile(struct hthead *req, int fd, char *path)
432 {
433     struct pattern *pat;
434
435     if((pat = findmatch(path, 0, 0)) == NULL) {
436         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
437         return;
438     }
439     handle(req, fd, path, pat);
440 }
441
442 static char *findfile(char *path, char *name, struct stat *sb)
443 {
444     DIR *dir;
445     struct stat sbuf;
446     struct dirent *dent;
447     char *p, *fp, *ret;
448     
449     if(sb == NULL)
450         sb = &sbuf;
451     if((dir = opendir(path)) == NULL)
452         return(NULL);
453     ret = NULL;
454     while((dent = readdir(dir)) != NULL) {
455         /* Ignore backup files.
456          * XXX: There is probably a better and more extensible way to
457          * do this. */
458         if(dent->d_name[strlen(dent->d_name) - 1] == '~')
459             continue;
460         if((p = strchr(dent->d_name, '.')) == NULL)
461             continue;
462         if(strncmp(dent->d_name, name, strlen(name)))
463             continue;
464         fp = sprintf3("%s/%s", path, dent->d_name);
465         if(stat(fp, sb))
466             continue;
467         if(!S_ISREG(sb->st_mode))
468             continue;
469         ret = sstrdup(fp);
470         break;
471     }
472     closedir(dir);
473     return(ret);
474 }
475
476 static void handledir(struct hthead *req, int fd, char *path)
477 {
478     struct config **cfs;
479     int i, o;
480     struct stat sb;
481     char *inm, *ipath, *cpath;
482     struct pattern *pat;
483     
484     cpath = sprintf2("%s/", path);
485     cfs = getconfigs(cpath);
486     for(i = 0; cfs[i] != NULL; i++) {
487         if(cfs[i]->index != NULL) {
488             for(o = 0; cfs[i]->index[o] != NULL; o++) {
489                 inm = cfs[i]->index[o];
490                 ipath = sprintf2("%s/%s", path, inm);
491                 if(!stat(ipath, &sb) && S_ISREG(sb.st_mode)) {
492                     handlefile(req, fd, ipath);
493                     free(ipath);
494                     goto out;
495                 }
496                 free(ipath);
497                 
498                 if(!strchr(inm, '.') && ((ipath = findfile(path, inm, NULL)) != NULL)) {
499                     handlefile(req, fd, ipath);
500                     free(ipath);
501                     goto out;
502                 }
503             }
504             break;
505         }
506     }
507     if((pat = findmatch(cpath, 0, 1)) != NULL) {
508         handle(req, fd, cpath, pat);
509         goto out;
510     }
511     simpleerror(fd, 403, "Not Authorized", "Will not send listings for this directory.");
512     
513 out:
514     free(cpath);
515 }
516
517 static int checkpath(struct hthead *req, int fd, char *path, char *rest);
518
519 static int checkentry(struct hthead *req, int fd, char *path, char *rest, char *el)
520 {
521     struct stat sb;
522     char *newpath;
523     int rv;
524     
525     if(!el == '.') {
526         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
527         return(1);
528     }
529     if(!stat(sprintf3("%s/%s", path, el), &sb)) {
530         if(S_ISDIR(sb.st_mode)) {
531             if(!*rest) {
532                 stdredir(req, fd, 301, sprintf3("%s/", el));
533                 return(1);
534             }
535             newpath = sprintf2("%s/%s", path, el);
536             rv = checkpath(req, fd, newpath, rest + 1);
537             free(newpath);
538             return(rv);
539         } else if(S_ISREG(sb.st_mode)) {
540             newpath = sprintf2("%s/%s", path, el);
541             replrest(req, rest);
542             handlefile(req, fd, newpath);
543             free(newpath);
544             return(1);
545         }
546         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
547         return(1);
548     }
549     if(!strchr(el, '.') && ((newpath = findfile(path, el, NULL)) != NULL)) {
550         replrest(req, rest);
551         handlefile(req, fd, newpath);
552         free(newpath);
553         return(1);
554     }
555     return(0);
556 }
557
558 static int checkpath(struct hthead *req, int fd, char *path, char *rest)
559 {
560     struct config *cf;
561     char *p, *el;
562     int rv;
563     
564     el = NULL;
565     rv = 0;
566     
567     if(!strncmp(path, "./", 2))
568         path += 2;
569     cf = getconfig(path);
570     
571     if((p = strchr(rest, '/')) == NULL) {
572         el = unquoteurl(rest);
573         rest = "";
574     } else {
575         char buf[p - rest + 1];
576         memcpy(buf, rest, p - rest);
577         buf[p - rest] = 0;
578         el = unquoteurl(buf);
579         rest = p;
580     }
581     if(el == NULL) {
582         simpleerror(fd, 400, "Bad Request", "The requested URL contains an invalid escape sequence.");
583         rv = 1;
584         goto out;
585     }
586     if(strchr(el, '/') || (!*el && *rest)) {
587         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
588         rv = 1;
589         goto out;
590     }
591     if(!*el) {
592         replrest(req, rest);
593         handledir(req, fd, path);
594         return(1);
595     }
596     rv = checkentry(req, fd, path, rest, el);
597     
598 out:
599     if(el != NULL)
600         free(el);
601     return(rv);
602 }
603
604 static void serve(struct hthead *req, int fd)
605 {
606     now = time(NULL);
607     if(!checkpath(req, fd, ".", req->rest))
608         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
609 }
610
611 static void usage(FILE *out)
612 {
613     fprintf(out, "usage: dirplex [-hN] [-c CONFIG] DIR\n");
614 }
615
616 int main(int argc, char **argv)
617 {
618     int c;
619     int nodef;
620     char *gcf, *lcf, *clcf;
621     struct hthead *req;
622     int fd;
623     
624     nodef = 0;
625     lcf = NULL;
626     while((c = getopt(argc, argv, "hNc:")) >= 0) {
627         switch(c) {
628         case 'h':
629             usage(stdout);
630             exit(0);
631         case 'N':
632             nodef = 1;
633             break;
634         case 'c':
635             lcf = optarg;
636             break;
637         default:
638             usage(stderr);
639             exit(1);
640         }
641     }
642     if(argc - optind < 1) {
643         usage(stderr);
644         exit(1);
645     }
646     if(!nodef) {
647         if((gcf = findstdconf("ashd/dirplex.rc")) != NULL) {
648             gconfig = readconfig(gcf);
649             free(gcf);
650         }
651     }
652     if(lcf != NULL) {
653         if(strchr(lcf, '/') == NULL) {
654             if((clcf = findstdconf(sprintf3("ashd/%s", lcf))) == NULL) {
655                 flog(LOG_ERR, "could not find requested configuration `%s'", lcf);
656                 exit(1);
657             }
658             if((lconfig = readconfig(clcf)) == NULL)
659                 exit(1);
660             free(clcf);
661         } else {
662             if((lconfig = readconfig(lcf)) == NULL)
663                 exit(1);
664         }
665     }
666     if(chdir(argv[optind])) {
667         flog(LOG_ERR, "could not change directory to %s: %s", argv[optind], strerror(errno));
668         exit(1);
669     }
670     signal(SIGCHLD, SIG_IGN);
671     while(1) {
672         if((fd = recvreq(0, &req)) < 0) {
673             if(errno != 0)
674                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
675             break;
676         }
677         serve(req, fd);
678         freehthead(req);
679         close(fd);
680     }
681     return(0);
682 }