Send 404 for files with no matches.
[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
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <utils.h>
33 #include <mt.h>
34 #include <log.h>
35 #include <req.h>
36 #include <proc.h>
37 #include <resp.h>
38 #include <cf.h>
39
40 #define PAT_BASENAME 0
41 #define PAT_PATHNAME 1
42 #define PAT_ALL 2
43 #define PAT_DEFAULT 3
44
45 struct config {
46     struct config *next, *prev;
47     char *path;
48     time_t mtime;
49     struct child *children;
50     struct pattern *patterns;
51 };
52
53 struct rule {
54     int type;
55     char *pattern;
56 };
57
58 struct pattern {
59     struct pattern *next;
60     char *childnm;
61     struct rule **rules;
62 };
63
64 struct config *cflist;
65
66 static void freepattern(struct pattern *pat)
67 {
68     struct rule **rule;
69     
70     for(rule = pat->rules; *rule; rule++) {
71         if((*rule)->pattern != NULL)
72             free((*rule)->pattern);
73         free(*rule);
74     }
75     if(pat->childnm != NULL)
76         free(pat->childnm);
77     free(pat);
78 }
79
80 static void freeconfig(struct config *cf)
81 {
82     struct child *ch, *nch;
83     struct pattern *pat, *npat;
84     
85     if(cf->prev != NULL)
86         cf->prev->next = cf->next;
87     if(cf->next != NULL)
88         cf->next->prev = cf->prev;
89     if(cf == cflist)
90         cflist = cf->next;
91     free(cf->path);
92     for(ch = cf->children; ch != NULL; ch = nch) {
93         nch = ch->next;
94         freechild(ch);
95     }
96     for(pat = cf->patterns; pat != NULL; pat = npat) {
97         npat = pat->next;
98         freepattern(pat);
99     }
100     free(cf);
101 }
102
103 static struct child *getchild(struct config *cf, char *name)
104 {
105     struct child *ch;
106     
107     for(ch = cf->children; ch; ch = ch->next) {
108         if(!strcmp(ch->name, name))
109             break;
110     }
111     return(ch);
112 }
113
114 static struct rule *newrule(struct pattern *pat)
115 {
116     int i;
117     struct rule *rule;
118     
119     for(i = 0; pat->rules[i]; i++);
120     pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2));
121     rule = pat->rules[i] = szmalloc(sizeof(*rule));
122     pat->rules[i + 1] = NULL;
123     return(rule);
124 }
125
126 static struct pattern *newpattern(void)
127 {
128     struct pattern *pat;
129     
130     omalloc(pat);
131     pat->rules = szmalloc(sizeof(*pat->rules));
132     return(pat);
133 }
134
135 static struct pattern *parsepattern(struct cfstate *s)
136 {
137     struct pattern *pat;
138     struct rule *rule;
139     int sl;
140
141     if(!strcmp(s->argv[0], "match")) {
142         s->expstart = 1;
143         pat = newpattern();
144     } else {
145         return(NULL);
146     }
147     
148     sl = s->lno;
149     while(1) {
150         getcfline(s);
151         if(!strcmp(s->argv[0], "filename")) {
152             if(s->argc < 2) {
153                 flog(LOG_WARNING, "%s:%i: missing pattern for `filename' match", s->file, s->lno);
154                 continue;
155             }
156             rule = newrule(pat);
157             rule->type = PAT_BASENAME;
158             rule->pattern = sstrdup(s->argv[1]);
159         } else if(!strcmp(s->argv[0], "pathname")) {
160             if(s->argc < 2) {
161                 flog(LOG_WARNING, "%s:%i: missing pattern for `pathname' match", s->file, s->lno);
162                 continue;
163             }
164             rule = newrule(pat);
165             rule->type = PAT_PATHNAME;
166             rule->pattern = sstrdup(s->argv[1]);
167         } else if(!strcmp(s->argv[0], "all")) {
168             newrule(pat)->type = PAT_ALL;
169         } else if(!strcmp(s->argv[0], "default")) {
170             newrule(pat)->type = PAT_DEFAULT;
171         } else if(!strcmp(s->argv[0], "handler")) {
172             if(s->argc < 2) {
173                 flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", s->file, s->lno);
174                 continue;
175             }
176             if(pat->childnm != NULL)
177                 free(pat->childnm);
178             pat->childnm = sstrdup(s->argv[1]);
179         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
180             break;
181         } else {
182             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in pattern declaration", s->file, s->lno, s->argv[0]);
183         }
184     }
185     
186     if(pat->rules[0] == NULL) {
187         flog(LOG_WARNING, "%s:%i: missing rules in match declaration", s->file, sl);
188         freepattern(pat);
189         return(NULL);
190     }
191     if(pat->childnm == NULL) {
192         flog(LOG_WARNING, "%s:%i: missing handler in match declaration", s->file, sl);
193         freepattern(pat);
194         return(NULL);
195     }
196     return(pat);
197 }
198
199 static struct config *readconfig(char *path)
200 {
201     struct cfstate *s;
202     FILE *in;
203     struct config *cf;
204     struct child *child;
205     struct pattern *pat;
206     struct stat sb;
207     char *p;
208     
209     p = sprintf3("%s/.htrc", path);
210     if(stat(p, &sb))
211         return(NULL);
212     if((in = fopen(p, "r")) == NULL) {
213         flog(LOG_WARNING, "%s: %s", p, strerror(errno));
214         return(NULL);
215     }
216     s = mkcfparser(in, p);
217     omalloc(cf);
218     cf->mtime = sb.st_mtime;
219     cf->path = sstrdup(path);
220     
221     while(1) {
222         getcfline(s);
223         if((child = parsechild(s)) != NULL) {
224             child->next = cf->children;
225             cf->children = child;
226         } else if((pat = parsepattern(s)) != NULL) {
227             pat->next = cf->patterns;
228             cf->patterns = pat;
229         } else if(!strcmp(s->argv[0], "eof")) {
230             break;
231         } else {
232             flog(LOG_WARNING, "%s:%i: unknown directive `%s'", s->file, s->lno, s->argv[0]);
233         }
234     }
235     
236     freecfparser(s);
237     fclose(in);
238     return(cf);
239 }
240
241 static struct config *getconfig(char *path)
242 {
243     struct config *cf;
244     struct stat sb;
245     
246     for(cf = cflist; cf != NULL; cf = cf->next) {
247         if(!strcmp(cf->path, path)) {
248             if(stat(sprintf3("%s/.htrc", path), &sb))
249                 return(NULL);
250             if(sb.st_mtime != cf->mtime) {
251                 freeconfig(cf);
252                 break;
253             }
254             return(cf);
255         }
256     }
257     if((cf = readconfig(path)) != NULL) {
258         cf->next = cflist;
259         cflist = cf;
260     }
261     return(cf);
262 }
263
264 static struct config **getconfigs(char *file)
265 {
266     static struct config **ret = NULL;
267     struct {
268         struct config **b;
269         size_t s, d;
270     } buf;
271     struct config *cf;
272     char *tmp, *p;
273     
274     if(ret != NULL)
275         free(ret);
276     bufinit(buf);
277     tmp = sstrdup(file);
278     while(1) {
279         if((p = strrchr(tmp, '/')) == NULL)
280             break;
281         *p = 0;
282         if((cf = getconfig(tmp)) != NULL)
283             bufadd(buf, cf);
284     }
285     free(tmp);
286     if((cf = getconfig(".")) != NULL)
287         bufadd(buf, cf);
288     bufadd(buf, NULL);
289     return(ret = buf.b);
290 }
291
292 static struct child *findchild(char *file, char *name)
293 {
294     int i;
295     struct config **cfs;
296     struct child *ch;
297     
298     cfs = getconfigs(file);
299     for(i = 0; cfs[i] != NULL; i++) {
300         if((ch = getchild(cfs[i], name)) != NULL)
301             break;
302     }
303     return(ch);
304 }
305
306 static struct pattern *findmatch(char *file, int trydefault)
307 {
308     int i, c;
309     char *bn;
310     struct config **cfs;
311     struct pattern *pat;
312     struct rule *rule;
313     
314     if((bn = strrchr(file, '/')) != NULL)
315         bn++;
316     else
317         bn = file;
318     cfs = getconfigs(file);
319     for(c = 0; cfs[c] != NULL; c++) {
320         for(pat = cfs[c]->patterns; pat != NULL; pat = pat->next) {
321             for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
322                 if(rule->type == PAT_BASENAME) {
323                     if(fnmatch(rule->pattern, bn, 0))
324                         break;
325                 } else if(rule->type == PAT_PATHNAME) {
326                     if(fnmatch(rule->pattern, file, FNM_PATHNAME))
327                         break;
328                 } else if(rule->type == PAT_ALL) {
329                 } else if(rule->type == PAT_DEFAULT) {
330                     if(!trydefault)
331                         break;
332                 }
333             }
334             if(!rule)
335                 return(pat);
336         }
337     }
338     return(NULL);
339 }
340
341 static void handlefile(struct hthead *req, int fd, char *path)
342 {
343     struct pattern *pat;
344     struct child *ch;
345
346     headappheader(req, "X-Ash-File", path);
347     if(((pat = findmatch(path, 0)) == NULL) && ((pat = findmatch(path, 1)) == NULL)) {
348         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
349         return;
350     }
351     if((ch = findchild(path, pat->childnm)) == NULL) {
352         flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
353         simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
354         return;
355     }
356     
357     if(childhandle(ch, req, fd))
358         simpleerror(fd, 500, "Server Error", "The request handler crashed.");
359 }
360
361 static void handledir(struct hthead *req, int fd, char *path)
362 {
363     /* XXX: Todo */
364     simpleerror(fd, 403, "Not Authorized", "Will not send directory listings or indices yet.");
365 }
366
367 static int checkdir(struct hthead *req, int fd, char *path)
368 {
369     return(0);
370 }
371
372 static void serve(struct hthead *req, int fd)
373 {
374     char *p, *p2, *path, *tmp, *buf, *p3, *nm;
375     struct stat sb;
376     DIR *dir;
377     struct dirent *dent;
378     
379     nm = req->rest;
380     path = sstrdup(".");
381     p = nm;
382     while(1) {
383         if((p2 = strchr(p, '/')) == NULL) {
384         } else {
385             *(p2++) = 0;
386         }
387         
388         if(!*p) {
389             if(p2 == NULL) {
390                 if(stat(path, &sb)) {
391                     flog(LOG_WARNING, "failed to stat previously stated directory %s: %s", path, strerror(errno));
392                     simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
393                     goto fail;
394                 }
395                 break;
396             } else {
397                 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
398                 goto fail;
399             }
400         }
401         if(*p == '.') {
402             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
403             goto fail;
404         }
405         
406         getconfig(path);
407         
408         /*
409          * First, check the name verbatimely:
410          */
411         buf = sprintf3("%s/%s", path, p);
412         if(!stat(buf, &sb)) {
413             if(S_ISDIR(sb.st_mode)) {
414                 tmp = path;
415                 if(!strcmp(path, "."))
416                     path = sstrdup(p);
417                 else
418                     path = sprintf2("%s/%s", path, p);
419                 free(tmp);
420                 if(p2 == NULL) {
421                     stdredir(req, fd, 301, sprintf3("%s/", p));
422                     goto out;
423                 }
424                 if(checkdir(req, fd, path))
425                     break;
426                 goto next;
427             }
428             if(S_ISREG(sb.st_mode)) {
429                 tmp = path;
430                 path = sprintf2("%s/%s", path, p);
431                 free(tmp);
432                 break;
433             }
434             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
435             goto fail;
436         }
437
438         /*
439          * Check the file extensionlessly:
440          */
441         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
442             while((dent = readdir(dir)) != NULL) {
443                 buf = sprintf3("%s/%s", path, dent->d_name);
444                 if((p3 = strchr(dent->d_name, '.')) != NULL)
445                     *p3 = 0;
446                 if(strcmp(dent->d_name, p))
447                     continue;
448                 if(stat(buf, &sb))
449                     continue;
450                 if(!S_ISREG(sb.st_mode))
451                     continue;
452                 tmp = path;
453                 path = sstrdup(buf);
454                 free(tmp);
455                 break;
456             }
457             closedir(dir);
458             if(dent != NULL)
459                 break;
460         }
461         
462         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
463         goto fail;
464         
465     next:
466         if(p2 == NULL)
467             break;
468         p = p2;
469     }
470     if(p2 == NULL)
471         replrest(req, "");
472     else
473         replrest(req, p2);
474     if(!strncmp(path, "./", 2))
475         memmove(path, path + 2, strlen(path + 2) + 1);
476     if(S_ISDIR(sb.st_mode)) {
477         handledir(req, fd, path);
478     } else if(S_ISREG(sb.st_mode)) {
479         handlefile(req, fd, path);
480     } else {
481         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
482         goto fail;
483     }
484     goto out;
485     
486 fail:
487     /* No special handling, for now at least. */
488 out:
489     free(path);
490 }
491
492 int main(int argc, char **argv)
493 {
494     struct hthead *req;
495     int fd;
496     
497     if(argc < 2) {
498         flog(LOG_ERR, "usage: dirplex DIR");
499         exit(1);
500     }
501     if(chdir(argv[1])) {
502         flog(LOG_ERR, "could not change directory to %s: %s", argv[1], strerror(errno));
503         exit(1);
504     }
505     signal(SIGCHLD, SIG_IGN);
506     while(1) {
507         if((fd = recvreq(0, &req)) < 0) {
508             if(errno != 0)
509                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
510             break;
511         }
512         serve(req, fd);
513         freehthead(req);
514         close(fd);
515     }
516     return(0);
517 }