github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/utils/ssh/ssh_gocrypto.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ssh
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net"
    11  	"os"
    12  	"os/exec"
    13  	"os/user"
    14  	"strings"
    15  
    16  	"code.google.com/p/go.crypto/ssh"
    17  	"github.com/juju/utils"
    18  )
    19  
    20  const sshDefaultPort = 22
    21  
    22  // GoCryptoClient is an implementation of Client that
    23  // uses the embedded go.crypto/ssh SSH client.
    24  //
    25  // GoCryptoClient is intentionally limited in the
    26  // functionality that it enables, as it is currently
    27  // intended to be used only for non-interactive command
    28  // execution.
    29  type GoCryptoClient struct {
    30  	signers []ssh.Signer
    31  }
    32  
    33  // NewGoCryptoClient creates a new GoCryptoClient.
    34  //
    35  // If no signers are specified, NewGoCryptoClient will
    36  // use the private key generated by LoadClientKeys.
    37  func NewGoCryptoClient(signers ...ssh.Signer) (*GoCryptoClient, error) {
    38  	return &GoCryptoClient{signers: signers}, nil
    39  }
    40  
    41  // Command implements Client.Command.
    42  func (c *GoCryptoClient) Command(host string, command []string, options *Options) *Cmd {
    43  	shellCommand := utils.CommandString(command...)
    44  	signers := c.signers
    45  	if len(signers) == 0 {
    46  		signers = privateKeys()
    47  	}
    48  	user, host := splitUserHost(host)
    49  	port := sshDefaultPort
    50  	var proxyCommand []string
    51  	if options != nil {
    52  		if options.port != 0 {
    53  			port = options.port
    54  		}
    55  		proxyCommand = options.proxyCommand
    56  	}
    57  	logger.Tracef(`running (equivalent of): ssh "%s@%s" -p %d '%s'`, user, host, port, shellCommand)
    58  	return &Cmd{impl: &goCryptoCommand{
    59  		signers:      signers,
    60  		user:         user,
    61  		addr:         fmt.Sprintf("%s:%d", host, port),
    62  		command:      shellCommand,
    63  		proxyCommand: proxyCommand,
    64  	}}
    65  }
    66  
    67  // Copy implements Client.Copy.
    68  //
    69  // Copy is currently unimplemented, and will always return an error.
    70  func (c *GoCryptoClient) Copy(args []string, options *Options) error {
    71  	return fmt.Errorf("scp command is not implemented (OpenSSH scp not available in PATH)")
    72  }
    73  
    74  type goCryptoCommand struct {
    75  	signers      []ssh.Signer
    76  	user         string
    77  	addr         string
    78  	command      string
    79  	proxyCommand []string
    80  	stdin        io.Reader
    81  	stdout       io.Writer
    82  	stderr       io.Writer
    83  	client       *ssh.Client
    84  	sess         *ssh.Session
    85  }
    86  
    87  var sshDial = ssh.Dial
    88  
    89  var sshDialWithProxy = func(addr string, proxyCommand []string, config *ssh.ClientConfig) (*ssh.Client, error) {
    90  	if len(proxyCommand) == 0 {
    91  		return sshDial("tcp", addr, config)
    92  	}
    93  	// User has specified a proxy. Create a pipe and
    94  	// redirect the proxy command's stdin/stdout to it.
    95  	host, port, err := net.SplitHostPort(addr)
    96  	if err != nil {
    97  		host = addr
    98  	}
    99  	for i, arg := range proxyCommand {
   100  		arg = strings.Replace(arg, "%h", host, -1)
   101  		if port != "" {
   102  			arg = strings.Replace(arg, "%p", port, -1)
   103  		}
   104  		arg = strings.Replace(arg, "%r", config.User, -1)
   105  		proxyCommand[i] = arg
   106  	}
   107  	client, server := net.Pipe()
   108  	logger.Tracef(`executing proxy command %q`, proxyCommand)
   109  	cmd := exec.Command(proxyCommand[0], proxyCommand[1:]...)
   110  	cmd.Stdin = server
   111  	cmd.Stdout = server
   112  	cmd.Stderr = os.Stderr
   113  	if err := cmd.Start(); err != nil {
   114  		return nil, err
   115  	}
   116  	conn, chans, reqs, err := ssh.NewClientConn(client, addr, config)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	return ssh.NewClient(conn, chans, reqs), nil
   121  }
   122  
   123  func (c *goCryptoCommand) ensureSession() (*ssh.Session, error) {
   124  	if c.sess != nil {
   125  		return c.sess, nil
   126  	}
   127  	if len(c.signers) == 0 {
   128  		return nil, fmt.Errorf("no private keys available")
   129  	}
   130  	if c.user == "" {
   131  		currentUser, err := user.Current()
   132  		if err != nil {
   133  			return nil, fmt.Errorf("getting current user: %v", err)
   134  		}
   135  		c.user = currentUser.Username
   136  	}
   137  	config := &ssh.ClientConfig{
   138  		User: c.user,
   139  		Auth: []ssh.AuthMethod{
   140  			ssh.PublicKeysCallback(func() ([]ssh.Signer, error) {
   141  				return c.signers, nil
   142  			}),
   143  		},
   144  	}
   145  	client, err := sshDialWithProxy(c.addr, c.proxyCommand, config)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	sess, err := client.NewSession()
   150  	if err != nil {
   151  		client.Close()
   152  		return nil, err
   153  	}
   154  	c.client = client
   155  	c.sess = sess
   156  	c.sess.Stdin = c.stdin
   157  	c.sess.Stdout = c.stdout
   158  	c.sess.Stderr = c.stderr
   159  	return sess, nil
   160  }
   161  
   162  func (c *goCryptoCommand) Start() error {
   163  	sess, err := c.ensureSession()
   164  	if err != nil {
   165  		return err
   166  	}
   167  	if c.command == "" {
   168  		return sess.Shell()
   169  	}
   170  	return sess.Start(c.command)
   171  }
   172  
   173  func (c *goCryptoCommand) Close() error {
   174  	if c.sess == nil {
   175  		return nil
   176  	}
   177  	err0 := c.sess.Close()
   178  	err1 := c.client.Close()
   179  	if err0 == nil {
   180  		err0 = err1
   181  	}
   182  	c.sess = nil
   183  	c.client = nil
   184  	return err0
   185  }
   186  
   187  func (c *goCryptoCommand) Wait() error {
   188  	if c.sess == nil {
   189  		return fmt.Errorf("Command has not been started")
   190  	}
   191  	err := c.sess.Wait()
   192  	c.Close()
   193  	return err
   194  }
   195  
   196  func (c *goCryptoCommand) Kill() error {
   197  	if c.sess == nil {
   198  		return fmt.Errorf("Command has not been started")
   199  	}
   200  	return c.sess.Signal(ssh.SIGKILL)
   201  }
   202  
   203  func (c *goCryptoCommand) SetStdio(stdin io.Reader, stdout, stderr io.Writer) {
   204  	c.stdin = stdin
   205  	c.stdout = stdout
   206  	c.stderr = stderr
   207  }
   208  
   209  func (c *goCryptoCommand) StdinPipe() (io.WriteCloser, io.Reader, error) {
   210  	sess, err := c.ensureSession()
   211  	if err != nil {
   212  		return nil, nil, err
   213  	}
   214  	wc, err := sess.StdinPipe()
   215  	return wc, sess.Stdin, err
   216  }
   217  
   218  func (c *goCryptoCommand) StdoutPipe() (io.ReadCloser, io.Writer, error) {
   219  	sess, err := c.ensureSession()
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  	wc, err := sess.StdoutPipe()
   224  	return ioutil.NopCloser(wc), sess.Stdout, err
   225  }
   226  
   227  func (c *goCryptoCommand) StderrPipe() (io.ReadCloser, io.Writer, error) {
   228  	sess, err := c.ensureSession()
   229  	if err != nil {
   230  		return nil, nil, err
   231  	}
   232  	wc, err := sess.StderrPipe()
   233  	return ioutil.NopCloser(wc), sess.Stderr, err
   234  }
   235  
   236  func splitUserHost(s string) (user, host string) {
   237  	userHost := strings.SplitN(s, "@", 2)
   238  	if len(userHost) == 2 {
   239  		return userHost[0], userHost[1]
   240  	}
   241  	return "", userHost[0]
   242  }