diff --git a/lib/config/config.go b/lib/config/config.go
index 21020c407..0534d5f91 100644
--- a/lib/config/config.go
+++ b/lib/config/config.go
@@ -284,7 +284,7 @@ func (cfg *Configuration) ensureMyDevice(myID protocol.DeviceID) {
})
}
-func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]bool, error) {
+func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[protocol.DeviceID]*DeviceConfiguration, error) {
existingDevices := cfg.prepareDeviceList()
sharedFolders, err := cfg.prepareFolders(myID, existingDevices)
@@ -297,7 +297,7 @@ func (cfg *Configuration) prepareFoldersAndDevices(myID protocol.DeviceID) (map[
return existingDevices, nil
}
-func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
+func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]*DeviceConfiguration {
// Ensure that the device list is
// - free from duplicates
// - no devices with empty ID
@@ -309,14 +309,14 @@ func (cfg *Configuration) prepareDeviceList() map[protocol.DeviceID]bool {
})
// Build a list of available devices
- existingDevices := make(map[protocol.DeviceID]bool, len(cfg.Devices))
- for _, device := range cfg.Devices {
- existingDevices[device.DeviceID] = true
+ existingDevices := make(map[protocol.DeviceID]*DeviceConfiguration, len(cfg.Devices))
+ for i, device := range cfg.Devices {
+ existingDevices[device.DeviceID] = &cfg.Devices[i]
}
return existingDevices
}
-func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) (map[protocol.DeviceID][]string, error) {
+func (cfg *Configuration) prepareFolders(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) (map[protocol.DeviceID][]string, error) {
// Prepare folders and check for duplicates. Duplicates are bad and
// dangerous, can't currently be resolved in the GUI, and shouldn't
// happen when configured by the GUI. We return with an error in that
@@ -359,13 +359,13 @@ func (cfg *Configuration) prepareDevices(sharedFolders map[protocol.DeviceID][]s
}
}
-func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]bool) map[protocol.DeviceID]bool {
+func (cfg *Configuration) prepareIgnoredDevices(existingDevices map[protocol.DeviceID]*DeviceConfiguration) map[protocol.DeviceID]bool {
// The list of ignored devices should not contain any devices that have
// been manually added to the config.
newIgnoredDevices := cfg.IgnoredDevices[:0]
ignoredDevices := make(map[protocol.DeviceID]bool, len(cfg.IgnoredDevices))
for _, dev := range cfg.IgnoredDevices {
- if !existingDevices[dev.ID] {
+ if _, ok := existingDevices[dev.ID]; !ok {
ignoredDevices[dev.ID] = true
newIgnoredDevices = append(newIgnoredDevices, dev)
}
@@ -502,7 +502,7 @@ func ensureDevicePresent(devices []FolderDeviceConfiguration, myID protocol.Devi
return devices
}
-func ensureExistingDevices(devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]bool) []FolderDeviceConfiguration {
+func ensureExistingDevices(devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]*DeviceConfiguration) []FolderDeviceConfiguration {
count := len(devices)
i := 0
loop:
@@ -553,6 +553,23 @@ loop:
return devices[0:count]
}
+func ensureNoUntrustedTrustingSharing(f *FolderConfiguration, devices []FolderDeviceConfiguration, existingDevices map[protocol.DeviceID]*DeviceConfiguration) []FolderDeviceConfiguration {
+ for i := 0; i < len(devices); i++ {
+ dev := devices[i]
+ if dev.EncryptionPassword != "" {
+ // There's a password set, no check required
+ continue
+ }
+ if devCfg := existingDevices[dev.DeviceID]; devCfg.Untrusted {
+ l.Warnf("Folder %s (%s) is shared in trusted mode with untrusted device %s (%s); unsharing.", f.ID, f.Label, dev.DeviceID.Short(), devCfg.Name)
+ copy(devices[i:], devices[i+1:])
+ devices = devices[:len(devices)-1]
+ i--
+ }
+ }
+ return devices
+}
+
func cleanSymlinks(filesystem fs.Filesystem, dir string) {
if build.IsWindows {
// We don't do symlinks on Windows. Additionally, there may
@@ -611,7 +628,7 @@ func getFreePort(host string, ports ...int) (int, error) {
return addr.Port, nil
}
-func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
+func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) {
ensureZeroForNodefault(&FolderConfiguration{}, &defaults.Folder)
ensureZeroForNodefault(&DeviceConfiguration{}, &defaults.Device)
defaults.Folder.prepare(myID, existingDevices)
diff --git a/lib/config/config_test.go b/lib/config/config_test.go
index b70aab91c..e0d6ef22d 100644
--- a/lib/config/config_test.go
+++ b/lib/config/config_test.go
@@ -1489,6 +1489,65 @@ func TestXattrFilter(t *testing.T) {
}
}
+func TestUntrustedIntroducer(t *testing.T) {
+ fd, err := os.Open("testdata/untrustedintroducer.xml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ cfg, _, err := ReadXML(fd, device1)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(cfg.Devices) != 2 {
+ // ourselves and the remote in the config
+ t.Fatal("Expected two devices")
+ }
+
+ // Check that the introducer and auto-accept flags were negated
+ var foundUntrusted protocol.DeviceID
+ for _, d := range cfg.Devices {
+ if d.Name == "untrusted" {
+ foundUntrusted = d.DeviceID
+ if !d.Untrusted {
+ t.Error("untrusted device should be untrusted")
+ }
+ if d.Introducer {
+ t.Error("untrusted device should not be an introducer")
+ }
+ if d.AutoAcceptFolders {
+ t.Error("untrusted device should not auto-accept folders")
+ }
+ }
+ }
+ if foundUntrusted.Equals(protocol.EmptyDeviceID) {
+ t.Error("untrusted device not found")
+ }
+
+ // Folder A has the device added without a password, which is not permitted
+ folderA := cfg.FolderMap()["a"]
+ for _, dev := range folderA.Devices {
+ if dev.DeviceID == foundUntrusted {
+ t.Error("untrusted device should not be in folder A")
+ }
+ }
+
+ // Folder B has the device added with a password, this is just a sanity check
+ folderB := cfg.FolderMap()["b"]
+ found := false
+ for _, dev := range folderB.Devices {
+ if dev.DeviceID == foundUntrusted {
+ found = true
+ if dev.EncryptionPassword == "" {
+ t.Error("untrusted device should have a password in folder B")
+ }
+ }
+ }
+ if !found {
+ t.Error("untrusted device not found in folder B")
+ }
+}
+
// Verify that opening a config with myID == protocol.EmptyDeviceID doesn't add that ID to the config.
// Done in various places where config is needed, but the device ID isn't known.
func TestLoadEmptyDeviceID(t *testing.T) {
diff --git a/lib/config/deviceconfiguration.go b/lib/config/deviceconfiguration.go
index d2751002e..54a30affc 100644
--- a/lib/config/deviceconfiguration.go
+++ b/lib/config/deviceconfiguration.go
@@ -34,6 +34,19 @@ func (cfg *DeviceConfiguration) prepare(sharedFolders []string) {
}
cfg.IgnoredFolders = sortedObservedFolderSlice(ignoredFolders)
+
+ // A device cannot be simultaneously untrusted and an introducer, nor
+ // auto accept folders.
+ if cfg.Untrusted {
+ if cfg.Introducer {
+ l.Warnf("Device %s (%s) is both untrusted and an introducer, removing introducer flag", cfg.DeviceID.Short(), cfg.Name)
+ cfg.Introducer = false
+ }
+ if cfg.AutoAcceptFolders {
+ l.Warnf("Device %s (%s) is both untrusted and auto-accepting folders, removing auto-accept flag", cfg.DeviceID.Short(), cfg.Name)
+ cfg.AutoAcceptFolders = false
+ }
+ }
}
func (cfg *DeviceConfiguration) IgnoredFolder(folder string) bool {
diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go
index a65df1177..d6e076727 100644
--- a/lib/config/folderconfiguration.go
+++ b/lib/config/folderconfiguration.go
@@ -181,14 +181,16 @@ func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
return deviceIDs
}
-func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]bool) {
+func (f *FolderConfiguration) prepare(myID protocol.DeviceID, existingDevices map[protocol.DeviceID]*DeviceConfiguration) {
// Ensure that
// - any loose devices are not present in the wrong places
// - there are no duplicate devices
// - we are part of the devices
+ // - folder is not shared in trusted mode with an untrusted device
f.Devices = ensureExistingDevices(f.Devices, existingDevices)
f.Devices = ensureNoDuplicateFolderDevices(f.Devices)
f.Devices = ensureDevicePresent(f.Devices, myID)
+ f.Devices = ensureNoUntrustedTrustingSharing(f, f.Devices, existingDevices)
sort.Slice(f.Devices, func(a, b int) bool {
return f.Devices[a].DeviceID.Compare(f.Devices[b].DeviceID) == -1
diff --git a/lib/config/testdata/untrustedintroducer.xml b/lib/config/testdata/untrustedintroducer.xml
new file mode 100644
index 000000000..122f9a459
--- /dev/null
+++ b/lib/config/testdata/untrustedintroducer.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+ a complex password
+
+
+
+ dynamic
+ true
+ true
+
+