Repository navigation
Expand file tree
/
Copy pathshell.go
More file actions
181 lines (154 loc) · 3.87 KB
/
Copy pathshell.go
File metadata and controls
181 lines (154 loc) · 3.87 KB
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
package socket
import (
"bufio"
"context"
"encoding/json"
"fmt"
"log/slog"
"net"
"os"
"os/exec"
"runtime"
"sync"
"github.com/hashicorp/yamux"
"golang.org/x/term"
)
type winsize struct {
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
}
type ShellSession interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
Close() error
Wait() error
Resize(rows, cols uint16) error
}
func runShellServer(conn net.Conn) {
slog.Info("Starting yamux multiplexer and PTY Shell Server")
session, err := yamux.Server(conn, nil)
if err != nil {
slog.Error("Failed to start yamux server", "error", err)
return
}
defer session.Close()
dataStream, err := session.Accept()
if err != nil {
slog.Error("Failed to accept data stream", "error", err)
return
}
controlStream, err := session.Accept()
if err != nil {
slog.Error("Failed to accept control stream", "error", err)
return
}
shellSession, err := spawnShell()
if err != nil {
slog.Error("Failed to start shell", "error", err)
return
}
defer shellSession.Close()
// Listen for remote resize commands
go func() {
scanner := bufio.NewScanner(controlStream)
for scanner.Scan() {
var size winsize
if err := json.Unmarshal(scanner.Bytes(), &size); err == nil {
shellSession.Resize(size.Rows, size.Cols)
}
}
}()
go func() {
CopyWithTrace(shellSession, dataStream, "remote->pty")
}()
CopyWithTrace(dataStream, shellSession, "pty->remote")
shellSession.Wait()
}
func runShellClient(ctx context.Context, p2pConn net.Conn) {
slog.Info("Starting interactive shell client")
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
slog.Warn("Failed to set terminal to raw mode (not a TTY?)", "error", err)
} else {
defer term.Restore(int(os.Stdin.Fd()), oldState)
}
session, err := yamux.Client(p2pConn, nil)
if err != nil {
slog.Error("Failed to start yamux client", "error", err)
return
}
defer session.Close()
dataStream, err := session.Open()
if err != nil {
slog.Error("Failed to open data stream", "error", err)
return
}
controlStream, err := session.Open()
if err != nil {
slog.Error("Failed to open control stream", "error", err)
return
}
// Send initial size
if width, height, err := term.GetSize(int(os.Stdin.Fd())); err == nil {
fmt.Fprintf(controlStream, `{"cols": %d, "rows": %d}`+"\n", width, height)
}
// Handle Terminal Resizes
go handleTerminalResize(ctx, controlStream)
go func() {
CopyWithTrace(dataStream, os.Stdin, "stdin->remote")
}()
CopyWithTrace(os.Stdout, dataStream, "remote->stdout")
}
func runCommandServer(conn net.Conn, commandStr string) {
slog.Info("Starting command execution server", "command", commandStr)
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.Command("cmd.exe", "/c", commandStr)
} else {
cmd = exec.Command("/bin/sh", "-c", commandStr)
}
stdinPipe, err := cmd.StdinPipe()
if err != nil {
slog.Error("Failed to create stdin pipe", "error", err)
return
}
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
slog.Error("Failed to create stdout pipe", "error", err)
return
}
stderrPipe, err := cmd.StderrPipe()
if err != nil {
slog.Error("Failed to create stderr pipe", "error", err)
return
}
var wg sync.WaitGroup
wg.Add(2)
// Route stderr to our structured logging system
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stderrPipe)
for scanner.Scan() {
slog.Warn("Command stderr", "output", scanner.Text())
}
}()
go func() {
CopyWithTrace(stdinPipe, conn, "remote->stdin")
stdinPipe.Close()
}()
go func() {
defer wg.Done()
CopyWithTrace(conn, stdoutPipe, "stdout->remote")
}()
if err := cmd.Start(); err != nil {
slog.Error("Failed to start command", "error", err)
return
}
wg.Wait()
err = cmd.Wait()
if err != nil {
slog.Info("Command exited with error", "error", err)
} else {
slog.Info("Command exited successfully")
}
}