parent
ceb2d55afd
commit
65e6b8569e
|
@ -37,6 +37,7 @@ type Config struct {
|
|||
QueryLog QueryLogConfig `toml:"query_log"`
|
||||
NxLog NxLogConfig `toml:"nx_log"`
|
||||
BlockName BlockNameConfig `toml:"blacklist"`
|
||||
WhitelistName WhitelistNameConfig `toml:"whitelist"`
|
||||
BlockIP BlockIPConfig `toml:"ip_blacklist"`
|
||||
ForwardFile string `toml:"forwarding_rules"`
|
||||
CloakFile string `toml:"cloaking_rules"`
|
||||
|
@ -121,6 +122,12 @@ type BlockNameConfig struct {
|
|||
Format string `toml:"log_format"`
|
||||
}
|
||||
|
||||
type WhitelistNameConfig struct {
|
||||
File string `toml:"whitelist_file"`
|
||||
LogFile string `toml:"log_file"`
|
||||
Format string `toml:"log_format"`
|
||||
}
|
||||
|
||||
type BlockIPConfig struct {
|
||||
File string `toml:"blacklist_file"`
|
||||
LogFile string `toml:"log_file"`
|
||||
|
@ -294,6 +301,18 @@ func ConfigLoad(proxy *Proxy, svcFlag *string) error {
|
|||
proxy.blockNameFormat = config.BlockName.Format
|
||||
proxy.blockNameLogFile = config.BlockName.LogFile
|
||||
|
||||
if len(config.WhitelistName.Format) == 0 {
|
||||
config.WhitelistName.Format = "tsv"
|
||||
} else {
|
||||
config.WhitelistName.Format = strings.ToLower(config.WhitelistName.Format)
|
||||
}
|
||||
if config.WhitelistName.Format != "tsv" && config.WhitelistName.Format != "ltsv" {
|
||||
return errors.New("Unsupported whitelist log format")
|
||||
}
|
||||
proxy.whitelistNameFile = config.WhitelistName.File
|
||||
proxy.whitelistNameFormat = config.WhitelistName.Format
|
||||
proxy.whitelistNameLogFile = config.WhitelistName.LogFile
|
||||
|
||||
if len(config.BlockIP.Format) == 0 {
|
||||
config.BlockIP.Format = "tsv"
|
||||
} else {
|
||||
|
|
|
@ -341,6 +341,34 @@ cache_neg_ttl = 60
|
|||
|
||||
|
||||
|
||||
######################################################
|
||||
# Pattern-based whitelisting (blacklists bypass) #
|
||||
######################################################
|
||||
|
||||
## Whitelists support the same patterns as blacklists
|
||||
## If a name matches a whitelist entry, the corresponding session
|
||||
## will bypass names and IP filters.
|
||||
##
|
||||
## Time-based rules are also supported to make some websites only accessible at specific times of the day.
|
||||
|
||||
[whitelist]
|
||||
|
||||
## Path to the file of whitelisting rules (absolute, or relative to the same directory as the executable file)
|
||||
|
||||
# whitelist_file = 'whitelist.txt'
|
||||
|
||||
|
||||
## Optional path to a file logging whitelisted queries
|
||||
|
||||
# log_file = 'whitelisted.log'
|
||||
|
||||
|
||||
## Optional log format: tsv or ltsv (default: tsv)
|
||||
|
||||
# log_format = 'tsv'
|
||||
|
||||
|
||||
|
||||
##########################################
|
||||
# Time access restrictions #
|
||||
##########################################
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
###########################
|
||||
# Whitelist #
|
||||
###########################
|
||||
|
||||
## Rules for name-based query whitelisting, one per line
|
||||
##
|
||||
## Example of valid patterns:
|
||||
##
|
||||
## ads.* | matches anything with an "ads." prefix
|
||||
## *.example.com | matches example.com and all names within that zone such as www.example.com
|
||||
## example.com | identical to the above
|
||||
## *sex* | matches any name containing that substring
|
||||
## ads[0-9]* | matches "ads" followed by one or more digits
|
||||
## ads*.example* | *, ? and [] can be used anywhere, but prefixes/suffixes are faster
|
||||
|
||||
|
||||
|
||||
## Time-based rules
|
||||
|
||||
# *.youtube.* @time-to-play
|
||||
# facebook.com @play
|
|
@ -88,6 +88,9 @@ func (plugin *PluginBlockIP) Reload() error {
|
|||
}
|
||||
|
||||
func (plugin *PluginBlockIP) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
||||
if pluginsState.sessionData["whitelisted"] != nil {
|
||||
return nil
|
||||
}
|
||||
answers := msg.Answer
|
||||
if len(answers) == 0 {
|
||||
return nil
|
||||
|
|
|
@ -83,6 +83,9 @@ func (plugin *PluginBlockName) Reload() error {
|
|||
}
|
||||
|
||||
func (plugin *PluginBlockName) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
||||
if pluginsState.sessionData["whitelisted"] != nil {
|
||||
return nil
|
||||
}
|
||||
questions := msg.Question
|
||||
if len(questions) != 1 {
|
||||
return nil
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/jedisct1/dlog"
|
||||
"github.com/miekg/dns"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
type PluginWhitelistName struct {
|
||||
allWeeklyRanges *map[string]WeeklyRanges
|
||||
patternMatcher *PatternMatcher
|
||||
logger *lumberjack.Logger
|
||||
format string
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Name() string {
|
||||
return "whitelist_name"
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Description() string {
|
||||
return "Whitelists DNS queries matching name patterns"
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Init(proxy *Proxy) error {
|
||||
dlog.Noticef("Loading the set of whitelisting rules from [%s]", proxy.whitelistNameFile)
|
||||
bin, err := ioutil.ReadFile(proxy.whitelistNameFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plugin.allWeeklyRanges = proxy.allWeeklyRanges
|
||||
plugin.patternMatcher = NewPatternPatcher()
|
||||
for lineNo, line := range strings.Split(string(bin), "\n") {
|
||||
line = strings.TrimFunc(line, unicode.IsSpace)
|
||||
if len(line) == 0 || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
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 whitelist rules at line %d -- Unexpected @ character", 1+lineNo)
|
||||
continue
|
||||
}
|
||||
var weeklyRanges *WeeklyRanges
|
||||
if len(timeRangeName) > 0 {
|
||||
weeklyRangesX, ok := (*plugin.allWeeklyRanges)[timeRangeName]
|
||||
if !ok {
|
||||
dlog.Errorf("Time range [%s] not found at line %d", timeRangeName, 1+lineNo)
|
||||
} else {
|
||||
weeklyRanges = &weeklyRangesX
|
||||
}
|
||||
}
|
||||
if _, err := plugin.patternMatcher.Add(line, weeklyRanges, lineNo+1); err != nil {
|
||||
dlog.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(proxy.whitelistNameLogFile) == 0 {
|
||||
return nil
|
||||
}
|
||||
plugin.logger = &lumberjack.Logger{LocalTime: true, MaxSize: proxy.logMaxSize, MaxAge: proxy.logMaxAge, MaxBackups: proxy.logMaxBackups, Filename: proxy.whitelistNameLogFile, Compress: true}
|
||||
plugin.format = proxy.whitelistNameFormat
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Drop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *PluginWhitelistName) Eval(pluginsState *PluginsState, msg *dns.Msg) error {
|
||||
questions := msg.Question
|
||||
if len(questions) != 1 {
|
||||
return nil
|
||||
}
|
||||
qName := strings.ToLower(StripTrailingDot(questions[0].Name))
|
||||
whitelist, reason, xweeklyRanges := plugin.patternMatcher.Eval(qName)
|
||||
var weeklyRanges *WeeklyRanges
|
||||
if xweeklyRanges != nil {
|
||||
weeklyRanges = xweeklyRanges.(*WeeklyRanges)
|
||||
}
|
||||
if whitelist {
|
||||
if weeklyRanges != nil && !weeklyRanges.Match() {
|
||||
whitelist = false
|
||||
}
|
||||
}
|
||||
if whitelist {
|
||||
if pluginsState.sessionData == nil {
|
||||
pluginsState.sessionData = make(map[string]interface{})
|
||||
}
|
||||
pluginsState.sessionData["whitelisted"] = true
|
||||
if plugin.logger != 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)
|
||||
}
|
||||
if plugin.logger == nil {
|
||||
return errors.New("Log file not initialized")
|
||||
}
|
||||
plugin.logger.Write([]byte(line))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -45,6 +45,9 @@ func InitPluginsGlobals(pluginsGlobals *PluginsGlobals, proxy *Proxy) error {
|
|||
if len(proxy.queryLogFile) != 0 {
|
||||
*queryPlugins = append(*queryPlugins, Plugin(new(PluginQueryLog)))
|
||||
}
|
||||
if len(proxy.whitelistNameFile) != 0 {
|
||||
*queryPlugins = append(*queryPlugins, Plugin(new(PluginWhitelistName)))
|
||||
}
|
||||
if len(proxy.blockNameFile) != 0 {
|
||||
*queryPlugins = append(*queryPlugins, Plugin(new(PluginBlockName)))
|
||||
}
|
||||
|
|
|
@ -38,8 +38,11 @@ type Proxy struct {
|
|||
nxLogFile string
|
||||
nxLogFormat string
|
||||
blockNameFile string
|
||||
whitelistNameFile string
|
||||
blockNameLogFile string
|
||||
whitelistNameLogFile string
|
||||
blockNameFormat string
|
||||
whitelistNameFormat string
|
||||
blockIPFile string
|
||||
blockIPLogFile string
|
||||
blockIPFormat string
|
||||
|
|
Loading…
Reference in New Issue