github.com/puellanivis/breton@v0.2.16/lib/files/sftpfiles/host.go (about) 1 package sftpfiles 2 3 import ( 4 "errors" 5 "net/url" 6 "sync" 7 8 "github.com/puellanivis/breton/lib/os/user" 9 10 "github.com/pkg/sftp" 11 "golang.org/x/crypto/ssh" 12 ) 13 14 // Host defines a set of connection settings to a specific host/user combination, 15 // and manages a common SFTP connection to that host with those credentials. 16 type Host struct { 17 mu sync.Mutex 18 conn *ssh.Client 19 cl *sftp.Client 20 21 uri *url.URL 22 23 auths []ssh.AuthMethod 24 25 ignoreHostkey bool 26 hostkey ssh.HostKeyCallback 27 hostkeyAlgos []string 28 } 29 30 var ( 31 userInit sync.Once 32 defaultUser *url.Userinfo 33 ) 34 35 func getUser() *url.Userinfo { 36 userInit.Do(func() { 37 name, err := user.CurrentUsername() 38 if err != nil { 39 return 40 } 41 42 defaultUser = url.User(name) 43 }) 44 45 return defaultUser 46 } 47 48 // NewHost returns a Host defined for a specific host/user based on a given URL. 49 // No connection is made, and no authentication or hostkey validation is defined. 50 func NewHost(uri *url.URL) *Host { 51 var auths []ssh.AuthMethod 52 53 user := getUser() 54 if uri.User != nil { 55 user = url.User(uri.User.Username()) 56 57 if pw, ok := uri.User.Password(); ok { 58 auths = append(auths, ssh.Password(pw)) 59 } 60 } 61 62 uri = &url.URL{ 63 Scheme: "ssh", 64 Host: uri.Host, 65 User: user, 66 } 67 68 if uri.Port() == "" { 69 uri.Host += ":22" 70 } 71 72 return &Host{ 73 uri: uri, 74 auths: auths, 75 } 76 } 77 78 // Name returns an identifying name of the Host composed of the authority section of the URL: //user[:pass]@hostname:port 79 func (h *Host) Name() string { 80 return h.uri.String() 81 } 82 83 func (h *Host) close() error { 84 if h.cl == nil { 85 return nil 86 } 87 88 err := h.cl.Close() 89 if err2 := h.conn.Close(); err == nil { 90 err = err2 91 } 92 93 h.cl, h.conn = nil, nil 94 95 return err 96 } 97 98 // Close closes and invalidates the Host's current connection. 99 func (h *Host) Close() error { 100 h.mu.Lock() 101 defer h.mu.Unlock() 102 103 return h.close() 104 } 105 106 func (h *Host) client() *sftp.Client { 107 if h.cl == nil { 108 return nil 109 } 110 111 if _, err := h.cl.Getwd(); err != nil { 112 // We cannot get the current working directory, 113 // So, invalidate our connections, and return nil. 114 _ = h.close() 115 116 return nil 117 } 118 119 return h.cl 120 } 121 122 // GetClient returns the currently connected Client connected to by the Host. 123 // It returns nil if the Host is not currently connected. 124 func (h *Host) GetClient() *sftp.Client { 125 h.mu.Lock() 126 defer h.mu.Unlock() 127 128 return h.client() 129 } 130 131 // Connect either returns the currently connected Client, or makes a new connection based on Host. 132 func (h *Host) Connect() (*sftp.Client, error) { 133 h.mu.Lock() 134 defer h.mu.Unlock() 135 136 if cl := h.client(); cl != nil { 137 return cl, nil 138 } 139 140 hk := h.hostkey 141 if h.ignoreHostkey { 142 hk = ssh.InsecureIgnoreHostKey() 143 } 144 145 if hk == nil { 146 return nil, errors.New("no hostkey validation defined") 147 } 148 149 conn, err := ssh.Dial("tcp", h.uri.Host, &ssh.ClientConfig{ 150 User: h.uri.User.Username(), 151 Auth: h.cloneAuths(), 152 HostKeyCallback: hk, 153 HostKeyAlgorithms: h.hostkeyAlgos, 154 }) 155 if err != nil { 156 return nil, err 157 } 158 159 cl, err := sftp.NewClient(conn) 160 if err != nil { 161 conn.Close() 162 return nil, err 163 } 164 165 h.conn, h.cl = conn, cl 166 167 return cl, nil 168 } 169 170 func (h *Host) cloneAuths() []ssh.AuthMethod { 171 return append([]ssh.AuthMethod{}, h.auths...) 172 } 173 174 // addAuths is an internal convenience func to add any number of auths. 175 func (h *Host) addAuths(auths ...ssh.AuthMethod) []ssh.AuthMethod { 176 return h.SetAuths(append(h.cloneAuths(), auths...)) 177 } 178 179 // AddAuth adds the given ssh.AuthMethod to the authorization methods for the Host, and return the previous value. 180 func (h *Host) AddAuth(auth ssh.AuthMethod) []ssh.AuthMethod { 181 return h.addAuths(auth) 182 } 183 184 // SetAuths sets the slice of ssh.AuthMethod on the Host, and returns the previous value. 185 func (h *Host) SetAuths(auths []ssh.AuthMethod) []ssh.AuthMethod { 186 save := h.auths 187 188 h.auths = auths 189 190 return save 191 } 192 193 // IgnoreHostKeys sets a flag that Host should ignore Host keys when connecting. 194 // THIS IS INSECURE. 195 func (h *Host) IgnoreHostKeys(state bool) bool { 196 save := h.ignoreHostkey 197 198 h.ignoreHostkey = state 199 200 return save 201 } 202 203 // SetHostKeyCallback sets the current hostkey callback for the Host, and returns the previous value. 204 func (h *Host) SetHostKeyCallback(cb ssh.HostKeyCallback, algos []string) (ssh.HostKeyCallback, []string) { 205 saveHK, saveAlgos := h.hostkey, h.hostkeyAlgos 206 207 h.hostkey = cb 208 h.hostkeyAlgos = algos 209 210 return saveHK, saveAlgos 211 }