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