-
Notifications
You must be signed in to change notification settings - Fork 0
/
mitten.go
151 lines (127 loc) Β· 3.36 KB
/
mitten.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
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"github.com/creack/pty"
"golang.org/x/term"
)
func GetFreePort() (port int, err error) {
var a *net.TCPAddr
if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil {
var l *net.TCPListener
if l, err = net.ListenTCP("tcp", a); err == nil {
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
}
return
}
func GenerateToken() string {
size := 16
rb := make([]byte, size)
_, err := rand.Read(rb)
if err != nil {
panic(err)
}
rs := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(rb)
return rs
}
const banner string = `
βββββ
ββ ββ
βββ ββ
βββββ βββββββββ
β β βββ
β β mitten β
β β magic! ββ
βββββ βββ
βββββββββββββ
`
var bannerHeight int = strings.Count(banner, "\n")
const mittenMarker string = "MITTEN=true"
type Tunnel struct {
Command string
ForwardSpec string
}
func run() error {
if len(os.Args) == 1 {
return fmt.Errorf("no host specified")
}
httpProxyTunnel, err := setupHTTPProxy()
if err != nil {
return fmt.Errorf("create HTTP proxy: %w", err)
}
sftpTunnel, err := setupSFTP()
if err != nil {
return fmt.Errorf("create HTTP proxy: %w", err)
}
cmdline := []string{
"-t", // Force pty allocation
"-o", "ExitOnForwardFailure=yes", // Exit on forwarding failure
httpProxyTunnel.ForwardSpec, // Forward the proxy port
sftpTunnel.ForwardSpec, // Forward the SFTP port
}
cmdline = append(cmdline, os.Args[1:]...) // Add all that user specified
cmd := exec.Command("ssh", cmdline...)
// Start the command with a pty.
ptmx, err := pty.Start(cmd)
if err != nil {
return fmt.Errorf("execute ssh: %w", err)
}
// Make sure to close the pty at the end.
defer func() { ptmx.Close() }() // Best effort.
// Handle pty size.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
// Set stdin in raw mode.
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
defer func() { term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
// Export the environment variables
mittenCommand := fmt.Sprintf(` %s%secho -e '%s';`+mittenMarker, httpProxyTunnel.Command, sftpTunnel.Command, banner)
shellFinder := NewShellFindReader(ptmx)
// Copy stdin to the pty and the pty to stdout.
go func() {
_, _ = io.Copy(ptmx, os.Stdin)
}()
go func() {
<-shellFinder.Found
_, err := io.Copy(ptmx, strings.NewReader(mittenCommand))
if err != nil {
log.Fatalf("write mitten command to the remote: %v", err)
}
<-shellFinder.SkippedEcho
_, err = ptmx.Write([]byte("\n"))
if err != nil {
log.Fatalf("write mitten command to the remote: %v", err)
}
}()
_, _ = io.Copy(os.Stdout, shellFinder)
return nil
}
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "mitten: %v\n", err)
os.Exit(1)
}
}