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