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