9d3fb766c2b44babe008f0e3448eb9fb63d1cd88
[ashd.git] / lib / cf.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 <unistd.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <glob.h>
25 #include <libgen.h>
26 #include <sys/socket.h>
27 #include <time.h>
28 #include <errno.h>
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 #include <utils.h>
34 #include <cf.h>
35 #include <mt.h>
36 #include <proc.h>
37 #include <log.h>
38
39 #define CH_SOCKET 0
40 #define CH_FORK 1
41
42 struct stdchild {
43     int type;
44     char **argv;
45     char **envp;
46     int fd;
47     int agains;
48     time_t lastrep;
49 };
50
51 static int parsefile(struct cfstate *s, FILE *in);
52 static void stdmerge(struct child *old, struct child *new);
53 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
54 static void stddestroy(struct child *ch);
55
56 static int doinclude(struct cfstate *s, char *spec)
57 {
58     int rv, i;
59     FILE *inc;
60     glob_t globm;
61     char *fbk, *dir, *fspec;
62
63     rv = 0;
64     fbk = s->file;
65     if(spec[0] == '/') {
66         fspec = spec;
67     } else {
68         dir = sstrdup(fbk);
69         fspec = sprintf3("%s/%s", dirname(dir), spec);
70         free(dir);
71     }
72     if(glob(fspec, 0, NULL, &globm))
73         return(0);
74     for(i = 0; i < globm.gl_pathc; i++) {
75         if((inc = fopen(globm.gl_pathv[i], "r")) != NULL) {
76             s->file = globm.gl_pathv[i];
77             if(parsefile(s, inc)) {
78                 fclose(inc);
79                 rv = 1;
80                 goto out;
81             }
82             fclose(inc);
83             inc = NULL;
84         }
85     }
86     
87 out:
88     globfree(&globm);
89     s->file = fbk;
90     return(rv);
91 }
92
93 static int parsefile(struct cfstate *s, FILE *in)
94 {
95     int i;
96     char line[1024];
97     int eof, argc;
98     int ind, indst[80], indl;
99     char *p, **w;
100     
101     s->lno = 0;
102     indst[indl = 0] = 0;
103     eof = 0;
104     while(1) {
105         if(fgets(line, sizeof(line), in) == NULL) {
106             eof = 1;
107             line[0] = 0;
108         }
109         s->lno++;
110         if(line[0]) {
111             for(p = line + strlen(line) - 1; p >= line; p--) {
112                 if(isspace(*p))
113                     *p = 0;
114                 else
115                     break;
116             }
117         }
118         for(ind = 0, p = line; *p; p++) {
119             if(*p == ' ') {
120                 ind++;
121             } else if(*p == '\t') {
122                 ind = ind - (ind % 8) + 8;
123             } else {
124                 break;
125             }
126         }
127         if(!eof && (!*p || (*p == '#')))
128             continue;
129         
130     reindent:
131         if(ind > indst[indl]) {
132             indst[++indl] = ind;
133             if(!s->expstart) {
134                 s->res = tokenize("start");
135                 if(yield())
136                     return(1);
137             } else {
138                 s->expstart = 0;
139             }
140         } else {
141             if(s->expstart) {
142                 s->res = tokenize("end");
143                 if(yield())
144                     return(1);
145                 s->expstart = 0;
146             }
147             while(ind < indst[indl]) {
148                 indl--;
149                 s->res = tokenize("end");
150                 if(yield())
151                     return(1);
152             }
153             if(ind > indst[indl]) {
154                 flog(LOG_WARNING, "%s:%i: unexpected indentation level", s->file, s->lno);
155                 goto reindent;
156             }
157         }
158         
159         if(eof)
160             return(0);
161         
162         argc = calen(w = tokenize(line));
163         if(argc < 1) {
164             /* Shouldn't happen, but... */
165             freeca(w);
166             continue;
167         }
168         
169         if(indl == 0) {
170             if(!strcmp(w[0], "include")) {
171                 for(i = 1; i < argc; i++) {
172                     if(doinclude(s, w[i])) {
173                         freeca(w);
174                         return(1);
175                     }
176                 }
177                 freeca(w);
178                 continue;
179             }
180         }
181         
182         if(!strcmp(w[0], "start") ||
183            !strcmp(w[0], "end") || 
184            !strcmp(w[0], "eof")) {
185             flog(LOG_WARNING, "%s:%i: illegal directive: %s", s->file, s->lno, w[0]);
186         } else {
187             s->res = w;
188             if(yield())
189                 return(1);
190         }
191     }
192 }
193
194 static void parsefn(struct muth *mt, va_list args)
195 {
196     vavar(struct cfstate *, s);
197     vavar(FILE *, in);
198     vavar(char *, file);
199     
200     s->file = sstrdup(file);
201     if(parsefile(s, in))
202         goto out;
203     do {
204         s->res = tokenize("eof");
205     } while(!yield());
206     
207 out:
208     free(s->file);
209 }
210
211 char **getcfline(struct cfstate *s)
212 {
213     freeca(s->argv);
214     if(s->res == NULL)
215         resume(s->pf, 0);
216     s->argc = calen(s->argv = s->res);
217     s->res = NULL;
218     return(s->argv);
219 }
220
221 struct cfstate *mkcfparser(FILE *in, char *name)
222 {
223     struct cfstate *s;
224     
225     omalloc(s);
226     s->pf = mustart(parsefn, s, in, name);
227     return(s);
228 }
229
230 void freecfparser(struct cfstate *s)
231 {
232     resume(s->pf, -1);
233     freeca(s->argv);
234     freeca(s->res);
235     free(s);
236 }
237
238 char *findstdconf(char *name)
239 {
240     char *home, *path, *p, *p2, *t;
241     
242     if((home = getenv("HOME")) != NULL) {
243         if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
244             return(t);
245         free(t);
246     }
247     if((path = getenv("PATH")) != NULL) {
248         path = sstrdup(path);
249         for(p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
250             if((p2 = strrchr(p, '/')) == NULL)
251                 continue;
252             *p2 = 0;
253             if(!access(t = sprintf2("%s/etc/%s", p, name), R_OK)) {
254                 free(path);
255                 return(t);
256             }
257             free(t);
258         }
259         free(path);
260     }
261     return(NULL);
262 }
263
264 struct child *newchild(char *name, struct chandler *iface, void *pdata)
265 {
266     struct child *ch;
267     
268     omalloc(ch);
269     ch->name = sstrdup(name);
270     ch->iface = iface;
271     ch->pdata = pdata;
272     return(ch);
273 }
274
275 void freechild(struct child *ch)
276 {
277     if(ch->iface->destroy != NULL)
278         ch->iface->destroy(ch);
279     if(ch->name != NULL)
280         free(ch->name);
281     free(ch);
282 }
283
284 void mergechildren(struct child *dst, struct child *src)
285 {
286     struct child *ch1, *ch2;
287     
288     for(ch1 = dst; ch1 != NULL; ch1 = ch1->next) {
289         for(ch2 = src; ch2 != NULL; ch2 = ch2->next) {
290             if(ch1->iface->merge && !strcmp(ch1->name, ch2->name)) {
291                 ch1->iface->merge(ch1, ch2);
292                 break;
293             }
294         }
295     }
296 }
297
298 void skipcfblock(struct cfstate *s)
299 {
300     char **w;
301     
302     while(1) {
303         w = getcfline(s);
304         if(!strcmp(w[0], "end") || !strcmp(w[0], "eof"))
305             return;
306     }
307 }
308
309 static struct chandler stdhandler = {
310     .handle = stdhandle,
311     .merge = stdmerge,
312     .destroy = stddestroy,
313 };
314
315 static char **expandargs(struct stdchild *sd)
316 {
317     int i;
318     char **ret, *p, *p2, *p3, *np, *env;
319     struct charbuf exp;
320     
321     ret = szmalloc(sizeof(*ret) * (calen(sd->argv) + 1));
322     bufinit(exp);
323     for(i = 0; sd->argv[i] != NULL; i++) {
324         if((p = strchr(sd->argv[i], '$')) == NULL) {
325             ret[i] = sstrdup(sd->argv[i]);
326         } else {
327             exp.d = 0;
328             for(p2 = sd->argv[i]; p != NULL; p2 = np, p = strchr(np, '$')) {
329                 bufcat(exp, p2, p - p2);
330                 if(p[1] == '{') {
331                     if((p3 = strchr((p += 2), '}')) == NULL)
332                         break;
333                     np = p3 + 1;
334                 } else {
335                     for(p3 = ++p; *p3; p3++) {
336                         if(!(((*p3 >= 'a') && (*p3 <= 'z')) ||
337                              ((*p3 >= 'A') && (*p3 <= 'Z')) ||
338                              ((*p3 >= '0') && (*p3 <= '9')) ||
339                              (*p3 == '_'))) {
340                             break;
341                         }
342                     }
343                     np = p3;
344                 }
345                 char temp[(p3 - p) + 1];
346                 memcpy(temp, p, p3 - p);
347                 temp[p3 - p] = 0;
348                 if((env = getenv(temp)) != NULL)
349                     bufcatstr(exp, env);
350             }
351             bufcatstr2(exp, np);
352             ret[i] = sstrdup(exp.b);
353         }
354     }
355     ret[i] = NULL;
356     buffree(exp);
357     return(ret);
358 }
359
360 struct sidata {
361     struct stdchild *sd;
362     void (*sinit)(void *);
363     void *sdata;
364 };
365
366 static void stdinit(void *data)
367 {
368     struct sidata *d = data;
369     int i;
370         
371     for(i = 0; d->sd->envp[i]; i += 2)
372         putenv(sprintf2("%s=%s", d->sd->envp[i], d->sd->envp[i + 1]));
373     if(d->sinit != NULL)
374         d->sinit(d->sdata);
375 }
376
377 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *sdata)
378 {
379     struct stdchild *sd = ch->pdata;
380     int serr;
381     char **args;
382     struct sidata idat;
383     
384     if(sd->type == CH_SOCKET) {
385         idat = (struct sidata) {.sd = sd, .sinit = chinit, .sdata = sdata};
386         if(sd->fd < 0) {
387             args = expandargs(sd);
388             sd->fd = stdmkchild(args, stdinit, &idat);
389             freeca(args);
390         }
391         if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
392             serr = errno;
393             if((serr == EPIPE) || (serr == ECONNRESET)) {
394                 /* Assume that the child has crashed and restart it. */
395                 close(sd->fd);
396                 args = expandargs(sd);
397                 sd->fd = stdmkchild(args, stdinit, &idat);
398                 freeca(args);
399                 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
400                     goto ok;
401                 serr = errno;
402             }
403             if(serr == EAGAIN) {
404                 if(sd->agains++ == 0) {
405                     flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
406                     sd->lastrep = time(NULL);
407                 }
408             } else {
409                 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
410                 close(sd->fd);
411                 sd->fd = -1;
412             }
413             return(-1);
414         }
415     ok:
416         if((sd->agains > 0) && ((time(NULL) - sd->lastrep) > 10)) {
417             flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
418             sd->agains = 0;
419         }
420     } else if(sd->type == CH_FORK) {
421         args = expandargs(sd);
422         if(stdforkserve(args, req, fd, chinit, sdata) < 0) {
423             freeca(args);
424             return(-1);
425         }
426         freeca(args);
427     }
428     return(0);
429 }
430
431 static void stdmerge(struct child *dst, struct child *src)
432 {
433     struct stdchild *od, *nd;
434     
435     if(src->iface == &stdhandler) {
436         nd = dst->pdata;
437         od = src->pdata;
438         nd->fd = od->fd;
439         od->fd = -1;
440     }
441 }
442
443 static void stddestroy(struct child *ch)
444 {
445     struct stdchild *d = ch->pdata;
446     
447     if(d->fd >= 0)
448         close(d->fd);
449     if(d->argv)
450         freeca(d->argv);
451     if(d->envp)
452         freeca(d->envp);
453     free(d);
454 }
455
456 struct child *parsechild(struct cfstate *s)
457 {
458     struct child *ch;
459     struct stdchild *d;
460     struct charvbuf envbuf;
461     int i;
462     int sl;
463     
464     sl = s->lno;
465     if(!strcmp(s->argv[0], "child")) {
466         s->expstart = 1;
467         if(s->argc < 2) {
468             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
469             skipcfblock(s);
470             return(NULL);
471         }
472         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
473         d->type = CH_SOCKET;
474     } else if(!strcmp(s->argv[0], "fchild")) {
475         s->expstart = 1;
476         if(s->argc < 2) {
477             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
478             skipcfblock(s);
479             return(NULL);
480         }
481         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
482         d->type = CH_FORK;
483     } else {
484         return(NULL);
485     }
486     d->fd = -1;
487     
488     bufinit(envbuf);
489     while(1) {
490         getcfline(s);
491         if(!strcmp(s->argv[0], "exec")) {
492             if(s->argc < 2) {
493                 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
494                 continue;
495             }
496             d->argv = szmalloc(sizeof(*d->argv) * s->argc);
497             for(i = 0; i < s->argc - 1; i++)
498                 d->argv[i] = sstrdup(s->argv[i + 1]);
499         } else if(!strcmp(s->argv[0], "env")) {
500             if(s->argc < 3) {
501                 flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
502                 continue;
503             }
504             bufadd(envbuf, sstrdup(s->argv[1]));
505             bufadd(envbuf, sstrdup(s->argv[2]));
506         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
507             break;
508         } else {
509             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
510         }
511     }
512     bufadd(envbuf, NULL);
513     d->envp = envbuf.b;
514     if(d->argv == NULL) {
515         flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
516         freechild(ch);
517         return(NULL);
518     }
519     return(ch);
520 }
521
522 int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
523 {
524     return(ch->iface->handle(ch, req, fd, chinit, idata));
525 }