Unquote URL escapes in dirplex elements.
[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         if((tmp = unquoteurl(p)) == NULL) {
498             simpleerror(fd, 400, "Bad Request", "The requested URL contains an invalid escape sequence.");
499             goto fail;
500         }
501         strcpy(p, tmp);
502         free(tmp);
503         
504         if(!*p) {
505             if(p2 == NULL) {
506                 if(stat(path, &sb)) {
507                     flog(LOG_WARNING, "failed to stat previously stated directory %s: %s", path, strerror(errno));
508                     simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
509                     goto fail;
510                 }
511                 break;
512             } else {
513                 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
514                 goto fail;
515             }
516         }
517         if(*p == '.') {
518             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
519             goto fail;
520         }
521         
522         getconfig(path);
523         
524         /*
525          * First, check the name verbatimely:
526          */
527         buf = sprintf3("%s/%s", path, p);
528         if(!stat(buf, &sb)) {
529             if(S_ISDIR(sb.st_mode)) {
530                 tmp = path;
531                 if(!strcmp(path, "."))
532                     path = sstrdup(p);
533                 else
534                     path = sprintf2("%s/%s", path, p);
535                 free(tmp);
536                 if(p2 == NULL) {
537                     stdredir(req, fd, 301, sprintf3("%s/", p));
538                     goto out;
539                 }
540                 if(checkdir(req, fd, path))
541                     break;
542                 goto next;
543             }
544             if(S_ISREG(sb.st_mode)) {
545                 tmp = path;
546                 path = sprintf2("%s/%s", path, p);
547                 free(tmp);
548                 break;
549             }
550             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
551             goto fail;
552         }
553
554         /*
555          * Check the file extensionlessly:
556          */
557         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
558             while((dent = readdir(dir)) != NULL) {
559                 buf = sprintf3("%s/%s", path, dent->d_name);
560                 if((p3 = strchr(dent->d_name, '.')) != NULL)
561                     *p3 = 0;
562                 if(strcmp(dent->d_name, p))
563                     continue;
564                 if(stat(buf, &sb))
565                     continue;
566                 if(!S_ISREG(sb.st_mode))
567                     continue;
568                 tmp = path;
569                 path = sstrdup(buf);
570                 free(tmp);
571                 break;
572             }
573             closedir(dir);
574             if(dent != NULL)
575                 break;
576         }
577         
578         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
579         goto fail;
580         
581     next:
582         if(p2 == NULL)
583             break;
584         p = p2;
585     }
586     if(p2 == NULL)
587         replrest(req, "");
588     else
589         replrest(req, p2);
590     if(!strncmp(path, "./", 2))
591         memmove(path, path + 2, strlen(path + 2) + 1);
592     if(S_ISDIR(sb.st_mode)) {
593         handledir(req, fd, path);
594     } else if(S_ISREG(sb.st_mode)) {
595         handlefile(req, fd, path);
596     } else {
597         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
598         goto fail;
599     }
600     goto out;
601     
602 fail:
603     /* No special handling, for now at least. */
604 out:
605     free(path);
606 }
607
608 static void usage(FILE *out)
609 {
610     fprintf(out, "usage: dirplex [-hN] [-c CONFIG] DIR\n");
611 }
612
613 int main(int argc, char **argv)
614 {
615     int c;
616     int nodef;
617     char *gcf, *lcf;
618     struct hthead *req;
619     int fd;
620     
621     nodef = 0;
622     lcf = NULL;
623     while((c = getopt(argc, argv, "hNc:")) >= 0) {
624         switch(c) {
625         case 'h':
626             usage(stdout);
627             exit(0);
628         case 'N':
629             nodef = 1;
630             break;
631         case 'c':
632             lcf = optarg;
633             break;
634         default:
635             usage(stderr);
636             exit(1);
637         }
638     }
639     if(argc - optind < 1) {
640         usage(stderr);
641         exit(1);
642     }
643     if(!nodef) {
644         if((gcf = findstdconf("ashd/dirplex.rc")) != NULL) {
645             gconfig = readconfig(gcf);
646             free(gcf);
647         }
648     }
649     if(lcf != NULL) {
650         if((lconfig = readconfig(lcf)) == NULL)
651             exit(1);
652     }
653     if(chdir(argv[optind])) {
654         flog(LOG_ERR, "could not change directory to %s: %s", argv[optind], strerror(errno));
655         exit(1);
656     }
657     signal(SIGCHLD, SIG_IGN);
658     while(1) {
659         if((fd = recvreq(0, &req)) < 0) {
660             if(errno != 0)
661                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
662             break;
663         }
664         serve(req, fd);
665         freehthead(req);
666         close(fd);
667     }
668     return(0);
669 }