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 }