dnscrypt-proxy/dnscrypt-proxy/plugin_block_name.go

350 lines
10 KiB
Go
Raw Normal View History

package main
import (
2018-01-17 17:03:42 +01:00
"errors"
"fmt"
"io/ioutil"
2018-01-17 17:03:42 +01:00
"net"
2018-01-17 08:46:42 +01:00
"path/filepath"
2018-01-31 22:18:11 +01:00
"strconv"
"strings"
2018-01-17 17:03:42 +01:00
"time"
2018-01-17 16:06:30 +01:00
"unicode"
"github.com/hashicorp/go-immutable-radix"
"github.com/jedisct1/dlog"
"github.com/miekg/dns"
lumberjack "gopkg.in/natefinch/lumberjack.v2"
)
type PluginBlockType int
const (
2018-01-17 17:25:43 +01:00
PluginBlockTypeNone PluginBlockType = iota
PluginBlockTypePrefix
PluginBlockTypeSuffix
PluginBlockTypeSubstring
PluginBlockTypePattern
)
type PluginBlockName struct {
blockedPrefixes *iradix.Tree
blockedSuffixes *iradix.Tree
allWeeklyRanges *map[string]WeeklyRanges
weeklyRangesIndirect map[string]*WeeklyRanges
blockedSubstrings []string
blockedPatterns []string
logger *lumberjack.Logger
format string
2018-01-31 22:18:11 +01:00
}
type TimeRange struct {
2018-01-31 22:49:40 +01:00
after int
before int
2018-01-31 22:18:11 +01:00
}
type WeeklyRanges struct {
ranges [7][]TimeRange
}
type TimeRangeStr struct {
After string
Before string
}
2018-01-31 22:49:40 +01:00
type WeeklyRangesStr struct {
Sun, Mon, Tue, Wed, Thu, Fri, Sat []TimeRangeStr
}
func (plugin *PluginBlockName) Name() string {
return "block_name"
}
func (plugin *PluginBlockName) Description() string {
return "Block DNS queries matching name patterns"
}
func (plugin *PluginBlockName) Init(proxy *Proxy) error {
dlog.Noticef("Loading the set of blocking rules from [%s]", proxy.blockNameFile)
bin, err := ioutil.ReadFile(proxy.blockNameFile)
if err != nil {
return err
}
plugin.allWeeklyRanges = proxy.allWeeklyRanges
plugin.weeklyRangesIndirect = make(map[string]*WeeklyRanges)
plugin.blockedPrefixes = iradix.New()
plugin.blockedSuffixes = iradix.New()
for lineNo, line := range strings.Split(string(bin), "\n") {
2018-01-17 16:06:30 +01:00
line = strings.TrimFunc(line, unicode.IsSpace)
if len(line) == 0 || strings.HasPrefix(line, "#") {
continue
}
2018-01-31 22:18:11 +01:00
parts := strings.Split(line, "@")
timeRangeName := ""
if len(parts) == 2 {
line = strings.TrimFunc(parts[0], unicode.IsSpace)
timeRangeName = strings.TrimFunc(parts[1], unicode.IsSpace)
} else if len(parts) > 2 {
dlog.Errorf("Syntax error in block rules at line %d -- Unexpected @ character", 1+lineNo)
continue
}
leadingStar := strings.HasPrefix(line, "*")
trailingStar := strings.HasSuffix(line, "*")
blockType := PluginBlockTypeNone
2018-01-17 08:46:42 +01:00
if isGlobCandidate(line) {
blockType = PluginBlockTypePattern
_, err := filepath.Match(line, "example.com")
if len(line) < 2 || err != nil {
2018-01-17 09:44:03 +01:00
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
2018-01-17 08:46:42 +01:00
continue
}
} else if leadingStar && trailingStar {
blockType = PluginBlockTypeSubstring
if len(line) < 3 {
2018-01-17 09:44:03 +01:00
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
continue
}
line = line[1 : len(line)-1]
} else if trailingStar {
blockType = PluginBlockTypePrefix
if len(line) < 2 {
2018-01-17 09:44:03 +01:00
dlog.Errorf("Syntax error in block rules at line %d", 1+lineNo)
continue
}
line = line[:len(line)-1]
} else {
blockType = PluginBlockTypeSuffix
if leadingStar {
line = line[1:]
}
2018-01-17 17:25:43 +01:00
line = strings.TrimPrefix(line, ".")
}
if len(line) == 0 {
2018-01-17 09:44:03 +01:00
dlog.Errorf("Syntax error in block rule at line %d", 1+lineNo)
continue
}
var weeklyRanges *WeeklyRanges
2018-01-31 22:18:11 +01:00
if len(timeRangeName) > 0 {
weeklyRangesX, ok := (*plugin.allWeeklyRanges)[timeRangeName]
2018-01-31 22:18:11 +01:00
if !ok {
dlog.Errorf("Time range [%s] not found at line %d", timeRangeName, 1+lineNo)
} else {
weeklyRanges = &weeklyRangesX
2018-01-31 22:18:11 +01:00
}
}
line = strings.ToLower(line)
switch blockType {
case PluginBlockTypeSubstring:
plugin.blockedSubstrings = append(plugin.blockedSubstrings, line)
if weeklyRanges != nil {
plugin.weeklyRangesIndirect[line] = weeklyRanges
}
2018-01-17 08:46:42 +01:00
case PluginBlockTypePattern:
plugin.blockedPatterns = append(plugin.blockedPatterns, line)
if weeklyRanges != nil {
plugin.weeklyRangesIndirect[line] = weeklyRanges
}
case PluginBlockTypePrefix:
plugin.blockedPrefixes, _, _ = plugin.blockedPrefixes.Insert([]byte(line), weeklyRanges)
case PluginBlockTypeSuffix:
plugin.blockedSuffixes, _, _ = plugin.blockedSuffixes.Insert([]byte(StringReverse(line)), weeklyRanges)
default:
dlog.Fatal("Unexpected block type")
}
}
2018-01-17 17:03:42 +01:00
if len(proxy.blockNameLogFile) == 0 {
return nil
}
plugin.logger = &lumberjack.Logger{LocalTime: true, MaxSize: proxy.logMaxSize, MaxAge: proxy.logMaxAge, MaxBackups: proxy.logMaxBackups, Filename: proxy.blockNameLogFile, Compress: true}
2018-01-17 17:03:42 +01:00
plugin.format = proxy.blockNameFormat
2018-01-21 16:07:44 +01:00
return nil
}
func (plugin *PluginBlockName) Drop() error {
return nil
}
func (plugin *PluginBlockName) Reload() error {
return nil
}
func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
questions := msg.Question
if len(questions) != 1 {
return nil
}
2018-01-17 17:03:42 +01:00
qName := strings.ToLower(StripTrailingDot(questions[0].Name))
2018-01-20 13:30:19 +01:00
if len(qName) < 2 {
return nil
}
2018-01-17 17:03:42 +01:00
revQname := StringReverse(qName)
reject, reason := false, ""
var weeklyRanges *WeeklyRanges
2018-01-17 17:03:42 +01:00
if !reject {
if match, weeklyRangesX, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQname)); found {
if len(match) == len(qName) || revQname[len(match)] == '.' {
reject, reason, weeklyRanges = true, "*."+StringReverse(string(match)), weeklyRangesX.(*WeeklyRanges)
} else if len(match) < len(revQname) && len(revQname) > 0 {
if i := strings.LastIndex(revQname, "."); i > 0 {
pName := revQname[:i]
if match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(pName)); found {
if len(match) == len(pName) || pName[len(match)] == '.' {
reject, reason, weeklyRanges = true, "*."+StringReverse(string(match)), weeklyRangesX.(*WeeklyRanges)
}
}
}
2018-01-17 17:03:42 +01:00
}
}
}
2018-01-17 17:03:42 +01:00
if !reject {
match, weeklyRangesX, found := plugin.blockedPrefixes.Root().LongestPrefix([]byte(qName))
2018-01-17 17:03:42 +01:00
if found {
reject, reason, weeklyRanges = true, string(match)+"*", weeklyRangesX.(*WeeklyRanges)
2018-01-17 17:03:42 +01:00
}
}
2018-01-17 17:03:42 +01:00
if !reject {
for _, substring := range plugin.blockedSubstrings {
if strings.Contains(qName, substring) {
2018-01-17 17:03:42 +01:00
reject, reason = true, "*"+substring+"*"
weeklyRanges = plugin.weeklyRangesIndirect[substring]
2018-01-17 17:03:42 +01:00
break
}
}
}
if !reject {
for _, pattern := range plugin.blockedPatterns {
if found, _ := filepath.Match(pattern, qName); found {
reject, reason = true, pattern
weeklyRanges = plugin.weeklyRangesIndirect[pattern]
2018-01-17 17:03:42 +01:00
break
}
}
}
if reject {
if weeklyRanges != nil && !weeklyRanges.Match() {
reject = false
}
}
2018-01-17 17:03:42 +01:00
if reject {
pluginsState.action = PluginsActionReject
if plugin.logger != nil {
2018-01-17 17:03:42 +01:00
var clientIPStr string
if pluginsState.clientProto == "udp" {
clientIPStr = (*pluginsState.clientAddr).(*net.UDPAddr).IP.String()
} else {
clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String()
}
var line string
if plugin.format == "tsv" {
now := time.Now()
year, month, day := now.Date()
hour, minute, second := now.Clock()
tsStr := fmt.Sprintf("[%d-%02d-%02d %02d:%02d:%02d]", year, int(month), day, hour, minute, second)
line = fmt.Sprintf("%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), StringQuote(reason))
} else if plugin.format == "ltsv" {
line = fmt.Sprintf("time:%d\thost:%s\tqname:%s\tmessage:%s\n", time.Now().Unix(), clientIPStr, StringQuote(qName), StringQuote(reason))
} else {
dlog.Fatalf("Unexpected log format: [%s]", plugin.format)
}
if plugin.logger == nil {
2018-01-17 17:03:42 +01:00
return errors.New("Log file not initialized")
}
plugin.logger.Write([]byte(line))
2018-01-17 08:46:42 +01:00
}
}
return nil
}
2018-01-17 08:46:42 +01:00
func isGlobCandidate(str string) bool {
for i, c := range str {
if c == '?' || c == '[' {
return true
} else if c == '*' && i != 0 && i != len(str)-1 {
return true
}
}
return false
}
func daySecsFromStr(str string) (int, error) {
parts := strings.Split(str, ":")
if len(parts) != 2 {
return -1, fmt.Errorf("Syntax error in a time expression: [%s]", str)
}
hours, err := strconv.Atoi(parts[0])
if err != nil || hours < 0 || hours > 23 {
return -1, fmt.Errorf("Syntax error in a time expression: [%s]", str)
}
minutes, err := strconv.Atoi(parts[1])
if err != nil || minutes < 0 || minutes > 59 {
return -1, fmt.Errorf("Syntax error in a time expression: [%s]", str)
}
return (hours*60 + minutes) * 60, nil
}
func parseTimeRanges(timeRangesStr []TimeRangeStr) ([]TimeRange, error) {
timeRanges := []TimeRange{}
for _, timeRangeStr := range timeRangesStr {
after, err := daySecsFromStr(timeRangeStr.After)
if err != nil {
return timeRanges, err
}
before, err := daySecsFromStr(timeRangeStr.Before)
if err != nil {
return timeRanges, err
}
if after == before {
after, before = -1, 86402
}
timeRanges = append(timeRanges, TimeRange{after: after, before: before})
}
return timeRanges, nil
}
func parseWeeklyRanges(weeklyRangesStr WeeklyRangesStr) (WeeklyRanges, error) {
weeklyRanges := WeeklyRanges{}
weeklyRangesStrX := [7][]TimeRangeStr{weeklyRangesStr.Sun, weeklyRangesStr.Mon, weeklyRangesStr.Tue, weeklyRangesStr.Wed, weeklyRangesStr.Thu, weeklyRangesStr.Fri, weeklyRangesStr.Sat}
for day, weeklyRangeStrX := range weeklyRangesStrX {
timeRanges, err := parseTimeRanges(weeklyRangeStrX)
if err != nil {
return weeklyRanges, err
}
weeklyRanges.ranges[day] = timeRanges
}
return weeklyRanges, nil
}
func ParseAllWeeklyRanges(allWeeklyRangesStr map[string]WeeklyRangesStr) (*map[string]WeeklyRanges, error) {
allWeeklyRanges := make(map[string]WeeklyRanges)
for weeklyRangesName, weeklyRangesStr := range allWeeklyRangesStr {
weeklyRanges, err := parseWeeklyRanges(weeklyRangesStr)
if err != nil {
return nil, err
}
allWeeklyRanges[weeklyRangesName] = weeklyRanges
}
return &allWeeklyRanges, nil
}
func (weeklyRanges *WeeklyRanges) Match() bool {
now := time.Now().Local()
day := now.Weekday()
weeklyRange := weeklyRanges.ranges[day]
if len(weeklyRange) == 0 {
return false
}
hour, min, _ := now.Clock()
nowX := (hour*60 + min) * 60
for _, timeRange := range weeklyRange {
if timeRange.after > timeRange.before {
if nowX >= timeRange.after || nowX <= timeRange.before {
return true
}
} else if nowX >= timeRange.after && nowX <= timeRange.before {
return true
}
}
return false
}