Added per protocol responseToDSL function + misc cleanup with operators

dev
Ice3man543 2020-12-21 15:51:43 +05:30
parent d631074e35
commit ed84bb187b
6 changed files with 99 additions and 152 deletions

View File

@ -47,16 +47,5 @@ func (m *Matcher) CompileMatchers() error {
} else {
m.condition = ORCondition
}
// Setup the part of the request to match, if any.
if m.Part != "" {
m.part, ok = PartTypes[m.Part]
if !ok {
return fmt.Errorf("unknown matcher part specified: %s", m.Part)
}
} else {
m.part = BodyPart
}
return nil
}

View File

@ -19,27 +19,27 @@ func (m *Matcher) Match(resp *http.Response, body, headers string, duration time
return m.isNegative(m.matchSizeCode(len(body)))
case WordsMatcher:
// Match the parts as required for word check
if m.part == BodyPart {
if m.Part == "body" {
return m.isNegative(m.matchWords(body))
} else if m.part == HeaderPart {
} else if m.Part == "header" {
return m.isNegative(m.matchWords(headers))
} else {
return m.isNegative(m.matchWords(headers) || m.matchWords(body))
}
case RegexMatcher:
// Match the parts as required for regex check
if m.part == BodyPart {
if m.Part == "body" {
return m.isNegative(m.matchRegex(body))
} else if m.part == HeaderPart {
} else if m.Part == "header" {
return m.isNegative(m.matchRegex(headers))
} else {
return m.isNegative(m.matchRegex(headers) || m.matchRegex(body))
}
case BinaryMatcher:
// Match the parts as required for binary characters check
if m.part == BodyPart {
if m.Part == "body" {
return m.isNegative(m.matchBinary(body))
} else if m.part == HeaderPart {
} else if m.Part == "header" {
return m.isNegative(m.matchBinary(headers))
} else {
return m.isNegative(m.matchBinary(headers) || m.matchBinary(body))
@ -55,7 +55,8 @@ func (m *Matcher) Match(resp *http.Response, body, headers string, duration time
// MatchDNS matches a dns response against a given matcher
func (m *Matcher) MatchDNS(msg *dns.Msg) bool {
switch m.matcherType {
// [WIP] add dns status code matcher
case StatusMatcher:
return m.isNegative(m.matchStatusCode(msg.Rcode))
case SizeMatcher:
return m.matchSizeCode(msg.Len())
case WordsMatcher:
@ -71,7 +72,6 @@ func (m *Matcher) MatchDNS(msg *dns.Msg) bool {
// Match complex query
return m.matchDSL(DNSToMap(msg, ""))
}
return false
}

View File

@ -6,12 +6,21 @@ import (
"github.com/Knetic/govaluate"
)
// Matcher is used to identify whether a template was successful.
// Matcher is used to match a part in the output from a protocol.
type Matcher struct {
// Type is the type of the matcher
Type string `yaml:"type"`
// matcherType is the internal type of the matcher
matcherType MatcherType
// Condition is the optional condition between two matcher variables
//
// By default, the condition is assumed to be OR.
Condition string `yaml:"condition,omitempty"`
// Part is the part of the data to match
Part string `yaml:"part,omitempty"`
// Negative specifies if the match should be reversed
// It will only match if the condition is not true.
Negative bool `yaml:"negative,omitempty"`
// Name is matcher Name
Name string `yaml:"name,omitempty"`
@ -23,32 +32,16 @@ type Matcher struct {
Words []string `yaml:"words,omitempty"`
// Regex are the regex pattern required to be present in the response
Regex []string `yaml:"regex,omitempty"`
// regexCompiled is the compiled variant
regexCompiled []*regexp.Regexp
// Binary are the binary characters required to be present in the response
Binary []string `yaml:"binary,omitempty"`
// DSL are the dsl queries
DSL []string `yaml:"dsl,omitempty"`
// dslCompiled is the compiled variant
dslCompiled []*govaluate.EvaluableExpression
// Condition is the optional condition between two matcher variables
//
// By default, the condition is assumed to be OR.
Condition string `yaml:"condition,omitempty"`
// condition is the condition of the matcher
condition ConditionType
// Part is the part of the request to match
//
// By default, matching is performed in request body.
Part string `yaml:"part,omitempty"`
// part is the part of the request to match
part Part
// Negative specifies if the match should be reversed
// It will only match if the condition is not true.
Negative bool `yaml:"negative,omitempty"`
// cached data for the compiled matcher
condition ConditionType
matcherType MatcherType
regexCompiled []*regexp.Regexp
dslCompiled []*govaluate.EvaluableExpression
}
// MatcherType is the type of the matcher specified
@ -95,30 +88,6 @@ var ConditionTypes = map[string]ConditionType{
"or": ORCondition,
}
// Part is the part of the request to match
type Part int
const (
// BodyPart matches body of the response.
BodyPart Part = iota + 1
// HeaderPart matches headers of the response.
HeaderPart
// AllPart matches both response body and headers of the response.
AllPart
)
// PartTypes is an table for conversion of part type from string.
var PartTypes = map[string]Part{
"body": BodyPart,
"header": HeaderPart,
"all": AllPart,
}
// GetPart returns the part of the matcher
func (m *Matcher) GetPart() Part {
return m.part
}
// isNegative reverts the results of the match if the matcher
// is of type negative.
func (m *Matcher) isNegative(data bool) bool {

View File

@ -1,85 +0,0 @@
package matchers
import (
"fmt"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/miekg/dns"
)
const defaultFormat = "%s"
// HTTPToMap Converts HTTP to Matcher Map
func HTTPToMap(resp *http.Response, body, headers string, duration time.Duration, format string) (m map[string]interface{}) {
m = make(map[string]interface{})
if format == "" {
format = defaultFormat
}
m[fmt.Sprintf(format, "content_length")] = resp.ContentLength
m[fmt.Sprintf(format, "status_code")] = resp.StatusCode
for k, v := range resp.Header {
k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_")))
m[fmt.Sprintf(format, k)] = strings.Join(v, " ")
}
m[fmt.Sprintf(format, "all_headers")] = headers
m[fmt.Sprintf(format, "body")] = body
if r, err := httputil.DumpResponse(resp, true); err == nil {
m[fmt.Sprintf(format, "raw")] = string(r)
}
// Converts duration to seconds (floating point) for DSL syntax
m[fmt.Sprintf(format, "duration")] = duration.Seconds()
return m
}
// DNSToMap Converts DNS to Matcher Map
func DNSToMap(msg *dns.Msg, format string) (m map[string]interface{}) {
m = make(map[string]interface{})
if format == "" {
format = defaultFormat
}
m[fmt.Sprintf(format, "rcode")] = msg.Rcode
var qs string
for _, question := range msg.Question {
qs += fmt.Sprintln(question.String())
}
m[fmt.Sprintf(format, "question")] = qs
var exs string
for _, extra := range msg.Extra {
exs += fmt.Sprintln(extra.String())
}
m[fmt.Sprintf(format, "extra")] = exs
var ans string
for _, answer := range msg.Answer {
ans += fmt.Sprintln(answer.String())
}
m[fmt.Sprintf(format, "answer")] = ans
var nss string
for _, ns := range msg.Ns {
nss += fmt.Sprintln(ns.String())
}
m[fmt.Sprintf(format, "ns")] = nss
m[fmt.Sprintf(format, "raw")] = msg.String()
return m
}

View File

@ -0,0 +1,42 @@
package dns
import (
"bytes"
"github.com/miekg/dns"
)
// responseToDSLMap converts a DNS response to a map for use in DSL matching
func responseToDSLMap(msg *dns.Msg) map[string]interface{} {
data := make(map[string]interface{}, 6)
data["rcode"] = msg.Rcode
buffer := &bytes.Buffer{}
for _, question := range msg.Question {
buffer.WriteString(question.String())
}
data["question"] = buffer.String()
buffer.Reset()
for _, extra := range msg.Extra {
buffer.WriteString(extra.String())
}
data["extra"] = buffer.String()
buffer.Reset()
for _, answer := range msg.Answer {
buffer.WriteString(answer.String())
}
data["answer"] = buffer.String()
buffer.Reset()
for _, ns := range msg.Ns {
buffer.WriteString(ns.String())
}
data["ns"] = buffer.String()
buffer.Reset()
data["raw"] = msg.String()
return data
}

View File

@ -0,0 +1,32 @@
package http
import (
"net/http"
"net/http/httputil"
"strings"
"time"
)
// responseToDSLMap converts a HTTP response to a map for use in DSL matching
func responseToDSLMap(resp *http.Response, body, headers string, duration time.Duration, extra map[string]interface{}) map[string]interface{} {
data := make(map[string]interface{}, len(extra)+6+len(resp.Header))
for k, v := range extra {
data[k] = v
}
data["content_length"] = resp.ContentLength
data["status_code"] = resp.StatusCode
for k, v := range resp.Header {
k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_")))
data[k] = strings.Join(v, " ")
}
data["all_headers"] = headers
data["body"] = body
if r, err := httputil.DumpResponse(resp, true); err == nil {
data["raw"] = string(r)
}
data["duration"] = duration.Seconds()
return data
}