github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/utils/ssh/ssh_openssh.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  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"os/exec"
    12  	"strings"
    13  
    14  	"github.com/juju/utils"
    15  )
    16  
    17  var opensshCommonOptions = []string{"-o", "StrictHostKeyChecking no"}
    18  
    19  // default identities will not be attempted if
    20  // -i is specified and they are not explcitly
    21  // included.
    22  var defaultIdentities = []string{
    23  	"~/.ssh/identity",
    24  	"~/.ssh/id_rsa",
    25  	"~/.ssh/id_dsa",
    26  	"~/.ssh/id_ecdsa",
    27  }
    28  
    29  type opensshCommandKind int
    30  
    31  const (
    32  	sshKind opensshCommandKind = iota
    33  	scpKind
    34  )
    35  
    36  // sshpassWrap wraps the command/args with sshpass if it is found in $PATH
    37  // and the SSHPASS environment variable is set. Otherwise, the original
    38  // command/args are returned.
    39  func sshpassWrap(cmd string, args []string) (string, []string) {
    40  	if os.Getenv("SSHPASS") != "" {
    41  		if path, err := exec.LookPath("sshpass"); err == nil {
    42  			return path, append([]string{"-e", cmd}, args...)
    43  		}
    44  	}
    45  	return cmd, args
    46  }
    47  
    48  // OpenSSHClient is an implementation of Client that
    49  // uses the ssh and scp executables found in $PATH.
    50  type OpenSSHClient struct{}
    51  
    52  // NewOpenSSHClient creates a new OpenSSHClient.
    53  // If the ssh and scp programs cannot be found
    54  // in $PATH, then an error is returned.
    55  func NewOpenSSHClient() (*OpenSSHClient, error) {
    56  	var c OpenSSHClient
    57  	if _, err := exec.LookPath("ssh"); err != nil {
    58  		return nil, err
    59  	}
    60  	if _, err := exec.LookPath("scp"); err != nil {
    61  		return nil, err
    62  	}
    63  	return &c, nil
    64  }
    65  
    66  func opensshOptions(options *Options, commandKind opensshCommandKind) []string {
    67  	args := append([]string{}, opensshCommonOptions...)
    68  	if options == nil {
    69  		options = &Options{}
    70  	}
    71  	if len(options.proxyCommand) > 0 {
    72  		args = append(args, "-o", "ProxyCommand "+utils.CommandString(options.proxyCommand...))
    73  	}
    74  	if !options.passwordAuthAllowed {
    75  		args = append(args, "-o", "PasswordAuthentication no")
    76  	}
    77  	if options.allocatePTY {
    78  		args = append(args, "-t", "-t") // twice to force
    79  	}
    80  	identities := append([]string{}, options.identities...)
    81  	if pk := PrivateKeyFiles(); len(pk) > 0 {
    82  		// Add client keys as implicit identities
    83  		identities = append(identities, pk...)
    84  	}
    85  	// If any identities are specified, the
    86  	// default ones must be explicitly specified.
    87  	if len(identities) > 0 {
    88  		for _, identity := range defaultIdentities {
    89  			path, err := utils.NormalizePath(identity)
    90  			if err != nil {
    91  				logger.Warningf("failed to normalize path %q: %v", identity, err)
    92  				continue
    93  			}
    94  			if _, err := os.Stat(path); err == nil {
    95  				identities = append(identities, path)
    96  			}
    97  		}
    98  	}
    99  	for _, identity := range identities {
   100  		args = append(args, "-i", identity)
   101  	}
   102  	if options.port != 0 {
   103  		port := fmt.Sprint(options.port)
   104  		if commandKind == scpKind {
   105  			// scp uses -P instead of -p (-p means preserve).
   106  			args = append(args, "-P", port)
   107  		} else {
   108  			args = append(args, "-p", port)
   109  		}
   110  	}
   111  	return args
   112  }
   113  
   114  // Command implements Client.Command.
   115  func (c *OpenSSHClient) Command(host string, command []string, options *Options) *Cmd {
   116  	args := opensshOptions(options, sshKind)
   117  	args = append(args, host)
   118  	if len(command) > 0 {
   119  		args = append(args, command...)
   120  	}
   121  	bin, args := sshpassWrap("ssh", args)
   122  	logger.Debugf("running: %s %s", bin, utils.CommandString(args...))
   123  	return &Cmd{impl: &opensshCmd{exec.Command(bin, args...)}}
   124  }
   125  
   126  // Copy implements Client.Copy.
   127  func (c *OpenSSHClient) Copy(args []string, userOptions *Options) error {
   128  	var options Options
   129  	if userOptions != nil {
   130  		options = *userOptions
   131  		options.allocatePTY = false // doesn't make sense for scp
   132  	}
   133  	allArgs := opensshOptions(&options, scpKind)
   134  	allArgs = append(allArgs, args...)
   135  	bin, allArgs := sshpassWrap("scp", allArgs)
   136  	cmd := exec.Command(bin, allArgs...)
   137  	var stderr bytes.Buffer
   138  	cmd.Stderr = &stderr
   139  	logger.Debugf("running: %s %s", bin, utils.CommandString(args...))
   140  	if err := cmd.Run(); err != nil {
   141  		stderr := strings.TrimSpace(stderr.String())
   142  		if len(stderr) > 0 {
   143  			err = fmt.Errorf("%v (%v)", err, stderr)
   144  		}
   145  		return err
   146  	}
   147  	return nil
   148  }
   149  
   150  type opensshCmd struct {
   151  	*exec.Cmd
   152  }
   153  
   154  func (c *opensshCmd) SetStdio(stdin io.Reader, stdout, stderr io.Writer) {
   155  	c.Stdin, c.Stdout, c.Stderr = stdin, stdout, stderr
   156  }
   157  
   158  func (c *opensshCmd) StdinPipe() (io.WriteCloser, io.Reader, error) {
   159  	wc, err := c.Cmd.StdinPipe()
   160  	if err != nil {
   161  		return nil, nil, err
   162  	}
   163  	return wc, c.Stdin, nil
   164  }
   165  
   166  func (c *opensshCmd) StdoutPipe() (io.ReadCloser, io.Writer, error) {
   167  	rc, err := c.Cmd.StdoutPipe()
   168  	if err != nil {
   169  		return nil, nil, err
   170  	}
   171  	return rc, c.Stdout, nil
   172  }
   173  
   174  func (c *opensshCmd) StderrPipe() (io.ReadCloser, io.Writer, error) {
   175  	rc, err := c.Cmd.StderrPipe()
   176  	if err != nil {
   177  		return nil, nil, err
   178  	}
   179  	return rc, c.Stderr, nil
   180  }
   181  
   182  func (c *opensshCmd) Kill() error {
   183  	if c.Process == nil {
   184  		return fmt.Errorf("process has not been started")
   185  	}
   186  	return c.Process.Kill()
   187  }