Fixed a couple of bugs in 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     cf->prev = NULL;
306     if(cflist != NULL)
307         cflist->prev = cf;
308     cflist = cf;
309     return(cf);
310 }
311
312 static struct config **getconfigs(char *file)
313 {
314     static struct config **ret = NULL;
315     struct {
316         struct config **b;
317         size_t s, d;
318     } buf;
319     struct config *cf;
320     char *tmp, *p;
321     
322     if(ret != NULL)
323         free(ret);
324     bufinit(buf);
325     tmp = sstrdup(file);
326     while(1) {
327         if((p = strrchr(tmp, '/')) == NULL)
328             break;
329         *p = 0;
330         if((cf = getconfig(tmp)) != NULL)
331             bufadd(buf, cf);
332     }
333     free(tmp);
334     if((cf = getconfig(".")) != NULL)
335         bufadd(buf, cf);
336     if(lconfig != NULL)
337         bufadd(buf, lconfig);
338     if(gconfig != NULL)
339         bufadd(buf, gconfig);
340     bufadd(buf, NULL);
341     return(ret = buf.b);
342 }
343
344 static struct child *findchild(char *file, char *name)
345 {
346     int i;
347     struct config **cfs;
348     struct child *ch;
349     
350     cfs = getconfigs(file);
351     for(i = 0; cfs[i] != NULL; i++) {
352         if((ch = getchild(cfs[i], name)) != NULL)
353             break;
354     }
355     return(ch);
356 }
357
358 static struct pattern *findmatch(char *file, int trydefault)
359 {
360     int i, o, c;
361     char *bn;
362     struct config **cfs;
363     struct pattern *pat;
364     struct rule *rule;
365     
366     if((bn = strrchr(file, '/')) != NULL)
367         bn++;
368     else
369         bn = file;
370     cfs = getconfigs(file);
371     for(c = 0; cfs[c] != NULL; c++) {
372         for(pat = cfs[c]->patterns; pat != NULL; pat = pat->next) {
373             for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
374                 if(rule->type == PAT_BASENAME) {
375                     for(o = 0; rule->patterns[o] != NULL; o++) {
376                         if(!fnmatch(rule->patterns[o], bn, 0))
377                             break;
378                     }
379                     if(rule->patterns[o] == NULL)
380                         break;
381                 } else if(rule->type == PAT_PATHNAME) {
382                     for(o = 0; rule->patterns[o] != NULL; o++) {
383                         if(!fnmatch(rule->patterns[o], file, FNM_PATHNAME))
384                             break;
385                     }
386                     if(rule->patterns[o] == NULL)
387                         break;
388                 } else if(rule->type == PAT_ALL) {
389                 } else if(rule->type == PAT_DEFAULT) {
390                     if(!trydefault)
391                         break;
392                 }
393             }
394             if(!rule)
395                 return(pat);
396         }
397     }
398     return(NULL);
399 }
400
401 static void handlefile(struct hthead *req, int fd, char *path)
402 {
403     struct pattern *pat;
404     struct child *ch;
405
406     headappheader(req, "X-Ash-File", path);
407     if(((pat = findmatch(path, 0)) == NULL) && ((pat = findmatch(path, 1)) == NULL)) {
408         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
409         return;
410     }
411     if(pat->fchild) {
412         stdforkserve(pat->fchild, req, fd);
413     } else {
414         if((ch = findchild(path, pat->childnm)) == NULL) {
415             flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
416             simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
417             return;
418         }
419         if(childhandle(ch, req, fd))
420             simpleerror(fd, 500, "Server Error", "The request handler crashed.");
421     }
422 }
423
424 static void handledir(struct hthead *req, int fd, char *path)
425 {
426     struct config **cfs;
427     int i, o;
428     struct stat sb;
429     char *inm, *ipath, *p;
430     DIR *dir;
431     struct dirent *dent;
432     
433     cfs = getconfigs(sprintf3("%s/", path));
434     for(i = 0; cfs[i] != NULL; i++) {
435         if(cfs[i]->index != NULL) {
436             for(o = 0; cfs[i]->index[o] != NULL; o++) {
437                 inm = cfs[i]->index[o];
438                 ipath = sprintf2("%s/%s", path, inm);
439                 if(!stat(ipath, &sb) && S_ISREG(sb.st_mode)) {
440                     handlefile(req, fd, ipath);
441                     free(ipath);
442                     return;
443                 }
444                 free(ipath);
445                 
446                 ipath = NULL;
447                 if(!strchr(inm, '.') && ((dir = opendir(path)) != NULL)) {
448                     while((dent = readdir(dir)) != NULL) {
449                         if((p = strchr(dent->d_name, '.')) == NULL)
450                             continue;
451                         if(strncmp(dent->d_name, inm, strlen(inm)))
452                             continue;
453                         ipath = sprintf2("%s/%s", path, dent->d_name);
454                         if(stat(ipath, &sb) || !S_ISREG(sb.st_mode)) {
455                             free(ipath);
456                             ipath = NULL;
457                             continue;
458                         }
459                         break;
460                     }
461                     closedir(dir);
462                 }
463                 if(ipath != NULL) {
464                     handlefile(req, fd, ipath);
465                     free(ipath);
466                     return;
467                 }
468             }
469             break;
470         }
471     }
472     /* XXX: Directory listings */
473     simpleerror(fd, 403, "Not Authorized", "Will not send listings for this directory.");
474 }
475
476 static int checkdir(struct hthead *req, int fd, char *path)
477 {
478     return(0);
479 }
480
481 static void serve(struct hthead *req, int fd)
482 {
483     char *p, *p2, *path, *tmp, *buf, *p3, *nm;
484     struct stat sb;
485     DIR *dir;
486     struct dirent *dent;
487     
488     now = time(NULL);
489     nm = req->rest;
490     path = sstrdup(".");
491     p = nm;
492     while(1) {
493         if((p2 = strchr(p, '/')) == NULL) {
494         } else {
495             *(p2++) = 0;
496         }
497         
498         if(!*p) {
499             if(p2 == NULL) {
500                 if(stat(path, &sb)) {
501                     flog(LOG_WARNING, "failed to stat previously stated directory %s: %s", path, strerror(errno));
502                     simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
503                     goto fail;
504                 }
505                 break;
506             } else {
507                 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
508                 goto fail;
509             }
510         }
511         if(*p == '.') {
512             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
513             goto fail;
514         }
515         
516         getconfig(path);
517         
518         /*
519          * First, check the name verbatimely:
520          */
521         buf = sprintf3("%s/%s", path, p);
522         if(!stat(buf, &sb)) {
523             if(S_ISDIR(sb.st_mode)) {
524                 tmp = path;
525                 if(!strcmp(path, "."))
526                     path = sstrdup(p);
527                 else
528                     path = sprintf2("%s/%s", path, p);
529                 free(tmp);
530                 if(p2 == NULL) {
531                     stdredir(req, fd, 301, sprintf3("%s/", p));
532                     goto out;
533                 }
534                 if(checkdir(req, fd, path))
535                     break;
536                 goto next;
537             }
538             if(S_ISREG(sb.st_mode)) {
539                 tmp = path;
540                 path = sprintf2("%s/%s", path, p);
541                 free(tmp);
542                 break;
543             }
544             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
545             goto fail;
546         }
547
548         /*
549          * Check the file extensionlessly:
550          */
551         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
552             while((dent = readdir(dir)) != NULL) {
553                 buf = sprintf3("%s/%s", path, dent->d_name);
554                 if((p3 = strchr(dent->d_name, '.')) != NULL)
555                     *p3 = 0;
556                 if(strcmp(dent->d_name, p))
557                     continue;
558                 if(stat(buf, &sb))
559                     continue;
560                 if(!S_ISREG(sb.st_mode))
561                     continue;
562                 tmp = path;
563                 path = sstrdup(buf);
564                 free(tmp);
565                 break;
566             }
567             closedir(dir);
568             if(dent != NULL)
569                 break;
570         }
571         
572         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
573         goto fail;
574         
575     next:
576         if(p2 == NULL)
577             break;
578         p = p2;
579     }
580     if(p2 == NULL)
581         replrest(req, "");
582     else
583         replrest(req, p2);
584     if(!strncmp(path, "./", 2))
585         memmove(path, path + 2, strlen(path + 2) + 1);
586     if(S_ISDIR(sb.st_mode)) {
587         handledir(req, fd, path);
588     } else if(S_ISREG(sb.st_mode)) {
589         handlefile(req, fd, path);
590     } else {
591         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
592         goto fail;
593     }
594     goto out;
595     
596 fail:
597     /* No special handling, for now at least. */
598 out:
599     free(path);
600 }
601
602 static void usage(FILE *out)
603 {
604     fprintf(out, "usage: dirplex [-hN] [-c CONFIG] DIR\n");
605 }
606
607 int main(int argc, char **argv)
608 {
609     int c;
610     int nodef;
611     char *gcf, *lcf;
612     struct hthead *req;
613     int fd;
614     
615     nodef = 0;
616     lcf = NULL;
617     while((c = getopt(argc, argv, "hNc:")) >= 0) {
618         switch(c) {
619         case 'h':
620             usage(stdout);
621             exit(0);
622         case 'N':
623             nodef = 1;
624             break;
625         case 'c':
626             lcf = optarg;
627             break;
628         default:
629             usage(stderr);
630             exit(1);
631         }
632     }
633     if(argc - optind < 1) {
634         usage(stderr);
635         exit(1);
636     }
637     if(!nodef) {
638         if((gcf = findstdconf("ashd/dirplex.rc")) != NULL) {
639             gconfig = readconfig(gcf);
640             free(gcf);
641         }
642     }
643     if(lcf != NULL) {
644         if((lconfig = readconfig(lcf)) == NULL)
645             exit(1);
646     }
647     if(chdir(argv[optind])) {
648         flog(LOG_ERR, "could not change directory to %s: %s", argv[optind], strerror(errno));
649         exit(1);
650     }
651     signal(SIGCHLD, SIG_IGN);
652     while(1) {
653         if((fd = recvreq(0, &req)) < 0) {
654             if(errno != 0)
655                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
656             break;
657         }
658         serve(req, fd);
659         freehthead(req);
660         close(fd);
661     }
662     return(0);
663 }