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 }