Use childhandle() in dirplex too.
[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 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
289 static 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
332 out:
333     free(buf);
334     return(pat);
335 }
336
337 static void handlefile(struct hthead *req, int fd, char *path)
338 {
339     struct pattern *pat;
340     struct child *ch;
341
342     headappheader(req, "X-Ash-File", path);
343     if(((pat = findmatch(path, 0)) == NULL) && ((pat = findmatch(path, 1)) == NULL)) {
344         /* XXX: Send a 500 error? 404? */
345         return;
346     }
347     if((ch = findchild(path, pat->childnm)) == NULL) {
348         flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
349         simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", pat->childnm);
350         return;
351     }
352     
353     if(childhandle(ch, req, fd))
354         simpleerror(fd, 500, "Server Error", "The request handler crashed.");
355 }
356
357 static void handledir(struct hthead *req, int fd, char *path)
358 {
359     /* XXX: Todo */
360     simpleerror(fd, 403, "Not Authorized", "Will not send directory listings or indices yet.");
361 }
362
363 static int checkdir(struct hthead *req, int fd, char *path)
364 {
365     return(0);
366 }
367
368 static 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;
374     
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));
388                     simpleerror(fd, 500, "Internal Server Error", "The server encountered an unexpected condition.");
389                     goto fail;
390                 }
391                 break;
392             } else {
393                 simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
394                 goto fail;
395             }
396         }
397         if(*p == '.') {
398             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
399             goto fail;
400         }
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);
416                 if(checkdir(req, fd, path))
417                     break;
418                 goto next;
419             }
420             if(S_ISREG(sb.st_mode)) {
421                 tmp = path;
422                 path = sprintf2("%s/%s", path, p);
423                 free(tmp);
424                 break;
425             }
426             simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
427             goto fail;
428         }
429
430         /*
431          * Check the file extensionlessly:
432          */
433         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
434             while((dent = readdir(dir)) != NULL) {
435                 buf = sprintf3("%s/%s", path, dent->d_name);
436                 if((p3 = strchr(dent->d_name, '.')) != NULL)
437                     *p3 = 0;
438                 if(strcmp(dent->d_name, p))
439                     continue;
440                 if(stat(buf, &sb))
441                     continue;
442                 if(!S_ISREG(sb.st_mode))
443                     continue;
444                 tmp = path;
445                 path = sstrdup(buf);
446                 free(tmp);
447                 break;
448             }
449             closedir(dir);
450             if(dent != NULL)
451                 break;
452         }
453         
454         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
455         goto fail;
456         
457     next:
458         if(p2 == NULL)
459             break;
460         p = p2;
461     }
462     if(p2 == NULL)
463         replrest(req, "");
464     else
465         replrest(req, p2);
466     if(!strncmp(path, "./", 2))
467         memmove(path, path + 2, strlen(path + 2) + 1);
468     if(S_ISDIR(sb.st_mode)) {
469         handledir(req, fd, path);
470     } else if(S_ISREG(sb.st_mode)) {
471         handlefile(req, fd, path);
472     } else {
473         simpleerror(fd, 404, "Not Found", "The requested URL has no corresponding resource.");
474         goto fail;
475     }
476     goto out;
477     
478 fail:
479     /* No special handling, for now at least. */
480 out:
481     free(path);
482 }
483
484 int main(int argc, char **argv)
485 {
486     struct hthead *req;
487     int fd;
488     
489     if(argc < 2) {
490         flog(LOG_ERR, "usage: dirplex DIR");
491         exit(1);
492     }
493     if(chdir(argv[1])) {
494         flog(LOG_ERR, "could not change directory to %s: %s", argv[1], strerror(errno));
495         exit(1);
496     }
497     signal(SIGCHLD, SIG_IGN);
498     while(1) {
499         if((fd = recvreq(0, &req)) < 0) {
500             if(errno != 0)
501                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
502             break;
503         }
504         serve(req, fd);
505         freehthead(req);
506         close(fd);
507     }
508     return(0);
509 }