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 }