diff --git a/firewall/master.go b/firewall/master.go index fee5adba..ab368cf3 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -616,14 +616,24 @@ matchLoop: } func checkCustomFilterList(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { - // block if the domain name appears in the custom filter list + // block if the domain name appears in the custom filter list (check for subdomains if enabled) if conn.Entity.Domain != "" { - if customlists.LookupDomain(conn.Entity.Domain) { + if customlists.LookupDomain(conn.Entity.Domain, p.FilterSubDomains()) { conn.Block("Domains appears in the custom user list", customlists.CfgOptionCustomListBlockingKey) return true } } + // block if any of the CNAME appears in the custom filter list (check for subdomains if enabled) + if len(conn.Entity.CNAME) > 0 && p.FilterCNAMEs() { + for _, cname := range conn.Entity.CNAME { + if customlists.LookupDomain(cname, p.FilterSubDomains()) { + conn.Block("CNAME appears in the custom user list", customlists.CfgOptionCustomListBlockingKey) + return true + } + } + } + // block if ip addresses appears in the custom filter list if conn.Entity.IP != nil { if customlists.LookupIP(&conn.Entity.IP) { diff --git a/intel/customlists/config.go b/intel/customlists/config.go index f011827a..08a947b6 100644 --- a/intel/customlists/config.go +++ b/intel/customlists/config.go @@ -1,11 +1,15 @@ package customlists -import "github.com/safing/portbase/config" +import ( + "strings" + + "github.com/safing/portbase/config" +) var ( // CfgOptionCustomListBlockingKey is the config key for the listen address.. CfgOptionCustomListBlockingKey = "filter/customListBlocking" - cfgOptionCustomListBlockingOrder = 37 + cfgOptionCustomListBlockingOrder = 35 cfgOptionCustomListCategoryAnnotation = "Filter Lists" ) @@ -14,11 +18,35 @@ var ( ) func registerConfig() error { + help := strings.ReplaceAll(`Put all domains, Ip addresses, country codes and autonomous system that you want to block in a file in which each entry is on a new line. + Lines that start with a '#' symbol are ignored. + Everything after the first space/tab is ignored. + Example: +""" +# Domains: +example.com +google.com + +# IP addresses +1.2.3.4 +4.3.2.1 + +# Countries +AU +BG + +# Autonomous Systems +AS123 +""" +> * All the records are stored in RAM, careful with large block lists. +> * Hosts files are not supported.`, "\"", "`") + // register a setting for the file path in the ui err := config.Register(&config.Option{ Name: "Custom Filter List", Key: CfgOptionCustomListBlockingKey, Description: "Path to the file that contains a list of Domain, IP addresses, country codes and autonomous systems that you want to block", + Help: help, OptType: config.OptTypeString, ExpertiseLevel: config.ExpertiseLevelExpert, ReleaseLevel: config.ReleaseLevelStable, diff --git a/intel/customlists/lists.go b/intel/customlists/lists.go index c9d0f410..753541fe 100644 --- a/intel/customlists/lists.go +++ b/intel/customlists/lists.go @@ -9,6 +9,7 @@ import ( "github.com/miekg/dns" "github.com/safing/portbase/log" + "github.com/safing/portbase/notifications" "github.com/safing/portmaster/network/netutils" ) @@ -19,11 +20,18 @@ var ( domainsFilterList map[string]struct{} ) +const numberOfZeroIPsUntilWarning = 100 + func parseFile(filePath string) error { + // ignore empty file path + if filePath == "" { + return nil + } + // open the file if possible file, err := os.Open(filePath) if err != nil { - log.Warningf("Custom filter: failed to parse file: \"%s\"", filePath) + log.Warningf("intel/customlists: failed to parse file: \"%s\"", filePath) return err } defer file.Close() @@ -34,11 +42,13 @@ func parseFile(filePath string) error { autonomousSystemsFilterList = make(map[uint]struct{}) domainsFilterList = make(map[string]struct{}) + var numberOfZeroIPs uint64 + // read filter file line by line scanner := bufio.NewScanner(file) // the scanner will error out if the line is greater than 64K, in this case it is enough for scanner.Scan() { - parseLine(scanner.Text()) + parseLine(scanner.Text(), &numberOfZeroIPs) } // check for scanner error @@ -46,12 +56,17 @@ func parseFile(filePath string) error { return err } - log.Infof("Custom filter: list loaded successful: %s", filePath) + if numberOfZeroIPs >= numberOfZeroIPsUntilWarning { + log.Warning("intel/customlists: Too many zero IP addresses.") + notifications.NotifyWarn("too_many_zero_ips", "Too many zero IP addresses. Check your custom filter list.", "Hosts file format is not spported.") + } + + log.Infof("intel/customlists: list loaded successful: %s", filePath) return nil } -func parseLine(line string) { +func parseLine(line string, numberOfZeroIPs *uint64) { // ignore empty lines and comment lines if len(line) == 0 || line[0] == '#' { return @@ -69,6 +84,13 @@ func parseLine(line string) { ip := net.ParseIP(field) if ip != nil { ipAddressesFilterList[ip.String()] = struct{}{} + + // check if its zero ip + for i := 0; i < len(ip); i++ { + if ip[i] != 0 { + *numberOfZeroIPs++ + } + } } // check if it's a Autonomous system (example AS123) diff --git a/intel/customlists/module.go b/intel/customlists/module.go index 2d5feba6..18c90c8f 100644 --- a/intel/customlists/module.go +++ b/intel/customlists/module.go @@ -5,14 +5,20 @@ import ( "net" "os" "regexp" + "strings" + "sync" "time" "github.com/safing/portbase/modules" + "golang.org/x/net/publicsuffix" ) var module *modules.Module -const configChangeEvent = "config change" +const ( + configModuleName = "config" + configChangeEvent = "config change" +) // Helper variables for parsing the input file var ( @@ -23,6 +29,8 @@ var ( var ( filterListFilePath string filterListFileModifiedTime time.Time + + parseLock sync.RWMutex ) func init() { @@ -38,7 +46,7 @@ func prep() error { // register to hook to update after config change. if err := module.RegisterEventHook( - module.Name, + configModuleName, configChangeEvent, "update custom filter list", func(ctx context.Context, obj interface{}) error { @@ -54,17 +62,20 @@ func prep() error { func start() error { // register timer to run every periodically and check for file updates - module.NewTask("Custom filter list file update check", func(context.Context, *modules.Task) error { + module.NewTask("intel/customlists list file update check", func(context.Context, *modules.Task) error { _ = checkAndUpdateFilterList() return nil }).Repeat(10 * time.Minute) - // parse the file for the first time at start + // parse the file at startup _ = parseFile(getFilePath()) return nil } func checkAndUpdateFilterList() error { + parseLock.Lock() + defer parseLock.Unlock() + // get path and try to get its info filePath := getFilePath() fileInfo, err := os.Stat(filePath) @@ -92,9 +103,20 @@ func LookupIP(ip *net.IP) bool { } // LookupDomain checks if the Domain is in a custom filter list -func LookupDomain(domain string) bool { - _, ok := domainsFilterList[domain] - return ok +func LookupDomain(fullDomain string, filterSubdomains bool) bool { + if filterSubdomains { + listOfDomains := splitDomain(fullDomain) + for _, domain := range listOfDomains { + _, ok := domainsFilterList[domain] + if ok { + return true + } + } + } else { + _, ok := domainsFilterList[fullDomain] + return ok + } + return false } // LookupASN checks if the Autonomous system number is in a custom filter list @@ -108,3 +130,29 @@ func LookupCountry(countryCode string) bool { _, ok := countryCodesFilterList[countryCode] return ok } + +func splitDomain(domain string) []string { + domain = strings.Trim(domain, ".") + suffix, _ := publicsuffix.PublicSuffix(domain) + if suffix == domain { + return []string{domain} + } + + domainWithoutSuffix := domain[:len(domain)-len(suffix)] + domainWithoutSuffix = strings.Trim(domainWithoutSuffix, ".") + + splitted := strings.FieldsFunc(domainWithoutSuffix, func(r rune) bool { + return r == '.' + }) + + domains := make([]string, 0, len(splitted)) + for idx := range splitted { + + d := strings.Join(splitted[idx:], ".") + "." + suffix + if d[len(d)-1] != '.' { + d += "." + } + domains = append(domains, d) + } + return domains +} diff --git a/profile/config.go b/profile/config.go index e371a175..3df5e3be 100644 --- a/profile/config.go +++ b/profile/config.go @@ -64,9 +64,11 @@ var ( cfgOptionFilterLists config.StringArrayOption cfgOptionFilterListsOrder = 34 + // Setting "Custom Filter List" at order 35 + CfgOptionFilterSubDomainsKey = "filter/includeSubdomains" cfgOptionFilterSubDomains config.IntOption // security level option - cfgOptionFilterSubDomainsOrder = 35 + cfgOptionFilterSubDomainsOrder = 36 // DNS Filtering.