-
Notifications
You must be signed in to change notification settings - Fork 0
/
ssh_session.go
145 lines (121 loc) · 3.07 KB
/
ssh_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
package ssh_mesh
import (
"context"
"fmt"
"io"
"log"
"log/slog"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// WIP: The SSH 'gateway' will not have a real shell / sftp session (except for debug). Instead, the session is used as a
// general purpose communication.
//
// Notes on sessions:
// -N - do not start a session at all
// -T - do not get a pty
// If ssh is piped, a terminal will not be allocated - but no flushing seems to happen.
//
// Inside a session - ~. close, ~B break, ~C CLI, ~# connections, ~? help
// Basic untrusted session handler.
func SessionHandler(ctx context.Context, sconn *SSHSMux, newChannel ssh.NewChannel) {
ch, reqs, _ := newChannel.Accept()
env := []*KV{}
for req := range reqs {
// "shell", "exec", "env", "subsystem"
// For pty: signal, break, pty-req, window-change
slog.Info("ssh-session", "type", req.Type)
switch req.Type {
case "shell", "exec":
// This is normally the last command in a channel.
// Env and pty are called first.
//
var payload = struct{ Value string }{}
ssh.Unmarshal(req.Payload, &payload)
req.Reply(true, nil)
sconn.SessionStream = ch
go execHandlerInternal(ch, env, payload.Value)
case "subsystem":
var payload = struct{ Value string }{}
ssh.Unmarshal(req.Payload, &payload)
if "sftp" != payload.Value {
req.Reply(false, nil)
} else {
sftpHandler(req, sconn, ch)
}
case "env":
var kv KV
// Typical: LANG
ssh.Unmarshal(req.Payload, &kv)
env = append(env, &kv)
if req.WantReply {
req.Reply(true, nil)
}
default:
if req.WantReply {
req.Reply(true, nil)
}
}
}
}
func sftpHandler(req *ssh.Request, sconn *SSHSMux, ch ssh.Channel) {
sftp.NewRequestServer(ch, sftp.Handlers{
FileGet: sconn,
FilePut: sconn,
FileCmd: sconn,
FileList: sconn,
})
}
func (c *SSHSMux) Filelist(request *sftp.Request) (sftp.ListerAt, error) {
//TODO implement me
panic("implement me")
}
// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove
func (c *SSHSMux) Filecmd(request *sftp.Request) error {
//TODO implement me
panic("implement me")
}
func (c *SSHSMux) Filewrite(request *sftp.Request) (io.WriterAt, error) {
//TODO implement me
panic("implement me")
}
func (c *SSHSMux) Fileread(request *sftp.Request) (io.ReaderAt, error) {
//TODO implement me
panic("implement me")
}
func execHandlerInternal(ch ssh.Channel, kv []*KV, cmd string) {
fmt.Fprint(ch, "{}\n")
// Logs or other info can be sent
data := make([]byte, 1024)
for {
n, err := ch.Read(data)
if err != nil {
return
}
slog.Info("ssh-session-in", "data", string(data[0:n]))
}
}
type KV struct {
Key, Value string
}
func (sshc *SSHCMux) ClientSession() {
c := sshc.SSHClient
// Open a session )
go func() {
sc, r, err := c.OpenChannel("session", nil)
if err != nil {
log.Println("Failed to open session", err)
return
}
go ssh.DiscardRequests(r)
data := make([]byte, 1024)
for {
n, err := sc.Read(data)
if err != nil {
log.Println("Failed to read", err)
return
}
log.Println("IN:", string(data[0:n]))
}
}()
}