mirror of
https://github.com/coredns/coredns.git
synced 2026-08-20 23:08:28 -04:00
* plugin/template: Add expr-lang variables Signed-off-by: Andri Yngvason <andri@yngvason.is> * plugin/template: Add extra expressions that must match Signed-off-by: Andri Yngvason <andri@yngvason.is> * plugin/template: README: Add embedded device resolution example Signed-off-by: Andri Yngvason <andri@yngvason.is> --------- Signed-off-by: Andri Yngvason <andri@yngvason.is>
225 lines
5.8 KiB
Go
225 lines
5.8 KiB
Go
package template
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
gotmpl "text/template"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/pkg/upstream"
|
|
|
|
"github.com/expr-lang/expr"
|
|
"github.com/expr-lang/expr/ast"
|
|
"github.com/expr-lang/expr/builtin"
|
|
"github.com/expr-lang/expr/parser"
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// maxRegexpLen is a hard limit on the length of a regex pattern to prevent
|
|
// OOM during regex compilation with malicious input.
|
|
const maxRegexpLen = 10000
|
|
|
|
var varNameRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
|
|
|
func isExprIdentifier(name string) bool {
|
|
tree, err := parser.Parse(name)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
ident, ok := tree.Node.(*ast.IdentifierNode)
|
|
return ok && ident.Value == name
|
|
}
|
|
|
|
func init() { plugin.Register("template", setupTemplate) }
|
|
|
|
func setupTemplate(c *caddy.Controller) error {
|
|
handler, err := templateParse(c)
|
|
if err != nil {
|
|
return plugin.Error("template", err)
|
|
}
|
|
|
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
|
handler.Next = next
|
|
return handler
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func templateParse(c *caddy.Controller) (handler Handler, err error) {
|
|
handler.Templates = make([]template, 0)
|
|
|
|
for c.Next() {
|
|
if !c.NextArg() {
|
|
return handler, c.ArgErr()
|
|
}
|
|
class, ok := dns.StringToClass[c.Val()]
|
|
if !ok {
|
|
return handler, c.Errf("invalid query class %s", c.Val())
|
|
}
|
|
|
|
if !c.NextArg() {
|
|
return handler, c.ArgErr()
|
|
}
|
|
qtype, ok := dns.StringToType[c.Val()]
|
|
if !ok {
|
|
return handler, c.Errf("invalid RR class %s", c.Val())
|
|
}
|
|
|
|
zones := plugin.OriginsFromArgsOrServerBlock(c.RemainingArgs(), c.ServerBlockKeys)
|
|
handler.Zones = append(handler.Zones, zones...)
|
|
t := template{qclass: class, qtype: qtype, zones: zones}
|
|
|
|
t.regex = make([]*regexp.Regexp, 0)
|
|
templatePrefix := ""
|
|
|
|
t.answer = make([]*gotmpl.Template, 0)
|
|
t.upstream = upstream.New()
|
|
|
|
varEnv := exprEnv(context.Background(), nil, &templateData{})
|
|
|
|
for c.NextBlock() {
|
|
switch c.Val() {
|
|
case "match":
|
|
args := c.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
for _, regex := range args {
|
|
if len(regex) > maxRegexpLen {
|
|
return handler, c.Errf("regex pattern too long: %d > %d", len(regex), maxRegexpLen)
|
|
}
|
|
r, err := regexp.Compile(regex)
|
|
if err != nil {
|
|
return handler, c.Errf("could not parse regex: %s, %v", regex, err)
|
|
}
|
|
templatePrefix = templatePrefix + regex + " "
|
|
t.regex = append(t.regex, r)
|
|
}
|
|
|
|
case "answer":
|
|
args := c.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
for _, answer := range args {
|
|
tmpl, err := newTemplate("answer", answer)
|
|
if err != nil {
|
|
return handler, c.Errf("could not compile template: %s, %v", c.Val(), err)
|
|
}
|
|
t.answer = append(t.answer, tmpl)
|
|
}
|
|
|
|
case "additional":
|
|
args := c.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
for _, additional := range args {
|
|
tmpl, err := newTemplate("additional", additional)
|
|
if err != nil {
|
|
return handler, c.Errf("could not compile template: %s, %v\n", c.Val(), err)
|
|
}
|
|
t.additional = append(t.additional, tmpl)
|
|
}
|
|
|
|
case "authority":
|
|
args := c.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
for _, authority := range args {
|
|
tmpl, err := newTemplate("authority", authority)
|
|
if err != nil {
|
|
return handler, c.Errf("could not compile template: %s, %v\n", c.Val(), err)
|
|
}
|
|
t.authority = append(t.authority, tmpl)
|
|
}
|
|
|
|
case "var":
|
|
args := c.RemainingArgs()
|
|
if len(args) < 2 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
if !varNameRegexp.MatchString(args[0]) {
|
|
return handler, c.Errf("invalid variable name %q", args[0])
|
|
}
|
|
_, isEnv := varEnv[args[0]]
|
|
_, isBuiltin := builtin.Index[args[0]]
|
|
if isEnv || isBuiltin || !isExprIdentifier(args[0]) {
|
|
return handler, c.Errf("variable name %q is reserved", args[0])
|
|
}
|
|
prog, err := expr.Compile(strings.Join(args[1:], " "), expr.Env(varEnv), expr.DisableBuiltin("type"))
|
|
if err != nil {
|
|
return handler, c.Errf("could not compile expression: %s, %v", args[0], err)
|
|
}
|
|
if rt := prog.Node().Type(); rt == nil || rt.Kind() == reflect.Interface {
|
|
varEnv[args[0]] = new(any)
|
|
} else {
|
|
varEnv[args[0]] = reflect.Zero(rt).Interface()
|
|
}
|
|
t.vars = append(t.vars, variable{name: args[0], prog: prog})
|
|
|
|
case "expr":
|
|
args := c.RemainingArgs()
|
|
if len(args) == 0 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
prog, err := expr.Compile(strings.Join(args, " "), expr.Env(varEnv), expr.DisableBuiltin("type"))
|
|
if err != nil {
|
|
return handler, c.Errf("could not compile expression: %v", err)
|
|
}
|
|
t.exprs = append(t.exprs, prog)
|
|
|
|
case "rcode":
|
|
if !c.NextArg() {
|
|
return handler, c.ArgErr()
|
|
}
|
|
rcode, ok := dns.StringToRcode[c.Val()]
|
|
if !ok {
|
|
return handler, c.Errf("unknown rcode %s", c.Val())
|
|
}
|
|
t.rcode = rcode
|
|
|
|
case "ederror":
|
|
args := c.RemainingArgs()
|
|
if len(args) != 1 && len(args) != 2 {
|
|
return handler, c.ArgErr()
|
|
}
|
|
|
|
code, err := strconv.ParseUint(args[0], 10, 16)
|
|
if err != nil {
|
|
return handler, c.Errf("error parsing extended DNS error code %s, %v\n", c.Val(), err)
|
|
}
|
|
if len(args) == 2 {
|
|
t.ederror = &ederror{code: uint16(code), reason: args[1]}
|
|
} else {
|
|
t.ederror = &ederror{code: uint16(code)}
|
|
}
|
|
|
|
case "fallthrough":
|
|
t.fall.SetZonesFromArgs(c.RemainingArgs())
|
|
|
|
case "upstream":
|
|
// remove soon
|
|
c.RemainingArgs()
|
|
default:
|
|
return handler, c.ArgErr()
|
|
}
|
|
}
|
|
|
|
if len(t.regex) == 0 {
|
|
t.regex = append(t.regex, regexp.MustCompile(".*"))
|
|
}
|
|
|
|
handler.Templates = append(handler.Templates, t)
|
|
}
|
|
|
|
return handler, nil
|
|
}
|