1
0

guard.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package fetch
  2. import (
  3. "context"
  4. "fmt"
  5. "net"
  6. "net/http"
  7. "net/netip"
  8. "net/url"
  9. "syscall"
  10. "time"
  11. "golang.org/x/net/http/httpproxy"
  12. )
  13. // A web page decides which other addresses rekopub connects to: it can
  14. // redirect, and it names its images and cover. Without a guard, a page on
  15. // the internet could make rekopub send requests to the reader's own
  16. // computer or to devices on their network, such as a router's admin page.
  17. // So when the page itself is on the internet, connections to local and
  18. // private addresses are refused. The check is made on the address actually
  19. // dialled, after DNS lookup, so a hostname that resolves to a local address
  20. // is caught too.
  21. // BlockedError reports a refused connection to a local or private address.
  22. type BlockedError struct {
  23. Addr netip.Addr
  24. }
  25. func (e *BlockedError) Error() string {
  26. return fmt.Sprintf("connection to %s refused: it is a local or private address", e.Addr)
  27. }
  28. // IsLocal reports whether host is, or resolves to, an address that isn't on
  29. // the public internet, such as localhost or a server on the home network.
  30. // Local connections are allowed only when the page itself is local.
  31. func IsLocal(ctx context.Context, host string) bool {
  32. if ip, err := netip.ParseAddr(host); err == nil {
  33. return !isPublic(ip)
  34. }
  35. addrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
  36. if err != nil {
  37. return false
  38. }
  39. for _, ip := range addrs {
  40. if !isPublic(ip) {
  41. return true
  42. }
  43. }
  44. return false
  45. }
  46. // nonPublic lists ranges that aren't on the public internet and that
  47. // netip.Addr's methods don't cover.
  48. var nonPublic = []netip.Prefix{
  49. netip.MustParsePrefix("0.0.0.0/8"), // "this network"
  50. netip.MustParsePrefix("100.64.0.0/10"), // carrier-grade NAT, also used by Tailscale
  51. netip.MustParsePrefix("192.0.0.0/24"), // IETF protocol assignments
  52. netip.MustParsePrefix("198.18.0.0/15"), // network benchmarking
  53. netip.MustParsePrefix("240.0.0.0/4"), // reserved
  54. }
  55. // isPublic reports whether ip is a public internet address. Loopback,
  56. // private, link-local (which includes cloud metadata services), multicast
  57. // and reserved addresses are not.
  58. func isPublic(ip netip.Addr) bool {
  59. ip = ip.Unmap()
  60. if !ip.IsGlobalUnicast() || ip.IsPrivate() {
  61. return false
  62. }
  63. for _, p := range nonPublic {
  64. if p.Contains(ip) {
  65. return false
  66. }
  67. }
  68. return true
  69. }
  70. // permitted decides whether a guarded client may connect to an address.
  71. // Tests replace it, because their servers are all on localhost.
  72. var permitted = func(ap netip.AddrPort) bool { return isPublic(ap.Addr()) }
  73. // newClient returns the HTTP client for pages and images. Unless
  74. // allowLocal is set, it refuses to connect to local and private addresses.
  75. func newClient(allowLocal bool) *http.Client {
  76. t := http.DefaultTransport.(*http.Transport).Clone()
  77. if !allowLocal {
  78. t.DialContext = guardedDial(proxyAddrs())
  79. }
  80. return &http.Client{Timeout: 60 * time.Second, Transport: t}
  81. }
  82. // guardedDial returns a dial function that refuses local and private
  83. // addresses, except for the proxies in proxies: a proxy is the user's own
  84. // choice and is often on their network. Requests sent through a proxy are
  85. // dialled by the proxy, so the guard can't check them.
  86. func guardedDial(proxies map[string]bool) func(ctx context.Context, network, addr string) (net.Conn, error) {
  87. // The same settings as http.DefaultTransport.
  88. plain := &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}
  89. guarded := &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second,
  90. Control: func(_, address string, _ syscall.RawConn) error {
  91. ap, err := netip.ParseAddrPort(address)
  92. if err != nil {
  93. return err
  94. }
  95. if !permitted(ap) {
  96. return &BlockedError{Addr: ap.Addr().Unmap()}
  97. }
  98. return nil
  99. }}
  100. return func(ctx context.Context, network, addr string) (net.Conn, error) {
  101. if proxies[addr] {
  102. return plain.DialContext(ctx, network, addr)
  103. }
  104. return guarded.DialContext(ctx, network, addr)
  105. }
  106. }
  107. // proxyAddrs returns the host:port of the proxies set in the environment
  108. // (HTTP_PROXY and HTTPS_PROXY), which http.DefaultTransport uses.
  109. func proxyAddrs() map[string]bool {
  110. cfg := httpproxy.FromEnvironment()
  111. addrs := map[string]bool{}
  112. for _, raw := range []string{cfg.HTTPProxy, cfg.HTTPSProxy} {
  113. if raw == "" {
  114. continue
  115. }
  116. u, err := url.Parse(raw)
  117. if err != nil || u.Host == "" {
  118. // A proxy may be given without a scheme, as "proxy:3128".
  119. if u, err = url.Parse("http://" + raw); err != nil {
  120. continue
  121. }
  122. }
  123. port := u.Port()
  124. if port == "" {
  125. switch u.Scheme {
  126. case "https":
  127. port = "443"
  128. case "socks5", "socks5h":
  129. port = "1080"
  130. default:
  131. port = "80"
  132. }
  133. }
  134. addrs[net.JoinHostPort(u.Hostname(), port)] = true
  135. }
  136. return addrs
  137. }