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