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