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