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