| package ssh |
| |
| import ( |
| "bufio" |
| "fmt" |
| "net" |
| "net/http" |
| "net/url" |
| "time" |
| |
| "golang.org/x/net/proxy" |
| ) |
| |
| // Dialer implements for SSH over HTTP Proxy. |
| type proxyDialer struct { |
| proxy proxyInfo |
| // forwarding Dialer |
| forward proxy.Dialer |
| } |
| |
| type proxyInfo struct { |
| // HTTP Proxy host or host:port |
| host string |
| // HTTP Proxy scheme |
| scheme string |
| // An immutable encapsulation of username and password details for a URL |
| userInfo *url.Userinfo |
| } |
| |
| func newProxyInfo(host, scheme, username, password string) *proxyInfo { |
| p := &proxyInfo{ |
| host: host, |
| scheme: scheme, |
| } |
| |
| p.userInfo = url.UserPassword(username, password) |
| |
| if p.scheme == "" { |
| p.scheme = "http" |
| } |
| |
| return p |
| } |
| |
| func (p *proxyInfo) url() *url.URL { |
| return &url.URL{ |
| Scheme: p.scheme, |
| User: p.userInfo, |
| Host: p.host, |
| } |
| } |
| |
| func (p *proxyDialer) Dial(network, addr string) (net.Conn, error) { |
| // Dial the proxy host |
| c, err := p.forward.Dial(network, p.proxy.host) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| err = c.SetDeadline(time.Now().Add(15 * time.Second)) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Generate request URL to host accessed through the proxy |
| reqUrl := &url.URL{ |
| Scheme: "", |
| Host: addr, |
| } |
| |
| // Create a request object using the CONNECT method to instruct the proxy server to tunnel a protocol other than HTTP. |
| req, err := http.NewRequest("CONNECT", reqUrl.String(), nil) |
| if err != nil { |
| c.Close() |
| return nil, err |
| } |
| |
| // If http proxy requires authentication, configure settings for basic authentication. |
| if p.proxy.userInfo.String() != "" { |
| username := p.proxy.userInfo.Username() |
| password, _ := p.proxy.userInfo.Password() |
| req.SetBasicAuth(username, password) |
| req.Header.Add("Proxy-Authorization", req.Header.Get("Authorization")) |
| } |
| |
| // Do not close the connection after sending this request and reading its response. |
| req.Close = false |
| |
| // Writes the request in the form expected by an HTTP proxy. |
| err = req.Write(c) |
| if err != nil { |
| c.Close() |
| return nil, err |
| } |
| |
| res, err := http.ReadResponse(bufio.NewReader(c), req) |
| |
| if err != nil { |
| res.Body.Close() |
| c.Close() |
| return nil, err |
| } |
| |
| res.Body.Close() |
| |
| if res.StatusCode != http.StatusOK { |
| c.Close() |
| return nil, fmt.Errorf("Connection Error: StatusCode: %d", res.StatusCode) |
| } |
| |
| return c, nil |
| } |
| |
| // NewHttpProxyDialer generate Http Proxy Dialer |
| func newHttpProxyDialer(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { |
| var proxyUserName, proxyPassword string |
| if u.User != nil { |
| proxyUserName = u.User.Username() |
| proxyPassword, _ = u.User.Password() |
| } |
| |
| pd := &proxyDialer{ |
| proxy: *newProxyInfo(u.Host, u.Scheme, proxyUserName, proxyPassword), |
| forward: forward, |
| } |
| |
| return pd, nil |
| } |
| |
| // RegisterDialerType register schemes used by `proxy.FromURL` |
| func RegisterDialerType() { |
| proxy.RegisterDialerType("http", newHttpProxyDialer) |
| proxy.RegisterDialerType("https", newHttpProxyDialer) |
| } |
| |
| // NewHttpProxyConn create a connection to connect through the proxy server. |
| func newHttpProxyConn(p *proxyInfo, targetAddr string) (net.Conn, error) { |
| pd, err := proxy.FromURL(p.url(), proxy.Direct) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| proxyConn, err := pd.Dial("tcp", targetAddr) |
| |
| if err != nil { |
| return nil, err |
| } |
| |
| return proxyConn, err |
| } |