Send 404 for files with no matches.
[ashd.git] / src / dirplex.c
CommitLineData
992ce9ef
FT
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>
d422fdfd 37#include <resp.h>
06c1a718 38#include <cf.h>
992ce9ef 39
992ce9ef
FT
40#define PAT_BASENAME 0
41#define PAT_PATHNAME 1
42#define PAT_ALL 2
43#define PAT_DEFAULT 3
44
45struct config {
46 struct config *next, *prev;
47 char *path;
48 time_t mtime;
49 struct child *children;
50 struct pattern *patterns;
51};
52
992ce9ef
FT
53struct rule {
54 int type;
55 char *pattern;
56};
57
58struct pattern {
59 struct pattern *next;
60 char *childnm;
61 struct rule **rules;
62};
63
64struct config *cflist;
65
992ce9ef
FT
66static 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
80static 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
992ce9ef
FT
103static 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
114static 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));
cceb3611 121 rule = pat->rules[i] = szmalloc(sizeof(*rule));
992ce9ef
FT
122 pat->rules[i + 1] = NULL;
123 return(rule);
124}
125
126static 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
06c1a718
FT
135static 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
992ce9ef
FT
199static struct config *readconfig(char *path)
200{
06c1a718
FT
201 struct cfstate *s;
202 FILE *in;
992ce9ef 203 struct config *cf;
992ce9ef
FT
204 struct child *child;
205 struct pattern *pat;
992ce9ef 206 struct stat sb;
06c1a718 207 char *p;
992ce9ef 208
cceb3611
FT
209 p = sprintf3("%s/.htrc", path);
210 if(stat(p, &sb))
992ce9ef 211 return(NULL);
06c1a718
FT
212 if((in = fopen(p, "r")) == NULL) {
213 flog(LOG_WARNING, "%s: %s", p, strerror(errno));
992ce9ef 214 return(NULL);
06c1a718
FT
215 }
216 s = mkcfparser(in, p);
992ce9ef
FT
217 omalloc(cf);
218 cf->mtime = sb.st_mtime;
219 cf->path = sstrdup(path);
06c1a718
FT
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]);
992ce9ef 233 }
06c1a718 234 }
992ce9ef 235
06c1a718
FT
236 freecfparser(s);
237 fclose(in);
992ce9ef
FT
238 return(cf);
239}
240
241static 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)) {
cceb3611 248 if(stat(sprintf3("%s/.htrc", path), &sb))
992ce9ef
FT
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
8dce6d19 264static struct config **getconfigs(char *file)
992ce9ef 265{
8dce6d19
FT
266 static struct config **ret = NULL;
267 struct {
268 struct config **b;
269 size_t s, d;
270 } buf;
992ce9ef 271 struct config *cf;
8dce6d19
FT
272 char *tmp, *p;
273
274 if(ret != NULL)
275 free(ret);
276 bufinit(buf);
277 tmp = sstrdup(file);
992ce9ef 278 while(1) {
8dce6d19 279 if((p = strrchr(tmp, '/')) == NULL)
992ce9ef 280 break;
8dce6d19
FT
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
292static 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)
992ce9ef
FT
301 break;
302 }
992ce9ef
FT
303 return(ch);
304}
305
306static struct pattern *findmatch(char *file, int trydefault)
307{
8dce6d19
FT
308 int i, c;
309 char *bn;
310 struct config **cfs;
992ce9ef
FT
311 struct pattern *pat;
312 struct rule *rule;
313
314 if((bn = strrchr(file, '/')) != NULL)
315 bn++;
316 else
317 bn = file;
8dce6d19
FT
318 cfs = getconfigs(file);
319 for(c = 0; cfs[c] != NULL; c++) {
320 for(pat = cfs[c]->patterns; pat != NULL; pat = pat->next) {
992ce9ef
FT
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)
8dce6d19 335 return(pat);
992ce9ef
FT
336 }
337 }
8dce6d19 338 return(NULL);
992ce9ef
FT
339}
340
54cefaba 341static void handlefile(struct hthead *req, int fd, char *path)
992ce9ef 342{
992ce9ef
FT
343 struct pattern *pat;
344 struct child *ch;
54cefaba
FT
345
346 headappheader(req, "X-Ash-File", path);
347 if(((pat = findmatch(path, 0)) == NULL) && ((pat = findmatch(path, 1)) == NULL)) {
d58ed97c 348 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
992ce9ef
FT
349 return;
350 }
54cefaba 351 if((ch = findchild(path, pat->childnm)) == NULL) {
992ce9ef 352 flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
d422fdfd 353 simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
992ce9ef
FT
354 return;
355 }
356
d1b065b5
FT
357 if(childhandle(ch, req, fd))
358 simpleerror(fd, 500, "Server Error", "The request handler crashed.");
54cefaba
FT
359}
360
361static void handledir(struct hthead *req, int fd, char *path)
362{
d422fdfd 363 /* XXX: Todo */
06c1a718 364 simpleerror(fd, 403, "Not Authorized", "Will not send directory listings or indices yet.");
54cefaba
FT
365}
366
367static int checkdir(struct hthead *req, int fd, char *path)
368{
369 return(0);
370}
371
372static 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;
992ce9ef 378
54cefaba
FT
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));
d422fdfd 392 simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
54cefaba
FT
393 goto fail;
394 }
395 break;
396 } else {
d422fdfd 397 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
398 goto fail;
399 }
400 }
d422fdfd
FT
401 if(*p == '.') {
402 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba 403 goto fail;
d422fdfd 404 }
54cefaba
FT
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);
755faed0
FT
420 if(p2 == NULL) {
421 stdredir(req, fd, 301, sprintf3("%s/", p));
422 goto out;
423 }
54cefaba
FT
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 }
d422fdfd 434 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
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
d422fdfd 462 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
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 {
d422fdfd 481 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
482 goto fail;
483 }
484 goto out;
485
486fail:
d422fdfd 487 /* No special handling, for now at least. */
54cefaba
FT
488out:
489 free(path);
992ce9ef
FT
490}
491
492int 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}