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