github.com/devops-filetransfer/sshego@v7.0.4+incompatible/pty.go (about)

     1  package sshego
     2  
     3  /*
     4  See blog: https://blog.gopheracademy.com/go-and-ssh/
     5  
     6  Code from https://github.com/jpillora/go-and-ssh
     7  licensed under:
     8  
     9  #### MIT License
    10  
    11  Copyright (c) 2014 Jaime Pillora <dev@jpillora.com>
    12  
    13  Permission is hereby granted, free of charge, to any person obtaining
    14  a copy of this software and associated documentation files (the
    15  'Software'), to deal in the Software without restriction, including
    16  without limitation the rights to use, copy, modify, merge, publish,
    17  distribute, sublicense, and/or sell copies of the Software, and to
    18  permit persons to whom the Software is furnished to do so, subject to
    19  the following conditions:
    20  
    21  The above copyright notice and this permission notice shall be
    22  included in all copies or substantial portions of the Software.
    23  
    24  THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
    25  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    26  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    27  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    28  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    29  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    30  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    31  */
    32  
    33  // A small SSH daemon providing bash sessions
    34  //
    35  // Server:
    36  // cd my/new/dir/
    37  // #generate server keypair
    38  // ssh-keygen -t rsa
    39  // go get -v .
    40  // go run sshd.go
    41  //
    42  // Client:
    43  // ssh foo@localhost -p 2200 #pass=bar
    44  
    45  import (
    46  	"context"
    47  	"encoding/binary"
    48  	"fmt"
    49  	"io"
    50  	"log"
    51  	"os/exec"
    52  	"sync"
    53  
    54  	"github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh"
    55  )
    56  
    57  type ConnectionAlert struct {
    58  	PortOne  chan ssh.Channel
    59  	ShutDown chan struct{}
    60  }
    61  
    62  func (cfg *SshegoConfig) handleChannels(ctx context.Context, chans <-chan ssh.NewChannel, sshconn ssh.Conn, ca *ConnectionAlert) {
    63  	// Service the incoming Channel channel in go routine
    64  	var shut chan struct{}
    65  	if ca != nil {
    66  		shut = ca.ShutDown
    67  	}
    68  	// avoid shutdown race by getting this early.
    69  	reqStop := cfg.Esshd.Halt.ReqStopChan()
    70  	for {
    71  		select {
    72  		case newChannel, stillOpen := <-chans:
    73  			if !stillOpen {
    74  				return
    75  			}
    76  			go cfg.handleChannel(ctx, newChannel, sshconn, ca)
    77  		case <-reqStop:
    78  			return
    79  		case <-shut:
    80  			return
    81  		case <-ctx.Done():
    82  			return
    83  		}
    84  	}
    85  }
    86  
    87  func (cfg *SshegoConfig) handleChannel(ctx context.Context, newChannel ssh.NewChannel, sshconn ssh.Conn, ca *ConnectionAlert) {
    88  
    89  	// Since we're handling a shell, we expect a
    90  	// channel type of "session". The spec also describes
    91  	// "x11", "direct-tcpip" and "forwarded-tcpip"
    92  	// channel types.
    93  	if newChannel == nil {
    94  		// can happen on shutdown
    95  		return
    96  	}
    97  	t := newChannel.ChannelType()
    98  
    99  	if t == "direct-tcpip" {
   100  		handleDirectTcp(ctx, cfg.Halt, newChannel, ca)
   101  	}
   102  
   103  	if t != "session" {
   104  		if len(cfg.CustomChannelHandlers) > 0 {
   105  			cb, ok := cfg.CustomChannelHandlers[t]
   106  			if ok {
   107  				go cb(newChannel, sshconn, ca)
   108  				return
   109  			}
   110  		}
   111  		newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", t))
   112  		return
   113  	}
   114  
   115  	// t == "session", request to open a shell
   116  
   117  	// At this point, we have the opportunity to reject the client's
   118  	// request for another logical connection
   119  	connection, requests, err := newChannel.Accept()
   120  	if err != nil {
   121  		log.Printf("Could not accept channel (%s)", err)
   122  		return
   123  	}
   124  
   125  	// Fire up bash for this session
   126  	bash := exec.Command("bash")
   127  
   128  	// Prepare teardown function
   129  	close := func() {
   130  		connection.Close()
   131  		_, err := bash.Process.Wait()
   132  		if err != nil {
   133  			log.Printf("Failed to exit bash (%s)", err)
   134  		}
   135  		log.Printf("Session closed")
   136  	}
   137  
   138  	// Allocate a terminal for this channel
   139  	log.Print("Successful login, creating pty...")
   140  	bashf, err := ptyStart(bash)
   141  	if err != nil {
   142  		log.Printf("Could not start pty (%s)", err)
   143  		close()
   144  		return
   145  	}
   146  
   147  	//pipe session to bash and visa-versa
   148  	var once sync.Once
   149  	go func() {
   150  		io.Copy(connection, bashf)
   151  		once.Do(close)
   152  	}()
   153  	go func() {
   154  		io.Copy(bashf, connection)
   155  		once.Do(close)
   156  	}()
   157  
   158  	// Sessions have out-of-band requests such as "shell", "pty-req" and "env"
   159  	go func() {
   160  		for req := range requests {
   161  			switch req.Type {
   162  			case "shell":
   163  				// We only accept the default shell
   164  				// (i.e. no command in the Payload)
   165  				if len(req.Payload) == 0 {
   166  					req.Reply(true, nil)
   167  				}
   168  			case "pty-req":
   169  				termLen := req.Payload[3]
   170  				w, h := parseDims(req.Payload[termLen+4:])
   171  				SetWinsize(bashf.Fd(), w, h)
   172  				// Responding true (OK) here will let the client
   173  				// know we have a pty ready for input
   174  				req.Reply(true, nil)
   175  			case "window-change":
   176  				w, h := parseDims(req.Payload)
   177  				SetWinsize(bashf.Fd(), w, h)
   178  			}
   179  		}
   180  	}()
   181  }
   182  
   183  // =======================
   184  
   185  // parseDims extracts terminal dimensions (width x height) from the provided buffer.
   186  func parseDims(b []byte) (uint32, uint32) {
   187  	w := binary.BigEndian.Uint32(b)
   188  	h := binary.BigEndian.Uint32(b[4:])
   189  	return w, h
   190  }
   191  
   192  // ======================
   193  
   194  // Winsize stores the Height and Width of a terminal.
   195  type Winsize struct {
   196  	Height uint16
   197  	Width  uint16
   198  	x      uint16 // unused
   199  	y      uint16 // unused
   200  }