github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/upstreamproxy/proxy_http.go (about)

     1  /*
     2   * Copyright (c) 2015, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  /*
    20   * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
    21   * All rights reserved.
    22   *
    23   * Redistribution and use in source and binary forms, with or without
    24   * modification, are permitted provided that the following conditions are met:
    25   *
    26   *  * Redistributions of source code must retain the above copyright notice,
    27   *    this list of conditions and the following disclaimer.
    28   *
    29   *  * Redistributions in binary form must reproduce the above copyright notice,
    30   *    this list of conditions and the following disclaimer in the documentation
    31   *    and/or other materials provided with the distribution.
    32   *
    33   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    34   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    35   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    37   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    38   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    39   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    40   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    41   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    43   * POSSIBILITY OF SUCH DAMAGE.
    44   */
    45  
    46  package upstreamproxy
    47  
    48  import (
    49  	"bufio"
    50  	"fmt"
    51  	"net"
    52  	"net/http"
    53  	"net/http/httputil"
    54  	"net/url"
    55  	"time"
    56  
    57  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
    58  	"golang.org/x/net/proxy"
    59  )
    60  
    61  // httpProxy is a HTTP connect proxy.
    62  type httpProxy struct {
    63  	hostPort      string
    64  	username      string
    65  	password      string
    66  	forward       proxy.Dialer
    67  	customHeaders http.Header
    68  }
    69  
    70  func newHTTP(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
    71  	hp := new(httpProxy)
    72  	hp.hostPort = uri.Host
    73  	hp.forward = forward
    74  	if uri.User != nil {
    75  		hp.username = uri.User.Username()
    76  		hp.password, _ = uri.User.Password()
    77  	}
    78  
    79  	if upstreamProxyConfig, ok := forward.(*UpstreamProxyConfig); ok {
    80  		hp.customHeaders = upstreamProxyConfig.CustomHeaders
    81  	}
    82  
    83  	return hp, nil
    84  }
    85  
    86  func (hp *httpProxy) Dial(network, addr string) (net.Conn, error) {
    87  	// Dial and create the http client connection.
    88  	pc := &proxyConn{
    89  		authState:     HTTP_AUTH_STATE_UNCHALLENGED,
    90  		dialFn:        hp.forward.Dial,
    91  		proxyAddr:     hp.hostPort,
    92  		customHeaders: hp.customHeaders,
    93  	}
    94  	err := pc.makeNewClientConn()
    95  	if err != nil {
    96  		// Already wrapped in proxyError
    97  		return nil, err
    98  	}
    99  
   100  handshakeLoop:
   101  	for {
   102  		err := pc.handshake(addr, hp.username, hp.password)
   103  		if err != nil {
   104  			// Already wrapped in proxyError
   105  			return nil, err
   106  		}
   107  		switch pc.authState {
   108  		case HTTP_AUTH_STATE_SUCCESS:
   109  			pc.hijackedConn, pc.staleReader = pc.httpClientConn.Hijack()
   110  			return pc, nil
   111  		case HTTP_AUTH_STATE_CHALLENGED:
   112  			continue
   113  		default:
   114  			break handshakeLoop
   115  		}
   116  	}
   117  	return nil, proxyError(fmt.Errorf("unknown handshake error in state %v", pc.authState))
   118  }
   119  
   120  type proxyConn struct {
   121  	dialFn         DialFunc
   122  	proxyAddr      string
   123  	customHeaders  http.Header
   124  	httpClientConn *httputil.ClientConn //lint:ignore SA1019 httputil.ClientConn used for client-side hijack
   125  	hijackedConn   net.Conn
   126  	staleReader    *bufio.Reader
   127  	authResponse   *http.Response
   128  	authState      HttpAuthState
   129  	authenticator  HttpAuthenticator
   130  }
   131  
   132  func (pc *proxyConn) handshake(addr, username, password string) error {
   133  	// HACK: prefix addr of the form 'hostname:port' with a 'http' scheme
   134  	// so it could be parsed by url.Parse
   135  	reqURL, err := common.SafeParseURL("http://" + addr)
   136  	if err != nil {
   137  		pc.httpClientConn.Close()
   138  		pc.authState = HTTP_AUTH_STATE_FAILURE
   139  		return proxyError(fmt.Errorf("failed to parse proxy address: %v", err))
   140  	}
   141  	reqURL.Scheme = ""
   142  
   143  	req, err := http.NewRequest("CONNECT", reqURL.String(), nil)
   144  	if err != nil {
   145  		pc.httpClientConn.Close()
   146  		pc.authState = HTTP_AUTH_STATE_FAILURE
   147  		return proxyError(fmt.Errorf("create proxy request: %v", err))
   148  	}
   149  	req.Close = false
   150  	req.Header.Set("User-Agent", "")
   151  
   152  	for k, s := range pc.customHeaders {
   153  		// handle special Host header case
   154  		if k == "Host" {
   155  			if len(s) > 0 {
   156  				// hack around 'CONNECT' special case:
   157  				// https://golang.org/src/net/http/request.go#L476
   158  				// using URL.Opaque, see URL.RequestURI() https://golang.org/src/net/url/url.go#L915
   159  				req.URL.Opaque = req.Host
   160  				req.URL.Path = " "
   161  				req.Host = s[0]
   162  			}
   163  		} else {
   164  			req.Header[k] = s
   165  		}
   166  	}
   167  
   168  	if pc.authState == HTTP_AUTH_STATE_CHALLENGED {
   169  		err := pc.authenticator.Authenticate(req, pc.authResponse)
   170  		if err != nil {
   171  			pc.authState = HTTP_AUTH_STATE_FAILURE
   172  			// Already wrapped in proxyError
   173  			return err
   174  		}
   175  	}
   176  
   177  	resp, err := pc.httpClientConn.Do(req)
   178  
   179  	//lint:ignore SA1019 httputil.ClientConn used for client-side hijack
   180  	errPersistEOF := httputil.ErrPersistEOF
   181  
   182  	if err != nil && err != errPersistEOF {
   183  		pc.httpClientConn.Close()
   184  		pc.authState = HTTP_AUTH_STATE_FAILURE
   185  		return proxyError(fmt.Errorf("making proxy request: %v", err))
   186  	}
   187  
   188  	if resp.StatusCode == 200 {
   189  		pc.authState = HTTP_AUTH_STATE_SUCCESS
   190  		return nil
   191  	}
   192  
   193  	if resp.StatusCode == 407 {
   194  		if pc.authState == HTTP_AUTH_STATE_UNCHALLENGED {
   195  			var authErr error
   196  			pc.authenticator, authErr = NewHttpAuthenticator(resp, username, password)
   197  			if authErr != nil {
   198  				pc.httpClientConn.Close()
   199  				pc.authState = HTTP_AUTH_STATE_FAILURE
   200  				// Already wrapped in proxyError
   201  				return authErr
   202  			}
   203  		}
   204  
   205  		pc.authState = HTTP_AUTH_STATE_CHALLENGED
   206  		pc.authResponse = resp
   207  		if username == "" {
   208  			pc.httpClientConn.Close()
   209  			pc.authState = HTTP_AUTH_STATE_FAILURE
   210  			return proxyError(fmt.Errorf("no username credentials provided for proxy auth"))
   211  		}
   212  		if err == errPersistEOF {
   213  			// The server may send Connection: close,
   214  			// at this point we just going to create a new
   215  			// ClientConn and continue the handshake
   216  			err = pc.makeNewClientConn()
   217  			if err != nil {
   218  				// Already wrapped in proxyError
   219  				return err
   220  			}
   221  		}
   222  
   223  		headers := resp.Header[http.CanonicalHeaderKey("proxy-connection")]
   224  		for _, header := range headers {
   225  			if header == "close" {
   226  				// The server has signaled that it will close the
   227  				// connection. Create a new ClientConn and continue the
   228  				// handshake.
   229  				err = pc.makeNewClientConn()
   230  				if err != nil {
   231  					// Already wrapped in proxyError
   232  					return err
   233  				}
   234  				break
   235  			}
   236  		}
   237  
   238  		return nil
   239  	}
   240  	pc.authState = HTTP_AUTH_STATE_FAILURE
   241  	return proxyError(fmt.Errorf("handshake error: %v, response status: %s", err, resp.Status))
   242  }
   243  
   244  func (pc *proxyConn) makeNewClientConn() error {
   245  	c, err := pc.dialFn("tcp", pc.proxyAddr)
   246  	if pc.httpClientConn != nil {
   247  		pc.httpClientConn.Close()
   248  	}
   249  	if err != nil {
   250  		return proxyError(fmt.Errorf("makeNewClientConn: %v", err))
   251  	}
   252  	//lint:ignore SA1019 httputil.ClientConn used for client-side hijack
   253  	pc.httpClientConn = httputil.NewClientConn(c, nil)
   254  	return nil
   255  }
   256  
   257  func (pc *proxyConn) Read(b []byte) (int, error) {
   258  	if pc.staleReader != nil {
   259  		if pc.staleReader.Buffered() > 0 {
   260  			return pc.staleReader.Read(b)
   261  		}
   262  		pc.staleReader = nil
   263  	}
   264  	return pc.hijackedConn.Read(b)
   265  }
   266  
   267  func (pc *proxyConn) Write(b []byte) (int, error) {
   268  	return pc.hijackedConn.Write(b)
   269  }
   270  
   271  func (pc *proxyConn) Close() error {
   272  	return pc.hijackedConn.Close()
   273  }
   274  
   275  func (pc *proxyConn) LocalAddr() net.Addr {
   276  	return pc.hijackedConn.LocalAddr()
   277  }
   278  
   279  // RemoteAddr returns the network address of the proxy that
   280  // the proxyConn is connected to.
   281  func (pc *proxyConn) RemoteAddr() net.Addr {
   282  	// Note: returning nil here can crash "tls".
   283  	return pc.hijackedConn.RemoteAddr()
   284  }
   285  
   286  func (pc *proxyConn) SetDeadline(t time.Time) error {
   287  	return proxyError(fmt.Errorf("not supported"))
   288  }
   289  
   290  func (pc *proxyConn) SetReadDeadline(t time.Time) error {
   291  	return proxyError(fmt.Errorf("not supported"))
   292  }
   293  
   294  func (pc *proxyConn) SetWriteDeadline(t time.Time) error {
   295  	return proxyError(fmt.Errorf("not supported"))
   296  }
   297  
   298  func init() {
   299  	proxy.RegisterDialerType("http", newHTTP)
   300  }