Bläddra i källkod

Merge pull request #105 from efryntov/master

HTTP proxy auth
Rod Hynes 10 år sedan
förälder
incheckning
1e7abcd113

+ 23 - 19
psiphon/TCPConn.go

@@ -21,11 +21,10 @@ package psiphon
 
 import (
 	"errors"
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/upstreamproxy"
 	"net"
 	"sync"
 	"time"
-
-	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/upstreamproxy"
 )
 
 // TCPConn is a customized TCP connection that:
@@ -75,7 +74,7 @@ func makeTCPDialer(config *DialConfig) func(network, addr string) (net.Conn, err
 				ProxyURIString:  config.UpstreamProxyUrl,
 			})
 
-		dialer = func(network, addr string) (net.Conn, error) {
+		dialer = func(network, addr string) (conn net.Conn, err error) {
 
 			// The entire upstream dial is wrapped in an explicit timeout. This
 			// may include network connection read and writes when proxy auth negotation
@@ -85,24 +84,29 @@ func makeTCPDialer(config *DialConfig) func(network, addr string) (net.Conn, err
 				conn net.Conn
 				err  error
 			}
-			resultChannel := make(chan *upstreamDialResult, 2)
-			time.AfterFunc(config.ConnectTimeout, func() {
-				// TODO: we could "interrupt" the underlying TCPConn at this point, as
-				// it's being abandoned. But we don't have a reference to it. It's left
-				// to the outer DialConfig.PendingConns to track and clean up that TCPConn.
-				resultChannel <- &upstreamDialResult{nil, errors.New("upstreamproxy dial timeout")}
-			})
-			go func() {
-				conn, err := upstreamDialer(network, addr)
-				resultChannel <- &upstreamDialResult{conn, err}
-			}()
-			result := <-resultChannel
-
-			if _, ok := result.err.(*upstreamproxy.Error); ok {
-				NoticeUpstreamProxyError(result.err)
+			if config.ConnectTimeout != 0 {
+				resultChannel := make(chan *upstreamDialResult, 2)
+				time.AfterFunc(config.ConnectTimeout, func() {
+					// TODO: we could "interrupt" the underlying TCPConn at this point, as
+					// it's being abandoned. But we don't have a reference to it. It's left
+					// to the outer DialConfig.PendingConns to track and clean up that TCPConn.
+					resultChannel <- &upstreamDialResult{nil, errors.New("upstreamproxy dial timeout")}
+				})
+				go func() {
+					conn, err := upstreamDialer(network, addr)
+					resultChannel <- &upstreamDialResult{conn, err}
+				}()
+				result := <-resultChannel
+
+				conn, err = result.conn, result.err
+			} else {
+				conn, err = upstreamDialer(network, addr)
 			}
 
-			return result.conn, result.err
+			if _, ok := err.(*upstreamproxy.Error); ok {
+				NoticeUpstreamProxyError(err)
+			}
+			return conn, err
 		}
 	}
 

+ 29 - 7
psiphon/upstreamproxy/auth_basic.go

@@ -33,20 +33,42 @@ const (
 )
 
 type BasicHttpAuthenticator struct {
-	state BasicHttpAuthState
+	state    BasicHttpAuthState
+	username string
+	password string
 }
 
-func newBasicAuthenticator() *BasicHttpAuthenticator {
-	return &BasicHttpAuthenticator{state: BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED}
+func newBasicAuthenticator(username, password string) *BasicHttpAuthenticator {
+	return &BasicHttpAuthenticator{
+		state:    BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
+		username: username,
+		password: password,
+	}
 }
 
-func (a *BasicHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response, username, password string) error {
+func (a *BasicHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
 	if a.state == BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
-		auth := username + ":" + password
-		req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
 		a.state = BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
-		return nil
+		return a.PreAuthenticate(req)
 	} else {
 		return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
 	}
 }
+
+func (a *BasicHttpAuthenticator) IsConnectionBased() bool {
+	return false
+}
+
+func (a *BasicHttpAuthenticator) IsComplete() bool {
+	return a.state == BASIC_HTTP_AUTH_STATE_RESPONSE_GENERATED
+}
+
+func (a *BasicHttpAuthenticator) Reset() {
+	a.state = BASIC_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
+}
+
+func (a *BasicHttpAuthenticator) PreAuthenticate(req *http.Request) error {
+	auth := a.username + ":" + a.password
+	req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
+	return nil
+}

+ 60 - 22
psiphon/upstreamproxy/auth_digest.go

@@ -36,11 +36,18 @@ const (
 )
 
 type DigestHttpAuthenticator struct {
-	state DigestHttpAuthState
+	state         DigestHttpAuthState
+	username      string
+	password      string
+	digestHeaders *DigestHeaders
 }
 
-func newDigestAuthenticator() *DigestHttpAuthenticator {
-	return &DigestHttpAuthenticator{state: DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED}
+func newDigestAuthenticator(username, password string) *DigestHttpAuthenticator {
+	return &DigestHttpAuthenticator{
+		state:    DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
+		username: username,
+		password: password,
+	}
 }
 
 /* Adapted from https://github.com/ryanjdew/http-digest-auth-client */
@@ -64,7 +71,6 @@ type DigestHeaders struct {
 // ApplyAuth adds proper auth header to the passed request
 func (d *DigestHeaders) ApplyAuth(req *http.Request) {
 	d.Nc += 0x1
-	d.Cnonce = randomKey()
 	d.Method = req.Method
 	d.digestChecksum()
 	response := h(strings.Join([]string{d.HA1, d.Nonce, fmt.Sprintf("%08x", d.Nc),
@@ -124,7 +130,7 @@ func h(data string) string {
 	return fmt.Sprintf("%x", digest.Sum(nil))
 }
 
-func (a *DigestHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response, username, password string) error {
+func (a *DigestHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
 	if a.state != DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED {
 		return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
 	}
@@ -152,25 +158,57 @@ func (a *DigestHttpAuthenticator) Authenticate(req *http.Request, resp *http.Res
 
 	algorithm := digestParams["algorithm"]
 
-	d := &DigestHeaders{}
-	if req.Method == "CONNECT" {
-		d.Uri = req.URL.Host
-	} else {
-		d.Uri = req.URL.Scheme + "://" + req.URL.Host + req.URL.RequestURI()
+	if _, ok := digestParams["stale"]; ok {
+		// Server indicated that the nonce is stale
+		// Reset auth cache and state
+		a.digestHeaders = nil
+		a.state = DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
+		return nil
 	}
-	d.Realm = digestParams["realm"]
-	d.Qop = digestParams["qop"]
-	d.Nonce = digestParams["nonce"]
-	d.Opaque = digestParams["opaque"]
-	if algorithm == "" {
-		d.Algorithm = "MD5"
-	} else {
-		d.Algorithm = digestParams["algorithm"]
+
+	if a.digestHeaders == nil {
+		d := &DigestHeaders{}
+		if req.Method == "CONNECT" {
+			d.Uri = req.URL.Host
+		} else {
+			d.Uri = req.URL.Scheme + "://" + req.URL.Host + req.URL.RequestURI()
+		}
+		d.Realm = digestParams["realm"]
+		d.Qop = digestParams["qop"]
+		d.Nonce = digestParams["nonce"]
+		d.Opaque = digestParams["opaque"]
+		if algorithm == "" {
+			d.Algorithm = "MD5"
+		} else {
+			d.Algorithm = digestParams["algorithm"]
+		}
+		d.Nc = 0x0
+		d.Cnonce = randomKey()
+		d.Username = a.username
+		d.Password = a.password
+		a.digestHeaders = d
 	}
-	d.Nc = 0x0
-	d.Username = username
-	d.Password = password
-	d.ApplyAuth(req)
+
+	a.digestHeaders.ApplyAuth(req)
 	a.state = DIGEST_HTTP_AUTH_STATE_RESPONSE_GENERATED
 	return nil
 }
+
+func (a *DigestHttpAuthenticator) IsConnectionBased() bool {
+	return false
+}
+
+func (a *DigestHttpAuthenticator) IsComplete() bool {
+	return a.state == DIGEST_HTTP_AUTH_STATE_RESPONSE_GENERATED
+}
+
+func (a *DigestHttpAuthenticator) Reset() {
+	a.state = DIGEST_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
+}
+
+func (a *DigestHttpAuthenticator) PreAuthenticate(req *http.Request) error {
+	if a.digestHeaders != nil {
+		a.digestHeaders.ApplyAuth(req)
+	}
+	return nil
+}

+ 29 - 7
psiphon/upstreamproxy/auth_ntlm.go

@@ -36,14 +36,20 @@ const (
 )
 
 type NTLMHttpAuthenticator struct {
-	state NTLMHttpAuthState
+	state    NTLMHttpAuthState
+	username string
+	password string
 }
 
-func newNTLMAuthenticator() *NTLMHttpAuthenticator {
-	return &NTLMHttpAuthenticator{state: NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED}
+func newNTLMAuthenticator(username, password string) *NTLMHttpAuthenticator {
+	return &NTLMHttpAuthenticator{
+		state:    NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED,
+		username: username,
+		password: password,
+	}
 }
 
-func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response, username, password string) error {
+func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Response) error {
 	if a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED {
 		return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
 	}
@@ -78,19 +84,19 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 	} else if a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE1_GENERATED {
 		// Parse username for domain in form DOMAIN\username
 		var NTDomain, NTUser string
-		parts := strings.SplitN(username, "\\", 2)
+		parts := strings.SplitN(a.username, "\\", 2)
 		if len(parts) == 2 {
 			NTDomain = parts[0]
 			NTUser = parts[1]
 		} else {
 			NTDomain = ""
-			NTUser = username
+			NTUser = a.username
 		}
 		challengeBytes, err := base64.StdEncoding.DecodeString(challenge)
 		if err != nil {
 			return proxyError(fmt.Errorf("NTLM challeenge base 64 decoding: %v", err))
 		}
-		session.SetUserInfo(NTUser, password, NTDomain)
+		session.SetUserInfo(NTUser, a.password, NTDomain)
 		ntlmChallenge, err := ntlm.ParseChallengeMessage(challengeBytes)
 		if err != nil {
 			return proxyError(err)
@@ -108,3 +114,19 @@ func (a *NTLMHttpAuthenticator) Authenticate(req *http.Request, resp *http.Respo
 
 	return proxyError(fmt.Errorf("Authorization is not accepted by the proxy server"))
 }
+
+func (a *NTLMHttpAuthenticator) IsConnectionBased() bool {
+	return true
+}
+
+func (a *NTLMHttpAuthenticator) IsComplete() bool {
+	return a.state == NTLM_HTTP_AUTH_STATE_RESPONSE_TYPE3_GENERATED
+}
+
+func (a *NTLMHttpAuthenticator) Reset() {
+	a.state = NTLM_HTTP_AUTH_STATE_CHALLENGE_RECEIVED
+}
+
+func (a *NTLMHttpAuthenticator) PreAuthenticate(req *http.Request) error {
+	return nil
+}

+ 9 - 5
psiphon/upstreamproxy/http_authenticator.go

@@ -35,7 +35,11 @@ const (
 )
 
 type HttpAuthenticator interface {
-	Authenticate(req *http.Request, resp *http.Response, username, pasword string) error
+	PreAuthenticate(req *http.Request) error
+	Authenticate(req *http.Request, resp *http.Response) error
+	IsConnectionBased() bool
+	IsComplete() bool
+	Reset()
 }
 
 func parseAuthChallenge(resp *http.Response) (map[string]string, error) {
@@ -57,7 +61,7 @@ func parseAuthChallenge(resp *http.Response) (map[string]string, error) {
 	return challenges, nil
 }
 
-func NewHttpAuthenticator(resp *http.Response) (HttpAuthenticator, error) {
+func NewHttpAuthenticator(resp *http.Response, username, password string) (HttpAuthenticator, error) {
 
 	challenges, err := parseAuthChallenge(resp)
 	if err != nil {
@@ -67,11 +71,11 @@ func NewHttpAuthenticator(resp *http.Response) (HttpAuthenticator, error) {
 
 	// NTLM > Digest > Basic
 	if _, ok := challenges["NTLM"]; ok {
-		return newNTLMAuthenticator(), nil
+		return newNTLMAuthenticator(username, password), nil
 	} else if _, ok := challenges["Digest"]; ok {
-		return newDigestAuthenticator(), nil
+		return newDigestAuthenticator(username, password), nil
 	} else if _, ok := challenges["Basic"]; ok {
-		return newBasicAuthenticator(), nil
+		return newBasicAuthenticator(username, password), nil
 	}
 
 	//Unsupported scheme

+ 2 - 2
psiphon/upstreamproxy/proxy_http.go

@@ -145,7 +145,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 	req.Header.Set("User-Agent", "")
 
 	if pc.authState == HTTP_AUTH_STATE_CHALLENGED {
-		err := pc.authenticator.Authenticate(req, pc.authResponse, username, password)
+		err := pc.authenticator.Authenticate(req, pc.authResponse)
 		if err != nil {
 			pc.authState = HTTP_AUTH_STATE_FAILURE
 			//Already wrapped in proxyError
@@ -169,7 +169,7 @@ func (pc *proxyConn) handshake(addr, username, password string) error {
 	if resp.StatusCode == 407 {
 		if pc.authState == HTTP_AUTH_STATE_UNCHALLENGED {
 			var auth_err error = nil
-			pc.authenticator, auth_err = NewHttpAuthenticator(resp)
+			pc.authenticator, auth_err = NewHttpAuthenticator(resp, username, password)
 			if auth_err != nil {
 				pc.httpClientConn.Close()
 				pc.authState = HTTP_AUTH_STATE_FAILURE

+ 141 - 38
psiphon/upstreamproxy/transport_proxy_auth.go

@@ -28,16 +28,21 @@ import (
 	"net"
 	"net/http"
 	"strings"
+	"sync"
 )
 
+const HTTP_STAT_LINE_LENGTH = 12
+
 // ProxyAuthTransport provides support for proxy authentication when doing plain HTTP
 // by tapping into HTTP conversation and adding authentication headers to the requests
 // when requested by server
 type ProxyAuthTransport struct {
 	*http.Transport
-	Dial     DialFunc
-	Username string
-	Password string
+	Dial          DialFunc
+	Username      string
+	Password      string
+	Authenticator HttpAuthenticator
+	mu            sync.Mutex
 }
 
 func NewProxyAuthTransport(rawTransport *http.Transport) (*ProxyAuthTransport, error) {
@@ -49,6 +54,7 @@ func NewProxyAuthTransport(rawTransport *http.Transport) (*ProxyAuthTransport, e
 	proxyUrlFn := rawTransport.Proxy
 	if proxyUrlFn != nil {
 		wrappedDialFn := tr.wrapTransportDial()
+		rawTransport.Dial = wrappedDialFn
 		proxyUrl, err := proxyUrlFn(nil)
 		if err != nil {
 			return nil, err
@@ -56,20 +62,83 @@ func NewProxyAuthTransport(rawTransport *http.Transport) (*ProxyAuthTransport, e
 		if proxyUrl.Scheme != "http" {
 			return nil, fmt.Errorf("Only HTTP proxy supported, for SOCKS use http.Transport with custom dialers & upstreamproxy.NewProxyDialFunc")
 		}
-		tr.Username = proxyUrl.User.Username()
-		tr.Password, _ = proxyUrl.User.Password()
-		rawTransport.Dial = wrappedDialFn
+		if proxyUrl.User != nil {
+			tr.Username = proxyUrl.User.Username()
+			tr.Password, _ = proxyUrl.User.Password()
+		}
+		// strip username and password from the proxyURL because
+		// we do not want the wrapped transport to handle authentication
+		proxyUrl.User = nil
+		rawTransport.Proxy = http.ProxyURL(proxyUrl)
 	}
 
 	tr.Transport = rawTransport
 	return tr, nil
 }
 
+func (tr *ProxyAuthTransport) preAuthenticateRequest(req *http.Request) error {
+	tr.mu.Lock()
+	defer tr.mu.Unlock()
+	if tr.Authenticator == nil {
+		return nil
+	}
+	return tr.Authenticator.PreAuthenticate(req)
+}
+
 func (tr *ProxyAuthTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
 	if req.URL.Scheme != "http" {
 		return nil, fmt.Errorf("Only plain HTTP supported, for HTTPS use http.Transport with DialTLS & upstreamproxy.NewProxyDialFunc")
 	}
-	return tr.Transport.RoundTrip(req)
+	err = tr.preAuthenticateRequest(req)
+	if err != nil {
+		return nil, err
+	}
+
+	var ha HttpAuthenticator = nil
+
+	//Clone request early because RoundTrip will destroy request Body
+	newReq := cloneRequest(req)
+
+	resp, err = tr.Transport.RoundTrip(newReq)
+
+	if err != nil {
+		return resp, proxyError(err)
+	}
+
+	if resp.StatusCode == 407 {
+		tr.mu.Lock()
+		defer tr.mu.Unlock()
+		ha, err = NewHttpAuthenticator(resp, tr.Username, tr.Password)
+		if err != nil {
+			return nil, err
+		}
+		if ha.IsConnectionBased() {
+			return nil, proxyError(fmt.Errorf("Connection based auth was not handled by transportConn!"))
+		}
+		tr.Authenticator = ha
+	authenticationLoop:
+		for {
+			newReq = cloneRequest(req)
+			err = tr.Authenticator.Authenticate(newReq, resp)
+			if err != nil {
+				return nil, err
+			}
+			resp, err = tr.Transport.RoundTrip(newReq)
+
+			if err != nil {
+				return resp, proxyError(err)
+			}
+			if resp.StatusCode != 407 {
+				if tr.Authenticator != nil && tr.Authenticator.IsComplete() {
+					tr.Authenticator.Reset()
+				}
+				break authenticationLoop
+			} else {
+			}
+		}
+	}
+	return resp, err
+
 }
 
 // wrapTransportDial wraps original transport Dial function
@@ -87,19 +156,36 @@ func (tr *ProxyAuthTransport) wrapTransportDial() DialFunc {
 	}
 }
 
+func cloneRequest(r *http.Request) *http.Request {
+	// shallow copy of the struct
+	r2 := new(http.Request)
+	*r2 = *r
+	// deep copy of the Header
+	r2.Header = make(http.Header)
+	for k, s := range r.Header {
+		r2.Header[k] = s
+	}
+
+	if r.Body != nil {
+		body, _ := ioutil.ReadAll(r.Body)
+		defer r.Body.Close()
+		// restore original request Body
+		// drained by ReadAll()
+		r.Body = ioutil.NopCloser(bytes.NewReader(body))
+
+		r2.Body = ioutil.NopCloser(bytes.NewReader(body))
+	}
+	return r2
+}
+
 type transportConn struct {
 	net.Conn
 	requestInterceptor io.Writer
 	reqDone            chan struct{}
 	errChannel         chan error
-	// a buffered Reader from the raw net.Conn so we could Peek at the data
-	// without advancing the 'read' pointer
-	connReader *bufio.Reader
-	// last written request holder
-	lastRequest   *http.Request
-	authenticator HttpAuthenticator
-	authState     HttpAuthState
-	transport     *ProxyAuthTransport
+	lastRequest        *http.Request
+	authenticator      HttpAuthenticator
+	transport          *ProxyAuthTransport
 }
 
 func newTransportConn(c net.Conn, tr *ProxyAuthTransport) *transportConn {
@@ -107,7 +193,6 @@ func newTransportConn(c net.Conn, tr *ProxyAuthTransport) *transportConn {
 		Conn:       c,
 		reqDone:    make(chan struct{}),
 		errChannel: make(chan error),
-		connReader: bufio.NewReader(c),
 		transport:  tr,
 	}
 	// Intercept outgoing request as it is written out to server and store it
@@ -115,10 +200,11 @@ func newTransportConn(c net.Conn, tr *ProxyAuthTransport) *transportConn {
 	//NOTE that pipelining is currently not supported
 	pr, pw := io.Pipe()
 	tc.requestInterceptor = pw
+	requestReader := bufio.NewReader(pr)
 	go func() {
 	requestInterceptLoop:
 		for {
-			req, err := http.ReadRequest(bufio.NewReader(pr))
+			req, err := http.ReadRequest(requestReader)
 			if err != nil {
 				tc.Conn.Close()
 				pr.Close()
@@ -139,34 +225,53 @@ func newTransportConn(c net.Conn, tr *ProxyAuthTransport) *transportConn {
 
 // Read peeks into the new response and checks if the proxy requests authentication
 // If so, the last intercepted request is authenticated against the response
-// authentication challenge and replayed
-func (tc *transportConn) Read(p []byte) (int, error) {
-	peeked, err := tc.connReader.Peek(12)
-	if err != nil {
-		return 0, err
+// in case of connection based auth scheme(i.e. NTLM)
+// All the non-connection based schemes are handled by the ProxyAuthTransport.RoundTrip()
+func (tc *transportConn) Read(p []byte) (n int, read_err error) {
+	n, read_err = tc.Conn.Read(p)
+	if n < HTTP_STAT_LINE_LENGTH {
+		return
 	}
-	line := string(peeked)
 	select {
 	case _ = <-tc.reqDone:
+		line := string(p[:HTTP_STAT_LINE_LENGTH])
 		//This is a new response
 		//Let's see if proxy requests authentication
 		f := strings.SplitN(line, " ", 2)
+
+		readBufferReader := io.NewSectionReader(bytes.NewReader(p), 0, int64(n))
+		responseReader := bufio.NewReader(readBufferReader)
 		if (f[0] == "HTTP/1.0" || f[0] == "HTTP/1.1") && f[1] == "407" {
-			resp, err := http.ReadResponse(tc.connReader, nil)
+			resp, err := http.ReadResponse(responseReader, nil)
 			if err != nil {
 				return 0, err
 			}
-			// make sure we read the body of the response so that
-			// we don't block the reader
+			ha, err := NewHttpAuthenticator(resp, tc.transport.Username, tc.transport.Password)
+			if err != nil {
+				return 0, err
+			}
+			// If connection based auth is requested, we are going to
+			// authenticate request on this very connection
+			// otherwise just return what we read
+			if !ha.IsConnectionBased() {
+				return
+			}
+
+			// Drain the rest of the response
+			// in order to perform auth handshake
+			// on the connection
+			readBufferReader.Seek(0, 0)
+			responseReader = bufio.NewReader(io.MultiReader(readBufferReader, tc.Conn))
+			resp, err = http.ReadResponse(responseReader, nil)
+			if err != nil {
+				return 0, err
+			}
+
 			ioutil.ReadAll(resp.Body)
 			resp.Body.Close()
 
-			if tc.authState == HTTP_AUTH_STATE_UNCHALLENGED {
-				tc.authenticator, err = NewHttpAuthenticator(resp)
-				if err != nil {
-					return 0, err
-				}
-				tc.authState = HTTP_AUTH_STATE_CHALLENGED
+			if tc.authenticator == nil {
+				tc.authenticator = ha
 			}
 
 			if resp.Close == true {
@@ -178,23 +283,21 @@ func (tc *transportConn) Read(p []byte) (int, error) {
 				if err != nil {
 					return 0, err
 				}
-				tc.connReader = bufio.NewReader(tc.Conn)
 			}
 
-			// Authenticate and replay the request
-			err = tc.authenticator.Authenticate(tc.lastRequest, resp, tc.transport.Username, tc.transport.Password)
+			// Authenticate and replay the request on the connection
+			err = tc.authenticator.Authenticate(tc.lastRequest, resp)
 			if err != nil {
 				return 0, err
 			}
 			tc.lastRequest.WriteProxy(tc)
 			return tc.Read(p)
 		}
-	case err = <-tc.errChannel:
+	case err := <-tc.errChannel:
 		return 0, err
 	default:
 	}
-	n, err := tc.connReader.Read(p)
-	return n, err
+	return
 }
 
 func (tc *transportConn) Write(p []byte) (n int, err error) {