/
ssh.go
146 lines (127 loc) · 3.02 KB
/
ssh.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
package mallory
import (
"errors"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"os/user"
"sync"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
//
type SSH struct {
// global config file
Cfg *Config
// connect URL
URL *url.URL
// SSH client
Client *ssh.Client
// SSH client config
CliCfg *ssh.ClientConfig
// direct fetcher
Direct *Direct
// only re-dial once
sf Group
l sync.RWMutex
}
// Create and initialize
func NewSSH(c *Config) (self *SSH, err error) {
self = &SSH{
Cfg: c,
CliCfg: &ssh.ClientConfig{},
}
// e.g. ssh://user:passwd@192.168.1.1:1122
self.URL, err = url.Parse(c.File.RemoteServer)
if err != nil {
return
}
if self.URL.User != nil {
self.CliCfg.User = self.URL.User.Username()
} else {
u, err := user.Current()
if err != nil {
return self, err
}
// u.Name is the full name, should not be used
self.CliCfg.User = u.Username
}
self.CliCfg.HostKeyCallback = ssh.InsecureIgnoreHostKey()
// 1) try RSA keyring first
for {
id_rsa := c.File.PrivateKey
pem, err := ioutil.ReadFile(id_rsa)
if err != nil {
L.Printf("ReadFile %s failed:%s\n", c.File.PrivateKey, err)
break
}
signer, err := ssh.ParsePrivateKey(pem)
if err != nil {
L.Printf("ParsePrivateKey %s failed:%s\n", c.File.PrivateKey, err)
break
}
self.CliCfg.Auth = append(self.CliCfg.Auth, ssh.PublicKeys(signer))
// stop !!
break
}
// 2) try password
for {
if self.URL.User == nil {
break
}
if pass, ok := self.URL.User.Password(); ok {
self.CliCfg.Auth = append(self.CliCfg.Auth, ssh.Password(pass))
}
// stop here!!
break
}
// 3) try ssh agent
if sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
agentAuth := ssh.PublicKeysCallback(agent.NewClient(sshAgent).Signers)
self.CliCfg.Auth = append(self.CliCfg.Auth, agentAuth)
}
if len(self.CliCfg.Auth) == 0 {
//TODO: keyboard intercative
err = errors.New("Invalid auth method, please add password or generate ssh keys")
return
}
// first time to dial to remote server, make sure it is available
self.Client, err = ssh.Dial("tcp", self.URL.Host, self.CliCfg)
if err != nil {
return
}
dial := func(network, addr string) (c net.Conn, err error) {
self.l.RLock()
cli := self.Client
self.l.RUnlock()
c, err = cli.Dial(network, addr)
if err == nil {
return
}
L.Printf("dial %s failed: %s, reconnecting ssh server %s...\n", addr, err, self.URL.Host)
clif, err := self.sf.Do(network+addr, func() (interface{}, error) {
return ssh.Dial("tcp", self.URL.Host, self.CliCfg)
})
if err != nil {
L.Printf("connect ssh server %s failed: %s\n", self.URL.Host, err)
return
}
cli = clif.(*ssh.Client)
self.l.Lock()
self.Client = cli
self.l.Unlock()
return cli.Dial(network, addr)
}
self.Direct = &Direct{
Tr: &http.Transport{Dial: dial},
}
return
}
func (self *SSH) ServeHTTP(w http.ResponseWriter, r *http.Request) {
self.Direct.ServeHTTP(w, r)
}
func (self *SSH) Connect(w http.ResponseWriter, r *http.Request) {
self.Direct.Connect(w, r)
}