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 }