lib: Improved EAGAIN reporting friendliness further.
[ashd.git] / lib / cf.c
CommitLineData
06c1a718
FT
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>
dd281054 25#include <libgen.h>
3d74fc07 26#include <sys/socket.h>
32bcaa06 27#include <time.h>
06c1a718
FT
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
1924fe8c
FT
42struct stdchild {
43 int type;
44 char **argv;
3b77f136 45 char **envp;
1924fe8c 46 int fd;
f2b4b031 47 int agains;
32bcaa06 48 time_t lastrep;
1924fe8c
FT
49};
50
dd281054 51static int parsefile(struct cfstate *s, FILE *in);
1924fe8c
FT
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);
dd281054
FT
55
56static int doinclude(struct cfstate *s, char *spec)
06c1a718 57{
dd281054
FT
58 int rv, i;
59 FILE *inc;
06c1a718 60 glob_t globm;
dd281054
FT
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;
06c1a718
FT
96 char line[1024];
97 int eof, argc;
98 int ind, indst[80], indl;
dd281054 99 char *p, **w;
06c1a718
FT
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++;
c3b91092
FT
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 }
06c1a718
FT
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")) {
06c1a718 171 for(i = 1; i < argc; i++) {
dd281054
FT
172 if(doinclude(s, w[i])) {
173 freeca(w);
174 return(1);
06c1a718
FT
175 }
176 }
177 freeca(w);
06c1a718
FT
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
0fc6fd13
FT
238char *findstdconf(char *name)
239{
f9a65eb2 240 char *home, *path, *p, *p2, *t;
0fc6fd13 241
f9a65eb2
FT
242 if((home = getenv("HOME")) != NULL) {
243 if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
13975be5 244 return(t);
13975be5 245 free(t);
0fc6fd13 246 }
f9a65eb2
FT
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 }
0fc6fd13
FT
261 return(NULL);
262}
263
1924fe8c 264struct child *newchild(char *name, struct chandler *iface, void *pdata)
06c1a718
FT
265{
266 struct child *ch;
267
268 omalloc(ch);
269 ch->name = sstrdup(name);
1924fe8c
FT
270 ch->iface = iface;
271 ch->pdata = pdata;
06c1a718
FT
272 return(ch);
273}
274
275void freechild(struct child *ch)
276{
1924fe8c
FT
277 if(ch->iface->destroy != NULL)
278 ch->iface->destroy(ch);
06c1a718
FT
279 if(ch->name != NULL)
280 free(ch->name);
06c1a718
FT
281 free(ch);
282}
283
1924fe8c
FT
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
06c1a718
FT
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
1924fe8c
FT
309static struct chandler stdhandler = {
310 .handle = stdhandle,
311 .merge = stdmerge,
312 .destroy = stddestroy,
313};
314
b3eb750f
FT
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 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
091936b4
FT
360struct sidata {
361 struct stdchild *sd;
362 void (*sinit)(void *);
363 void *sdata;
364};
365
366static 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
377static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *sdata)
1924fe8c 378{
3b77f136 379 struct stdchild *sd = ch->pdata;
45b19d96 380 int serr;
b3eb750f 381 char **args;
091936b4 382 struct sidata idat;
3b77f136
FT
383
384 if(sd->type == CH_SOCKET) {
7a092cef 385 idat = (struct sidata) {.sd = sd, .sinit = chinit, .sdata = sdata};
b3eb750f
FT
386 if(sd->fd < 0) {
387 args = expandargs(sd);
091936b4 388 sd->fd = stdmkchild(args, stdinit, &idat);
b3eb750f
FT
389 freeca(args);
390 }
3b77f136 391 if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
45b19d96
FT
392 serr = errno;
393 if((serr == EPIPE) || (serr == ECONNRESET)) {
1924fe8c 394 /* Assume that the child has crashed and restart it. */
3b77f136 395 close(sd->fd);
b3eb750f 396 args = expandargs(sd);
091936b4 397 sd->fd = stdmkchild(args, stdinit, &idat);
b3eb750f 398 freeca(args);
3b77f136 399 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
f2b4b031 400 goto ok;
6f761f0a 401 serr = errno;
1924fe8c 402 }
f2b4b031 403 if(serr == EAGAIN) {
32bcaa06 404 if(sd->agains++ == 0) {
f2b4b031 405 flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
32bcaa06
FT
406 sd->lastrep = time(NULL);
407 }
f2b4b031 408 } else {
82405659 409 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
3b77f136
FT
410 close(sd->fd);
411 sd->fd = -1;
fc35a3ef 412 }
1924fe8c
FT
413 return(-1);
414 }
f2b4b031 415 ok:
32bcaa06 416 if((sd->agains > 0) && ((time(NULL) - sd->lastrep) > 10)) {
f2b4b031
FT
417 flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
418 sd->agains = 0;
419 }
3b77f136 420 } else if(sd->type == CH_FORK) {
b3eb750f 421 args = expandargs(sd);
091936b4 422 if(stdforkserve(args, req, fd, chinit, sdata) < 0) {
b3eb750f 423 freeca(args);
1924fe8c 424 return(-1);
b3eb750f
FT
425 }
426 freeca(args);
1924fe8c
FT
427 }
428 return(0);
429}
430
431static 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
443static 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);
3b77f136
FT
451 if(d->envp)
452 freeca(d->envp);
1924fe8c
FT
453 free(d);
454}
455
06c1a718
FT
456struct child *parsechild(struct cfstate *s)
457{
458 struct child *ch;
1924fe8c 459 struct stdchild *d;
3b77f136 460 struct charvbuf envbuf;
06c1a718
FT
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 }
1924fe8c
FT
472 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
473 d->type = CH_SOCKET;
06c1a718
FT
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 }
1924fe8c
FT
481 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
482 d->type = CH_FORK;
06c1a718
FT
483 } else {
484 return(NULL);
485 }
1924fe8c 486 d->fd = -1;
06c1a718 487
3b77f136 488 bufinit(envbuf);
06c1a718
FT
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 }
1924fe8c 496 d->argv = szmalloc(sizeof(*d->argv) * s->argc);
06c1a718 497 for(i = 0; i < s->argc - 1; i++)
1924fe8c 498 d->argv[i] = sstrdup(s->argv[i + 1]);
3b77f136
FT
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]));
06c1a718
FT
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 }
3b77f136
FT
512 bufadd(envbuf, NULL);
513 d->envp = envbuf.b;
1924fe8c 514 if(d->argv == NULL) {
06c1a718
FT
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
6a7a868e 522int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
06c1a718 523{
1924fe8c 524 return(ch->iface->handle(ch, req, fd, chinit, idata));
06c1a718 525}