https.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. package goproxy
  2. import (
  3. "bufio"
  4. "crypto/tls"
  5. "errors"
  6. "io"
  7. "io/ioutil"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "regexp"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "sync/atomic"
  17. )
  18. type ConnectActionLiteral int
  19. const (
  20. ConnectAccept = iota
  21. ConnectReject
  22. ConnectMitm
  23. ConnectHijack
  24. ConnectHTTPMitm
  25. ConnectProxyAuthHijack
  26. )
  27. var (
  28. OkConnect = &ConnectAction{Action: ConnectAccept, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
  29. MitmConnect = &ConnectAction{Action: ConnectMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
  30. HTTPMitmConnect = &ConnectAction{Action: ConnectHTTPMitm, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
  31. RejectConnect = &ConnectAction{Action: ConnectReject, TLSConfig: TLSConfigFromCA(&GoproxyCa)}
  32. httpsRegexp = regexp.MustCompile(`^https:\/\/`)
  33. )
  34. type ConnectAction struct {
  35. Action ConnectActionLiteral
  36. Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx)
  37. TLSConfig func(host string, ctx *ProxyCtx) (*tls.Config, error)
  38. }
  39. func stripPort(s string) string {
  40. ix := strings.IndexRune(s, ':')
  41. if ix == -1 {
  42. return s
  43. }
  44. return s[:ix]
  45. }
  46. func (proxy *ProxyHttpServer) dial(network, addr string) (c net.Conn, err error) {
  47. if proxy.Tr.Dial != nil {
  48. return proxy.Tr.Dial(network, addr)
  49. }
  50. return net.Dial(network, addr)
  51. }
  52. func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err error) {
  53. if proxy.ConnectDial == nil {
  54. return proxy.dial(network, addr)
  55. }
  56. return proxy.ConnectDial(network, addr)
  57. }
  58. func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
  59. ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
  60. hij, ok := w.(http.Hijacker)
  61. if !ok {
  62. panic("httpserver does not support hijacking")
  63. }
  64. proxyClient, _, e := hij.Hijack()
  65. if e != nil {
  66. panic("Cannot hijack connection " + e.Error())
  67. }
  68. ctx.Logf("Running %d CONNECT handlers", len(proxy.httpsHandlers))
  69. todo, host := OkConnect, r.URL.Host
  70. for i, h := range proxy.httpsHandlers {
  71. newtodo, newhost := h.HandleConnect(host, ctx)
  72. // If found a result, break the loop immediately
  73. if newtodo != nil {
  74. todo, host = newtodo, newhost
  75. ctx.Logf("on %dth handler: %v %s", i, todo, host)
  76. break
  77. }
  78. }
  79. switch todo.Action {
  80. case ConnectAccept:
  81. if !hasPort.MatchString(host) {
  82. host += ":80"
  83. }
  84. targetSiteCon, err := proxy.connectDial("tcp", host)
  85. if err != nil {
  86. httpError(proxyClient, ctx, err)
  87. return
  88. }
  89. ctx.Logf("Accepting CONNECT to %s", host)
  90. proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
  91. targetTCP, targetOK := targetSiteCon.(*net.TCPConn)
  92. proxyClientTCP, clientOK := proxyClient.(*net.TCPConn)
  93. if targetOK && clientOK {
  94. go copyAndClose(ctx, targetTCP, proxyClientTCP)
  95. go copyAndClose(ctx, proxyClientTCP, targetTCP)
  96. } else {
  97. go func() {
  98. var wg sync.WaitGroup
  99. wg.Add(2)
  100. go copyOrWarn(ctx, targetSiteCon, proxyClient, &wg)
  101. go copyOrWarn(ctx, proxyClient, targetSiteCon, &wg)
  102. wg.Wait()
  103. proxyClient.Close()
  104. targetSiteCon.Close()
  105. }()
  106. }
  107. case ConnectHijack:
  108. ctx.Logf("Hijacking CONNECT to %s", host)
  109. proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
  110. todo.Hijack(r, proxyClient, ctx)
  111. case ConnectHTTPMitm:
  112. proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
  113. ctx.Logf("Assuming CONNECT is plain HTTP tunneling, mitm proxying it")
  114. targetSiteCon, err := proxy.connectDial("tcp", host)
  115. if err != nil {
  116. ctx.Warnf("Error dialing to %s: %s", host, err.Error())
  117. return
  118. }
  119. for {
  120. client := bufio.NewReader(proxyClient)
  121. remote := bufio.NewReader(targetSiteCon)
  122. req, err := http.ReadRequest(client)
  123. if err != nil && err != io.EOF {
  124. ctx.Warnf("cannot read request of MITM HTTP client: %+#v", err)
  125. }
  126. if err != nil {
  127. return
  128. }
  129. req, resp := proxy.filterRequest(req, ctx)
  130. if resp == nil {
  131. if err := req.Write(targetSiteCon); err != nil {
  132. httpError(proxyClient, ctx, err)
  133. return
  134. }
  135. resp, err = http.ReadResponse(remote, req)
  136. if err != nil {
  137. httpError(proxyClient, ctx, err)
  138. return
  139. }
  140. defer resp.Body.Close()
  141. }
  142. resp = proxy.filterResponse(resp, ctx)
  143. if err := resp.Write(proxyClient); err != nil {
  144. httpError(proxyClient, ctx, err)
  145. return
  146. }
  147. }
  148. case ConnectMitm:
  149. proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
  150. ctx.Logf("Assuming CONNECT is TLS, mitm proxying it")
  151. // this goes in a separate goroutine, so that the net/http server won't think we're
  152. // still handling the request even after hijacking the connection. Those HTTP CONNECT
  153. // request can take forever, and the server will be stuck when "closed".
  154. // TODO: Allow Server.Close() mechanism to shut down this connection as nicely as possible
  155. tlsConfig := defaultTLSConfig
  156. if todo.TLSConfig != nil {
  157. var err error
  158. tlsConfig, err = todo.TLSConfig(host, ctx)
  159. if err != nil {
  160. httpError(proxyClient, ctx, err)
  161. return
  162. }
  163. }
  164. go func() {
  165. //TODO: cache connections to the remote website
  166. rawClientTls := tls.Server(proxyClient, tlsConfig)
  167. if err := rawClientTls.Handshake(); err != nil {
  168. ctx.Warnf("Cannot handshake client %v %v", r.Host, err)
  169. return
  170. }
  171. defer rawClientTls.Close()
  172. clientTlsReader := bufio.NewReader(rawClientTls)
  173. for !isEof(clientTlsReader) {
  174. req, err := http.ReadRequest(clientTlsReader)
  175. var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
  176. if err != nil && err != io.EOF {
  177. return
  178. }
  179. if err != nil {
  180. ctx.Warnf("Cannot read TLS request from mitm'd client %v %v", r.Host, err)
  181. return
  182. }
  183. req.RemoteAddr = r.RemoteAddr // since we're converting the request, need to carry over the original connecting IP as well
  184. ctx.Logf("req %v", r.Host)
  185. if !httpsRegexp.MatchString(req.URL.String()) {
  186. req.URL, err = url.Parse("https://" + r.Host + req.URL.String())
  187. }
  188. // Bug fix which goproxy fails to provide request
  189. // information URL in the context when does HTTPS MITM
  190. ctx.Req = req
  191. req, resp := proxy.filterRequest(req, ctx)
  192. if resp == nil {
  193. if err != nil {
  194. ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path)
  195. return
  196. }
  197. removeProxyHeaders(ctx, req)
  198. resp, err = ctx.RoundTrip(req)
  199. if err != nil {
  200. ctx.Warnf("Cannot read TLS response from mitm'd server %v", err)
  201. return
  202. }
  203. ctx.Logf("resp %v", resp.Status)
  204. }
  205. resp = proxy.filterResponse(resp, ctx)
  206. defer resp.Body.Close()
  207. text := resp.Status
  208. statusCode := strconv.Itoa(resp.StatusCode) + " "
  209. if strings.HasPrefix(text, statusCode) {
  210. text = text[len(statusCode):]
  211. }
  212. // always use 1.1 to support chunked encoding
  213. if _, err := io.WriteString(rawClientTls, "HTTP/1.1"+" "+statusCode+text+"\r\n"); err != nil {
  214. ctx.Warnf("Cannot write TLS response HTTP status from mitm'd client: %v", err)
  215. return
  216. }
  217. // Since we don't know the length of resp, return chunked encoded response
  218. // TODO: use a more reasonable scheme
  219. resp.Header.Del("Content-Length")
  220. resp.Header.Set("Transfer-Encoding", "chunked")
  221. if err := resp.Header.Write(rawClientTls); err != nil {
  222. ctx.Warnf("Cannot write TLS response header from mitm'd client: %v", err)
  223. return
  224. }
  225. if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil {
  226. ctx.Warnf("Cannot write TLS response header end from mitm'd client: %v", err)
  227. return
  228. }
  229. chunked := newChunkedWriter(rawClientTls)
  230. if _, err := io.Copy(chunked, resp.Body); err != nil {
  231. ctx.Warnf("Cannot write TLS response body from mitm'd client: %v", err)
  232. return
  233. }
  234. if err := chunked.Close(); err != nil {
  235. ctx.Warnf("Cannot write TLS chunked EOF from mitm'd client: %v", err)
  236. return
  237. }
  238. if _, err = io.WriteString(rawClientTls, "\r\n"); err != nil {
  239. ctx.Warnf("Cannot write TLS response chunked trailer from mitm'd client: %v", err)
  240. return
  241. }
  242. }
  243. ctx.Logf("Exiting on EOF")
  244. }()
  245. case ConnectProxyAuthHijack:
  246. proxyClient.Write([]byte("HTTP/1.1 407 Proxy Authentication Required\r\n"))
  247. todo.Hijack(r, proxyClient, ctx)
  248. case ConnectReject:
  249. if ctx.Resp != nil {
  250. if err := ctx.Resp.Write(proxyClient); err != nil {
  251. ctx.Warnf("Cannot write response that reject http CONNECT: %v", err)
  252. }
  253. }
  254. proxyClient.Close()
  255. }
  256. }
  257. func httpError(w io.WriteCloser, ctx *ProxyCtx, err error) {
  258. if _, err := io.WriteString(w, "HTTP/1.1 502 Bad Gateway\r\n\r\n"); err != nil {
  259. ctx.Warnf("Error responding to client: %s", err)
  260. }
  261. if err := w.Close(); err != nil {
  262. ctx.Warnf("Error closing client connection: %s", err)
  263. }
  264. }
  265. func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup) {
  266. if _, err := io.Copy(dst, src); err != nil {
  267. ctx.Warnf("Error copying to client: %s", err)
  268. }
  269. wg.Done()
  270. }
  271. func copyAndClose(ctx *ProxyCtx, dst, src *net.TCPConn) {
  272. if _, err := io.Copy(dst, src); err != nil {
  273. ctx.Warnf("Error copying to client: %s", err)
  274. }
  275. dst.CloseWrite()
  276. src.CloseRead()
  277. }
  278. func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn, error) {
  279. https_proxy := os.Getenv("HTTPS_PROXY")
  280. if https_proxy == "" {
  281. https_proxy = os.Getenv("https_proxy")
  282. }
  283. if https_proxy == "" {
  284. return nil
  285. }
  286. return proxy.NewConnectDialToProxy(https_proxy)
  287. }
  288. func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) {
  289. u, err := url.Parse(https_proxy)
  290. if err != nil {
  291. return nil
  292. }
  293. if u.Scheme == "" || u.Scheme == "http" {
  294. if strings.IndexRune(u.Host, ':') == -1 {
  295. u.Host += ":80"
  296. }
  297. return func(network, addr string) (net.Conn, error) {
  298. connectReq := &http.Request{
  299. Method: "CONNECT",
  300. URL: &url.URL{Opaque: addr},
  301. Host: addr,
  302. Header: make(http.Header),
  303. }
  304. c, err := proxy.dial(network, u.Host)
  305. if err != nil {
  306. return nil, err
  307. }
  308. connectReq.Write(c)
  309. // Read response.
  310. // Okay to use and discard buffered reader here, because
  311. // TLS server will not speak until spoken to.
  312. br := bufio.NewReader(c)
  313. resp, err := http.ReadResponse(br, connectReq)
  314. if err != nil {
  315. c.Close()
  316. return nil, err
  317. }
  318. defer resp.Body.Close()
  319. if resp.StatusCode != 200 {
  320. resp, err := ioutil.ReadAll(resp.Body)
  321. if err != nil {
  322. return nil, err
  323. }
  324. c.Close()
  325. return nil, errors.New("proxy refused connection" + string(resp))
  326. }
  327. return c, nil
  328. }
  329. }
  330. if u.Scheme == "https" {
  331. if strings.IndexRune(u.Host, ':') == -1 {
  332. u.Host += ":443"
  333. }
  334. return func(network, addr string) (net.Conn, error) {
  335. c, err := proxy.dial(network, u.Host)
  336. if err != nil {
  337. return nil, err
  338. }
  339. c = tls.Client(c, proxy.Tr.TLSClientConfig)
  340. connectReq := &http.Request{
  341. Method: "CONNECT",
  342. URL: &url.URL{Opaque: addr},
  343. Host: addr,
  344. Header: make(http.Header),
  345. }
  346. connectReq.Write(c)
  347. // Read response.
  348. // Okay to use and discard buffered reader here, because
  349. // TLS server will not speak until spoken to.
  350. br := bufio.NewReader(c)
  351. resp, err := http.ReadResponse(br, connectReq)
  352. if err != nil {
  353. c.Close()
  354. return nil, err
  355. }
  356. defer resp.Body.Close()
  357. if resp.StatusCode != 200 {
  358. body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 500))
  359. if err != nil {
  360. return nil, err
  361. }
  362. c.Close()
  363. return nil, errors.New("proxy refused connection" + string(body))
  364. }
  365. return c, nil
  366. }
  367. }
  368. return nil
  369. }
  370. func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) {
  371. return func(host string, ctx *ProxyCtx) (*tls.Config, error) {
  372. config := *defaultTLSConfig
  373. ctx.Logf("signing for %s", stripPort(host))
  374. cert, err := signHost(*ca, []string{stripPort(host)})
  375. if err != nil {
  376. ctx.Warnf("Cannot sign host certificate with provided CA: %s", err)
  377. return nil, err
  378. }
  379. config.Certificates = append(config.Certificates, cert)
  380. return &config, nil
  381. }
  382. }