// Opens a new SSH connection, local server ports (JNLP, HTTP) and forwards it to the corresponding ports on Jenkins.
func (self *SSHTunnelEstablisher) setupSSHTunnel(config *util.Config) {
	if self.ciHostURL == nil {
		return
	}

	if !config.PassCIAuth && config.SecretKey != "" {
		util.GOut("ssh-tunnel", "WARN: Secret key is not supported in combination with SSH tunnel. Implicitly setting %v to %v", "client>passAuth", "true")
		config.PassCIAuth = true
	}

	// Ensure no other SSL connections are still open.
	self.tearDownSSHTunnel(config)

	// Configuring the SSH client
	clientConfig := &ssh.ClientConfig{
		User: config.CITunnelSSHUsername,
		Auth: []ssh.AuthMethod{
			ssh.Password(config.CITunnelSSHPassword),
		},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			expected, actual := config.CITunnelSSHFingerprint, self.formatHostFingerprint(key)
			if actual != expected && expected != "-" {
				if expected == "" {
					return fmt.Errorf("The host fingerprint of '%v' is '%v'. Please add this to the configuration in order to connect.", hostname, actual)
				} else {
					return fmt.Errorf("The host fingerprint of '%v' is '%v' while '%v' was expected. Connection aborted.", hostname, actual, expected)
				}
			}
			return nil
		},
	}

	// Connecting to the SSH host
	if config.CITunnelSSHPort == 0 {
		config.CITunnelSSHPort = 22
	}
	sshAddress := fmt.Sprintf("%v:%v", config.CITunnelSSHAddress, config.CITunnelSSHPort)

	sshClient, err := ssh.Dial("tcp", sshAddress, clientConfig)
	if err == nil {
		self.closables = append(self.closables, sshClient)
		util.GOut("ssh-tunnel", "Successfully connected with '%v'.", sshAddress)
	} else {
		util.GOut("ssh-tunnel", "ERROR: Failed connecting with %v. Cause: %v", sshAddress, err)
		return
	}

	// Fetching target ports
	jnlpTargetAddress, err := self.formatJNLPHostAndPort(config)
	if err != nil {
		util.GOut("ssh-tunnel", "ERROR: Failed fetching JNLP port from '%v'. Cause: %v.", config.CIHostURI, err)
		return
	}

	httpTargetAddress := self.formatHttpHostAndPort()

	// Creating a local server listeners to use for port forwarding.
	httpListener, err1 := self.newLocalServerListener()
	jnlpListener, err2 := self.newLocalServerListener()

	if err1 != nil || err2 != nil {
		self.tearDownSSHTunnel(config)
		return
	}

	// Forward local connections to the HTTP(S)/JNLP ports.
	go self.forwardLocalConnectionsTo(config, sshClient, httpListener, httpTargetAddress)
	go self.forwardLocalConnectionsTo(config, sshClient, jnlpListener, jnlpTargetAddress)

	// Apply the tunnel configuration
	localCiURL, _ := url.Parse(self.ciHostURL.String())
	localCiURL.Host = httpListener.Addr().String()
	config.CIHostURI = localCiURL.String()
	util.JnlpArgs["-url"] = localCiURL.String()
	util.JnlpArgs["-tunnel"] = jnlpListener.Addr().String()

	// Mark tunnel as connected when we passed this line.
	self.tunnelConnected.Set(true)
}