| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| 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 { | |
| 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 | |
| } |