forked from matt2005/ssh2plink
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathmain.go
More file actions
304 lines (270 loc) · 11.8 KB
/
main.go
File metadata and controls
304 lines (270 loc) · 11.8 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
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
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/mikkeloscar/sshconfig"
)
const (
version = "1.1.0"
command = "plink.exe"
)
// boolean options from ssh that are not supported by plink
var dropBool = [...]string{"-f", "-g", "-G", "-k", "-K", "-q", "-y"}
// string options from ssh that are not supported by plink
var dropStr = [...]string{"-b", "-B", "-c", "-e", "-E", "-F", "-I", "-J", "-m", "-o", "-O", "-Q", "-S", "-w", "-W"}
func replaceOrSetArgValue(argToReplace string, newValue string, args []string) []string {
isSet := false
for i := 0; i < len(args); i = i + 1 {
if (args[i] == argToReplace) && ((i+1) < len(args) && !strings.HasPrefix(args[i+1], "-")) {
args[i+1] = newValue
isSet = true
break
}
}
if !isSet {
args = append([]string{argToReplace, newValue}, args...)
}
return args
}
func replaceArgs(argToReplace string, newArg string, args []string) []string {
for i := 0; i < len(args); i = i + 1 {
if args[i] == argToReplace {
args[i] = newArg
break
}
}
return args
}
func removeIndex(slice []string, s int) []string {
return append(slice[:s], slice[s+1:]...)
}
func hasEntry(arr []string, value string) bool {
res := false
for _, entry := range arr {
if entry == value {
res = true
break
}
}
return res
}
func removeSshOptionsUnsupportedByPlink(args []string) []string {
idxToRemove := []int{}
for i := 0; i < len(args); i = i + 1 {
if hasEntry(dropStr[:], args[i]) {
idxToRemove = append(idxToRemove, i, i+1)
} else if hasEntry(dropBool[:], args[i]) {
idxToRemove = append(idxToRemove, i)
}
}
if len(idxToRemove) > 0 {
for i := len(idxToRemove) - 1; i >= 0; i = i - 1 {
args = removeIndex(args, idxToRemove[i])
}
}
return args
}
// if existing the ssh config file is taken into account
// from the real progam arguments the first one is the host and it is checked if this Host could be found in ssh config
// if found and setting for HostName, Port, IdentityFile are found the correponding options for plink will be added or overwritten as options
func handleSshConfig(realArgs []string, args []string) []string {
home, err := os.UserHomeDir()
if err != nil {
fmt.Println(err)
}
if fileExists(filepath.Join(home, ".ssh", "config")) {
sshconfigs, err := sshconfig.Parse(filepath.Join(home, ".ssh", "config"))
if err != nil {
fmt.Println(err)
}
for _, sshCfg := range sshconfigs {
for _, host := range sshCfg.Host {
// first argument is the host
if host == realArgs[0] {
args = replaceArgs(realArgs[0], sshCfg.HostName, args)
if sshCfg.User != "" {
args = replaceOrSetArgValue("-l", sshCfg.User, args)
}
if sshCfg.IdentityFile != "" {
args = replaceOrSetArgValue("-i", sshCfg.IdentityFile, args)
}
if sshCfg.Port > 0 {
args = replaceOrSetArgValue("-p", strconv.Itoa(sshCfg.Port), args)
}
break
}
}
}
}
return args
}
// if program is started with -V VSCode is checking for the version of the ssh client
// so we are calling the real ssh client and returning the version as wanted
func handleSshVersion() {
sshPath, err := resolveCmd("ssh.exe")
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Printf("Please make sure %v is in your PATH (%v)\n", command, os.Getenv("PATH"))
return
}
sshCmdVersion := exec.Command(sshPath, "-V")
out, err := sshCmdVersion.CombinedOutput()
if err != nil {
fmt.Fprintf(os.Stderr, "ssh not could not started (wait): %s\n", err)
}
filterRegExp := regexp.MustCompile(`[\n]+`)
version := filterRegExp.ReplaceAllString(string(out), "")
fmt.Fprintf(os.Stdout, "%s", version)
}
// register all possible ssh options to be able to get the real arguments like host & shell
func registerSshOptions() {
flag.Bool("4", false, "Forces ssh to use IPv4 addresses only")
flag.Bool("6", false, "Forces ssh to use IPv6 addresses only")
flag.Bool("A", false, "Enables forwarding of connections from an authentication agent such as ssh-agent(1).")
flag.Bool("a", false, "Disables forwarding of the authentication agent connection.")
flag.Bool("C", false, "Requests compression of all data (including stdin, stdout, stderr, and data for forwarded X11, TCP and UNIX-domain connections).")
flag.Bool("f", false, "Requests ssh to go to background just before command execution.")
flag.Bool("G", false, "Causes ssh to print its configuration after evaluating Host and Match blocks and exit. ")
flag.Bool("g", false, "Allows remote hosts to connect to local forwarded ports. If used on a multiplexed connection, then this option must be specified on the master process.")
flag.Bool("K", false, "Enables GSSAPI-based authentication and forwarding (delegation) of GSSAPI credentials to the server. ")
flag.Bool("k", false, "Disables forwarding (delegation) of GSSAPI credentials to the server.")
flag.Bool("M", false, "Places the ssh client into “master” mode for connection sharing.")
flag.Bool("N", false, "Do not execute a remote command. This is useful for just forwarding ports. Refer to the description of SessionType in ssh_config(5) for details.")
flag.Bool("n", false, "Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the background.")
flag.Bool("q", false, "Quiet mode. Causes most warning and diagnostic messages to be suppressed.")
flag.Bool("s", false, "May be used to request invocation of a subsystem on the remote system. ")
flag.Bool("T", false, "Disable pseudo-terminal allocation.")
flag.Bool("t", false, "Force pseudo-terminal allocation.")
flag.Bool("V", false, "Display the version number and exit. ")
flag.Bool("v", false, "Verbose mode.")
flag.Bool("X", false, "Enables X11 forwarding. This can also be specified on a per-host basis in a configuration file. ")
flag.Bool("x", false, "Disables X11 forwarding. ")
flag.Bool("Y", false, "Enables trusted X11 forwarding. Trusted X11 forwardings are not subjected to the X11 SECURITY extension controls. ")
flag.Bool("y", false, "Send log information using the syslog(3) system module. By default this information is sent to stderr.")
flag.String("B", "", "-B <bind_interface> - Bind to the address of bind_interface before attempting to connect to the destination host.")
flag.String("b", "", "-b <bind_address> - Use bind_address on the local machine as the source address of the connection.")
flag.String("c", "", "-c <cipher_spec> - Selects the cipher specification for encrypting the session. cipher_spec is a comma-separated list of ciphers listed in order of preference.")
flag.String("D", "", "-D <[bind_address:]port> - Specifies a local “dynamic” application-level port forwarding.")
flag.String("E", "", "-E <log_file> - Append debug logs to log_file instead of standard error.")
flag.String("e", "", "-e <escape_char> - Sets the escape character for sessions with a pty (default: ~).")
flag.String("F", "", "-F <config_file> - Specifies an alternative per-user configuration file.")
flag.String("I", "", "-I <pkcs11> - Specify the PKCS#11 shared library ssh should use to communicate with a PKCS#11 token providing keys for user authentication.")
flag.String("i", "", "-i <identity_file> - Selects a file from which the identity (private key) for public key authentication is read.")
flag.String("J", "", "-J <destination> - Connect to the target host by first making a ssh connection to the jump host described by destination and then establishing a TCP forwarding to the ultimate destination from there.")
flag.String("L", "", "-L <[bind_address:]port:host:hostport>\n-L <[bind_address:]port:remote_socket>\n-L <local_socket:host:hostport>\n-L <local_socket:remote_socket> - Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side.")
flag.String("l", "", "-l <login_name> - Specifies the user to log in as on the remote machine.")
flag.String("m", "", "-m <mac_spec> - A comma-separated list of MAC (message authentication code) algorithms, specified in order of preference.")
flag.String("O", "", "-O <ctl_cmd> - Control an active connection multiplexing master process.")
flag.String("o", "", "-o <option> - Can be used to give options in the format used in the configuration file.")
flag.String("p", "", "-p <port> - Port to connect to on the remote host. ")
flag.String("Q", "", "-Q <query_option> - Queries for the algorithms supported.")
flag.String("R", "", "-R <address> - Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to be forwarded to the local side. ")
flag.String("S", "", "-S <ctl_path> - Specifies the location of a control socket for connection sharing, or the string “none” to disable connection sharing. ")
flag.String("W", "", "-W <host:port> - Requests that standard input and output on the client be forwarded to host on port over the secure channel.")
flag.String("w", "", "-w <local_tun[:remote_tun]> - Requests tunnel device forwarding with the specified tun(4) devices between the client (local_tun) and the server (remote_tun). ")
}
// fileExists checks if a file exists and is not a directory before we
// try using it to prevent further errors.
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// tries first to find the cmd file in the current working directory
// if not there loop through paths given in PATH environment variable and return filepath path if found
func resolveCmd(cmd string) (string, error) {
currProcess := os.Args[0]
cwd := filepath.Dir(currProcess)
osPathArr := strings.Split(os.Getenv("PATH"), ";")
pathArr := append([]string{cwd}, osPathArr...)
for _, path := range pathArr {
path = strings.TrimSpace(path)
if len(path) == 0 {
continue
}
cmdPath := filepath.Join(path, cmd)
if fileExists(cmdPath) {
return cmdPath, nil
}
}
return "", fmt.Errorf("Command not found in PATH: %v\n", cmd)
}
func trimArgs(argToTrim string, args []string) []string {
var result []string
for _, arg := range args {
if arg != argToTrim {
result = append(result, arg)
}
}
return result
}
func main() {
registerSshOptions()
argsWithCmd := os.Args
var args = make([]string, len(argsWithCmd[1:]))
copy(args, argsWithCmd[1:])
flag.Parse()
realArgs := flag.Args()
osName := runtime.GOOS
switch osName {
case "darwin":
fmt.Println("MAC OS not supported as plink.exe can't run here!")
return
case "linux":
fmt.Println("Linux not supported as plink.exe can't run here!")
return
case "windows":
// all fine
default:
fmt.Printf("%s not supported.\n", osName)
return
}
fullPlinkCmd, err := resolveCmd(command)
if err != nil {
fmt.Printf("Error: %v\n", err)
fmt.Printf("Please make sure %v is in your current working directory or in PATH (%v)\n", command, os.Getenv("PATH"))
return
}
if len(argsWithCmd) == 1 {
fmt.Fprintf(os.Stderr, "ssh2plink %v\n", version)
cmd := exec.Command(fullPlinkCmd, "-V")
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
cmd.Run()
return
}
if len(argsWithCmd) > 1 {
if argsWithCmd[1] == "-V" {
handleSshVersion()
} else {
// remove unsupported bool & string args
args = removeSshOptionsUnsupportedByPlink(args)
args = handleSshConfig(realArgs, args)
args = replaceArgs("-p", "-P", args)
// args = trimArgs("-T", args)
sshCmd := exec.Command(fullPlinkCmd, args...)
sshCmd.Stdout = os.Stdout
sshCmd.Stderr = os.Stderr
sshCmd.Stdin = os.Stdin
err := sshCmd.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to start sshCmd: %s %s\n", fullPlinkCmd, strings.Join(args, " "))
fmt.Fprintf(os.Stderr, "sshCmd.Start faild with: %s\n", err)
}
err = sshCmd.Wait()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to wait sshCmd: %s %s\n", fullPlinkCmd, strings.Join(args, " "))
fmt.Fprintf(os.Stderr, "sshCmd.Wait failed with: %s\n", err)
}
}
}
}