Added a first version of the directory multiplexer.
[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, char *nm)
351 {
352     char *p, *p2, *path, *ftmp, *buf, *rv, *p3;
353     struct stat sb;
354     DIR *dir;
355     struct dirent *dent;
356     
357     path = sstrdup(".");
358     p = nm;
359     rv = NULL;
360     while(1) {
361         if((p2 = strchr(p, '/')) == NULL) {
362         } else {
363             *(p2++) = 0;
364         }
365         
366         if(!*p) {
367             if(p2 == NULL) {
368                 rv = sstrdup(path);
369                 break;
370             } else {
371                 goto fail;
372             }
373         }
374         if(*p == '.')
375             goto fail;
376         
377         getconfig(path);
378         
379         /*
380          * First, check the name verbatimely:
381          */
382         buf = sprintf3("%s/%s", path, p);
383         if(!stat(buf, &sb)) {
384             if(S_ISDIR(sb.st_mode)) {
385                 ftmp = path;
386                 if(!strcmp(path, "."))
387                     path = sstrdup(p);
388                 else
389                     path = sprintf2("%s/%s", path, p);
390                 free(ftmp);
391                 goto next;
392             }
393             if(S_ISREG(sb.st_mode)) {
394                 rv = sstrdup(buf);
395                 break;
396             }
397             goto fail;
398         }
399
400         /*
401          * Check the file extensionlessly:
402          */
403         if(!strchr(p, '.') && ((dir = opendir(path)) != NULL)) {
404             while((dent = readdir(dir)) != NULL) {
405                 buf = sprintf3("%s/%s", path, dent->d_name);
406                 if((p3 = strchr(dent->d_name, '.')) != NULL)
407                     *p3 = 0;
408                 if(strcmp(dent->d_name, p))
409                     continue;
410                 if(stat(buf, &sb))
411                     continue;
412                 if(!S_ISREG(sb.st_mode))
413                     continue;
414                 rv = sstrdup(buf);
415                 break;
416             }
417             closedir(dir);
418             if(dent != NULL)
419                 break;
420         }
421         
422         goto fail;
423         
424     next:
425         if(p2 == NULL)
426             break;
427         p = p2;
428     }
429     if(!strncmp(rv, "./", 2))
430         memmove(rv, rv + 2, strlen(rv + 2) + 1);
431     goto out;
432     
433 fail:
434     rv = NULL;
435 out:
436     free(path);
437     return(rv);
438 }
439
440 static struct child *findchild(char *file, char *name)
441 {
442     char *buf, *p;
443     struct config *cf;
444     struct child *ch;
445
446     buf = sstrdup(file);
447     while(1) {
448         ch = NULL;
449         if(!strcmp(buf, "."))
450             break;
451         if((p = strrchr(buf, '/')) != NULL)
452             *p = 0;
453         else
454             strcpy(buf, ".");
455         cf = getconfig(buf);
456         if(cf == NULL)
457             continue;
458         if((ch = getchild(cf, name)) != NULL)
459             break;
460     }
461     free(buf);
462     return(ch);
463 }
464
465 static struct pattern *findmatch(char *file, int trydefault)
466 {
467     int i;
468     char *buf, *p, *bn;
469     struct config *cf;
470     struct pattern *pat;
471     struct rule *rule;
472     
473     if((bn = strrchr(file, '/')) != NULL)
474         bn++;
475     else
476         bn = file;
477     buf = sstrdup(file);
478     while(1) {
479         pat = NULL;
480         if(!strcmp(buf, "."))
481             break;
482         if((p = strrchr(buf, '/')) != NULL)
483             *p = 0;
484         else
485             strcpy(buf, ".");
486         cf = getconfig(buf);
487         if(cf == NULL)
488             continue;
489         for(pat = cf->patterns; pat != NULL; pat = pat->next) {
490             for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
491                 if(rule->type == PAT_BASENAME) {
492                     if(fnmatch(rule->pattern, bn, 0))
493                         break;
494                 } else if(rule->type == PAT_PATHNAME) {
495                     if(fnmatch(rule->pattern, file, FNM_PATHNAME))
496                         break;
497                 } else if(rule->type == PAT_ALL) {
498                 } else if(rule->type == PAT_DEFAULT) {
499                     if(!trydefault)
500                         break;
501                 }
502             }
503             if(!rule)
504                 goto out;
505         }
506     }
507
508 out:
509     free(buf);
510     return(pat);
511 }
512
513 static void forkchild(struct child *ch)
514 {
515     ch->fd = stdmkchild(ch->argv);
516 }
517
518 static void passreq(struct child *ch, struct hthead *req, int fd)
519 {
520     if(ch->fd < 0)
521         forkchild(ch);
522     if(sendreq(ch->fd, req, fd)) {
523         if(errno == EPIPE) {
524             /* Assume that the child has crashed and restart it. */
525             forkchild(ch);
526             if(!sendreq(ch->fd, req, fd))
527                 return;
528         }
529         flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(errno));
530         close(ch->fd);
531         ch->fd = -1;
532     }
533 }
534
535 static void serve(struct hthead *req, int fd)
536 {
537     char *file;
538     struct stat sb;
539     struct pattern *pat;
540     struct child *ch;
541     
542     if((file = findfile(req, req->rest)) == NULL) {
543         /* XXX: Do 404 handling */
544         return;
545     }
546     replrest(req, file);
547     if(stat(file, &sb)) {
548         flog(LOG_ERR, "could not stat previously found file %s: %s", file, strerror(errno));
549         free(file);
550         return;
551     }
552     if(!S_ISREG(sb.st_mode)) {
553         /* XXX: Handle default files or similar stuff. */
554         free(file);
555         return;
556     }
557     if(((pat = findmatch(file, 0)) == NULL) && ((pat = findmatch(file, 1)) == NULL)) {
558         /* XXX: Send a 500 error? 404? */
559         free(file);
560         return;
561     }
562     if((ch = findchild(file, pat->childnm)) == NULL) {
563         /* XXX: Send a 500 error. */
564         flog(LOG_ERR, "child %s requested, but was not declared", pat->childnm);
565         free(file);
566         return;
567     }
568     
569     if(ch->type == CH_SOCKET) {
570         passreq(ch, req, fd);
571     } else if(ch->type == CH_FORK) {
572         stdforkserve(ch->argv, req, fd);
573     }
574     
575     free(file);
576 }
577
578 int main(int argc, char **argv)
579 {
580     struct hthead *req;
581     int fd;
582     
583     if(argc < 2) {
584         flog(LOG_ERR, "usage: dirplex DIR");
585         exit(1);
586     }
587     if(chdir(argv[1])) {
588         flog(LOG_ERR, "could not change directory to %s: %s", argv[1], strerror(errno));
589         exit(1);
590     }
591     signal(SIGCHLD, SIG_IGN);
592     while(1) {
593         if((fd = recvreq(0, &req)) < 0) {
594             if(errno != 0)
595                 flog(LOG_ERR, "recvreq: %s", strerror(errno));
596             break;
597         }
598         serve(req, fd);
599         freehthead(req);
600         close(fd);
601     }
602     return(0);
603 }