lib: Transfer the responsibility of fopencookie bugs to the generic implementation.
[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     int agains;
47 };
48
49 static int parsefile(struct cfstate *s, FILE *in);
50 static void stdmerge(struct child *old, struct child *new);
51 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata);
52 static void stddestroy(struct child *ch);
53
54 static int doinclude(struct cfstate *s, char *spec)
55 {
56     int rv, i;
57     FILE *inc;
58     glob_t globm;
59     char *fbk, *dir, *fspec;
60
61     rv = 0;
62     fbk = s->file;
63     if(spec[0] == '/') {
64         fspec = spec;
65     } else {
66         dir = sstrdup(fbk);
67         fspec = sprintf3("%s/%s", dirname(dir), spec);
68         free(dir);
69     }
70     if(glob(fspec, 0, NULL, &globm))
71         return(0);
72     for(i = 0; i < globm.gl_pathc; i++) {
73         if((inc = fopen(globm.gl_pathv[i], "r")) != NULL) {
74             s->file = globm.gl_pathv[i];
75             if(parsefile(s, inc)) {
76                 fclose(inc);
77                 rv = 1;
78                 goto out;
79             }
80             fclose(inc);
81             inc = NULL;
82         }
83     }
84     
85 out:
86     globfree(&globm);
87     s->file = fbk;
88     return(rv);
89 }
90
91 static int parsefile(struct cfstate *s, FILE *in)
92 {
93     int i;
94     char line[1024];
95     int eof, argc;
96     int ind, indst[80], indl;
97     char *p, **w;
98     
99     s->lno = 0;
100     indst[indl = 0] = 0;
101     eof = 0;
102     while(1) {
103         if(fgets(line, sizeof(line), in) == NULL) {
104             eof = 1;
105             line[0] = 0;
106         }
107         s->lno++;
108         if(line[0]) {
109             for(p = line + strlen(line) - 1; p >= line; p--) {
110                 if(isspace(*p))
111                     *p = 0;
112                 else
113                     break;
114             }
115         }
116         for(ind = 0, p = line; *p; p++) {
117             if(*p == ' ') {
118                 ind++;
119             } else if(*p == '\t') {
120                 ind = ind - (ind % 8) + 8;
121             } else {
122                 break;
123             }
124         }
125         if(!eof && (!*p || (*p == '#')))
126             continue;
127         
128     reindent:
129         if(ind > indst[indl]) {
130             indst[++indl] = ind;
131             if(!s->expstart) {
132                 s->res = tokenize("start");
133                 if(yield())
134                     return(1);
135             } else {
136                 s->expstart = 0;
137             }
138         } else {
139             if(s->expstart) {
140                 s->res = tokenize("end");
141                 if(yield())
142                     return(1);
143                 s->expstart = 0;
144             }
145             while(ind < indst[indl]) {
146                 indl--;
147                 s->res = tokenize("end");
148                 if(yield())
149                     return(1);
150             }
151             if(ind > indst[indl]) {
152                 flog(LOG_WARNING, "%s:%i: unexpected indentation level", s->file, s->lno);
153                 goto reindent;
154             }
155         }
156         
157         if(eof)
158             return(0);
159         
160         argc = calen(w = tokenize(line));
161         if(argc < 1) {
162             /* Shouldn't happen, but... */
163             freeca(w);
164             continue;
165         }
166         
167         if(indl == 0) {
168             if(!strcmp(w[0], "include")) {
169                 for(i = 1; i < argc; i++) {
170                     if(doinclude(s, w[i])) {
171                         freeca(w);
172                         return(1);
173                     }
174                 }
175                 freeca(w);
176                 continue;
177             }
178         }
179         
180         if(!strcmp(w[0], "start") ||
181            !strcmp(w[0], "end") || 
182            !strcmp(w[0], "eof")) {
183             flog(LOG_WARNING, "%s:%i: illegal directive: %s", s->file, s->lno, w[0]);
184         } else {
185             s->res = w;
186             if(yield())
187                 return(1);
188         }
189     }
190 }
191
192 static void parsefn(struct muth *mt, va_list args)
193 {
194     vavar(struct cfstate *, s);
195     vavar(FILE *, in);
196     vavar(char *, file);
197     
198     s->file = sstrdup(file);
199     if(parsefile(s, in))
200         goto out;
201     do {
202         s->res = tokenize("eof");
203     } while(!yield());
204     
205 out:
206     free(s->file);
207 }
208
209 char **getcfline(struct cfstate *s)
210 {
211     freeca(s->argv);
212     if(s->res == NULL)
213         resume(s->pf, 0);
214     s->argc = calen(s->argv = s->res);
215     s->res = NULL;
216     return(s->argv);
217 }
218
219 struct cfstate *mkcfparser(FILE *in, char *name)
220 {
221     struct cfstate *s;
222     
223     omalloc(s);
224     s->pf = mustart(parsefn, s, in, name);
225     return(s);
226 }
227
228 void freecfparser(struct cfstate *s)
229 {
230     resume(s->pf, -1);
231     freeca(s->argv);
232     freeca(s->res);
233     free(s);
234 }
235
236 char *findstdconf(char *name)
237 {
238     char *home, *path, *p, *p2, *t;
239     
240     if((home = getenv("HOME")) != NULL) {
241         if(!access(t = sprintf2("%s/.ashd/etc/%s", home, name), R_OK))
242             return(t);
243         free(t);
244     }
245     if((path = getenv("PATH")) != NULL) {
246         path = sstrdup(path);
247         for(p = strtok(path, ":"); p != NULL; p = strtok(NULL, ":")) {
248             if((p2 = strrchr(p, '/')) == NULL)
249                 continue;
250             *p2 = 0;
251             if(!access(t = sprintf2("%s/etc/%s", p, name), R_OK)) {
252                 free(path);
253                 return(t);
254             }
255             free(t);
256         }
257         free(path);
258     }
259     return(NULL);
260 }
261
262 struct child *newchild(char *name, struct chandler *iface, void *pdata)
263 {
264     struct child *ch;
265     
266     omalloc(ch);
267     ch->name = sstrdup(name);
268     ch->iface = iface;
269     ch->pdata = pdata;
270     return(ch);
271 }
272
273 void freechild(struct child *ch)
274 {
275     if(ch->iface->destroy != NULL)
276         ch->iface->destroy(ch);
277     if(ch->name != NULL)
278         free(ch->name);
279     free(ch);
280 }
281
282 void mergechildren(struct child *dst, struct child *src)
283 {
284     struct child *ch1, *ch2;
285     
286     for(ch1 = dst; ch1 != NULL; ch1 = ch1->next) {
287         for(ch2 = src; ch2 != NULL; ch2 = ch2->next) {
288             if(ch1->iface->merge && !strcmp(ch1->name, ch2->name)) {
289                 ch1->iface->merge(ch1, ch2);
290                 break;
291             }
292         }
293     }
294 }
295
296 void skipcfblock(struct cfstate *s)
297 {
298     char **w;
299     
300     while(1) {
301         w = getcfline(s);
302         if(!strcmp(w[0], "end") || !strcmp(w[0], "eof"))
303             return;
304     }
305 }
306
307 static struct chandler stdhandler = {
308     .handle = stdhandle,
309     .merge = stdmerge,
310     .destroy = stddestroy,
311 };
312
313 static char **expandargs(struct stdchild *sd)
314 {
315     int i;
316     char **ret, *p, *p2, *p3, *np, *env;
317     struct charbuf exp;
318     
319     ret = szmalloc(sizeof(*ret) * (calen(sd->argv) + 1));
320     bufinit(exp);
321     for(i = 0; sd->argv[i] != NULL; i++) {
322         if((p = strchr(sd->argv[i], '$')) == NULL) {
323             ret[i] = sstrdup(sd->argv[i]);
324         } else {
325             exp.d = 0;
326             for(p2 = sd->argv[i]; p != NULL; p2 = np, p = strchr(np, '$')) {
327                 bufcat(exp, p2, p - p2);
328                 if(p[1] == '{') {
329                     if((p3 = strchr((p += 2), '}')) == NULL)
330                         break;
331                     np = p3 + 1;
332                 } else {
333                     for(p3 = ++p; *p3; p3++) {
334                         if(!(((*p3 >= 'a') && (*p3 <= 'z')) ||
335                              ((*p3 >= 'A') && (*p3 <= 'Z')) ||
336                              ((*p3 >= '0') && (*p3 <= '9')) ||
337                              (*p3 == '_'))) {
338                             break;
339                         }
340                     }
341                     np = p3;
342                 }
343                 char temp[(p3 - p) + 1];
344                 memcpy(temp, p, p3 - p);
345                 temp[p3 - p] = 0;
346                 if((env = getenv(temp)) != NULL)
347                     bufcatstr(exp, env);
348             }
349             bufcatstr2(exp, np);
350             ret[i] = sstrdup(exp.b);
351         }
352     }
353     ret[i] = NULL;
354     buffree(exp);
355     return(ret);
356 }
357
358 struct sidata {
359     struct stdchild *sd;
360     void (*sinit)(void *);
361     void *sdata;
362 };
363
364 static void stdinit(void *data)
365 {
366     struct sidata *d = data;
367     int i;
368         
369     for(i = 0; d->sd->envp[i]; i += 2)
370         putenv(sprintf2("%s=%s", d->sd->envp[i], d->sd->envp[i + 1]));
371     if(d->sinit != NULL)
372         d->sinit(d->sdata);
373 }
374
375 static int stdhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *sdata)
376 {
377     struct stdchild *sd = ch->pdata;
378     int serr;
379     char **args;
380     struct sidata idat;
381     
382     if(sd->type == CH_SOCKET) {
383         idat = (struct sidata) {.sd = sd, .sinit = chinit, .sdata = sdata};
384         if(sd->fd < 0) {
385             args = expandargs(sd);
386             sd->fd = stdmkchild(args, stdinit, &idat);
387             freeca(args);
388         }
389         if(sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT)) {
390             serr = errno;
391             if((serr == EPIPE) || (serr == ECONNRESET)) {
392                 /* Assume that the child has crashed and restart it. */
393                 close(sd->fd);
394                 args = expandargs(sd);
395                 sd->fd = stdmkchild(args, stdinit, &idat);
396                 freeca(args);
397                 if(!sendreq2(sd->fd, req, fd, MSG_NOSIGNAL | MSG_DONTWAIT))
398                     goto ok;
399                 serr = errno;
400             }
401             if(serr == EAGAIN) {
402                 if(sd->agains++ == 0)
403                     flog(LOG_WARNING, "request to child %s denied due to buffer overload", ch->name);
404             } else {
405                 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(serr));
406                 close(sd->fd);
407                 sd->fd = -1;
408             }
409             return(-1);
410         }
411     ok:
412         if(sd->agains > 0) {
413             flog(LOG_WARNING, "%i requests to child %s were denied due to buffer overload", sd->agains, ch->name);
414             sd->agains = 0;
415         }
416     } else if(sd->type == CH_FORK) {
417         args = expandargs(sd);
418         if(stdforkserve(args, req, fd, chinit, sdata) < 0) {
419             freeca(args);
420             return(-1);
421         }
422         freeca(args);
423     }
424     return(0);
425 }
426
427 static void stdmerge(struct child *dst, struct child *src)
428 {
429     struct stdchild *od, *nd;
430     
431     if(src->iface == &stdhandler) {
432         nd = dst->pdata;
433         od = src->pdata;
434         nd->fd = od->fd;
435         od->fd = -1;
436     }
437 }
438
439 static void stddestroy(struct child *ch)
440 {
441     struct stdchild *d = ch->pdata;
442     
443     if(d->fd >= 0)
444         close(d->fd);
445     if(d->argv)
446         freeca(d->argv);
447     if(d->envp)
448         freeca(d->envp);
449     free(d);
450 }
451
452 struct child *parsechild(struct cfstate *s)
453 {
454     struct child *ch;
455     struct stdchild *d;
456     struct charvbuf envbuf;
457     int i;
458     int sl;
459     
460     sl = s->lno;
461     if(!strcmp(s->argv[0], "child")) {
462         s->expstart = 1;
463         if(s->argc < 2) {
464             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
465             skipcfblock(s);
466             return(NULL);
467         }
468         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
469         d->type = CH_SOCKET;
470     } else if(!strcmp(s->argv[0], "fchild")) {
471         s->expstart = 1;
472         if(s->argc < 2) {
473             flog(LOG_WARNING, "%s:%i: missing name in child declaration", s->file, s->lno);
474             skipcfblock(s);
475             return(NULL);
476         }
477         ch = newchild(s->argv[1], &stdhandler, omalloc(d));
478         d->type = CH_FORK;
479     } else {
480         return(NULL);
481     }
482     d->fd = -1;
483     
484     bufinit(envbuf);
485     while(1) {
486         getcfline(s);
487         if(!strcmp(s->argv[0], "exec")) {
488             if(s->argc < 2) {
489                 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", s->file, s->lno);
490                 continue;
491             }
492             d->argv = szmalloc(sizeof(*d->argv) * s->argc);
493             for(i = 0; i < s->argc - 1; i++)
494                 d->argv[i] = sstrdup(s->argv[i + 1]);
495         } else if(!strcmp(s->argv[0], "env")) {
496             if(s->argc < 3) {
497                 flog(LOG_WARNING, "%s:%i: too few parameters to `env'", s->file, s->lno);
498                 continue;
499             }
500             bufadd(envbuf, sstrdup(s->argv[1]));
501             bufadd(envbuf, sstrdup(s->argv[2]));
502         } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
503             break;
504         } else {
505             flog(LOG_WARNING, "%s:%i: unknown directive `%s' in child declaration", s->file, s->lno, s->argv[0]);
506         }
507     }
508     bufadd(envbuf, NULL);
509     d->envp = envbuf.b;
510     if(d->argv == NULL) {
511         flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", s->file, sl, ch->name);
512         freechild(ch);
513         return(NULL);
514     }
515     return(ch);
516 }
517
518 int childhandle(struct child *ch, struct hthead *req, int fd, void (*chinit)(void *), void *idata)
519 {
520     return(ch->iface->handle(ch, req, fd, chinit, idata));
521 }