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