dirplex: Actually allow zero-argument index-file, as documented.
[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 <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
41struct stdchild {
42 int type;
43 char **argv;
44 char **envp;
45 int fd;
46 int agains;
47};
48
49static int parsefile(struct cfstate *s, FILE *in);
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);
53
54static int doinclude(struct cfstate *s, char *spec)
55{
56 int rv, i;
57 FILE *inc;
58 glob_t globm;
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;
94 char line[1024];
95 int eof, argc;
96 int ind, indst[80], indl;
97 char *p, **w;
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++;
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 }
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")) {
169 for(i = 1; i < argc; i++) {
170 if(doinclude(s, w[i])) {
171 freeca(w);
172 return(1);
173 }
174 }
175 freeca(w);
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
236char *findstdconf(char *name)
237{
238 char *home, *path, *p, *p2, *t;
239
240 if((home = getenv("HOME")) != NULL) {
241 if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
242 return(t);
243 free(t);
244 }
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 }
259 return(NULL);
260}
261
262struct child *newchild(char *name, struct chandler *iface, void *pdata)
263{
264 struct child *ch;
265
266 omalloc(ch);
267 ch->name = sstrdup(name);
268 ch->iface = iface;
269 ch->pdata = pdata;
270 return(ch);
271}
272
273void freechild(struct child *ch)
274{
275 if(ch->iface->destroy != NULL)
276 ch->iface->destroy(ch);
277 if(ch->name != NULL)
278 free(ch->name);
279 free(ch);
280}
281
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
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
307static struct chandler stdhandler = {
308 .handle = stdhandle,
309 .merge = stdmerge,
310 .destroy = stddestroy,
311};
312
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
358static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
359{
360 struct stdchild *sd = ch->pdata;
361 int serr;
362 char **args;
363
364 void stdinit(void *data)
365 {
366 int i;
367
368 for(i = 0; sd->envp[i]; i += 2)
369 putenv(sprintf2("%s=%s", sd->envp[i], sd->envp[i + 1]));
370 if(chinit != NULL)
371 chinit(data);
372 }
373
374 if(sd->type == CH_SOCKET) {
375 if(sd->fd < 0) {
376 args = expandargs(sd);
377 sd->fd = stdmkchild(args, stdinit, idata);
378 freeca(args);
379 }
380 if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
381 serr = errno;
382 if((serr == EPIPE) || (serr == ECONNRESET)) {
383 /* Assume that the child has crashed and restart it. */
384 close(sd->fd);
385 args = expandargs(sd);
386 sd->fd = stdmkchild(args, stdinit, idata);
387 freeca(args);
388 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
389 goto ok;
390 serr = errno;
391 }
392 if(serr == EAGAIN) {
393 if(sd->agains++ == 0)
394 flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
395 } else {
396 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
397 close(sd->fd);
398 sd->fd = -1;
399 }
400 return(-1);
401 }
402 ok:
403 if(sd->agains > 0) {
404 flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
405 sd->agains = 0;
406 }
407 } else if(sd->type == CH_FORK) {
408 args = expandargs(sd);
409 if(stdforkserve(args, req, fd, chinit, idata) < 0) {
410 freeca(args);
411 return(-1);
412 }
413 freeca(args);
414 }
415 return(0);
416}
417
418static void stdmerge(struct child *dst, struct child *src)
419{
420 struct stdchild *od, *nd;
421
422 if(src->iface == &stdhandler) {
423 nd = dst->pdata;
424 od = src->pdata;
425 nd->fd = od->fd;
426 od->fd = -1;
427 }
428}
429
430static void stddestroy(struct child *ch)
431{
432 struct stdchild *d = ch->pdata;
433
434 if(d->fd >= 0)
435 close(d->fd);
436 if(d->argv)
437 freeca(d->argv);
438 if(d->envp)
439 freeca(d->envp);
440 free(d);
441}
442
443struct child *parsechild(struct cfstate *s)
444{
445 struct child *ch;
446 struct stdchild *d;
447 struct charvbuf envbuf;
448 int i;
449 int sl;
450
451 sl = s->lno;
452 if(!strcmp(s->argv[0], "child")) {
453 s->expstart = 1;
454 if(s->argc < 2) {
455 flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
456 skipcfblock(s);
457 return(NULL);
458 }
459 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
460 d->type = CH_SOCKET;
461 } else if(!strcmp(s->argv[0], "fchild")) {
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 }
468 ch = newchild(s->argv[1], &stdhandler, omalloc(d));
469 d->type = CH_FORK;
470 } else {
471 return(NULL);
472 }
473 d->fd = -1;
474
475 bufinit(envbuf);
476 while(1) {
477 getcfline(s);
478 if(!strcmp(s->argv[0], "exec")) {
479 if(s->argc < 2) {
480 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
481 continue;
482 }
483 d->argv = szmalloc(sizeof(*d->argv) * s->argc);
484 for(i = 0; i < s->argc - 1; i++)
485 d->argv[i] = sstrdup(s->argv[i + 1]);
486 } else if(!strcmp(s->argv[0], "env")) {
487 if(s->argc < 3) {
488 flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
489 continue;
490 }
491 bufadd(envbuf, sstrdup(s->argv[1]));
492 bufadd(envbuf, sstrdup(s->argv[2]));
493 } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
494 break;
495 } else {
496 flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
497 }
498 }
499 bufadd(envbuf, NULL);
500 d->envp = envbuf.b;
501 if(d->argv == NULL) {
502 flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
503 freechild(ch);
504 return(NULL);
505 }
506 return(ch);
507}
508
509int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
510{
511 return(ch->iface->handle(ch, req, fd, chinit, idata));
512}