github.com/divyam234/rclone@v1.64.1/cmd/serve/sftp/connection.go (about) 1 //go:build !plan9 2 // +build !plan9 3 4 package sftp 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "os" 13 "regexp" 14 "strings" 15 16 "github.com/pkg/sftp" 17 "github.com/divyam234/rclone/fs" 18 "github.com/divyam234/rclone/fs/hash" 19 "github.com/divyam234/rclone/lib/terminal" 20 "github.com/divyam234/rclone/vfs" 21 "github.com/divyam234/rclone/vfs/vfsflags" 22 "golang.org/x/crypto/ssh" 23 ) 24 25 func describeConn(c interface { 26 RemoteAddr() net.Addr 27 LocalAddr() net.Addr 28 }) string { 29 return fmt.Sprintf("serve sftp %s->%s", c.RemoteAddr(), c.LocalAddr()) 30 } 31 32 // Return the exit status of the command 33 type exitStatus struct { 34 RC uint32 35 } 36 37 // The incoming exec command 38 type execCommand struct { 39 Command string 40 } 41 42 var shellUnEscapeRegex = regexp.MustCompile(`\\(.)`) 43 44 // Unescape a string that was escaped by rclone 45 func shellUnEscape(str string) string { 46 str = strings.ReplaceAll(str, "'\n'", "\n") 47 str = shellUnEscapeRegex.ReplaceAllString(str, `$1`) 48 return str 49 } 50 51 // Info about the current connection 52 type conn struct { 53 vfs *vfs.VFS 54 handlers sftp.Handlers 55 what string 56 } 57 58 // execCommand implements an extremely limited number of commands to 59 // interoperate with the rclone sftp backend 60 func (c *conn) execCommand(ctx context.Context, out io.Writer, command string) (err error) { 61 binary, args := command, "" 62 space := strings.Index(command, " ") 63 if space >= 0 { 64 binary = command[:space] 65 args = strings.TrimLeft(command[space+1:], " ") 66 } 67 args = shellUnEscape(args) 68 fs.Debugf(c.what, "exec command: binary = %q, args = %q", binary, args) 69 switch binary { 70 case "df": 71 about := c.vfs.Fs().Features().About 72 if about == nil { 73 return errors.New("df not supported") 74 } 75 usage, err := about(ctx) 76 if err != nil { 77 return fmt.Errorf("about failed: %w", err) 78 } 79 total, used, free := int64(-1), int64(-1), int64(-1) 80 if usage.Total != nil { 81 total = *usage.Total / 1024 82 } 83 if usage.Used != nil { 84 used = *usage.Used / 1024 85 } 86 if usage.Free != nil { 87 free = *usage.Free / 1024 88 } 89 perc := int64(0) 90 if total > 0 && used >= 0 { 91 perc = (100 * used) / total 92 } 93 _, err = fmt.Fprintf(out, ` Filesystem 1K-blocks Used Available Use%% Mounted on 94 /dev/root %d %d %d %d%% / 95 `, total, used, free, perc) 96 if err != nil { 97 return fmt.Errorf("send output failed: %w", err) 98 } 99 case "md5sum", "sha1sum": 100 ht := hash.MD5 101 if binary == "sha1sum" { 102 ht = hash.SHA1 103 } 104 if !c.vfs.Fs().Hashes().Contains(ht) { 105 return fmt.Errorf("%v hash not supported", ht) 106 } 107 var hashSum string 108 if args == "" { 109 // empty hash for no input 110 if ht == hash.MD5 { 111 hashSum = "d41d8cd98f00b204e9800998ecf8427e" 112 } else { 113 hashSum = "da39a3ee5e6b4b0d3255bfef95601890afd80709" 114 } 115 args = "-" 116 } else { 117 node, err := c.vfs.Stat(args) 118 if err != nil { 119 return fmt.Errorf("hash failed finding file %q: %w", args, err) 120 } 121 if node.IsDir() { 122 return errors.New("can't hash directory") 123 } 124 o, ok := node.DirEntry().(fs.ObjectInfo) 125 if !ok { 126 fs.Debugf(args, "File uploading - reading hash from VFS cache") 127 in, err := node.Open(os.O_RDONLY) 128 if err != nil { 129 return fmt.Errorf("hash vfs open failed: %w", err) 130 } 131 defer func() { 132 _ = in.Close() 133 }() 134 h, err := hash.NewMultiHasherTypes(hash.NewHashSet(ht)) 135 if err != nil { 136 return fmt.Errorf("hash vfs create multi-hasher failed: %w", err) 137 } 138 _, err = io.Copy(h, in) 139 if err != nil { 140 return fmt.Errorf("hash vfs copy failed: %w", err) 141 } 142 hashSum = h.Sums()[ht] 143 } else { 144 hashSum, err = o.Hash(ctx, ht) 145 if err != nil { 146 return fmt.Errorf("hash failed: %w", err) 147 } 148 } 149 } 150 _, err = fmt.Fprintf(out, "%s %s\n", hashSum, args) 151 if err != nil { 152 return fmt.Errorf("send output failed: %w", err) 153 } 154 case "echo": 155 // Special cases for legacy rclone command detection. 156 // Before rclone v1.49.0 the sftp backend used "echo 'abc' | md5sum" when 157 // detecting hash support, but was then changed to instead just execute 158 // md5sum/sha1sum (without arguments), which is handled above. The following 159 // code is therefore only necessary to support rclone versions older than 160 // v1.49.0 using a sftp remote connected to a rclone serve sftp instance 161 // running a newer version of rclone (e.g. latest). 162 switch args { 163 case "'abc' | md5sum": 164 if c.vfs.Fs().Hashes().Contains(hash.MD5) { 165 _, err = fmt.Fprintf(out, "0bee89b07a248e27c83fc3d5951213c1 -\n") 166 if err != nil { 167 return fmt.Errorf("send output failed: %w", err) 168 } 169 } else { 170 return errors.New("md5 hash not supported") 171 } 172 case "'abc' | sha1sum": 173 if c.vfs.Fs().Hashes().Contains(hash.SHA1) { 174 _, err = fmt.Fprintf(out, "03cfd743661f07975fa2f1220c5194cbaff48451 -\n") 175 if err != nil { 176 return fmt.Errorf("send output failed: %w", err) 177 } 178 } else { 179 return errors.New("sha1 hash not supported") 180 } 181 default: 182 _, err = fmt.Fprintf(out, "%s\n", args) 183 if err != nil { 184 return fmt.Errorf("send output failed: %w", err) 185 } 186 } 187 default: 188 return fmt.Errorf("%q not implemented", command) 189 } 190 return nil 191 } 192 193 // handle a new incoming channel request 194 func (c *conn) handleChannel(newChannel ssh.NewChannel) { 195 fs.Debugf(c.what, "Incoming channel: %s\n", newChannel.ChannelType()) 196 if newChannel.ChannelType() != "session" { 197 err := newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") 198 fs.Debugf(c.what, "Unknown channel type: %s\n", newChannel.ChannelType()) 199 if err != nil { 200 fs.Errorf(c.what, "Failed to reject unknown channel: %v", err) 201 } 202 return 203 } 204 channel, requests, err := newChannel.Accept() 205 if err != nil { 206 fs.Errorf(c.what, "could not accept channel: %v", err) 207 return 208 } 209 defer func() { 210 err := channel.Close() 211 if err != nil && err != io.EOF { 212 fs.Debugf(c.what, "Failed to close channel: %v", err) 213 } 214 }() 215 fs.Debugf(c.what, "Channel accepted\n") 216 217 isSFTP := make(chan bool, 1) 218 var command execCommand 219 220 // Handle out-of-band requests 221 go func(in <-chan *ssh.Request) { 222 for req := range in { 223 fs.Debugf(c.what, "Request: %v\n", req.Type) 224 ok := false 225 var subSystemIsSFTP bool 226 var reply []byte 227 switch req.Type { 228 case "subsystem": 229 fs.Debugf(c.what, "Subsystem: %s\n", req.Payload[4:]) 230 if string(req.Payload[4:]) == "sftp" { 231 ok = true 232 subSystemIsSFTP = true 233 } 234 case "exec": 235 err := ssh.Unmarshal(req.Payload, &command) 236 if err != nil { 237 fs.Errorf(c.what, "ignoring bad exec command: %v", err) 238 } else { 239 ok = true 240 subSystemIsSFTP = false 241 } 242 } 243 fs.Debugf(c.what, " - accepted: %v\n", ok) 244 err = req.Reply(ok, reply) 245 if err != nil { 246 fs.Errorf(c.what, "Failed to Reply to request: %v", err) 247 return 248 } 249 if ok { 250 // Wake up main routine after we have responded 251 isSFTP <- subSystemIsSFTP 252 } 253 } 254 }(requests) 255 256 // Wait for either subsystem "sftp" or "exec" request 257 if <-isSFTP { 258 if err := serveChannel(channel, c.handlers, c.what); err != nil { 259 fs.Errorf(c.what, "Failed to serve SFTP: %v", err) 260 } 261 } else { 262 var rc = uint32(0) 263 err := c.execCommand(context.TODO(), channel, command.Command) 264 if err != nil { 265 rc = 1 266 _, errPrint := fmt.Fprintf(channel.Stderr(), "%v\n", err) 267 if errPrint != nil { 268 fs.Errorf(c.what, "Failed to write to stderr: %v", errPrint) 269 } 270 fs.Debugf(c.what, "command %q failed with error: %v", command.Command, err) 271 } 272 _, err = channel.SendRequest("exit-status", false, ssh.Marshal(exitStatus{RC: rc})) 273 if err != nil { 274 fs.Errorf(c.what, "Failed to send exit status: %v", err) 275 } 276 } 277 } 278 279 // Service the incoming Channel channel in go routine 280 func (c *conn) handleChannels(chans <-chan ssh.NewChannel) { 281 for newChannel := range chans { 282 go c.handleChannel(newChannel) 283 } 284 } 285 286 func serveChannel(rwc io.ReadWriteCloser, h sftp.Handlers, what string) error { 287 fs.Debugf(what, "Starting SFTP server") 288 server := sftp.NewRequestServer(rwc, h) 289 defer func() { 290 err := server.Close() 291 if err != nil && err != io.EOF { 292 fs.Debugf(what, "Failed to close server: %v", err) 293 } 294 }() 295 err := server.Serve() 296 if err != nil && err != io.EOF { 297 return fmt.Errorf("completed with error: %w", err) 298 } 299 fs.Debugf(what, "exited session") 300 return nil 301 } 302 303 func serveStdio(f fs.Fs) error { 304 if terminal.IsTerminal(int(os.Stdout.Fd())) { 305 return errors.New("refusing to run SFTP server directly on a terminal. Please let sshd start rclone, by connecting with sftp or sshfs") 306 } 307 sshChannel := &stdioChannel{ 308 stdin: os.Stdin, 309 stdout: os.Stdout, 310 } 311 handlers := newVFSHandler(vfs.New(f, &vfsflags.Opt)) 312 return serveChannel(sshChannel, handlers, "stdio") 313 } 314 315 type stdioChannel struct { 316 stdin *os.File 317 stdout *os.File 318 } 319 320 func (c *stdioChannel) Read(data []byte) (int, error) { 321 return c.stdin.Read(data) 322 } 323 324 func (c *stdioChannel) Write(data []byte) (int, error) { 325 return c.stdout.Write(data) 326 } 327 328 func (c *stdioChannel) Close() error { 329 err1 := c.stdin.Close() 330 err2 := c.stdout.Close() 331 if err1 != nil { 332 return err1 333 } 334 return err2 335 }