diff --git a/dnscrypt-proxy/common.go b/dnscrypt-proxy/common.go index 214651c2..5406f6de 100644 --- a/dnscrypt-proxy/common.go +++ b/dnscrypt-proxy/common.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "net" + "strconv" "strings" "unicode" ) @@ -97,3 +98,8 @@ func StringTwoFields(str string) (string, string, bool) { } return a, b, true } + +func StringQuote(str string) string { + str = strconv.QuoteToGraphic(str) + return str[1 : len(str)-1] +} diff --git a/dnscrypt-proxy/config.go b/dnscrypt-proxy/config.go index d6434b0d..1e60f24e 100644 --- a/dnscrypt-proxy/config.go +++ b/dnscrypt-proxy/config.go @@ -25,7 +25,7 @@ type Config struct { CacheMinTTL uint32 `toml:"cache_min_ttl"` CacheMaxTTL uint32 `toml:"cache_max_ttl"` QueryLog QueryLogConfig `toml:"query_log"` - BlockName BlockNameConfig `toml:"block_name"` + BlockName BlockNameConfig `toml:"blacklist"` ForwardFile string `toml:"forwarding_rules"` ServersConfig map[string]ServerConfig `toml:"servers"` SourcesConfig map[string]SourceConfig `toml:"sources"` @@ -67,7 +67,9 @@ type QueryLogConfig struct { } type BlockNameConfig struct { - File string + File string `toml:"blacklist_file"` + LogFile string `toml:"log_file"` + Format string `toml:"log_format"` } func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { @@ -97,6 +99,7 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { proxy.cacheNegTTL = config.CacheNegTTL proxy.cacheMinTTL = config.CacheMinTTL proxy.cacheMaxTTL = config.CacheMaxTTL + if len(config.QueryLog.Format) == 0 { config.QueryLog.Format = "tsv" } else { @@ -107,7 +110,19 @@ func ConfigLoad(proxy *Proxy, svcFlag *string, config_file string) error { } proxy.queryLogFile = config.QueryLog.File proxy.queryLogFormat = config.QueryLog.Format + + if len(config.BlockName.Format) == 0 { + config.BlockName.Format = "tsv" + } else { + config.BlockName.Format = strings.ToLower(config.BlockName.Format) + } + if config.BlockName.Format != "tsv" && config.BlockName.Format != "ltsv" { + return errors.New("Unsupported block log format") + } proxy.blockNameFile = config.BlockName.File + proxy.blockNameFormat = config.BlockName.Format + proxy.blockNameLogFile = config.BlockName.LogFile + proxy.forwardFile = config.ForwardFile if len(config.ServerNames) == 0 { for serverName := range config.ServersConfig { diff --git a/dnscrypt-proxy/dnscrypt-proxy.toml b/dnscrypt-proxy/dnscrypt-proxy.toml index 2704413b..75069666 100644 --- a/dnscrypt-proxy/dnscrypt-proxy.toml +++ b/dnscrypt-proxy/dnscrypt-proxy.toml @@ -127,11 +127,21 @@ format = "tsv" ## ads*.example.* ## ads*.example[0-9]*.com -[block_name] +[blacklist] ## Full path to the file of blocking rules -# file = "blacklist.txt" +# blacklist_file = "blacklist.txt" + + +## Optional path to a file logging blocked queries + +# log_file = "blocked.log" + + +## Optional log format: tsv or ltsv (default: tsv) + +# log_format = "tsv" diff --git a/dnscrypt-proxy/main.go b/dnscrypt-proxy/main.go index 8d7889ef..94d3600a 100644 --- a/dnscrypt-proxy/main.go +++ b/dnscrypt-proxy/main.go @@ -35,6 +35,8 @@ type Proxy struct { queryLogFile string queryLogFormat string blockNameFile string + blockNameLogFile string + blockNameFormat string forwardFile string pluginsGlobals PluginsGlobals } diff --git a/dnscrypt-proxy/plugin_block_name.go b/dnscrypt-proxy/plugin_block_name.go index 55bd4f08..43ae16d9 100644 --- a/dnscrypt-proxy/plugin_block_name.go +++ b/dnscrypt-proxy/plugin_block_name.go @@ -1,9 +1,15 @@ package main import ( + "errors" + "fmt" "io/ioutil" + "net" + "os" "path/filepath" "strings" + "sync" + "time" "unicode" "github.com/hashicorp/go-immutable-radix" @@ -22,10 +28,13 @@ const ( ) type PluginBlockName struct { + sync.Mutex blockedPrefixes *iradix.Tree blockedSuffixes *iradix.Tree blockedSubstrings []string blockedPatterns []string + outFd *os.File + format string } func (plugin *PluginBlockName) Name() string { @@ -100,6 +109,17 @@ func (plugin *PluginBlockName) Init(proxy *Proxy) error { dlog.Fatal("Unexpected block type") } } + if len(proxy.blockNameLogFile) == 0 { + return nil + } + plugin.Lock() + defer plugin.Unlock() + outFd, err := os.OpenFile(proxy.blockNameLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + return err + } + plugin.outFd = outFd + plugin.format = proxy.blockNameFormat return nil } @@ -116,30 +136,66 @@ func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) er if len(questions) != 1 { return nil } - question := strings.ToLower(StripTrailingDot(questions[0].Name)) - revQuestion := StringReverse(question) - match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQuestion)) - if found { - if len(match) == len(question) || question[len(match)] == '.' { - pluginsState.action = PluginsActionReject - return nil + qName := strings.ToLower(StripTrailingDot(questions[0].Name)) + revQname := StringReverse(qName) + reject, reason := false, "" + if !reject { + match, _, found := plugin.blockedSuffixes.Root().LongestPrefix([]byte(revQname)) + if found { + if len(match) == len(qName) || qName[len(match)] == '.' { + reject, reason = true, "*"+string(match) + } } } - _, _, found = plugin.blockedPrefixes.Root().LongestPrefix([]byte(question)) - if found { + if !reject { + match, _, found := plugin.blockedPrefixes.Root().LongestPrefix([]byte(qName)) + if found { + reject, reason = true, string(match)+"*" + } + } + if !reject { + for _, substring := range plugin.blockedSubstrings { + if strings.Contains(substring, qName) { + reject, reason = true, "*"+substring+"*" + break + } + } + } + if !reject { + for _, pattern := range plugin.blockedPatterns { + if found, _ := filepath.Match(pattern, qName); found { + reject, reason = true, pattern + break + } + } + } + if reject { pluginsState.action = PluginsActionReject - return nil - } - for _, substring := range plugin.blockedSubstrings { - if strings.Contains(substring, question) { - pluginsState.action = PluginsActionReject - return nil - } - } - for _, pattern := range plugin.blockedPatterns { - if found, _ := filepath.Match(pattern, question); found { - pluginsState.action = PluginsActionReject - return nil + if plugin.outFd != nil { + 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) + } + plugin.Lock() + if plugin.outFd == nil { + return errors.New("Log file not initialized") + } + plugin.outFd.WriteString(line) + defer plugin.Unlock() } } return nil diff --git a/dnscrypt-proxy/plugin_query_log.go b/dnscrypt-proxy/plugin_query_log.go index f8ff032a..9f7f7430 100644 --- a/dnscrypt-proxy/plugin_query_log.go +++ b/dnscrypt-proxy/plugin_query_log.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "os" - "strings" "sync" "time" @@ -60,10 +59,7 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err } else { clientIPStr = (*pluginsState.clientAddr).(*net.TCPAddr).IP.String() } - qName := question.Name - if len(qName) > 1 && strings.HasSuffix(qName, ".") { - qName = qName[0 : len(qName)-1] - } + qName := StripTrailingDot(question.Name) qType, ok := dns.TypeToString[question.Qtype] if !ok { qType = string(qType) @@ -74,10 +70,10 @@ func (plugin *PluginQueryLog) Eval(pluginsState *PluginsState, msg *dns.Msg) err 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, qName, qType) + line = fmt.Sprintf("%s\t%s\t%s\t%s\n", tsStr, clientIPStr, StringQuote(qName), qType) } else if plugin.format == "ltsv" { line = fmt.Sprintf("time:%d\thost:%s\tmessage:%s\ttype:%s\n", - time.Now().Unix(), clientIPStr, qName, qType) + time.Now().Unix(), clientIPStr, StringQuote(qName), qType) } else { dlog.Fatalf("Unexpected log format: [%s]", plugin.format) }