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