Added a simple patplex.
[ashd.git] / src / patplex.c
CommitLineData
326e08fc
FT
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 <string.h>
22#include <unistd.h>
23#include <signal.h>
24#include <errno.h>
25#include <ctype.h>
26#include <regex.h>
27
28#ifdef HAVE_CONFIG_H
29#include <config.h>
30#endif
31#include <utils.h>
32#include <log.h>
33#include <req.h>
34#include <proc.h>
35#include <resp.h>
36
37#define CH_SOCKET 0
38#define CH_FORK 1
39
40#define PAT_REST 0
41#define PAT_URL 1
42#define PAT_METHOD 2
43#define PAT_HEADER 3
44#define PAT_ALL 4
45#define PAT_DEFAULT 5
46
47#define PATFL_MSS 1
48
49struct config {
50 struct child *children;
51 struct pattern *patterns;
52};
53
54struct child {
55 struct child *next;
56 char *name;
57 int type;
58 char **argv;
59 int fd;
60};
61
62struct rule {
63 int type;
64 int fl;
65 char *header;
66 regex_t *pattern;
67};
68
69struct pattern {
70 struct pattern *next;
71 char *childnm;
72 struct rule **rules;
73 char *restpat;
74};
75
76static struct config *config;
77
78static void freepattern(struct pattern *pat)
79{
80 struct rule **rule;
81
82 for(rule = pat->rules; *rule; rule++) {
83 if((*rule)->header != NULL)
84 free((*rule)->header);
85 if((*rule)->pattern != NULL) {
86 regfree((*rule)->pattern);
87 free((*rule)->pattern);
88 }
89 free(*rule);
90 }
91 if(pat->childnm != NULL)
92 free(pat->childnm);
93 free(pat);
94}
95
96static void freechild(struct child *ch)
97{
98 if(ch->fd != -1)
99 close(ch->fd);
100 if(ch->name != NULL)
101 free(ch->name);
102 if(ch->argv != NULL)
103 freeca(ch->argv);
104 free(ch);
105}
106
107static struct child *newchild(char *name, int type)
108{
109 struct child *ch;
110
111 omalloc(ch);
112 ch->name = sstrdup(name);
113 ch->type = type;
114 ch->fd = -1;
115 return(ch);
116}
117
118static struct child *getchild(struct config *cf, char *name)
119{
120 struct child *ch;
121
122 for(ch = cf->children; ch; ch = ch->next) {
123 if(!strcmp(ch->name, name))
124 break;
125 }
126 return(ch);
127}
128
129static struct rule *newrule(struct pattern *pat)
130{
131 int i;
132 struct rule *rule;
133
134 for(i = 0; pat->rules[i]; i++);
135 pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2));
136 rule = pat->rules[i] = szmalloc(sizeof(*rule));
137 pat->rules[i + 1] = NULL;
138 return(rule);
139}
140
141static struct pattern *newpattern(void)
142{
143 struct pattern *pat;
144
145 omalloc(pat);
146 pat->rules = szmalloc(sizeof(*pat->rules));
147 return(pat);
148}
149
150static regex_t *regalloc(char *regex, int flags)
151{
152 regex_t *ret;
153 int res;
154 char errbuf[256];
155
156 omalloc(ret);
157 if((res = regcomp(ret, regex, flags | REG_EXTENDED)) != 0) {
158 regerror(res, ret, errbuf, sizeof(errbuf));
159 flog(LOG_WARNING, "%s: %s", regex, errbuf);
160 free(ret);
161 return(NULL);
162 }
163 return(ret);
164}
165
166static struct config *readconfig(char *filename)
167{
168 int i;
169 struct config *cf;
170 FILE *s;
171 char line[1024];
172 char *p, **w;
173 int ind, eof;
174 int lno;
175 int state;
176 int rv;
177 int argc;
178 struct child *child;
179 struct pattern *pat;
180 struct rule *rule;
181 regex_t *regex;
182 int rxfl;
183
184 if((s = fopen(filename, "r")) == NULL)
185 return(NULL);
186 omalloc(cf);
187 eof = 0;
188 state = 0;
189 w = NULL;
190 lno = 0;
191 do {
192 if(fgets(line, sizeof(line), s) == NULL) {
193 eof = 1;
194 line[0] = 0;
195 }
196 lno++;
197 for(p = line; *p; p++) {
198 if(*p == '#')
199 continue;
200 if(!isspace(*p))
201 break;
202 }
203 ind = isspace(line[0]);
204 w = tokenize(line);
205 argc = calen(w);
206
207 retry:
208 if(state == 0) {
209 if(ind) {
210 flog(LOG_WARNING, "%s%i: unexpected line indentation in global scope", filename, lno);
211 goto next;
212 } else {
213 if(!w[0]) {
214 } else if(!strcmp(w[0], "child")) {
215 if(argc < 2) {
216 flog(LOG_WARNING, "%s:%i: missing name in child declaration", filename, lno);
217 goto next;
218 }
219 child = newchild(w[1], CH_SOCKET);
220 state = 1;
221 } else if(!strcmp(w[0], "fchild")) {
222 if(argc < 2) {
223 flog(LOG_WARNING, "%s:%i: missing name in child declaration", filename, lno);
224 goto next;
225 }
226 child = newchild(w[1], CH_FORK);
227 state = 1;
228 } else if(!strcmp(w[0], "match")) {
229 pat = newpattern();
230 state = 2;
231 } else {
232 flog(LOG_WARNING, "%s:%i: unknown directive %s", filename, lno, w[0]);
233 }
234 }
235 } else if(state == 1) {
236 if(ind) {
237 if(!w[0]) {
238 } else if(!strcmp(w[0], "exec")) {
239 if(argc < 2) {
240 flog(LOG_WARNING, "%s:%i: too few parameters to `exec'", filename, lno);
241 goto next;
242 }
243 child->argv = szmalloc(sizeof(*child->argv) * argc);
244 for(i = 0; i < argc - 1; i++)
245 child->argv[i] = sstrdup(w[i + 1]);
246 } else {
247 flog(LOG_WARNING, "%s:%i: unknown directive %s", filename, lno, w[0]);
248 }
249 } else {
250 state = 0;
251 if(child->argv == NULL) {
252 flog(LOG_WARNING, "%s:%i: missing `exec' in child declaration %s", filename, lno, child->name);
253 freechild(child);
254 goto retry;
255 }
256 child->next = cf->children;
257 cf->children = child;
258 goto retry;
259 }
260 } else if(state == 2) {
261 if(ind) {
262 rxfl = 0;
263 if(!w[0]) {
264 } else if(!strcmp(w[0], "point") ||
265 !strcmp(w[0], "url") ||
266 !strcmp(w[0], "method")) {
267 if(argc < 2) {
268 flog(LOG_WARNING, "%s:%i: missing pattern for `%s' match", w[0], filename, lno);
269 goto next;
270 }
271 if(argc >= 3) {
272 if(strchr(w[2], 'i'))
273 rxfl |= REG_ICASE;
274 }
275 if((regex = regalloc(w[1], rxfl)) == NULL) {
276 flog(LOG_WARNING, "%s:%i: invalid regex for `%s' match", w[0], filename, lno);
277 goto next;
278 }
279 rule = newrule(pat);
280 if(!strcmp(w[0], "point"))
281 rule->type = PAT_REST;
282 else if(!strcmp(w[0], "url"))
283 rule->type = PAT_URL;
284 else if(!strcmp(w[0], "method"))
285 rule->type = PAT_METHOD;
286 rule->pattern = regex;
287 if(argc >= 3) {
288 if(strchr(w[2], 's'))
289 rule->fl |= PATFL_MSS;
290 }
291 } else if(!strcmp(w[0], "header")) {
292 if(argc < 3) {
293 flog(LOG_WARNING, "%s:%i: missing header name or pattern for `header' match", filename, lno);
294 goto next;
295 }
296 if(argc >= 4) {
297 if(strchr(w[3], 'i'))
298 rxfl |= REG_ICASE;
299 }
300 if((regex = regalloc(w[2], rxfl)) == NULL) {
301 flog(LOG_WARNING, "%s:%i: invalid regex for `header' match", filename, lno);
302 goto next;
303 }
304 rule = newrule(pat);
305 rule->type = PAT_HEADER;
306 rule->header = sstrdup(w[1]);
307 rule->pattern = regex;
308 if(argc >= 4) {
309 if(strchr(w[3], 's'))
310 rule->fl |= PATFL_MSS;
311 }
312 } else if(!strcmp(w[0], "all")) {
313 newrule(pat)->type = PAT_ALL;
314 } else if(!strcmp(w[0], "default")) {
315 newrule(pat)->type = PAT_DEFAULT;
316 } else if(!strcmp(w[0], "handler")) {
317 if(argc < 2) {
318 flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", filename, lno);
319 goto next;
320 }
321 if(pat->childnm != NULL)
322 free(pat->childnm);
323 pat->childnm = sstrdup(w[1]);
324 } else if(!strcmp(w[0], "restpat")) {
325 if(argc < 2) {
326 flog(LOG_WARNING, "%s:%i: missing pattern for `restpat' directive", filename, lno);
327 goto next;
328 }
329 if(pat->restpat != NULL)
330 free(pat->restpat);
331 pat->restpat = sstrdup(w[1]);
332 } else {
333 flog(LOG_WARNING, "%s:%i: unknown directive %s", filename, lno, w[0]);
334 }
335 } else {
336 state = 0;
337 if(pat->rules[0] == NULL) {
338 flog(LOG_WARNING, "%s:%i: missing rules in match declaration", filename, lno);
339 freepattern(pat);
340 goto retry;
341 }
342 if(pat->childnm == NULL) {
343 flog(LOG_WARNING, "%s:%i: missing handler in match declaration", filename, lno);
344 freepattern(pat);
345 goto retry;
346 }
347 pat->next = cf->patterns;
348 cf->patterns = pat;
349 goto retry;
350 }
351 }
352
353 next:
354 freeca(w);
355 w = NULL;
356 } while(!eof);
357 rv = 0;
358
359 if(w != NULL)
360 freeca(w);
361 fclose(s);
362 return(cf);
363}
364
365static void exprestpat(struct hthead *req, struct pattern *pat, char **mstr)
366{
367 char *p, *p2, *hdr;
368 int mc;
369 struct charbuf buf;
370
371 if(mstr == NULL)
372 mc = 0;
373 else
374 for(mc = 0; mstr[mc]; mc++);
375 bufinit(buf);
376 for(p = pat->restpat; *p; ) {
377 if(*p == '$') {
378 p++;
379 if((*p >= '0') && (*p <= '9')) {
380 if(*p - '0' < mc)
381 bufcatstr(buf, mstr[*p - '0']);
382 p++;
383 } else if(*p == '_') {
384 bufcatstr(buf, req->rest);
385 p++;
386 } else if(*p == '$') {
387 bufadd(buf, '$');
388 p++;
389 } else if(*p == '{') {
390 if((p2 = strchr(p, '{')) == NULL) {
391 p++;
392 } else {
393 hdr = getheader(req, sprintf3("$.*s", p2 - p - 1, p + 1));
394 if(hdr)
395 bufcatstr(buf, hdr);
396 }
397 } else if(!*p) {
398 }
399 } else {
400 bufadd(buf, *(p++));
401 }
402 }
403 bufadd(buf, 0);
404 replrest(req, buf.b);
405 buffree(buf);
406}
407
408static char *findmatch(struct config *cf, struct hthead *req, int trydefault)
409{
410 int i, o;
411 struct pattern *pat;
412 struct rule *rule;
413 int rmo, matched;
414 char *pstr;
415 char **mstr;
416 regmatch_t gr[10];
417
418 mstr = NULL;
419 for(pat = cf->patterns; pat != NULL; pat = pat->next) {
420 rmo = -1;
421 for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
422 matched = 0;
423 if(rule->type == PAT_REST) {
424 if((matched = !regexec(rule->pattern, pstr = req->rest, 10, gr, 0)))
425 rmo = gr[0].rm_eo;
426 else
427 break;
428 } else if(rule->type == PAT_URL) {
429 if(!(matched = !regexec(rule->pattern, pstr = req->url, 10, gr, 0)))
430 break;
431 } else if(rule->type == PAT_METHOD) {
432 if(!(matched = !regexec(rule->pattern, pstr = req->method, 10, gr, 0)))
433 break;
434 } else if(rule->type == PAT_HEADER) {
435 if(!(pstr = getheader(req, rule->header)))
436 break;
437 if(!(matched = !regexec(rule->pattern, pstr, 10, gr, 0)))
438 break;
439 } else if(rule->type == PAT_ALL) {
440 } else if(rule->type == PAT_DEFAULT) {
441 if(!trydefault)
442 break;
443 }
444 if(matched && (rule->fl & PATFL_MSS)) {
445 if(mstr) {
446 flog(LOG_WARNING, "two pattern rules marked with `s' flag found (for handler %s)", pat->childnm);
447 freeca(mstr);
448 }
449 for(o = 0; o < 10; o++) {
450 if(gr[o].rm_so < 0)
451 break;
452 }
453 mstr = szmalloc((o + 1) * sizeof(*mstr));
454 for(o = 0; o < 10; o++) {
455 if(gr[o].rm_so < 0)
456 break;
457 mstr[o] = smalloc(gr[o].rm_eo - gr[o].rm_so + 1);
458 memcpy(mstr[o], pstr + gr[o].rm_so, gr[o].rm_eo - gr[o].rm_so);
459 mstr[o][gr[o].rm_eo - gr[o].rm_so] = 0;
460 }
461 }
462 }
463 if(!rule) {
464 if(pat->restpat) {
465 exprestpat(req, pat, mstr);
466 } else if(rmo != -1) {
467 replrest(req, req->rest + rmo);
468 }
469 if(mstr)
470 freeca(mstr);
471 return(pat->childnm);
472 }
473 if(mstr) {
474 freeca(mstr);
475 mstr = NULL;
476 }
477 }
478 return(NULL);
479}
480
481static void forkchild(struct child *ch)
482{
483 ch->fd = stdmkchild(ch->argv);
484}
485
486static void passreq(struct child *ch, struct hthead *req, int fd)
487{
488 if(ch->fd < 0)
489 forkchild(ch);
490 if(sendreq(ch->fd, req, fd)) {
491 if(errno == EPIPE) {
492 /* Assume that the child has crashed and restart it. */
493 forkchild(ch);
494 if(!sendreq(ch->fd, req, fd))
495 return;
496 }
497 flog(LOG_ERR, "could not pass on request to child %s: %s", ch->name, strerror(errno));
498 close(ch->fd);
499 ch->fd = -1;
500 }
501}
502
503static void serve(struct hthead *req, int fd)
504{
505 char *chnm;
506 struct child *ch;
507
508 if(((chnm = findmatch(config, req, 0)) == NULL) && ((chnm = findmatch(config, req, 1)) == NULL)) {
509 simpleerror(fd, 404, "Not Found", "The requested resource could not be found on this server.");
510 return;
511 }
512 if((ch = getchild(config, chnm)) == NULL) {
513 flog(LOG_ERR, "child %s requested, but was not declared", chnm);
514 simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", chnm);
515 return;
516 }
517
518 if(ch->type == CH_SOCKET) {
519 passreq(ch, req, fd);
520 } else if(ch->type == CH_FORK) {
521 stdforkserve(ch->argv, req, fd);
522 }
523}
524
525int main(int argc, char **argv)
526{
527 struct hthead *req;
528 int fd;
529
530 if(argc < 2) {
531 flog(LOG_ERR, "usage: patplex CONFIGFILE");
532 exit(1);
533 }
534 config = readconfig(argv[1]);
535 signal(SIGCHLD, SIG_IGN);
536 while(1) {
537 if((fd = recvreq(0, &req)) < 0) {
538 if(errno != 0)
539 flog(LOG_ERR, "recvreq: %s", strerror(errno));
540 break;
541 }
542 serve(req, fd);
543 freehthead(req);
544 close(fd);
545 }
546 return(0);
547}