main.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. package main
  2. import (
  3. "bufio"
  4. "errors"
  5. "flag"
  6. "fmt"
  7. "io/ioutil"
  8. "log"
  9. "net"
  10. "net/http"
  11. "os"
  12. "strings"
  13. "time"
  14. )
  15. var (
  16. lists = flag.String("lists", "/etc/blacklists", "Blacklist sources")
  17. invalid = []string{
  18. "localhost", "localhost.localdomain", "local", "broadcasthost",
  19. "ip6-localhost", "ip6-loopback", "ip6-localnet",
  20. "ip6-mcastprefix", "ip6-allnodes", "ip6-allrouters",
  21. "ip6-allhosts",
  22. }
  23. soa = `$TTL 1
  24. @ IN SOA localhost. root.localhost. (
  25. 0 ; serial
  26. 2w ; refresh
  27. 2w ; retry
  28. 2w ; expiry
  29. 2w ) ; negative cache ttl
  30. @ IN NS localhost.
  31. @ IN A 0.0.0.0
  32. `
  33. )
  34. type Server struct {
  35. Lists []string
  36. }
  37. func main() {
  38. flag.Parse()
  39. s := &Server{ readLists(), }
  40. s.Serve()
  41. }
  42. func readLists() []string {
  43. file, err := os.Open(*lists)
  44. if err != nil {
  45. return nil
  46. }
  47. defer file.Close()
  48. var sources []string
  49. scanner := bufio.NewScanner(file)
  50. for scanner.Scan() {
  51. sources = append(sources, scanner.Text())
  52. }
  53. return sources
  54. }
  55. func (s *Server) Serve() {
  56. http.HandleFunc("/", s.generateList)
  57. log.Fatal(http.ListenAndServe(":8000", nil))
  58. }
  59. func (s *Server) generateList(w http.ResponseWriter, r *http.Request) {
  60. c := make(chan string)
  61. for _, list := range s.Lists {
  62. go handle(list, c)
  63. }
  64. var zones []string
  65. for range s.Lists {
  66. zones = append(zones, <-c)
  67. }
  68. fmt.Fprintf(w, soa)
  69. fmt.Fprintf(w, strings.Join(zones, "\n"))
  70. }
  71. func handle(url string, zone chan string) {
  72. body, err := fetch(url)
  73. if err != nil {
  74. zone <- ""
  75. return
  76. }
  77. zone <- sanitize(body)
  78. }
  79. func fetch(url string) (string, error) {
  80. client := http.Client{
  81. Timeout: 3 * time.Second,
  82. }
  83. req, err := client.Get(url)
  84. if err != nil {
  85. fmt.Printf("Could not fetch %s: %s\n", url, err)
  86. return "", err
  87. }
  88. defer req.Body.Close()
  89. body, err := ioutil.ReadAll(req.Body)
  90. if err != nil {
  91. return "", err
  92. }
  93. return string(body), nil
  94. }
  95. func sanitize(records string) string {
  96. var sanitized []string
  97. for _, line := range strings.Split(records, "\n") {
  98. _, domain, err := parse_record(line)
  99. if err != nil {
  100. continue
  101. }
  102. // Domain is invalid (e.g., "localhost")
  103. if is_invalid(domain) {
  104. continue
  105. }
  106. // Domain is an IP address
  107. if net.ParseIP(domain) != nil {
  108. continue
  109. }
  110. sanitized = append(sanitized, domain + ".\tCNAME\t.\n")
  111. }
  112. return strings.Join(sanitized, "")
  113. }
  114. func parse_record(record string) (string, string, error) {
  115. if len(record) == 0 {
  116. return "", "", errors.New("Empty line")
  117. }
  118. if strings.HasPrefix(record, "#") {
  119. return "", "", errors.New("Comment")
  120. }
  121. words := strings.Fields(record)
  122. // Domain-only list
  123. if len(words) == 1 {
  124. return "", words[0], nil
  125. }
  126. return words[0], words[1], nil
  127. }
  128. func is_invalid(domain string) bool {
  129. for _, i := range invalid {
  130. if domain == i {
  131. return true
  132. }
  133. }
  134. return false
  135. }