From 4c6d2c5410c63e9c121e3ed54b3078b8de6828ef Mon Sep 17 00:00:00 2001 From: nielash Date: Mon, 18 Dec 2023 11:35:13 -0500 Subject: [PATCH] crypt: improve handling of undecryptable file names - fixes #5787 fixes #6439 fixes #6437 Before this change, undecryptable file names would be skipped very quietly (there was a log warning, but only at DEBUG level), failing to alert users of a potentially serious issue that needs attention. After this change, the log level is raised to NOTICE by default and a new --crypt-strict-names flag allows raising an error, for users who may prefer not to proceed if such an issue is detected. See https://forum.rclone.org/t/skipping-undecryptable-file-name-should-be-an-error/27115 https://github.com/rclone/rclone/issues/5787 --- backend/crypt/crypt.go | 46 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/backend/crypt/crypt.go b/backend/crypt/crypt.go index ea1fbccd1..6c19bb981 100644 --- a/backend/crypt/crypt.go +++ b/backend/crypt/crypt.go @@ -130,6 +130,16 @@ trying to recover an encrypted file with errors and it is desired to recover as much of the file as possible.`, Default: false, Advanced: true, + }, { + Name: "strict_names", + Help: `If set, this will raise an error when crypt comes across a filename that can't be decrypted. + +(By default, rclone will just log a NOTICE and continue as normal.) +This can happen if encrypted and unencrypted files are stored in the same +directory (which is not recommended.) It may also indicate a more serious +problem that should be investigated.`, + Default: false, + Advanced: true, }, { Name: "filename_encoding", Help: `How to encode the encrypted filename to text string. @@ -299,6 +309,7 @@ type Options struct { PassBadBlocks bool `config:"pass_bad_blocks"` FilenameEncoding string `config:"filename_encoding"` Suffix string `config:"suffix"` + StrictNames bool `config:"strict_names"` } // Fs represents a wrapped fs.Fs @@ -333,45 +344,64 @@ func (f *Fs) String() string { } // Encrypt an object file name to entries. -func (f *Fs) add(entries *fs.DirEntries, obj fs.Object) { +func (f *Fs) add(entries *fs.DirEntries, obj fs.Object) error { remote := obj.Remote() decryptedRemote, err := f.cipher.DecryptFileName(remote) if err != nil { - fs.Debugf(remote, "Skipping undecryptable file name: %v", err) - return + if f.opt.StrictNames { + return fmt.Errorf("%s: undecryptable file name detected: %v", remote, err) + } + fs.Logf(remote, "Skipping undecryptable file name: %v", err) + return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newObject(obj)) + return nil } // Encrypt a directory file name to entries. -func (f *Fs) addDir(ctx context.Context, entries *fs.DirEntries, dir fs.Directory) { +func (f *Fs) addDir(ctx context.Context, entries *fs.DirEntries, dir fs.Directory) error { remote := dir.Remote() decryptedRemote, err := f.cipher.DecryptDirName(remote) if err != nil { - fs.Debugf(remote, "Skipping undecryptable dir name: %v", err) - return + if f.opt.StrictNames { + return fmt.Errorf("%s: undecryptable dir name detected: %v", remote, err) + } + fs.Logf(remote, "Skipping undecryptable dir name: %v", err) + return nil } if f.opt.ShowMapping { fs.Logf(decryptedRemote, "Encrypts to %q", remote) } *entries = append(*entries, f.newDir(ctx, dir)) + return nil } // Encrypt some directory entries. This alters entries returning it as newEntries. func (f *Fs) encryptEntries(ctx context.Context, entries fs.DirEntries) (newEntries fs.DirEntries, err error) { newEntries = entries[:0] // in place filter + errors := 0 + var firsterr error for _, entry := range entries { switch x := entry.(type) { case fs.Object: - f.add(&newEntries, x) + err = f.add(&newEntries, x) case fs.Directory: - f.addDir(ctx, &newEntries, x) + err = f.addDir(ctx, &newEntries, x) default: return nil, fmt.Errorf("unknown object type %T", entry) } + if err != nil { + errors++ + if firsterr == nil { + firsterr = err + } + } + } + if firsterr != nil { + return nil, fmt.Errorf("there were %v undecryptable name errors. first error: %v", errors, firsterr) } return newEntries, nil }