2ca910a3427212c4e6e3f80abb32a47a63e2987a
[ashd.git] / lib / cf.c
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
41 struct stdchild {
42     int type;
43     char **argv;
44     char **envp;
45     int fd;
46 };
47
48 static int parsefile(struct cfstate *s, FILE *in);
49 static void stdmerge(struct child *old, struct child *new);
50 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
51 static void stddestroy(struct child *ch);
52
53 static int doinclude(struct cfstate *s, char *spec)
54 {
55     int rv, i;
56     FILE *inc;
57     glob_t globm;
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     
84 out:
85     globfree(&globm);
86     s->file = fbk;
87     return(rv);
88 }
89
90 static int parsefile(struct cfstate *s, FILE *in)
91 {
92     int i;
93     char line[1024];
94     int eof, argc;
95     int ind, indst[80], indl;
96     char *p, **w;
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++;
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             }
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")) {
168                 for(i = 1; i < argc; i++) {
169                     if(doinclude(s, w[i])) {
170                         freeca(w);
171                         return(1);
172                     }
173                 }
174                 freeca(w);
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
191 static 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     
204 out:
205     free(s->file);
206 }
207
208 char **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
218 struct 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
227 void freecfparser(struct cfstate *s)
228 {
229     resume(s->pf, -1);
230     freeca(s->argv);
231     freeca(s->res);
232     free(s);
233 }
234
235 char *findstdconf(char *name)
236 {
237     char *home, *path, *p, *p2, *t;
238     
239     if((home = getenv("HOME")) != NULL) {
240         if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
241             return(t);
242         free(t);
243     }
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     }
258     return(NULL);
259 }
260
261 struct child *newchild(char *name, struct chandler *iface, void *pdata)
262 {
263     struct child *ch;
264     
265     omalloc(ch);
266     ch->name = sstrdup(name);
267     ch->iface = iface;
268     ch->pdata = pdata;
269     return(ch);
270 }
271
272 void freechild(struct child *ch)
273 {
274     if(ch->iface->destroy != NULL)
275         ch->iface->destroy(ch);
276     if(ch->name != NULL)
277         free(ch->name);
278     free(ch);
279 }
280
281 void 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
295 void 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
306 static struct chandler stdhandler = {
307     .handle = stdhandle,
308     .merge = stdmerge,
309     .destroy = stddestroy,
310 };
311
312 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
313 {
314     struct stdchild *sd = ch->pdata;
315     int serr;
316     
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]));
323         if(chinit != NULL)
324             chinit(data);
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)) {
331             serr = errno;
332             if((serr == EPIPE) || (serr == ECONNRESET)) {
333                 /* Assume that the child has crashed and restart it. */
334                 close(sd->fd);
335                 sd->fd = stdmkchild(sd->argv, stdinit, idata);
336                 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
337                     return(0);
338             }
339             flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
340             if(serr != EAGAIN) {
341                 close(sd->fd);
342                 sd->fd = -1;
343             }
344             return(-1);
345         }
346     } else if(sd->type == CH_FORK) {
347         if(stdforkserve(sd->argv, req, fd, chinit, idata) < 0)
348             return(-1);
349     }
350     return(0);
351 }
352
353 static 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
365 static 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);
373     if(d->envp)
374         freeca(d->envp);
375     free(d);
376 }
377
378 struct child *parsechild(struct cfstate *s)
379 {
380     struct child *ch;
381     struct stdchild *d;
382     struct charvbuf envbuf;
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         }
394         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
395         d->type = CH_SOCKET;
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         }
403         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
404         d->type = CH_FORK;
405     } else {
406         return(NULL);
407     }
408     d->fd = -1;
409     
410     bufinit(envbuf);
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             }
418             d->argv = szmalloc(sizeof(*d->argv) * s->argc);
419             for(i = 0; i < s->argc - 1; i++)
420                 d->argv[i] = sstrdup(s->argv[i + 1]);
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]));
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     }
434     bufadd(envbuf, NULL);
435     d->envp = envbuf.b;
436     if(d->argv == NULL) {
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
444 int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
445 {
446     return(ch->iface->handle(ch, req, fd, chinit, idata));
447 }