|
|
@@ -36,6 +36,10 @@ var (
|
|
|
httpsRegexp = regexp.MustCompile(`^https:\/\/`)
|
|
|
)
|
|
|
|
|
|
+// ConnectAction enables the caller to override the standard connect flow.
|
|
|
+// When Action is ConnectHijack, it is up to the implementer to send the
|
|
|
+// HTTP 200, or any other valid http response back to the client from within the
|
|
|
+// Hijack func
|
|
|
type ConnectAction struct {
|
|
|
Action ConnectActionLiteral
|
|
|
Hijack func(req *http.Request, client net.Conn, ctx *ProxyCtx)
|
|
|
@@ -64,8 +68,16 @@ func (proxy *ProxyHttpServer) connectDial(network, addr string) (c net.Conn, err
|
|
|
return proxy.ConnectDial(network, addr)
|
|
|
}
|
|
|
|
|
|
+type halfClosable interface {
|
|
|
+ net.Conn
|
|
|
+ CloseWrite() error
|
|
|
+ CloseRead() error
|
|
|
+}
|
|
|
+
|
|
|
+var _ halfClosable = (*net.TCPConn)(nil)
|
|
|
+
|
|
|
func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request) {
|
|
|
- ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
|
|
|
+ ctx := &ProxyCtx{Req: r, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, certStore: proxy.CertStore}
|
|
|
|
|
|
hij, ok := w.(http.Hijacker)
|
|
|
if !ok {
|
|
|
@@ -102,8 +114,8 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
|
|
|
ctx.Logf("Accepting CONNECT to %s", host)
|
|
|
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
|
|
|
|
|
- targetTCP, targetOK := targetSiteCon.(*net.TCPConn)
|
|
|
- proxyClientTCP, clientOK := proxyClient.(*net.TCPConn)
|
|
|
+ targetTCP, targetOK := targetSiteCon.(halfClosable)
|
|
|
+ proxyClientTCP, clientOK := proxyClient.(halfClosable)
|
|
|
if targetOK && clientOK {
|
|
|
go copyAndClose(ctx, targetTCP, proxyClientTCP)
|
|
|
go copyAndClose(ctx, proxyClientTCP, targetTCP)
|
|
|
@@ -121,8 +133,6 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
|
|
|
}
|
|
|
|
|
|
case ConnectHijack:
|
|
|
- ctx.Logf("Hijacking CONNECT to %s", host)
|
|
|
- proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
|
|
todo.Hijack(r, proxyClient, ctx)
|
|
|
case ConnectHTTPMitm:
|
|
|
proxyClient.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
|
|
|
@@ -188,7 +198,7 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
|
|
|
clientTlsReader := bufio.NewReader(rawClientTls)
|
|
|
for !isEof(clientTlsReader) {
|
|
|
req, err := http.ReadRequest(clientTlsReader)
|
|
|
- var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), proxy: proxy}
|
|
|
+ var ctx = &ProxyCtx{Req: req, Session: atomic.AddInt64(&proxy.sess, 1), Proxy: proxy, UserData: ctx.UserData}
|
|
|
if err != nil && err != io.EOF {
|
|
|
return
|
|
|
}
|
|
|
@@ -209,6 +219,11 @@ func (proxy *ProxyHttpServer) handleHttps(w http.ResponseWriter, r *http.Request
|
|
|
|
|
|
req, resp := proxy.filterRequest(req, ctx)
|
|
|
if resp == nil {
|
|
|
+ if isWebSocketRequest(req) {
|
|
|
+ ctx.Logf("Request looks like websocket upgrade.")
|
|
|
+ proxy.serveWebsocketTLS(ctx, w, req, tlsConfig, rawClientTls)
|
|
|
+ return
|
|
|
+ }
|
|
|
if err != nil {
|
|
|
ctx.Warnf("Illegal URL %s", "https://"+r.Host+req.URL.Path)
|
|
|
return
|
|
|
@@ -293,7 +308,7 @@ func copyOrWarn(ctx *ProxyCtx, dst io.Writer, src io.Reader, wg *sync.WaitGroup)
|
|
|
wg.Done()
|
|
|
}
|
|
|
|
|
|
-func copyAndClose(ctx *ProxyCtx, dst, src *net.TCPConn) {
|
|
|
+func copyAndClose(ctx *ProxyCtx, dst, src halfClosable) {
|
|
|
if _, err := io.Copy(dst, src); err != nil {
|
|
|
ctx.Warnf("Error copying to client: %s", err)
|
|
|
}
|
|
|
@@ -314,6 +329,10 @@ func dialerFromEnv(proxy *ProxyHttpServer) func(network, addr string) (net.Conn,
|
|
|
}
|
|
|
|
|
|
func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(network, addr string) (net.Conn, error) {
|
|
|
+ return proxy.NewConnectDialToProxyWithHandler(https_proxy, nil)
|
|
|
+}
|
|
|
+
|
|
|
+func (proxy *ProxyHttpServer) NewConnectDialToProxyWithHandler(https_proxy string, connectReqHandler func(req *http.Request)) func(network, addr string) (net.Conn, error) {
|
|
|
u, err := url.Parse(https_proxy)
|
|
|
if err != nil {
|
|
|
return nil
|
|
|
@@ -329,6 +348,9 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
|
|
|
Host: addr,
|
|
|
Header: make(http.Header),
|
|
|
}
|
|
|
+ if connectReqHandler != nil {
|
|
|
+ connectReqHandler(connectReq)
|
|
|
+ }
|
|
|
c, err := proxy.dial(network, u.Host)
|
|
|
if err != nil {
|
|
|
return nil, err
|
|
|
@@ -355,7 +377,7 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
|
|
|
return c, nil
|
|
|
}
|
|
|
}
|
|
|
- if u.Scheme == "https" {
|
|
|
+ if u.Scheme == "https" || u.Scheme == "wss" {
|
|
|
if strings.IndexRune(u.Host, ':') == -1 {
|
|
|
u.Host += ":443"
|
|
|
}
|
|
|
@@ -371,6 +393,9 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
|
|
|
Host: addr,
|
|
|
Header: make(http.Header),
|
|
|
}
|
|
|
+ if connectReqHandler != nil {
|
|
|
+ connectReqHandler(connectReq)
|
|
|
+ }
|
|
|
connectReq.Write(c)
|
|
|
// Read response.
|
|
|
// Okay to use and discard buffered reader here, because
|
|
|
@@ -398,14 +423,28 @@ func (proxy *ProxyHttpServer) NewConnectDialToProxy(https_proxy string) func(net
|
|
|
|
|
|
func TLSConfigFromCA(ca *tls.Certificate) func(host string, ctx *ProxyCtx) (*tls.Config, error) {
|
|
|
return func(host string, ctx *ProxyCtx) (*tls.Config, error) {
|
|
|
- config := *defaultTLSConfig
|
|
|
+ var err error
|
|
|
+ var cert *tls.Certificate
|
|
|
+
|
|
|
+ hostname := stripPort(host)
|
|
|
+ config := defaultTLSConfig.Clone()
|
|
|
ctx.Logf("signing for %s", stripPort(host))
|
|
|
- cert, err := signHost(*ca, []string{stripPort(host)})
|
|
|
+
|
|
|
+ genCert := func() (*tls.Certificate, error) {
|
|
|
+ return signHost(*ca, []string{hostname})
|
|
|
+ }
|
|
|
+ if ctx.certStore != nil {
|
|
|
+ cert, err = ctx.certStore.Fetch(hostname, genCert)
|
|
|
+ } else {
|
|
|
+ cert, err = genCert()
|
|
|
+ }
|
|
|
+
|
|
|
if err != nil {
|
|
|
ctx.Warnf("Cannot sign host certificate with provided CA: %s", err)
|
|
|
return nil, err
|
|
|
}
|
|
|
- config.Certificates = append(config.Certificates, cert)
|
|
|
- return &config, nil
|
|
|
+
|
|
|
+ config.Certificates = append(config.Certificates, *cert)
|
|
|
+ return config, nil
|
|
|
}
|
|
|
}
|