lib: Add $HOME/.ashd/etc to the directories to search for slash-less files.
[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 *home, *path, *p, *p2, *t;
237     
238     if((home = getenv("HOME")) != NULL) {
239         if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
240             return(t);
241         free(t);
242     }
243     if((path = getenv("PATH")) != NULL) {
244         path = sstrdup(path);
245         for(p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
246             if((p2 = strrchr(p, '/')) == NULL)
247                 continue;
248             *p2 = 0;
249             if(!access(t = sprintf2("%s/etc/%s", p, name), R_OK)) {
250                 free(path);
251                 return(t);
252             }
253             free(t);
254         }
255         free(path);
256     }
257     return(NULL);
258 }
259
260 struct child *newchild(char *name, struct chandler *iface, void *pdata)
261 {
262     struct child *ch;
263     
264     omalloc(ch);
265     ch->name = sstrdup(name);
266     ch->iface = iface;
267     ch->pdata = pdata;
268     return(ch);
269 }
270
271 void freechild(struct child *ch)
272 {
273     if(ch->iface->destroy != NULL)
274         ch->iface->destroy(ch);
275     if(ch->name != NULL)
276         free(ch->name);
277     free(ch);
278 }
279
280 void mergechildren(struct child *dst, struct child *src)
281 {
282     struct child *ch1, *ch2;
283     
284     for(ch1 = dst; ch1 != NULL; ch1 = ch1->next) {
285         for(ch2 = src; ch2 != NULL; ch2 = ch2->next) {
286             if(ch1->iface->merge && !strcmp(ch1->name, ch2->name)) {
287                 ch1->iface->merge(ch1, ch2);
288                 break;
289             }
290         }
291     }
292 }
293
294 void skipcfblock(struct cfstate *s)
295 {
296     char **w;
297     
298     while(1) {
299         w = getcfline(s);
300         if(!strcmp(w[0], "end") || !strcmp(w[0], "eof"))
301             return;
302     }
303 }
304
305 static struct chandler stdhandler = {
306     .handle = stdhandle,
307     .merge = stdmerge,
308     .destroy = stddestroy,
309 };
310
311 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
312 {
313     struct stdchild *i = ch->pdata;
314     int serr;
315     
316     if(i->type == CH_SOCKET) {
317         if(i->fd < 0)
318             i->fd = stdmkchild(i->argv, chinit, idata);
319         if(sendreq2(i->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
320             serr = errno;
321             if((serr == EPIPE) || (serr == ECONNRESET)) {
322                 /* Assume that the child has crashed and restart it. */
323                 close(i->fd);
324                 i->fd = stdmkchild(i->argv, chinit, idata);
325                 if(!sendreq2(i->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
326                     return(0);
327             }
328             flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
329             if(serr != EAGAIN) {
330                 close(i->fd);
331                 i->fd = -1;
332             }
333             return(-1);
334         }
335     } else if(i->type == CH_FORK) {
336         if(stdforkserve(i->argv, req, fd, chinit, idata) < 0)
337             return(-1);
338     }
339     return(0);
340 }
341
342 static void stdmerge(struct child *dst, struct child *src)
343 {
344     struct stdchild *od, *nd;
345     
346     if(src->iface == &stdhandler) {
347         nd = dst->pdata;
348         od = src->pdata;
349         nd->fd = od->fd;
350         od->fd = -1;
351     }
352 }
353
354 static void stddestroy(struct child *ch)
355 {
356     struct stdchild *d = ch->pdata;
357     
358     if(d->fd >= 0)
359         close(d->fd);
360     if(d->argv)
361         freeca(d->argv);
362     free(d);
363 }
364
365 struct child *parsechild(struct cfstate *s)
366 {
367     struct child *ch;
368     struct stdchild *d;
369     int i;
370     int sl;
371     
372     sl = s->lno;
373     if(!strcmp(s->argv[0], "child")) {
374         s->expstart = 1;
375         if(s->argc < 2) {
376             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
377             skipcfblock(s);
378             return(NULL);
379         }
380         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
381         d->type = CH_SOCKET;
382     } else if(!strcmp(s->argv[0], "fchild")) {
383         s->expstart = 1;
384         if(s->argc < 2) {
385             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
386             skipcfblock(s);
387             return(NULL);
388         }
389         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
390         d->type = CH_FORK;
391     } else {
392         return(NULL);
393     }
394     d->fd = -1;
395     
396     while(1) {
397         getcfline(s);
398         if(!strcmp(s->argv[0], "exec")) {
399             if(s->argc < 2) {
400                 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
401                 continue;
402             }
403             d->argv = szmalloc(sizeof(*d->argv) * s->argc);
404             for(i = 0; i < s->argc - 1; i++)
405                 d->argv[i] = sstrdup(s->argv[i + 1]);
406         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
407             break;
408         } else {
409             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
410         }
411     }
412     if(d->argv == NULL) {
413         flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
414         freechild(ch);
415         return(NULL);
416     }
417     return(ch);
418 }
419
420 int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
421 {
422     return(ch->iface->handle(ch, req, fd, chinit, idata));
423 }