github.com/dacamp/packer@v0.10.2/provisioner/ansible/adapter.go (about) 1 package ansible 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 11 "github.com/mitchellh/packer/packer" 12 "golang.org/x/crypto/ssh" 13 ) 14 15 type adapter struct { 16 done <-chan struct{} 17 l net.Listener 18 config *ssh.ServerConfig 19 sftpCmd string 20 ui packer.Ui 21 comm packer.Communicator 22 } 23 24 func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *adapter { 25 return &adapter{ 26 done: done, 27 l: l, 28 config: config, 29 sftpCmd: sftpCmd, 30 ui: ui, 31 comm: comm, 32 } 33 } 34 35 func (c *adapter) Serve() { 36 c.ui.Say(fmt.Sprintf("SSH proxy: serving on %s", c.l.Addr())) 37 38 for { 39 // Accept will return if either the underlying connection is closed or if a connection is made. 40 // after returning, check to see if c.done can be received. If so, then Accept() returned because 41 // the connection has been closed. 42 conn, err := c.l.Accept() 43 select { 44 case <-c.done: 45 return 46 default: 47 if err != nil { 48 c.ui.Error(fmt.Sprintf("listen.Accept failed: %v", err)) 49 continue 50 } 51 go func(conn net.Conn) { 52 if err := c.Handle(conn, c.ui); err != nil { 53 c.ui.Error(err.Error()) 54 } 55 }(conn) 56 } 57 } 58 } 59 60 func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error { 61 c.ui.Message("SSH proxy: accepted connection") 62 _, chans, reqs, err := ssh.NewServerConn(conn, c.config) 63 if err != nil { 64 return errors.New("failed to handshake") 65 } 66 67 // discard all global requests 68 go ssh.DiscardRequests(reqs) 69 70 // Service the incoming NewChannels 71 for newChannel := range chans { 72 if newChannel.ChannelType() != "session" { 73 newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") 74 continue 75 } 76 77 go func(ch ssh.NewChannel) { 78 if err := c.handleSession(ch); err != nil { 79 c.ui.Error(err.Error()) 80 } 81 }(newChannel) 82 } 83 84 return nil 85 } 86 87 func (c *adapter) handleSession(newChannel ssh.NewChannel) error { 88 channel, requests, err := newChannel.Accept() 89 if err != nil { 90 return err 91 } 92 defer channel.Close() 93 94 done := make(chan struct{}) 95 96 // Sessions have requests such as "pty-req", "shell", "env", and "exec". 97 // see RFC 4254, section 6 98 go func(in <-chan *ssh.Request) { 99 env := make([]envRequestPayload, 4) 100 for req := range in { 101 switch req.Type { 102 case "pty-req": 103 // accept pty-req requests, but don't actually do anything. Necessary for OpenSSH and sudo. 104 req.Reply(true, nil) 105 106 case "env": 107 req.Reply(true, nil) 108 109 req, err := newEnvRequest(req) 110 if err != nil { 111 c.ui.Error(err.Error()) 112 continue 113 } 114 env = append(env, req.Payload) 115 case "exec": 116 req.Reply(true, nil) 117 118 req, err := newExecRequest(req) 119 if err != nil { 120 c.ui.Error(err.Error()) 121 close(done) 122 continue 123 } 124 125 if len(req.Payload) > 0 { 126 cmd := &packer.RemoteCmd{ 127 Stdin: channel, 128 Stdout: channel, 129 Stderr: channel.Stderr(), 130 Command: string(req.Payload), 131 } 132 133 if err := c.comm.Start(cmd); err != nil { 134 c.ui.Error(err.Error()) 135 close(done) 136 return 137 } 138 go func(cmd *packer.RemoteCmd, channel ssh.Channel) { 139 cmd.Wait() 140 141 exitStatus := make([]byte, 4) 142 binary.BigEndian.PutUint32(exitStatus, uint32(cmd.ExitStatus)) 143 channel.SendRequest("exit-status", false, exitStatus) 144 close(done) 145 }(cmd, channel) 146 } 147 148 case "subsystem": 149 req, err := newSubsystemRequest(req) 150 if err != nil { 151 c.ui.Error(err.Error()) 152 continue 153 } 154 155 switch req.Payload { 156 case "sftp": 157 c.ui.Say("starting sftp subsystem") 158 req.Reply(true, nil) 159 sftpCmd := c.sftpCmd 160 if len(sftpCmd) == 0 { 161 sftpCmd = "/usr/lib/sftp-server -e" 162 } 163 cmd := &packer.RemoteCmd{ 164 Stdin: channel, 165 Stdout: channel, 166 Stderr: channel.Stderr(), 167 Command: sftpCmd, 168 } 169 170 if err := c.comm.Start(cmd); err != nil { 171 c.ui.Error(err.Error()) 172 } 173 174 go func() { 175 cmd.Wait() 176 close(done) 177 }() 178 179 default: 180 req.Reply(false, nil) 181 182 } 183 default: 184 c.ui.Message(fmt.Sprintf("rejecting %s request", req.Type)) 185 req.Reply(false, nil) 186 } 187 } 188 }(requests) 189 190 <-done 191 return nil 192 } 193 194 func (c *adapter) Shutdown() { 195 c.l.Close() 196 } 197 198 type envRequest struct { 199 *ssh.Request 200 Payload envRequestPayload 201 } 202 203 type envRequestPayload struct { 204 Name string 205 Value string 206 } 207 208 func newEnvRequest(raw *ssh.Request) (*envRequest, error) { 209 r := new(envRequest) 210 r.Request = raw 211 212 if err := ssh.Unmarshal(raw.Payload, &r.Payload); err != nil { 213 return nil, err 214 } 215 216 return r, nil 217 } 218 219 func sshString(buf io.Reader) (string, error) { 220 var size uint32 221 err := binary.Read(buf, binary.BigEndian, &size) 222 if err != nil { 223 return "", err 224 } 225 226 b := make([]byte, size) 227 err = binary.Read(buf, binary.BigEndian, b) 228 if err != nil { 229 return "", err 230 } 231 return string(b), nil 232 } 233 234 type execRequest struct { 235 *ssh.Request 236 Payload execRequestPayload 237 } 238 239 type execRequestPayload string 240 241 func newExecRequest(raw *ssh.Request) (*execRequest, error) { 242 r := new(execRequest) 243 r.Request = raw 244 buf := bytes.NewReader(r.Request.Payload) 245 246 var err error 247 var payload string 248 if payload, err = sshString(buf); err != nil { 249 return nil, err 250 } 251 252 r.Payload = execRequestPayload(payload) 253 return r, nil 254 } 255 256 type subsystemRequest struct { 257 *ssh.Request 258 Payload subsystemRequestPayload 259 } 260 261 type subsystemRequestPayload string 262 263 func newSubsystemRequest(raw *ssh.Request) (*subsystemRequest, error) { 264 r := new(subsystemRequest) 265 r.Request = raw 266 buf := bytes.NewReader(r.Request.Payload) 267 268 var err error 269 var payload string 270 if payload, err = sshString(buf); err != nil { 271 return nil, err 272 } 273 274 r.Payload = subsystemRequestPayload(payload) 275 return r, nil 276 }