patplex: Add reparse action.
[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>
7a3871f6 27#include <limits.h>
578ad6b1 28#include <sys/wait.h>
326e08fc
FT
29
30#ifdef HAVE_CONFIG_H
31#include <config.h>
32#endif
33#include <utils.h>
34#include <log.h>
35#include <req.h>
36#include <proc.h>
37#include <resp.h>
06c1a718 38#include <cf.h>
326e08fc
FT
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
326e08fc
FT
45
46#define PATFL_MSS 1
4dc7f716 47#define PATFL_UNQ 2
326e08fc 48
597edd91
FT
49#define HND_CHILD 1
50#define HND_REPARSE 2
51
326e08fc
FT
52struct config {
53 struct child *children;
54 struct pattern *patterns;
55};
56
326e08fc
FT
57struct rule {
58 int type;
59 int fl;
60 char *header;
61 regex_t *pattern;
62};
63
acc2d159
FT
64struct headmod {
65 struct headmod *next;
66 char *name, *value;
67};
68
326e08fc
FT
69struct pattern {
70 struct pattern *next;
acc2d159 71 struct headmod *headers;
326e08fc
FT
72 char *childnm;
73 struct rule **rules;
74 char *restpat;
597edd91 75 int handler, prio, disable;
326e08fc
FT
76};
77
0fc6fd13 78static struct config *gconfig, *lconfig;
3a42b6b1 79static volatile int reload = 0;
326e08fc
FT
80
81static void freepattern(struct pattern *pat)
82{
83 struct rule **rule;
acc2d159 84 struct headmod *head;
326e08fc
FT
85
86 for(rule = pat->rules; *rule; rule++) {
87 if((*rule)->header != NULL)
88 free((*rule)->header);
89 if((*rule)->pattern != NULL) {
90 regfree((*rule)->pattern);
91 free((*rule)->pattern);
92 }
93 free(*rule);
94 }
acc2d159
FT
95 while((head = pat->headers) != NULL) {
96 pat->headers = head->next;
97 free(head->name);
98 free(head->value);
99 free(head);
100 }
326e08fc
FT
101 if(pat->childnm != NULL)
102 free(pat->childnm);
103 free(pat);
104}
105
3a42b6b1
FT
106static void freeconfig(struct config *cf)
107{
108 struct child *ch, *nch;
109 struct pattern *pat, *npat;
110
111 for(ch = cf->children; ch != NULL; ch = nch) {
112 nch = ch->next;
113 freechild(ch);
114 }
115 for(pat = cf->patterns; pat != NULL; pat = npat) {
116 npat = pat->next;
117 freepattern(pat);
118 }
119 free(cf);
120}
121
326e08fc
FT
122static struct child *getchild(struct config *cf, char *name)
123{
124 struct child *ch;
125
126 for(ch = cf->children; ch; ch = ch->next) {
127 if(!strcmp(ch->name, name))
128 break;
129 }
130 return(ch);
131}
132
133static struct rule *newrule(struct pattern *pat)
134{
135 int i;
136 struct rule *rule;
137
138 for(i = 0; pat->rules[i]; i++);
139 pat->rules = srealloc(pat->rules, sizeof(*pat->rules) * (i + 2));
140 rule = pat->rules[i] = szmalloc(sizeof(*rule));
141 pat->rules[i + 1] = NULL;
142 return(rule);
143}
144
145static struct pattern *newpattern(void)
146{
147 struct pattern *pat;
148
149 omalloc(pat);
150 pat->rules = szmalloc(sizeof(*pat->rules));
151 return(pat);
152}
153
154static regex_t *regalloc(char *regex, int flags)
155{
156 regex_t *ret;
157 int res;
158 char errbuf[256];
159
160 omalloc(ret);
161 if((res = regcomp(ret, regex, flags | REG_EXTENDED)) != 0) {
162 regerror(res, ret, errbuf, sizeof(errbuf));
163 flog(LOG_WARNING, "%s: %s", regex, errbuf);
164 free(ret);
165 return(NULL);
166 }
167 return(ret);
168}
169
06c1a718 170static struct pattern *parsepattern(struct cfstate *s)
326e08fc 171{
326e08fc 172 struct pattern *pat;
06c1a718 173 int sl;
326e08fc 174 struct rule *rule;
acc2d159 175 struct headmod *head;
326e08fc
FT
176 regex_t *regex;
177 int rxfl;
178
06c1a718
FT
179 if(!strcmp(s->argv[0], "match")) {
180 s->expstart = 1;
181 pat = newpattern();
182 } else {
326e08fc 183 return(NULL);
06c1a718
FT
184 }
185
186 sl = s->lno;
187 while(1) {
188 getcfline(s);
189 if(!strcmp(s->argv[0], "point") ||
190 !strcmp(s->argv[0], "url") ||
191 !strcmp(s->argv[0], "method")) {
192 if(s->argc < 2) {
193 flog(LOG_WARNING, "%s:%i: missing pattern for `%s' match", s->file, s->lno, s->argv[0]);
326e08fc 194 continue;
326e08fc 195 }
7026d187 196 rxfl = 0;
06c1a718
FT
197 if(s->argc >= 3) {
198 if(strchr(s->argv[2], 'i'))
199 rxfl |= REG_ICASE;
326e08fc 200 }
06c1a718
FT
201 if((regex = regalloc(s->argv[1], rxfl)) == NULL) {
202 flog(LOG_WARNING, "%s:%i: invalid regex for `%s' match", s->file, s->lno, s->argv[0]);
203 continue;
204 }
205 rule = newrule(pat);
206 if(!strcmp(s->argv[0], "point"))
207 rule->type = PAT_REST;
208 else if(!strcmp(s->argv[0], "url"))
209 rule->type = PAT_URL;
210 else if(!strcmp(s->argv[0], "method"))
211 rule->type = PAT_METHOD;
212 rule->pattern = regex;
213 if(s->argc >= 3) {
214 if(strchr(s->argv[2], 's'))
215 rule->fl |= PATFL_MSS;
4dc7f716
FT
216 if(strchr(s->argv[2], 'q'))
217 rule->fl |= PATFL_UNQ;
06c1a718
FT
218 }
219 } else if(!strcmp(s->argv[0], "header")) {
220 if(s->argc < 3) {
221 flog(LOG_WARNING, "%s:%i: missing header name or pattern for `header' match", s->file, s->lno);
222 continue;
223 }
7026d187 224 rxfl = 0;
06c1a718
FT
225 if(s->argc >= 4) {
226 if(strchr(s->argv[3], 'i'))
227 rxfl |= REG_ICASE;
228 }
229 if((regex = regalloc(s->argv[2], rxfl)) == NULL) {
230 flog(LOG_WARNING, "%s:%i: invalid regex for `header' match", s->file, s->lno);
231 continue;
232 }
233 rule = newrule(pat);
234 rule->type = PAT_HEADER;
235 rule->header = sstrdup(s->argv[1]);
236 rule->pattern = regex;
237 if(s->argc >= 4) {
238 if(strchr(s->argv[3], 's'))
239 rule->fl |= PATFL_MSS;
326e08fc 240 }
06c1a718
FT
241 } else if(!strcmp(s->argv[0], "all")) {
242 newrule(pat)->type = PAT_ALL;
243 } else if(!strcmp(s->argv[0], "default")) {
7a3871f6
FT
244 newrule(pat)->type = PAT_ALL;
245 pat->prio = -10;
246 } else if(!strcmp(s->argv[0], "order") || !strcmp(s->argv[0], "priority")) {
247 if(s->argc < 2) {
248 flog(LOG_WARNING, "%s:%i: missing specification for `%s' directive", s->file, s->lno, s->argv[0]);
249 continue;
250 }
251 pat->prio = atoi(s->argv[1]);
06c1a718
FT
252 } else if(!strcmp(s->argv[0], "handler")) {
253 if(s->argc < 2) {
254 flog(LOG_WARNING, "%s:%i: missing child name for `handler' directive", s->file, s->lno);
255 continue;
256 }
257 if(pat->childnm != NULL)
258 free(pat->childnm);
259 pat->childnm = sstrdup(s->argv[1]);
597edd91
FT
260 pat->handler = HND_CHILD;
261 } else if(!strcmp(s->argv[0], "reparse")) {
262 pat->handler = HND_REPARSE;
06c1a718
FT
263 } else if(!strcmp(s->argv[0], "restpat")) {
264 if(s->argc < 2) {
265 flog(LOG_WARNING, "%s:%i: missing pattern for `restpat' directive", s->file, s->lno);
266 continue;
267 }
268 if(pat->restpat != NULL)
269 free(pat->restpat);
270 pat->restpat = sstrdup(s->argv[1]);
8cc893f5 271 } else if(!strcmp(s->argv[0], "set") || !strcmp(s->argv[0], "xset")) {
acc2d159 272 if(s->argc < 3) {
8cc893f5 273 flog(LOG_WARNING, "%s:%i: missing header name or pattern for `%s' directive", s->file, s->lno, s->argv[0]);
acc2d159
FT
274 continue;
275 }
276 omalloc(head);
8cc893f5
FT
277 if(!strcmp(s->argv[0], "xset"))
278 head->name = sprintf2("X-Ash-%s", s->argv[1]);
279 else
280 head->name = sstrdup(s->argv[1]);
acc2d159
FT
281 head->value = sstrdup(s->argv[2]);
282 head->next = pat->headers;
283 pat->headers = head;
06c1a718
FT
284 } else if(!strcmp(s->argv[0], "end") || !strcmp(s->argv[0], "eof")) {
285 break;
286 } else {
287 flog(LOG_WARNING, "%s:%i: unknown directive `%s' in pattern declaration", s->file, s->lno, s->argv[0]);
326e08fc 288 }
06c1a718
FT
289 }
290
291 if(pat->rules[0] == NULL) {
292 flog(LOG_WARNING, "%s:%i: missing rules in match declaration", s->file, sl);
293 freepattern(pat);
294 return(NULL);
295 }
597edd91 296 if(pat->handler == 0) {
06c1a718
FT
297 flog(LOG_WARNING, "%s:%i: missing handler in match declaration", s->file, sl);
298 freepattern(pat);
299 return(NULL);
300 }
301 return(pat);
302}
303
304static struct config *readconfig(char *filename)
305{
306 struct cfstate *s;
307 struct config *cf;
308 struct child *ch;
309 struct pattern *pat;
310 FILE *in;
326e08fc 311
06c1a718
FT
312 if((in = fopen(filename, "r")) == NULL) {
313 flog(LOG_WARNING, "%s: %s", filename, strerror(errno));
314 return(NULL);
315 }
316 s = mkcfparser(in, filename);
317 omalloc(cf);
318
319 while(1) {
320 getcfline(s);
321 if((ch = parsechild(s)) != NULL) {
322 ch->next = cf->children;
323 cf->children = ch;
324 } else if((pat = parsepattern(s)) != NULL) {
325 pat->next = cf->patterns;
326 cf->patterns = pat;
327 } else if(!strcmp(s->argv[0], "eof")) {
328 break;
329 } else {
330 flog(LOG_WARNING, "%s:%i: unknown directive `%s'", s->file, s->lno, s->argv[0]);
331 }
332 }
333
334 freecfparser(s);
335 fclose(in);
326e08fc
FT
336 return(cf);
337}
338
339static void exprestpat(struct hthead *req, struct pattern *pat, char **mstr)
340{
341 char *p, *p2, *hdr;
342 int mc;
343 struct charbuf buf;
344
345 if(mstr == NULL)
346 mc = 0;
347 else
348 for(mc = 0; mstr[mc]; mc++);
349 bufinit(buf);
350 for(p = pat->restpat; *p; ) {
351 if(*p == '$') {
352 p++;
353 if((*p >= '0') && (*p <= '9')) {
354 if(*p - '0' < mc)
355 bufcatstr(buf, mstr[*p - '0']);
356 p++;
357 } else if(*p == '_') {
358 bufcatstr(buf, req->rest);
359 p++;
360 } else if(*p == '$') {
361 bufadd(buf, '$');
362 p++;
363 } else if(*p == '{') {
06c1a718 364 if((p2 = strchr(p, '}')) == NULL) {
326e08fc
FT
365 p++;
366 } else {
06c1a718 367 hdr = getheader(req, sprintf3("%.*s", p2 - p - 1, p + 1));
326e08fc
FT
368 if(hdr)
369 bufcatstr(buf, hdr);
370 }
371 } else if(!*p) {
372 }
373 } else {
374 bufadd(buf, *(p++));
375 }
376 }
377 bufadd(buf, 0);
378 replrest(req, buf.b);
379 buffree(buf);
380}
381
4dc7f716
FT
382static void qoffsets(char *buf, int *obuf, char *pstr, int unquote)
383{
384 int i, o, d1, d2;
385
386 if(unquote) {
387 i = o = 0;
388 while(pstr[i]) {
389 obuf[o] = i;
390 if((pstr[i] == '%') && ((d1 = hexdigit(pstr[i + 1])) >= 0) && ((d2 = hexdigit(pstr[i + 2])) >= 0)) {
391 buf[o] = (d1 << 4) | d2;
392 i += 3;
393 } else {
394 buf[o] = pstr[i];
395 i++;
396 }
397 o++;
398 }
399 buf[o] = 0;
8ea85a4e 400 obuf[o] = i;
4dc7f716
FT
401 } else {
402 for(i = 0; pstr[i]; i++) {
403 buf[i] = pstr[i];
404 obuf[i] = i;
405 }
406 buf[i] = 0;
8ea85a4e 407 obuf[i] = i;
4dc7f716
FT
408 }
409}
410
7a3871f6
FT
411struct match {
412 struct pattern *pat;
413 char **mstr;
414 int rmo;
415};
416
417static void freematch(struct match *match)
418{
419 freeca(match->mstr);
420 free(match);
421}
422
423static struct match *findmatch(struct config *cf, struct hthead *req, struct match *match)
326e08fc
FT
424{
425 int i, o;
426 struct pattern *pat;
427 struct rule *rule;
4dc7f716
FT
428 int rmo;
429 regex_t *rx;
326e08fc
FT
430 char *pstr;
431 char **mstr;
432 regmatch_t gr[10];
433
434 mstr = NULL;
435 for(pat = cf->patterns; pat != NULL; pat = pat->next) {
597edd91 436 if(pat->disable || (match && (pat->prio <= match->pat->prio)))
7a3871f6 437 continue;
326e08fc
FT
438 rmo = -1;
439 for(i = 0; (rule = pat->rules[i]) != NULL; i++) {
4dc7f716 440 rx = NULL;
326e08fc 441 if(rule->type == PAT_REST) {
4dc7f716
FT
442 rx = rule->pattern;
443 pstr = req->rest;
326e08fc 444 } else if(rule->type == PAT_URL) {
4dc7f716
FT
445 rx = rule->pattern;
446 pstr = req->url;
326e08fc 447 } else if(rule->type == PAT_METHOD) {
4dc7f716
FT
448 rx = rule->pattern;
449 pstr = req->method;
326e08fc 450 } else if(rule->type == PAT_HEADER) {
4dc7f716 451 rx = rule->pattern;
326e08fc
FT
452 if(!(pstr = getheader(req, rule->header)))
453 break;
4dc7f716
FT
454 }
455 if(rx != NULL) {
456 char pbuf[strlen(pstr) + 1];
457 int obuf[strlen(pstr) + 1];
458 qoffsets(pbuf, obuf, pstr, !!(rule->fl & PATFL_UNQ));
459 if(regexec(rx, pbuf, 10, gr, 0))
326e08fc 460 break;
4dc7f716
FT
461 else if(rule->type == PAT_REST)
462 rmo = obuf[gr[0].rm_eo];
463 if(rule->fl & PATFL_MSS) {
464 if(mstr) {
465 flog(LOG_WARNING, "two pattern rules marked with `s' flag found (for handler %s)", pat->childnm);
466 freeca(mstr);
467 }
468 for(o = 0; o < 10; o++) {
469 if(gr[o].rm_so < 0)
470 break;
471 }
472 mstr = szmalloc((o + 1) * sizeof(*mstr));
473 for(o = 0; o < 10; o++) {
474 if(gr[o].rm_so < 0)
475 break;
476 mstr[o] = smalloc(obuf[gr[o].rm_eo] - obuf[gr[o].rm_so] + 1);
477 memcpy(mstr[o], pstr + obuf[gr[o].rm_so], obuf[gr[o].rm_eo] - obuf[gr[o].rm_so]);
478 mstr[o][obuf[gr[o].rm_eo] - obuf[gr[o].rm_so]] = 0;
479 }
480 }
326e08fc 481 } else if(rule->type == PAT_ALL) {
326e08fc 482 }
326e08fc
FT
483 }
484 if(!rule) {
7a3871f6
FT
485 if(match)
486 freematch(match);
487 omalloc(match);
488 match->pat = pat;
489 match->mstr = mstr;
490 match->rmo = rmo;
326e08fc
FT
491 }
492 }
7a3871f6
FT
493 return(match);
494}
495
496static void execmatch(struct hthead *req, struct match *match)
497{
498 struct headmod *head;
499
500 if(match->pat->restpat)
501 exprestpat(req, match->pat, match->mstr);
502 else if(match->rmo != -1)
503 replrest(req, req->rest + match->rmo);
504 for(head = match->pat->headers; head != NULL; head = head->next) {
505 headrmheader(req, head->name);
506 headappheader(req, head->name, head->value);
507 }
326e08fc
FT
508}
509
f2f009c9
FT
510static void childerror(struct hthead *req, int fd)
511{
512 if(errno == EAGAIN)
513 simpleerror(fd, 500, "Server Error", "The request handler is overloaded.");
514 else
515 simpleerror(fd, 500, "Server Error", "The request handler crashed.");
516}
517
326e08fc
FT
518static void serve(struct hthead *req, int fd)
519{
7a3871f6 520 struct match *match;
326e08fc
FT
521 struct child *ch;
522
7a3871f6
FT
523 match = NULL;
524 match = findmatch(lconfig, req, match);
525 if(gconfig != NULL)
526 match = findmatch(gconfig, req, match);
527 if(match == NULL) {
326e08fc
FT
528 simpleerror(fd, 404, "Not Found", "The requested resource could not be found on this server.");
529 return;
530 }
7a3871f6 531 execmatch(req, match);
597edd91
FT
532 switch(match->pat->handler) {
533 case HND_CHILD:
534 ch = NULL;
535 if(ch == NULL)
536 ch = getchild(lconfig, match->pat->childnm);
537 if((ch == NULL) && (gconfig != NULL))
538 ch = getchild(gconfig, match->pat->childnm);
539 if(ch == NULL) {
540 flog(LOG_ERR, "child %s requested, but was not declared", match->pat->childnm);
541 simpleerror(fd, 500, "Configuration Error", "The server is erroneously configured. Handler %s was requested, but not declared.", match->pat->childnm);
542 break;
543 }
544 if(childhandle(ch, req, fd, NULL, NULL))
545 childerror(req, fd);
546 break;
547 case HND_REPARSE:
548 match->pat->disable = 1;
549 serve(req, fd);
550 match->pat->disable = 0;
551 break;
552 default:
553 abort();
554 }
7a3871f6 555 freematch(match);
326e08fc
FT
556}
557
3a42b6b1
FT
558static void reloadconf(char *nm)
559{
560 struct config *cf;
3a42b6b1
FT
561
562 if((cf = readconfig(nm)) == NULL) {
563 flog(LOG_WARNING, "could not reload configuration file `%s'", nm);
564 return;
565 }
1924fe8c 566 mergechildren(cf->children, lconfig->children);
3a42b6b1
FT
567 freeconfig(lconfig);
568 lconfig = cf;
569}
570
578ad6b1
FT
571static void chldhandler(int sig)
572{
573 pid_t pid;
3d6044ab 574 int st;
578ad6b1 575
3d6044ab
FT
576 while((pid = waitpid(-1, &st, WNOHANG)) > 0) {
577 if(WCOREDUMP(st))
578 flog(LOG_WARNING, "child process %i dumped core", pid);
579 }
578ad6b1
FT
580}
581
3a42b6b1
FT
582static void sighandler(int sig)
583{
584 if(sig == SIGHUP)
585 reload = 1;
586}
587
0fc6fd13
FT
588static void usage(FILE *out)
589{
590 fprintf(out, "usage: patplex [-hN] CONFIGFILE\n");
591}
592
326e08fc
FT
593int main(int argc, char **argv)
594{
0fc6fd13
FT
595 int c;
596 int nodef;
518c678c 597 char *gcf, *lcf;
326e08fc
FT
598 struct hthead *req;
599 int fd;
0fc6fd13
FT
600
601 nodef = 0;
602 while((c = getopt(argc, argv, "hN")) >= 0) {
603 switch(c) {
604 case 'h':
605 usage(stdout);
606 exit(0);
607 case 'N':
608 nodef = 1;
609 break;
610 default:
611 usage(stderr);
612 exit(1);
613 }
614 }
615 if(argc - optind < 1) {
616 usage(stderr);
326e08fc
FT
617 exit(1);
618 }
0fc6fd13
FT
619 if(!nodef) {
620 if((gcf = findstdconf("ashd/patplex.rc")) != NULL) {
621 gconfig = readconfig(gcf);
622 free(gcf);
623 }
624 }
518c678c
FT
625 if((strchr(lcf = argv[optind], '/')) == NULL) {
626 if((lcf = findstdconf(sprintf3("ashd/%s", lcf))) == NULL) {
627 flog(LOG_ERR, "could not find requested configuration file `%s'", argv[optind]);
628 exit(1);
629 }
630 }
631 if((lconfig = readconfig(lcf)) == NULL) {
632 flog(LOG_ERR, "could not read `%s'", lcf);
3a42b6b1
FT
633 exit(1);
634 }
578ad6b1 635 signal(SIGCHLD, chldhandler);
3a42b6b1 636 signal(SIGHUP, sighandler);
fd735432 637 signal(SIGPIPE, sighandler);
326e08fc 638 while(1) {
3a42b6b1 639 if(reload) {
518c678c 640 reloadconf(lcf);
3a42b6b1
FT
641 reload = 0;
642 }
326e08fc 643 if((fd = recvreq(0, &req)) < 0) {
6174a039
FT
644 if(errno == EINTR)
645 continue;
326e08fc
FT
646 if(errno != 0)
647 flog(LOG_ERR, "recvreq: %s", strerror(errno));
648 break;
649 }
650 serve(req, fd);
651 freehthead(req);
652 close(fd);
653 }
654 return(0);
655}