-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsession.go
312 lines (260 loc) · 7.89 KB
/
session.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package easyssh
import (
"encoding/binary"
"fmt"
"io"
"os"
"os/exec"
"sync"
"syscall"
"unsafe"
"github.com/kr/pty"
"golang.org/x/crypto/ssh"
)
// Request types used in sessions - RFC 4254 6.X
const (
SessionRequest = "session" // RFC 4254 6.1
PTYRequest = "pty-req" // RFC 4254 6.2
X11Request = "x11-req" // RFC 4254 6.3.1
X11ChannelRequest = "x11" // RFC 4254 6.3.2
EnvironmentRequest = "env" // RFC 4254 6.4
ShellRequest = "shell" // RFC 4254 6.5
ExecRequest = "exec" // RFC 4254 6.5
SubsystemRequest = "subsystem" // RFC 4254 6.5
WindowDimensionChangeRequest = "window-change" // RFC 4254 6.7
FlowControlRequest = "xon-off" // RFC 4254 6.8
SignalRequest = "signal" // RFC 4254 6.9
ExitStatusRequest = "exit-status" // RFC 4254 6.10
ExitSignalRequest = "exit-signal" // RFC 4254 6.10
)
// SessionHandler returns a ChannelHandler that implements standard SSH Sessions for PTY, shell, and exec capabilities
func SessionHandler() ChannelHandler { return ChannelHandlerFunc(SessionChannel) }
// SessionChannel acts as an SSH Session ChannelHandler
func SessionChannel(newChannel ssh.NewChannel, channel ssh.Channel, reqs <-chan *ssh.Request, sshConn ssh.Conn) {
// Get system shell
shell := os.Getenv("SHELL")
c := exec.Command(shell)
f, err := pty.Start(c)
if err != nil {
logger.Printf("Unable to start shell: %s", shell)
return
}
terminalModes := ssh.TerminalModes{}
// TODO: Find out if I should do anything with the terminal modes. Do any of the ptys/ttys know what to do with them
close := func() {
channel.Close()
err := c.Wait()
if err != nil {
logger.Printf("failed to exit bash (%s)", err)
}
logger.Printf("session closed")
}
go func(in <-chan *ssh.Request) {
env := []string{}
var command *exec.Cmd
for req := range in {
ok := false
switch req.Type {
case PTYRequest:
ok = true
pty := ptyReq{}
err = ssh.Unmarshal(req.Payload, &pty)
if err != nil {
logger.Printf("Unable to decode pty request: %s", err.Error())
}
setWinsize(f.Fd(), pty.Width, pty.Height)
logger.Printf("pty-req '%s'", pty.Term)
type termModeStruct struct {
//Key byte
Key uint8
Val uint32
}
termModes := []termModeStruct{}
working := []byte(pty.TermModes)
for {
if len(working) < 5 {
break
}
tm := termModeStruct{}
tm.Key = working[0]
tm.Val = binary.BigEndian.Uint32(working[1:5])
/*
err = ssh.Unmarshal(working, &tm)
if err != nil {
fmt.Println(err.Error())
break
}
*/
termModes = append(termModes, tm)
terminalModes[tm.Key] = tm.Val
working = working[5:]
}
go CopyReadWriters(channel, f, close)
case WindowDimensionChangeRequest:
win := windowDimensionReq{}
err = ssh.Unmarshal(req.Payload, &win)
if err != nil {
logger.Printf("Error reading window dimension change request: %s", err.Error())
}
setWinsize(f.Fd(), win.Width, win.Height)
continue //no response according to RFC 4254 6.7
case ShellRequest: // Shell requests should not have a payload - RFC 4254 6.7
ok = true
go CopyReadWriters(channel, f, close)
case ExecRequest:
ok = true
var cmd execRequest
err = ssh.Unmarshal(req.Payload, &cmd)
if err != nil {
continue
}
command = exec.Command("sh", "-c", cmd.Command) // Let shell do the parsing
logger.Printf("exec starting: %s", cmd.Command)
//c.Env = append(c.Env, env...)
exitStatus := exitStatusReq{}
fd, err := pty.Start(command)
if err != nil {
logger.Printf("Unable to wrap exec command in pty\n")
return
}
execClose := func() {
channel.Close()
logger.Printf("exec finished: %s", cmd.Command)
}
defer fd.Close()
go CopyReadWriters(channel, fd, execClose)
err = command.Wait()
/*
command.Stdout = channel
command.Stderr = channel
*/
//command.Stdin = channel // TODO: test how stdin works on exec on openssh server
//err = command.Run()
if err != nil {
logger.Printf("Error running exec : %s", err.Error())
e, ok := err.(*exec.ExitError)
errVal := 1
if ok {
status := e.Sys().(syscall.WaitStatus)
if status.Exited() {
errVal = status.ExitStatus()
exitStatus.ExitStatus = uint32(errVal)
channel.SendRequest(ExitStatusRequest, false, ssh.Marshal(exitStatus))
} else if status.Signaled() { // What is the difference between Siglnal and StopSignal
e := exitSignalReq{}
e.SignalName = status.Signal().String()
e.CoreDumped = status.CoreDump()
// TODO: Figure out other two fields
channel.SendRequest(ExitSignalRequest, false, ssh.Marshal(e))
}
}
} else {
channel.SendRequest(ExitStatusRequest, false, ssh.Marshal(exitStatus))
}
req.Reply(ok, nil)
close()
return
case EnvironmentRequest:
ok = true
e := envReq{}
err = ssh.Unmarshal(req.Payload, &e)
if err != nil {
continue
}
env = append(env, fmt.Sprintf("%s=%s", e.Name, e.Value))
case SignalRequest:
ok = true
sig := signalRequest{}
ssh.Unmarshal(req.Payload, &sig)
logger.Println("Received Signal: ", sig.Signal)
s := signalsMap[sig.Signal]
if command != nil {
command.Process.Signal(s)
} else {
c.Process.Signal(s)
}
}
req.Reply(ok, nil)
}
}(reqs)
}
var signalsMap = map[ssh.Signal]os.Signal{
ssh.SIGABRT: syscall.SIGABRT,
ssh.SIGALRM: syscall.SIGALRM,
ssh.SIGFPE: syscall.SIGFPE,
ssh.SIGHUP: syscall.SIGHUP,
ssh.SIGILL: syscall.SIGILL,
ssh.SIGINT: syscall.SIGINT,
ssh.SIGKILL: syscall.SIGKILL,
ssh.SIGPIPE: syscall.SIGPIPE,
ssh.SIGQUIT: syscall.SIGQUIT,
ssh.SIGSEGV: syscall.SIGSEGV,
ssh.SIGTERM: syscall.SIGTERM,
ssh.SIGUSR1: syscall.SIGUSR1,
ssh.SIGUSR2: syscall.SIGUSR2,
}
// CopyReadWriters copies biderectionally - output from a to b, and output of b into a. Calls the close function when unable to copy in either direction
func CopyReadWriters(a, b io.ReadWriter, close func()) {
var once sync.Once
go func() {
io.Copy(a, b)
once.Do(close)
}()
go func() {
io.Copy(b, a)
once.Do(close)
}()
}
// windowDimension represents channel request for window dimension change - RFC 4254 6.7
type windowDimensionReq struct {
Width uint32
Height uint32
WidthPixel uint32
HeightPixel uint32
}
// ptyReq represents the channel request for a PTY. RFC 4254 6.2
type ptyReq struct {
Term string
Width uint32
Height uint32
WidthPixel uint32
HeightPixel uint32
TermModes string
}
// envReq represents an "env" channel request - RFC 4254 6.4
type envReq struct {
Name string
Value string
}
// execRequest represents an "exec" channel request - RFC 4254 6.5
type execRequest struct {
Command string
}
// signalRequest represents a "signal" session channel request - RFC 4254 6.9
type signalRequest struct {
Signal ssh.Signal
}
// exitStatusReq represents an exit status for "exec" requests - RFC 4254 6.10
type exitStatusReq struct {
ExitStatus uint32
}
// exitSignalReq represents an exit signal for "exec" requests - RFC 4254 6.10
type exitSignalReq struct {
SignalName string
CoreDumped bool
ErrorMessage string
LanguageTag string
}
// winsize stores the Height and Width of a terminal in rows/columns and pixels - for syscall - http://linux.die.net/man/4/tty_ioctl
type winsize struct {
Row uint16
Col uint16
XPixel uint16 // unused
YPixel uint16 // unused
}
// SetWinsize uses syscall to set pty window size
func setWinsize(fd uintptr, w, h uint32) {
logger.Printf("Resize Window to %dx%d", w, h)
ws := &winsize{Col: uint16(w), Row: uint16(h)}
syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
}