Made non-absolute included filenames relative to the containing config file.
[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
992ce9ef
FT
264static struct child *findchild(char *file, char *name)
265{
266 char *buf, *p;
267 struct config *cf;
268 struct child *ch;
269
270 buf = sstrdup(file);
271 while(1) {
272 ch = NULL;
273 if(!strcmp(buf, "."))
274 break;
275 if((p = strrchr(buf, '/')) != NULL)
276 *p = 0;
277 else
278 strcpy(buf, ".");
279 cf = getconfig(buf);
280 if(cf == NULL)
281 continue;
282 if((ch = getchild(cf, name)) != NULL)
283 break;
284 }
285 free(buf);
286 return(ch);
287}
288
289static struct pattern *findmatch(char *file, int trydefault)
290{
291 int i;
292 char *buf, *p, *bn;
293 struct config *cf;
294 struct pattern *pat;
295 struct rule *rule;
296
297 if((bn = strrchr(file, '/')) != NULL)
298 bn++;
299 else
300 bn = file;
301 buf = sstrdup(file);
302 while(1) {
303 pat = NULL;
304 if(!strcmp(buf, "."))
305 break;
306 if((p = strrchr(buf, '/')) != NULL)
307 *p = 0;
308 else
309 strcpy(buf, ".");
310 cf = getconfig(buf);
311 if(cf == NULL)
312 continue;
313 for(pat = cf->patterns; pat != NULL; pat = pat->next) {
314 for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
315 if(rule->type == PAT_BASENAME) {
316 if(fnmatch(rule->pattern, bn, 0))
317 break;
318 } else if(rule->type == PAT_PATHNAME) {
319 if(fnmatch(rule->pattern, file, FNM_PATHNAME))
320 break;
321 } else if(rule->type == PAT_ALL) {
322 } else if(rule->type == PAT_DEFAULT) {
323 if(!trydefault)
324 break;
325 }
326 }
327 if(!rule)
328 goto out;
329 }
330 }
331
332out:
333 free(buf);
334 return(pat);
335}
336
54cefaba 337static void handlefile(struct hthead *req, int fd, char *path)
992ce9ef 338{
992ce9ef
FT
339 struct pattern *pat;
340 struct child *ch;
54cefaba
FT
341
342 headappheader(req, "X-Ash-File", path);
343 if(((pat = findmatch(path, 0)) == NULL) && ((pat = findmatch(path, 1)) == NULL)) {
992ce9ef 344 /* XXX: Send a 500 error? 404? */
992ce9ef
FT
345 return;
346 }
54cefaba 347 if((ch = findchild(path, pat->childnm)) == NULL) {
992ce9ef 348 flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
d422fdfd 349 simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
992ce9ef
FT
350 return;
351 }
352
d1b065b5
FT
353 if(childhandle(ch, req, fd))
354 simpleerror(fd, 500, "Server Error", "The request handler crashed.");
54cefaba
FT
355}
356
357static void handledir(struct hthead *req, int fd, char *path)
358{
d422fdfd 359 /* XXX: Todo */
06c1a718 360 simpleerror(fd, 403, "Not Authorized", "Will not send directory listings or indices yet.");
54cefaba
FT
361}
362
363static int checkdir(struct hthead *req, int fd, char *path)
364{
365 return(0);
366}
367
368static void serve(struct hthead *req, int fd)
369{
370 char *p, *p2, *path, *tmp, *buf, *p3, *nm;
371 struct stat sb;
372 DIR *dir;
373 struct dirent *dent;
992ce9ef 374
54cefaba
FT
375 nm = req->rest;
376 path = sstrdup(".");
377 p = nm;
378 while(1) {
379 if((p2 = strchr(p, '/')) == NULL) {
380 } else {
381 *(p2++) = 0;
382 }
383
384 if(!*p) {
385 if(p2 == NULL) {
386 if(stat(path, &sb)) {
387 flog(LOG_WARNING, "failed to stat previously stated directory %s: %s", path, strerror(errno));
d422fdfd 388 simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
54cefaba
FT
389 goto fail;
390 }
391 break;
392 } else {
d422fdfd 393 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
394 goto fail;
395 }
396 }
d422fdfd
FT
397 if(*p == '.') {
398 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba 399 goto fail;
d422fdfd 400 }
54cefaba
FT
401
402 getconfig(path);
403
404 /*
405 * First, check the name verbatimely:
406 */
407 buf = sprintf3("%s/%s", path, p);
408 if(!stat(buf, &sb)) {
409 if(S_ISDIR(sb.st_mode)) {
410 tmp = path;
411 if(!strcmp(path, "."))
412 path = sstrdup(p);
413 else
414 path = sprintf2("%s/%s", path, p);
415 free(tmp);
755faed0
FT
416 if(p2 == NULL) {
417 stdredir(req, fd, 301, sprintf3("%s/", p));
418 goto out;
419 }
54cefaba
FT
420 if(checkdir(req, fd, path))
421 break;
422 goto next;
423 }
424 if(S_ISREG(sb.st_mode)) {
425 tmp = path;
426 path = sprintf2("%s/%s", path, p);
427 free(tmp);
428 break;
429 }
d422fdfd 430 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
431 goto fail;
432 }
433
434 /*
435 * Check the file extensionlessly:
436 */
437 if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
438 while((dent = readdir(dir)) != NULL) {
439 buf = sprintf3("%s/%s", path, dent->d_name);
440 if((p3 = strchr(dent->d_name, '.')) != NULL)
441 *p3 = 0;
442 if(strcmp(dent->d_name, p))
443 continue;
444 if(stat(buf, &sb))
445 continue;
446 if(!S_ISREG(sb.st_mode))
447 continue;
448 tmp = path;
449 path = sstrdup(buf);
450 free(tmp);
451 break;
452 }
453 closedir(dir);
454 if(dent != NULL)
455 break;
456 }
457
d422fdfd 458 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
459 goto fail;
460
461 next:
462 if(p2 == NULL)
463 break;
464 p = p2;
465 }
466 if(p2 == NULL)
467 replrest(req, "");
468 else
469 replrest(req, p2);
470 if(!strncmp(path, "./", 2))
471 memmove(path, path + 2, strlen(path + 2) + 1);
472 if(S_ISDIR(sb.st_mode)) {
473 handledir(req, fd, path);
474 } else if(S_ISREG(sb.st_mode)) {
475 handlefile(req, fd, path);
476 } else {
d422fdfd 477 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
54cefaba
FT
478 goto fail;
479 }
480 goto out;
481
482fail:
d422fdfd 483 /* No special handling, for now at least. */
54cefaba
FT
484out:
485 free(path);
992ce9ef
FT
486}
487
488int main(int argc, char **argv)
489{
490 struct hthead *req;
491 int fd;
492
493 if(argc < 2) {
494 flog(LOG_ERR, "usage: dirplex DIR");
495 exit(1);
496 }
497 if(chdir(argv[1])) {
498 flog(LOG_ERR, "could not change directory to %s: %s", argv[1], strerror(errno));
499 exit(1);
500 }
501 signal(SIGCHLD, SIG_IGN);
502 while(1) {
503 if((fd = recvreq(0, &req)) < 0) {
504 if(errno != 0)
505 flog(LOG_ERR, "recvreq: %s", strerror(errno));
506 break;
507 }
508 serve(req, fd);
509 freehthead(req);
510 close(fd);
511 }
512 return(0);
513}