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