/*
* Copyright 2011 NCHOVY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.rule.http;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Validate;
import org.krakenapps.ahocorasick.AhoCorasickSearch;
import org.krakenapps.ahocorasick.Pair;
import org.krakenapps.ahocorasick.Pattern;
import org.krakenapps.ahocorasick.SearchContext;
import org.krakenapps.rule.Rule;
import org.krakenapps.rule.RuleDatabase;
import org.krakenapps.rule.RuleGroup;
import org.krakenapps.rule.http.VariableRegexRule.ParameterValue;
import org.krakenapps.rule.parser.GenericRule;
import org.krakenapps.rule.parser.GenericRuleOption;
import org.krakenapps.rule.parser.GenericRuleSyntax;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "http-rule-engine")
@Provides
public class DefaultHttpRuleEngine implements HttpRuleEngine {
private final Logger logger = LoggerFactory.getLogger(DefaultHttpRuleEngine.class.getName());
private BundleContext bc;
private GenericRuleSyntax syntax = new GenericRuleSyntax();
private volatile AhoCorasickSearch acm = null;
private volatile Map<String, List<HttpRequestRule>> requestRuleMap = new ConcurrentHashMap<String, List<HttpRequestRule>>();
private volatile Map<String, List<HttpResponseRule>> responseRuleMap = new ConcurrentHashMap<String, List<HttpResponseRule>>();
public DefaultHttpRuleEngine(BundleContext bc) {
this.bc = bc;
}
@Override
public String getName() {
return "http";
}
@Override
public String getDescription() {
return "Default http rule engine";
}
@Override
public Collection<Rule> getRules() {
List<Rule> rules = new LinkedList<Rule>();
for (List<HttpRequestRule> r : requestRuleMap.values())
rules.addAll(r);
for (List<HttpResponseRule> r : responseRuleMap.values())
rules.addAll(r);
return rules;
}
@Override
public void reload() {
File dir = new File(System.getProperty("kraken.data.dir"), "kraken-http-rule");
dir.mkdirs();
AhoCorasickSearch s = new AhoCorasickSearch();
Map<String, List<HttpRequestRule>> reqs = new ConcurrentHashMap<String, List<HttpRequestRule>>();
Map<String, List<HttpResponseRule>> resps = new ConcurrentHashMap<String, List<HttpResponseRule>>();
File[] files = dir.listFiles(new RuleFileFilter());
for (File f : files) {
loadRules(s, reqs, resps, f);
}
RuleDatabase ruleDb = getRuleDatabase();
if (ruleDb != null) {
for (RuleGroup group : ruleDb.getRuleGroups("http")) {
for (Rule rule : group.getRules()) {
try {
parse(s, reqs, resps, rule.toString());
} catch (ParseException e) {
}
}
}
}
s.compile();
this.acm = s;
this.requestRuleMap = reqs;
this.responseRuleMap = resps;
}
private RuleDatabase getRuleDatabase() {
String className = RuleDatabase.class.getName();
ServiceReference ref = bc.getServiceReference(className);
if (ref == null)
return null;
return (RuleDatabase) bc.getService(ref);
}
@Validate
public void start() {
reload();
}
private void loadRules(AhoCorasickSearch fsm, Map<String, List<HttpRequestRule>> reqs,
Map<String, List<HttpResponseRule>> resps, File f) {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
while (true) {
String line = br.readLine();
if (line == null)
break;
parse(fsm, reqs, resps, line);
}
} catch (Exception e) {
logger.error("kraken http rule: cannot open http-rule file", e);
} finally {
try {
if (br != null)
br.close();
} catch (IOException e) {
}
}
}
private void parse(AhoCorasickSearch fsm, Map<String, List<HttpRequestRule>> reqs,
Map<String, List<HttpResponseRule>> resps, String line) throws ParseException {
line = line.trim();
if (line.isEmpty() || line.startsWith(";"))
return;
GenericRule r = syntax.eval(line);
Rule rule = null;
String type = r.get("type");
String path = r.get("path");
if (type.equals("rfi")) {
String var = r.get("var");
rule = new RemoteFileInclusionRule(r.getId(), r.getMessage(), path, var);
} else if (type.equals("lfi")) {
Map<String, String> params = new HashMap<String, String>();
String name = null;
for (GenericRuleOption o : r.getOptions()) {
if (o.getName().equals("var")) {
if (name != null)
params.put(name, null);
name = o.getValue();
} else if (name != null && o.getName().equals("value")) {
params.put(name, o.getValue());
name = null;
}
}
if (name != null)
params.put(name, null);
rule = new LocalFileInclusionRule(r.getId(), r.getMessage(), path, params);
} else if (type.equals("regex")) {
Map<String, ParameterValue> params = new HashMap<String, VariableRegexRule.ParameterValue>();
String name = null;
for (GenericRuleOption o : r.getOptions()) {
if (o.getName().equals("var")) {
if (name != null)
params.put(name, null);
name = o.getValue();
} else if (name != null && o.getName().equals("value")) {
params.put(name, new ParameterValue(o.getValue()));
name = null;
} else if (name != null && o.getName().equals("regex")) {
params.put(name, new ParameterValue(o.getValue(), true));
name = null;
}
}
if (name != null)
params.put(name, null);
rule = new VariableRegexRule(r.getId(), r.getMessage(), path, params);
}
try {
if (rule != null) {
rule.getReferences().addAll(convert(r.getAll("reference")));
rule.getCveNames().addAll(r.getAll("cve"));
fsm.addKeyword(new RulePattern(path, rule));
if (!reqs.containsKey(rule.getId()))
reqs.put(rule.getId(), new ArrayList<HttpRequestRule>());
reqs.get(rule.getId()).add((HttpRequestRule) rule);
}
} catch (UnsupportedEncodingException e) {
}
}
private Collection<URL> convert(Collection<String> references) {
List<URL> urls = new ArrayList<URL>();
for (String reference : references) {
try {
urls.add(new URL(reference));
} catch (MalformedURLException e) {
}
}
return urls;
}
@Override
public Collection<HttpRequestRule> getRequestRules() {
List<HttpRequestRule> rules = new LinkedList<HttpRequestRule>();
for (List<HttpRequestRule> r : requestRuleMap.values()) {
rules.addAll(r);
}
return rules;
}
@Override
public Collection<HttpRequestRule> getRequestRules(String id) {
return requestRuleMap.get(id);
}
@Override
public Collection<HttpResponseRule> getResponseRules() {
List<HttpResponseRule> rules = new LinkedList<HttpResponseRule>();
for (List<HttpResponseRule> r : responseRuleMap.values()) {
rules.addAll(r);
}
return rules;
}
@Override
public Collection<HttpResponseRule> getResponseRules(String id) {
return responseRuleMap.get(id);
}
@Override
public Collection<HttpRequestRule> matchAll(HttpRequestContext context) {
Set<HttpRequestRule> matches = new HashSet<HttpRequestRule>();
byte[] bytes = getPathBytes(context.getPath());
SearchContext sctx = new SearchContext();
sctx.setIncludeFailurePatterns(true);
List<Pair> pairs = acm.search(bytes, sctx);
for (Pair p : pairs) {
Rule rule = ((RulePattern) p.getPattern()).getRule();
HttpRequestRule httpRule = (HttpRequestRule) rule;
if (httpRule.match(context))
matches.add(httpRule);
}
return matches;
}
@Override
public HttpRequestRule match(HttpRequestContext context) {
byte[] bytes = getPathBytes(context.getPath());
SearchContext sctx = new SearchContext();
sctx.setIncludeFailurePatterns(true);
List<Pair> pairs = acm.search(bytes, sctx);
for (Pair p : pairs) {
Rule rule = ((RulePattern) p.getPattern()).getRule();
HttpRequestRule httpRule = (HttpRequestRule) rule;
if (httpRule.match(context))
return httpRule;
}
return null;
}
private byte[] getPathBytes(String path) {
byte[] bytes = null;
try {
bytes = path.getBytes("utf-8");
} catch (UnsupportedEncodingException e) {
}
return bytes;
}
@Override
public HttpResponseRule match(HttpRequestContext req, HttpResponseContext resp) {
throw new UnsupportedOperationException("not implemented yet");
}
@Override
public Collection<HttpResponseRule> matchAll(HttpRequestContext req, HttpResponseContext resp) {
throw new UnsupportedOperationException("not implemented yet");
}
private static class RuleFileFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".rules");
}
}
private static class RulePattern implements Pattern {
private byte[] b;
private Rule rule;
public RulePattern(String path, Rule rule) throws UnsupportedEncodingException {
this.b = path.getBytes("utf-8");
this.rule = rule;
}
public Rule getRule() {
return rule;
}
@Override
public byte[] getKeyword() {
return b;
}
@Override
public String toString() {
return rule.toString();
}
}
}