Merge branch 'master' into duplex
[ashd.git] / lib / cf.c
... / ...
CommitLineData
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
42struct stdchild {
43 int type;
44 char **argv;
45 char **envp;
46 int fd;
47 int agains;
48 time_t lastrep;
49};
50
51static int parsefile(struct cfstate *s, FILE *in);
52static void stdmerge(struct child *old, struct child *new);
53static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
54static void stddestroy(struct child *ch);
55
56static 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
87out:
88 globfree(&globm);
89 s->file = fbk;
90 return(rv);
91}
92
93static 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
194static 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
207out:
208 free(s->file);
209}
210
211char **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
221struct 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
230void freecfparser(struct cfstate *s)
231{
232 resume(s->pf, -1);
233 freeca(s->argv);
234 freeca(s->res);
235 free(s);
236}
237
238char *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
264struct 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
275void 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
284void 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
298void 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
309static struct chandler stdhandler = {
310 .handle = stdhandle,
311 .merge = stdmerge,
312 .destroy = stddestroy,
313};
314
315static 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 np = p;
333 break;
334 }
335 np = p3 + 1;
336 } else {
337 for(p3 = ++p; *p3; p3++) {
338 if(!(((*p3 >= 'a') && (*p3 <= 'z')) ||
339 ((*p3 >= 'A') && (*p3 <= 'Z')) ||
340 ((*p3 >= '0') && (*p3 <= '9')) ||
341 (*p3 == '_'))) {
342 break;
343 }
344 }
345 np = p3;
346 }
347 char temp[(p3 - p) + 1];
348 memcpy(temp, p, p3 - p);
349 temp[p3 - p] = 0;
350 if((env = getenv(temp)) != NULL)
351 bufcatstr(exp, env);
352 }
353 bufcatstr2(exp, np);
354 ret[i] = sstrdup(exp.b);
355 }
356 }
357 ret[i] = NULL;
358 buffree(exp);
359 return(ret);
360}
361
362struct sidata {
363 struct stdchild *sd;
364 void (*sinit)(void *);
365 void *sdata;
366};
367
368static void stdinit(void *data)
369{
370 struct sidata *d = data;
371 int i;
372
373 for(i = 0; d->sd->envp[i]; i += 2)
374 putenv(sprintf2("%s=%s", d->sd->envp[i], d->sd->envp[i + 1]));
375 if(d->sinit != NULL)
376 d->sinit(d->sdata);
377}
378
379static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *sdata)
380{
381 struct stdchild *sd = ch->pdata;
382 int serr;
383 char **args;
384 struct sidata idat;
385
386 if(sd->type == CH_SOCKET) {
387 idat = (struct sidata) {.sd = sd, .sinit = chinit, .sdata = sdata};
388 if(sd->fd < 0) {
389 args = expandargs(sd);
390 sd->fd = stdmkchild(args, stdinit, &idat);
391 freeca(args);
392 }
393 if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
394 serr = errno;
395 if((serr == EPIPE) || (serr == ECONNRESET)) {
396 /* Assume that the child has crashed and restart it. */
397 close(sd->fd);
398 args = expandargs(sd);
399 sd->fd = stdmkchild(args, stdinit, &idat);
400 freeca(args);
401 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
402 goto ok;
403 serr = errno;
404 }
405 if(serr == EAGAIN) {
406 if(sd->agains++ == 0) {
407 flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
408 sd->lastrep = time(NULL);
409 }
410 } else {
411 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
412 close(sd->fd);
413 sd->fd = -1;
414 }
415 return(-1);
416 }
417 ok:
418 if((sd->agains > 0) && ((time(NULL) - sd->lastrep) > 10)) {
419 flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
420 sd->agains = 0;
421 }
422 } else if(sd->type == CH_FORK) {
423 args = expandargs(sd);
424 if(stdforkserve(args, req, fd, chinit, sdata) < 0) {
425 freeca(args);
426 return(-1);
427 }
428 freeca(args);
429 }
430 return(0);
431}
432
433static void stdmerge(struct child *dst, struct child *src)
434{
435 struct stdchild *od, *nd;
436
437 if(src->iface == &stdhandler) {
438 nd = dst->pdata;
439 od = src->pdata;
440 nd->fd = od->fd;
441 od->fd = -1;
442 }
443}
444
445static void stddestroy(struct child *ch)
446{
447 struct stdchild *d = ch->pdata;
448
449 if(d->fd >= 0)
450 close(d->fd);
451 if(d->argv)
452 freeca(d->argv);
453 if(d->envp)
454 freeca(d->envp);
455 free(d);
456}
457
458struct child *parsechild(struct cfstate *s)
459{
460 struct child *ch;
461 struct stdchild *d;
462 struct charvbuf envbuf;
463 int i;
464 int sl;
465
466 sl = s->lno;
467 if(!strcmp(s->argv[0], "child")) {
468 s->expstart = 1;
469 if(s->argc < 2) {
470 flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
471 skipcfblock(s);
472 return(NULL);
473 }
474 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
475 d->type = CH_SOCKET;
476 } else if(!strcmp(s->argv[0], "fchild")) {
477 s->expstart = 1;
478 if(s->argc < 2) {
479 flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
480 skipcfblock(s);
481 return(NULL);
482 }
483 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
484 d->type = CH_FORK;
485 } else {
486 return(NULL);
487 }
488 d->fd = -1;
489
490 bufinit(envbuf);
491 while(1) {
492 getcfline(s);
493 if(!strcmp(s->argv[0], "exec")) {
494 if(s->argc < 2) {
495 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
496 continue;
497 }
498 d->argv = szmalloc(sizeof(*d->argv) * s->argc);
499 for(i = 0; i < s->argc - 1; i++)
500 d->argv[i] = sstrdup(s->argv[i + 1]);
501 } else if(!strcmp(s->argv[0], "env")) {
502 if(s->argc < 3) {
503 flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
504 continue;
505 }
506 bufadd(envbuf, sstrdup(s->argv[1]));
507 bufadd(envbuf, sstrdup(s->argv[2]));
508 } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
509 break;
510 } else {
511 flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
512 }
513 }
514 bufadd(envbuf, NULL);
515 d->envp = envbuf.b;
516 if(d->argv == NULL) {
517 flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
518 freechild(ch);
519 return(NULL);
520 }
521 return(ch);
522}
523
524int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
525{
526 return(ch->iface->handle(ch, req, fd, chinit, idata));
527}