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  }