dirplex: Made it easier to include configuration "modules".
[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 void handledir(struct hthead *req, int fd, char *path)
443 {
444     struct config **cfs;
445     int i, o;
446     struct stat sb;
447     char *inm, *ipath, *p;
448     DIR *dir;
449     struct dirent *dent;
450     struct pattern *pat;
451     
452     path = sprintf2("%s/", path);
453     cfs = getconfigs(path);
454     for(i = 0; cfs[i] != NULL; i++) {
455         if(cfs[i]->index != NULL) {
456             for(o = 0; cfs[i]->index[o] != NULL; o++) {
457                 inm = cfs[i]->index[o];
458                 ipath = sprintf2("%s/%s", path, inm);
459                 if(!stat(ipath, &sb) && S_ISREG(sb.st_mode)) {
460                     handlefile(req, fd, ipath);
461                     free(ipath);
462                     goto out;
463                 }
464                 free(ipath);
465                 
466                 ipath = NULL;
467                 if(!strchr(inm, '.') && ((dir = opendir(path)) != NULL)) {
468                     while((dent = readdir(dir)) != NULL) {
469                         if((p = strchr(dent->d_name, '.')) == NULL)
470                             continue;
471                         if(strncmp(dent->d_name, inm, strlen(inm)))
472                             continue;
473                         ipath = sprintf2("%s/%s", path, dent->d_name);
474                         if(stat(ipath, &sb) || !S_ISREG(sb.st_mode)) {
475                             free(ipath);
476                             ipath = NULL;
477                             continue;
478                         }
479                         break;
480                     }
481                     closedir(dir);
482                 }
483                 if(ipath != NULL) {
484                     handlefile(req, fd, ipath);
485                     free(ipath);
486                     goto out;
487                 }
488             }
489             break;
490         }
491     }
492     if((pat = findmatch(path, 0, 1)) != NULL) {
493         handle(req, fd, path, pat);
494         goto out;
495     }
496     simpleerror(fd, 403, "Not Authorized", "Will not send listings for this directory.");
497     
498 out:
499     free(path);
500 }
501
502 static int checkdir(struct hthead *req, int fd, char *path)
503 {
504     return(0);
505 }
506
507 static void serve(struct hthead *req, int fd)
508 {
509     char *p, *p2, *path, *tmp, *buf, *p3, *nm;
510     struct stat sb;
511     DIR *dir;
512     struct dirent *dent;
513     
514     now = time(NULL);
515     nm = req->rest;
516     path = sstrdup(".");
517     p = nm;
518     while(1) {
519         if((p2 = strchr(p, '/')) == NULL) {
520         } else {
521             *(p2++) = 0;
522         }
523         if((tmp = unquoteurl(p)) == NULL) {
524             simpleerror(fd, 400, "Bad Request", "The requested URL contains an invalid escape sequence.");
525             goto fail;
526         }
527         strcpy(p, tmp);
528         free(tmp);
529         
530         if(!*p) {
531             if(p2 == NULL) {
532                 if(stat(path, &sb)) {
533                     flog(LOG_WARNING, "failed to stat previously stated directory %s: %s", path, strerror(errno));
534                     simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
535                     goto fail;
536                 }
537                 break;
538             } else {
539                 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
540                 goto fail;
541             }
542         }
543         if(*p == '.') {
544             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
545             goto fail;
546         }
547         
548         getconfig(path);
549         
550         /*
551          * First, check the name verbatimely:
552          */
553         buf = sprintf3("%s/%s", path, p);
554         if(!stat(buf, &sb)) {
555             if(S_ISDIR(sb.st_mode)) {
556                 tmp = path;
557                 if(!strcmp(path, "."))
558                     path = sstrdup(p);
559                 else
560                     path = sprintf2("%s/%s", path, p);
561                 free(tmp);
562                 if(p2 == NULL) {
563                     stdredir(req, fd, 301, sprintf3("%s/", p));
564                     goto out;
565                 }
566                 if(checkdir(req, fd, path))
567                     break;
568                 goto next;
569             }
570             if(S_ISREG(sb.st_mode)) {
571                 tmp = path;
572                 path = sprintf2("%s/%s", path, p);
573                 free(tmp);
574                 break;
575             }
576             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
577             goto fail;
578         }
579
580         /*
581          * Check the file extensionlessly:
582          */
583         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
584             while((dent = readdir(dir)) != NULL) {
585                 buf = sprintf3("%s/%s", path, dent->d_name);
586                 if((p3 = strchr(dent->d_name, '.')) != NULL)
587                     *p3 = 0;
588                 if(strcmp(dent->d_name, p))
589                     continue;
590                 if(stat(buf, &sb))
591                     continue;
592                 if(!S_ISREG(sb.st_mode))
593                     continue;
594                 tmp = path;
595                 path = sstrdup(buf);
596                 free(tmp);
597                 break;
598             }
599             closedir(dir);
600             if(dent != NULL)
601                 break;
602         }
603         
604         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
605         goto fail;
606         
607     next:
608         if(p2 == NULL)
609             break;
610         p = p2;
611     }
612     if(p2 == NULL)
613         replrest(req, "");
614     else
615         replrest(req, p2);
616     if(!strncmp(path, "./", 2))
617         memmove(path, path + 2, strlen(path + 2) + 1);
618     if(S_ISDIR(sb.st_mode)) {
619         handledir(req, fd, path);
620     } else if(S_ISREG(sb.st_mode)) {
621         handlefile(req, fd, path);
622     } else {
623         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
624         goto fail;
625     }
626     goto out;
627     
628 fail:
629     /* No special handling, for now at least. */
630 out:
631     free(path);
632 }
633
634 static void usage(FILE *out)
635 {
636     fprintf(out, "usage: dirplex [-hN] [-c CONFIG] DIR\n");
637 }
638
639 int main(int argc, char **argv)
640 {
641     int c;
642     int nodef;
643     char *gcf, *lcf, *clcf;
644     struct hthead *req;
645     int fd;
646     
647     nodef = 0;
648     lcf = NULL;
649     while((c = getopt(argc, argv, "hNc:")) >= 0) {
650         switch(c) {
651         case 'h':
652             usage(stdout);
653             exit(0);
654         case 'N':
655             nodef = 1;
656             break;
657         case 'c':
658             lcf = optarg;
659             break;
660         default:
661             usage(stderr);
662             exit(1);
663         }
664     }
665     if(argc - optind < 1) {
666         usage(stderr);
667         exit(1);
668     }
669     if(!nodef) {
670         if((gcf = findstdconf("ashd/dirplex.rc")) != NULL) {
671             gconfig = readconfig(gcf);
672             free(gcf);
673         }
674     }
675     if(lcf != NULL) {
676         if(strchr(lcf, '/') == NULL) {
677             if((clcf = findstdconf(sprintf3("ashd/%s", lcf))) == NULL) {
678                 flog(LOG_ERR, "could not find requested configuration `%s'", lcf);
679                 exit(1);
680             }
681             if((lconfig = readconfig(clcf)) == NULL)
682                 exit(1);
683             free(clcf);
684         } else {
685             if((lconfig = readconfig(lcf)) == NULL)
686                 exit(1);
687         }
688     }
689     if(chdir(argv[optind])) {
690         flog(LOG_ERR, "could not change directory to %s: %s", argv[optind], strerror(errno));
691         exit(1);
692     }
693     signal(SIGCHLD, SIG_IGN);
694     while(1) {
695         if((fd = recvreq(0, &req)) < 0) {
696             if(errno != 0)
697                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
698             break;
699         }
700         serve(req, fd);
701         freehthead(req);
702         close(fd);
703     }
704     return(0);
705 }