lib: Added environment-variable expansion for stdchild arguments.
[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 char **expandargs(struct stdchild *sd)
313 {
314     int i;
315     char **ret, *p, *p2, *p3, *np, *env;
316     struct charbuf exp;
317     
318     ret = szmalloc(sizeof(*ret) * (calen(sd->argv) + 1));
319     bufinit(exp);
320     for(i = 0; sd->argv[i] != NULL; i++) {
321         if((p = strchr(sd->argv[i], '$')) == NULL) {
322             ret[i] = sstrdup(sd->argv[i]);
323         } else {
324             exp.d = 0;
325             for(p2 = sd->argv[i]; p != NULL; p2 = np, p = strchr(np, '$')) {
326                 bufcat(exp, p2, p - p2);
327                 if(p[1] == '{') {
328                     if((p3 = strchr((p += 2), '}')) == NULL)
329                         break;
330                     np = p3 + 1;
331                 } else {
332                     for(p3 = ++p; *p3; p3++) {
333                         if(!(((*p3 >= 'a') && (*p3 <= 'z')) ||
334                              ((*p3 >= 'A') && (*p3 <= 'Z')) ||
335                              ((*p3 >= '0') && (*p3 <= '9')) ||
336                              (*p3 == '_'))) {
337                             break;
338                         }
339                     }
340                     np = p3;
341                 }
342                 char temp[(p3 - p) + 1];
343                 memcpy(temp, p, p3 - p);
344                 temp[p3 - p] = 0;
345                 if((env = getenv(temp)) != NULL)
346                     bufcatstr(exp, env);
347             }
348             bufcatstr2(exp, np);
349             ret[i] = sstrdup(exp.b);
350         }
351     }
352     ret[i] = NULL;
353     buffree(exp);
354     return(ret);
355 }
356
357 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
358 {
359     struct stdchild *sd = ch->pdata;
360     int serr;
361     char **args;
362     
363     void stdinit(void *data)
364     {
365         int i;
366         
367         for(i = 0; sd->envp[i]; i += 2)
368             putenv(sprintf2("%s=%s", sd->envp[i], sd->envp[i + 1]));
369         if(chinit != NULL)
370             chinit(data);
371     }
372     
373     if(sd->type == CH_SOCKET) {
374         if(sd->fd < 0) {
375             args = expandargs(sd);
376             sd->fd = stdmkchild(args, stdinit, idata);
377             freeca(args);
378         }
379         if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
380             serr = errno;
381             if((serr == EPIPE) || (serr == ECONNRESET)) {
382                 /* Assume that the child has crashed and restart it. */
383                 close(sd->fd);
384                 args = expandargs(sd);
385                 sd->fd = stdmkchild(args, stdinit, idata);
386                 freeca(args);
387                 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
388                     return(0);
389             }
390             flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
391             if(serr != EAGAIN) {
392                 close(sd->fd);
393                 sd->fd = -1;
394             }
395             return(-1);
396         }
397     } else if(sd->type == CH_FORK) {
398         args = expandargs(sd);
399         if(stdforkserve(args, req, fd, chinit, idata) < 0) {
400             freeca(args);
401             return(-1);
402         }
403         freeca(args);
404     }
405     return(0);
406 }
407
408 static void stdmerge(struct child *dst, struct child *src)
409 {
410     struct stdchild *od, *nd;
411     
412     if(src->iface == &stdhandler) {
413         nd = dst->pdata;
414         od = src->pdata;
415         nd->fd = od->fd;
416         od->fd = -1;
417     }
418 }
419
420 static void stddestroy(struct child *ch)
421 {
422     struct stdchild *d = ch->pdata;
423     
424     if(d->fd >= 0)
425         close(d->fd);
426     if(d->argv)
427         freeca(d->argv);
428     if(d->envp)
429         freeca(d->envp);
430     free(d);
431 }
432
433 struct child *parsechild(struct cfstate *s)
434 {
435     struct child *ch;
436     struct stdchild *d;
437     struct charvbuf envbuf;
438     int i;
439     int sl;
440     
441     sl = s->lno;
442     if(!strcmp(s->argv[0], "child")) {
443         s->expstart = 1;
444         if(s->argc < 2) {
445             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
446             skipcfblock(s);
447             return(NULL);
448         }
449         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
450         d->type = CH_SOCKET;
451     } else if(!strcmp(s->argv[0], "fchild")) {
452         s->expstart = 1;
453         if(s->argc < 2) {
454             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
455             skipcfblock(s);
456             return(NULL);
457         }
458         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
459         d->type = CH_FORK;
460     } else {
461         return(NULL);
462     }
463     d->fd = -1;
464     
465     bufinit(envbuf);
466     while(1) {
467         getcfline(s);
468         if(!strcmp(s->argv[0], "exec")) {
469             if(s->argc < 2) {
470                 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
471                 continue;
472             }
473             d->argv = szmalloc(sizeof(*d->argv) * s->argc);
474             for(i = 0; i < s->argc - 1; i++)
475                 d->argv[i] = sstrdup(s->argv[i + 1]);
476         } else if(!strcmp(s->argv[0], "env")) {
477             if(s->argc < 3) {
478                 flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
479                 continue;
480             }
481             bufadd(envbuf, sstrdup(s->argv[1]));
482             bufadd(envbuf, sstrdup(s->argv[2]));
483         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
484             break;
485         } else {
486             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
487         }
488     }
489     bufadd(envbuf, NULL);
490     d->envp = envbuf.b;
491     if(d->argv == NULL) {
492         flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
493         freechild(ch);
494         return(NULL);
495     }
496     return(ch);
497 }
498
499 int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
500 {
501     return(ch->iface->handle(ch, req, fd, chinit, idata));
502 }