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