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