package main import ( "bufio" "errors" "flag" "fmt" "io/ioutil" "log" "net" "net/http" "os" "strings" "time" ) var ( lists = flag.String("lists", "/etc/blacklists", "Blacklist sources") invalid = []string{ "localhost", "localhost.localdomain", "local", "broadcasthost", "ip6-localhost", "ip6-loopback", "ip6-localnet", "ip6-mcastprefix", "ip6-allnodes", "ip6-allrouters", "ip6-allhosts", } soa = `$TTL 1 @ IN SOA localhost. root.localhost. ( 0 ; serial 2w ; refresh 2w ; retry 2w ; expiry 2w ) ; negative cache ttl @ IN NS localhost. @ IN A 0.0.0.0 ` ) type Server struct { Lists []string } func main() { flag.Parse() s := &Server{ readLists(), } s.Serve() } func readLists() []string { file, err := os.Open(*lists) if err != nil { return nil } defer file.Close() var sources []string scanner := bufio.NewScanner(file) for scanner.Scan() { sources = append(sources, scanner.Text()) } return sources } func (s *Server) Serve() { http.HandleFunc("/", s.generateList) log.Fatal(http.ListenAndServe(":8000", nil)) } func (s *Server) generateList(w http.ResponseWriter, r *http.Request) { c := make(chan string) for _, list := range s.Lists { go handle(list, c) } var zones []string for range s.Lists { zones = append(zones, <-c) } fmt.Fprintf(w, soa) fmt.Fprintf(w, strings.Join(zones, "\n")) } func handle(url string, zone chan string) { body, err := fetch(url) if err != nil { zone <- "" return } zone <- sanitize(body) } func fetch(url string) (string, error) { client := http.Client{ Timeout: 3 * time.Second, } req, err := client.Get(url) if err != nil { fmt.Printf("Could not fetch %s: %s\n", url, err) return "", err } defer req.Body.Close() body, err := ioutil.ReadAll(req.Body) if err != nil { return "", err } return string(body), nil } func sanitize(records string) string { var sanitized []string for _, line := range strings.Split(records, "\n") { _, domain, err := parse_record(line) if err != nil { continue } // Domain is invalid (e.g., "localhost") if is_invalid(domain) { continue } // Domain is an IP address if net.ParseIP(domain) != nil { continue } sanitized = append(sanitized, domain + ".\tCNAME\t.\n") } return strings.Join(sanitized, "") } func parse_record(record string) (string, string, error) { if len(record) == 0 { return "", "", errors.New("Empty line") } if strings.HasPrefix(record, "#") { return "", "", errors.New("Comment") } words := strings.Fields(record) // Domain-only list if len(words) == 1 { return "", words[0], nil } return words[0], words[1], nil } func is_invalid(domain string) bool { for _, i := range invalid { if domain == i { return true } } return false }