449 lines
14 KiB
Go
449 lines
14 KiB
Go
package rewrite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/request"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// stringRewriter rewrites a string
|
|
type stringRewriter interface {
|
|
rewriteString(src string) string
|
|
}
|
|
|
|
// regexStringRewriter can be used to rewrite strings by regex pattern.
|
|
// it contains all the information required to detect and execute a rewrite
|
|
// on a string.
|
|
type regexStringRewriter struct {
|
|
pattern *regexp.Regexp
|
|
replacement string
|
|
}
|
|
|
|
var _ stringRewriter = ®exStringRewriter{}
|
|
|
|
func newStringRewriter(pattern *regexp.Regexp, replacement string) stringRewriter {
|
|
return ®exStringRewriter{pattern, replacement}
|
|
}
|
|
|
|
func (r *regexStringRewriter) rewriteString(src string) string {
|
|
regexGroups := r.pattern.FindStringSubmatch(src)
|
|
if len(regexGroups) == 0 {
|
|
return src
|
|
}
|
|
s := r.replacement
|
|
for groupIndex, groupValue := range regexGroups {
|
|
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
|
|
s = strings.Replace(s, groupIndexStr, groupValue, -1)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// remapStringRewriter maps a dedicated string to another string
|
|
// it also maps a the domain of a sub domain.
|
|
type remapStringRewriter struct {
|
|
orig string
|
|
replacement string
|
|
}
|
|
|
|
var _ stringRewriter = &remapStringRewriter{}
|
|
|
|
func newRemapStringRewriter(orig, replacement string) stringRewriter {
|
|
return &remapStringRewriter{orig, replacement}
|
|
}
|
|
|
|
func (r *remapStringRewriter) rewriteString(src string) string {
|
|
if src == r.orig {
|
|
return r.replacement
|
|
}
|
|
if strings.HasSuffix(src, "."+r.orig) {
|
|
return src[0:len(src)-len(r.orig)] + r.replacement
|
|
}
|
|
return src
|
|
}
|
|
|
|
// suffixStringRewriter maps a dedicated suffix string to another string
|
|
type suffixStringRewriter struct {
|
|
suffix string
|
|
replacement string
|
|
}
|
|
|
|
var _ stringRewriter = &suffixStringRewriter{}
|
|
|
|
func newSuffixStringRewriter(orig, replacement string) stringRewriter {
|
|
return &suffixStringRewriter{orig, replacement}
|
|
}
|
|
|
|
func (r *suffixStringRewriter) rewriteString(src string) string {
|
|
if strings.HasSuffix(src, r.suffix) {
|
|
return strings.TrimSuffix(src, r.suffix) + r.replacement
|
|
}
|
|
return src
|
|
}
|
|
|
|
// nameRewriterResponseRule maps a record name according to a stringRewriter.
|
|
type nameRewriterResponseRule struct {
|
|
stringRewriter
|
|
}
|
|
|
|
func (r *nameRewriterResponseRule) RewriteResponse(rr dns.RR) {
|
|
rr.Header().Name = r.rewriteString(rr.Header().Name)
|
|
}
|
|
|
|
// valueRewriterResponseRule maps a record value according to a stringRewriter.
|
|
type valueRewriterResponseRule struct {
|
|
stringRewriter
|
|
}
|
|
|
|
func (r *valueRewriterResponseRule) RewriteResponse(rr dns.RR) {
|
|
value := getRecordValueForRewrite(rr)
|
|
if value != "" {
|
|
new := r.rewriteString(value)
|
|
if new != value {
|
|
setRewrittenRecordValue(rr, new)
|
|
}
|
|
}
|
|
}
|
|
|
|
const (
|
|
// ExactMatch matches only on exact match of the name in the question section of a request
|
|
ExactMatch = "exact"
|
|
// PrefixMatch matches when the name begins with the matching string
|
|
PrefixMatch = "prefix"
|
|
// SuffixMatch matches when the name ends with the matching string
|
|
SuffixMatch = "suffix"
|
|
// SubstringMatch matches on partial match of the name in the question section of a request
|
|
SubstringMatch = "substring"
|
|
// RegexMatch matches when the name in the question section of a request matches a regular expression
|
|
RegexMatch = "regex"
|
|
|
|
// AnswerMatch matches an answer rewrite
|
|
AnswerMatch = "answer"
|
|
// AutoMatch matches the auto name answer rewrite
|
|
AutoMatch = "auto"
|
|
// NameMatch matches the name answer rewrite
|
|
NameMatch = "name"
|
|
// ValueMatch matches the value answer rewrite
|
|
ValueMatch = "value"
|
|
)
|
|
|
|
type nameRuleBase struct {
|
|
nextAction string
|
|
auto bool
|
|
replacement string
|
|
static ResponseRules
|
|
}
|
|
|
|
func newNameRuleBase(nextAction string, auto bool, replacement string, staticResponses ResponseRules) nameRuleBase {
|
|
return nameRuleBase{
|
|
nextAction: nextAction,
|
|
auto: auto,
|
|
replacement: replacement,
|
|
static: staticResponses,
|
|
}
|
|
}
|
|
|
|
// responseRuleFor create for auto mode dynamically response rewriters for name and value
|
|
// reverting the mapping done by the name rewrite rule, which can be found in the state.
|
|
func (rule *nameRuleBase) responseRuleFor(state request.Request) (ResponseRules, Result) {
|
|
if !rule.auto {
|
|
return rule.static, RewriteDone
|
|
}
|
|
|
|
rewriter := newRemapStringRewriter(state.Req.Question[0].Name, state.Name())
|
|
rules := ResponseRules{
|
|
&nameRewriterResponseRule{rewriter},
|
|
&valueRewriterResponseRule{rewriter},
|
|
}
|
|
return append(rules, rule.static...), RewriteDone
|
|
}
|
|
|
|
// Mode returns the processing nextAction
|
|
func (rule *nameRuleBase) Mode() string { return rule.nextAction }
|
|
|
|
// exactNameRule rewrites the current request based upon exact match of the name
|
|
// in the question section of the request.
|
|
type exactNameRule struct {
|
|
nameRuleBase
|
|
from string
|
|
}
|
|
|
|
func newExactNameRule(nextAction string, orig, replacement string, answers ResponseRules) Rule {
|
|
return &exactNameRule{
|
|
newNameRuleBase(nextAction, true, replacement, answers),
|
|
orig,
|
|
}
|
|
}
|
|
|
|
func (rule *exactNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
if rule.from == state.Name() {
|
|
state.Req.Question[0].Name = rule.replacement
|
|
return rule.responseRuleFor(state)
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// prefixNameRule rewrites the current request when the name begins with the matching string.
|
|
type prefixNameRule struct {
|
|
nameRuleBase
|
|
prefix string
|
|
}
|
|
|
|
func newPrefixNameRule(nextAction string, auto bool, prefix, replacement string, answers ResponseRules) Rule {
|
|
return &prefixNameRule{
|
|
newNameRuleBase(nextAction, auto, replacement, answers),
|
|
prefix,
|
|
}
|
|
}
|
|
|
|
func (rule *prefixNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
if strings.HasPrefix(state.Name(), rule.prefix) {
|
|
state.Req.Question[0].Name = rule.replacement + strings.TrimPrefix(state.Name(), rule.prefix)
|
|
return rule.responseRuleFor(state)
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// suffixNameRule rewrites the current request when the name ends with the matching string.
|
|
type suffixNameRule struct {
|
|
nameRuleBase
|
|
suffix string
|
|
}
|
|
|
|
func newSuffixNameRule(nextAction string, auto bool, suffix, replacement string, answers ResponseRules) Rule {
|
|
var rules ResponseRules
|
|
if auto {
|
|
// for a suffix rewriter better standard response rewrites can be done
|
|
// just by using the original suffix/replacement in the opposite order
|
|
rewriter := newSuffixStringRewriter(replacement, suffix)
|
|
rules = ResponseRules{
|
|
&nameRewriterResponseRule{rewriter},
|
|
&valueRewriterResponseRule{rewriter},
|
|
}
|
|
}
|
|
return &suffixNameRule{
|
|
newNameRuleBase(nextAction, false, replacement, append(rules, answers...)),
|
|
suffix,
|
|
}
|
|
}
|
|
|
|
func (rule *suffixNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
if strings.HasSuffix(state.Name(), rule.suffix) {
|
|
state.Req.Question[0].Name = strings.TrimSuffix(state.Name(), rule.suffix) + rule.replacement
|
|
return rule.responseRuleFor(state)
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// substringNameRule rewrites the current request based upon partial match of the
|
|
// name in the question section of the request.
|
|
type substringNameRule struct {
|
|
nameRuleBase
|
|
substring string
|
|
}
|
|
|
|
func newSubstringNameRule(nextAction string, auto bool, substring, replacement string, answers ResponseRules) Rule {
|
|
return &substringNameRule{
|
|
newNameRuleBase(nextAction, auto, replacement, answers),
|
|
substring,
|
|
}
|
|
}
|
|
|
|
func (rule *substringNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
if strings.Contains(state.Name(), rule.substring) {
|
|
state.Req.Question[0].Name = strings.Replace(state.Name(), rule.substring, rule.replacement, -1)
|
|
return rule.responseRuleFor(state)
|
|
}
|
|
return nil, RewriteIgnored
|
|
}
|
|
|
|
// regexNameRule rewrites the current request when the name in the question
|
|
// section of the request matches a regular expression.
|
|
type regexNameRule struct {
|
|
nameRuleBase
|
|
pattern *regexp.Regexp
|
|
}
|
|
|
|
func newRegexNameRule(nextAction string, auto bool, pattern *regexp.Regexp, replacement string, answers ResponseRules) Rule {
|
|
return ®exNameRule{
|
|
newNameRuleBase(nextAction, auto, replacement, answers),
|
|
pattern,
|
|
}
|
|
}
|
|
|
|
func (rule *regexNameRule) Rewrite(ctx context.Context, state request.Request) (ResponseRules, Result) {
|
|
regexGroups := rule.pattern.FindStringSubmatch(state.Name())
|
|
if len(regexGroups) == 0 {
|
|
return nil, RewriteIgnored
|
|
}
|
|
s := rule.replacement
|
|
for groupIndex, groupValue := range regexGroups {
|
|
groupIndexStr := "{" + strconv.Itoa(groupIndex) + "}"
|
|
s = strings.Replace(s, groupIndexStr, groupValue, -1)
|
|
}
|
|
state.Req.Question[0].Name = s
|
|
return rule.responseRuleFor(state)
|
|
}
|
|
|
|
// newNameRule creates a name matching rule based on exact, partial, or regex match
|
|
func newNameRule(nextAction string, args ...string) (Rule, error) {
|
|
var matchType, rewriteQuestionFrom, rewriteQuestionTo string
|
|
if len(args) < 2 {
|
|
return nil, fmt.Errorf("too few arguments for a name rule")
|
|
}
|
|
if len(args) == 2 {
|
|
matchType = ExactMatch
|
|
rewriteQuestionFrom = plugin.Name(args[0]).Normalize()
|
|
rewriteQuestionTo = plugin.Name(args[1]).Normalize()
|
|
}
|
|
if len(args) >= 3 {
|
|
matchType = strings.ToLower(args[0])
|
|
if matchType == RegexMatch {
|
|
rewriteQuestionFrom = args[1]
|
|
rewriteQuestionTo = args[2]
|
|
} else {
|
|
rewriteQuestionFrom = plugin.Name(args[1]).Normalize()
|
|
rewriteQuestionTo = plugin.Name(args[2]).Normalize()
|
|
}
|
|
}
|
|
if matchType == ExactMatch || matchType == SuffixMatch {
|
|
if !hasClosingDot(rewriteQuestionFrom) {
|
|
rewriteQuestionFrom = rewriteQuestionFrom + "."
|
|
}
|
|
if !hasClosingDot(rewriteQuestionTo) {
|
|
rewriteQuestionTo = rewriteQuestionTo + "."
|
|
}
|
|
}
|
|
|
|
var err error
|
|
var answers ResponseRules
|
|
auto := false
|
|
if len(args) > 3 {
|
|
auto, answers, err = parseAnswerRules(matchType, args[3:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
switch matchType {
|
|
case ExactMatch:
|
|
if _, err := isValidRegexPattern(rewriteQuestionTo, rewriteQuestionFrom); err != nil {
|
|
return nil, err
|
|
}
|
|
return newExactNameRule(nextAction, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
|
|
case PrefixMatch:
|
|
return newPrefixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
|
|
case SuffixMatch:
|
|
return newSuffixNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
|
|
case SubstringMatch:
|
|
return newSubstringNameRule(nextAction, auto, rewriteQuestionFrom, rewriteQuestionTo, answers), nil
|
|
case RegexMatch:
|
|
rewriteQuestionFromPattern, err := isValidRegexPattern(rewriteQuestionFrom, rewriteQuestionTo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rewriteQuestionTo := plugin.Name(args[2]).Normalize()
|
|
return newRegexNameRule(nextAction, auto, rewriteQuestionFromPattern, rewriteQuestionTo, answers), nil
|
|
default:
|
|
return nil, fmt.Errorf("name rule supports only exact, prefix, suffix, substring, and regex name matching, received: %s", matchType)
|
|
}
|
|
}
|
|
|
|
func parseAnswerRules(name string, args []string) (auto bool, rules ResponseRules, err error) {
|
|
auto = false
|
|
arg := 0
|
|
nameRules := 0
|
|
last := ""
|
|
if len(args) < 2 {
|
|
return false, nil, fmt.Errorf("invalid arguments for %s rule", name)
|
|
}
|
|
for arg < len(args) {
|
|
if last == "" && args[arg] != AnswerMatch {
|
|
if last == "" {
|
|
return false, nil, fmt.Errorf("exceeded the number of arguments for a non-answer rule argument for %s rule", name)
|
|
}
|
|
return false, nil, fmt.Errorf("exceeded the number of arguments for %s answer rule for %s rule", last, name)
|
|
}
|
|
if args[arg] == AnswerMatch {
|
|
arg++
|
|
}
|
|
if len(args)-arg == 0 {
|
|
return false, nil, fmt.Errorf("type missing for answer rule for %s rule", name)
|
|
}
|
|
last = args[arg]
|
|
arg++
|
|
switch last {
|
|
case AutoMatch:
|
|
auto = true
|
|
continue
|
|
case NameMatch:
|
|
if len(args)-arg < 2 {
|
|
return false, nil, fmt.Errorf("%s answer rule for %s rule: 2 arguments required", last, name)
|
|
}
|
|
rewriteAnswerFrom := args[arg]
|
|
rewriteAnswerTo := args[arg+1]
|
|
rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
|
|
rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize()
|
|
if err != nil {
|
|
return false, nil, fmt.Errorf("%s answer rule for %s rule: %s", last, name, err)
|
|
}
|
|
rules = append(rules, &nameRewriterResponseRule{newStringRewriter(rewriteAnswerFromPattern, rewriteAnswerTo)})
|
|
arg += 2
|
|
nameRules++
|
|
case ValueMatch:
|
|
if len(args)-arg < 2 {
|
|
return false, nil, fmt.Errorf("%s answer rule for %s rule: 2 arguments required", last, name)
|
|
}
|
|
rewriteAnswerFrom := args[arg]
|
|
rewriteAnswerTo := args[arg+1]
|
|
rewriteAnswerFromPattern, err := isValidRegexPattern(rewriteAnswerFrom, rewriteAnswerTo)
|
|
rewriteAnswerTo = plugin.Name(rewriteAnswerTo).Normalize()
|
|
if err != nil {
|
|
return false, nil, fmt.Errorf("%s answer rule for %s rule: %s", last, name, err)
|
|
}
|
|
rules = append(rules, &valueRewriterResponseRule{newStringRewriter(rewriteAnswerFromPattern, rewriteAnswerTo)})
|
|
arg += 2
|
|
default:
|
|
return false, nil, fmt.Errorf("invalid type %q for answer rule for %s rule", last, name)
|
|
}
|
|
}
|
|
|
|
if auto && nameRules > 0 {
|
|
return false, nil, fmt.Errorf("auto name answer rule cannot be combined with explicit name anwer rules")
|
|
}
|
|
return
|
|
}
|
|
|
|
// hasClosingDot returns true if s has a closing dot at the end.
|
|
func hasClosingDot(s string) bool {
|
|
return strings.HasSuffix(s, ".")
|
|
}
|
|
|
|
// getSubExprUsage returns the number of subexpressions used in s.
|
|
func getSubExprUsage(s string) int {
|
|
subExprUsage := 0
|
|
for i := 0; i <= 100; i++ {
|
|
if strings.Contains(s, "{"+strconv.Itoa(i)+"}") {
|
|
subExprUsage++
|
|
}
|
|
}
|
|
return subExprUsage
|
|
}
|
|
|
|
// isValidRegexPattern returns a regular expression for pattern matching or errors, if any.
|
|
func isValidRegexPattern(rewriteFrom, rewriteTo string) (*regexp.Regexp, error) {
|
|
rewriteFromPattern, err := regexp.Compile(rewriteFrom)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid regex matching pattern: %s", rewriteFrom)
|
|
}
|
|
if getSubExprUsage(rewriteTo) > rewriteFromPattern.NumSubexp() {
|
|
return nil, fmt.Errorf("the rewrite regex pattern (%s) uses more subexpressions than its corresponding matching regex pattern (%s)", rewriteTo, rewriteFrom)
|
|
}
|
|
return rewriteFromPattern, nil
|
|
}
|