diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-11-10 17:17:34 +0100 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2020-11-16 19:03:37 +0100 |
commit | a07fb45f3672167dda329f64618f93ad69851a35 (patch) | |
tree | 2b60fae6cd8d7715829e57da758056589e591e9d /conf | |
parent | fetcher: introduce downloader utility (diff) | |
download | wireguard-windows-a07fb45f3672167dda329f64618f93ad69851a35.tar.xz wireguard-windows-a07fb45f3672167dda329f64618f93ad69851a35.zip |
conf: move configuration to C:\Program Files\WireGuard\Data
It doesn't get wiped out on Windows upgrades.
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'conf')
-rw-r--r-- | conf/migration_windows.go | 62 | ||||
-rw-r--r-- | conf/path_windows.go | 135 |
2 files changed, 163 insertions, 34 deletions
diff --git a/conf/migration_windows.go b/conf/migration_windows.go index 72b298b6..9e5e1eb9 100644 --- a/conf/migration_windows.go +++ b/conf/migration_windows.go @@ -6,46 +6,58 @@ package conf import ( + "io/ioutil" "log" + "os" "path/filepath" - "strings" "golang.org/x/sys/windows" ) -func maybeMigrate(c string) { +func maybeMigrateConfiguration(c string) { if disableAutoMigration { return } - - vol := filepath.VolumeName(c) - withoutVol := strings.TrimPrefix(c, vol) - oldRoot := filepath.Join(vol, "\\windows.old") - oldC := filepath.Join(oldRoot, withoutVol) - - sd, err := windows.GetNamedSecurityInfo(oldRoot, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) - if err == windows.ERROR_PATH_NOT_FOUND || err == windows.ERROR_FILE_NOT_FOUND { - return - } + oldRoot, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_DEFAULT) if err != nil { - log.Printf("Not migrating configuration from ‘%s’ due to GetNamedSecurityInfo error: %v", oldRoot, err) return } - owner, defaulted, err := sd.Owner() + oldC := filepath.Join(oldRoot, "WireGuard", "Configurations") + files, err := ioutil.ReadDir(oldC) if err != nil { - log.Printf("Not migrating configuration from ‘%s’ due to GetSecurityDescriptorOwner error: %v", oldRoot, err) - return - } - if defaulted || (!owner.IsWellKnown(windows.WinLocalSystemSid) && !owner.IsWellKnown(windows.WinBuiltinAdministratorsSid)) { - log.Printf("Not migrating configuration from ‘%s’, as it is not explicitly owned by SYSTEM or Built-in Administrators, but rather ‘%v’", oldRoot, owner) return } - err = windows.MoveFileEx(windows.StringToUTF16Ptr(oldC), windows.StringToUTF16Ptr(c), windows.MOVEFILE_COPY_ALLOWED) - if err != nil { - if err != windows.ERROR_FILE_NOT_FOUND && err != windows.ERROR_ALREADY_EXISTS { - log.Printf("Not migrating configuration from ‘%s’ due to error when moving files: %v", oldRoot, err) + for i := range files { + if files[i].IsDir() { + continue } - return + fileName := files[i].Name() + newPath := filepath.Join(c, fileName) + newFile, err := os.OpenFile(newPath, os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + continue + } + oldPath := filepath.Join(oldC, fileName) + oldConfig, err := ioutil.ReadFile(oldPath) + if err != nil { + newFile.Close() + os.Remove(newPath) + continue + } + _, err = newFile.Write(oldConfig) + if err != nil { + newFile.Close() + os.Remove(newPath) + continue + } + newFile.Close() + os.Remove(oldPath) + log.Printf("Migrated configuration from ‘%s’ to ‘%s’", oldPath, newPath) + } + if os.Remove(oldC) == nil { + oldLog := filepath.Join(oldRoot, "WireGuard", "log.bin") + oldRoot := filepath.Join(oldRoot, "WireGuard") + os.Remove(oldLog) + os.Remove(oldRoot) } - log.Printf("Migrated configuration from ‘%s’", oldRoot) } diff --git a/conf/path_windows.go b/conf/path_windows.go index a53968c5..0d7e09b3 100644 --- a/conf/path_windows.go +++ b/conf/path_windows.go @@ -6,10 +6,17 @@ package conf import ( + "debug/pe" + "errors" + "fmt" "os" "path/filepath" + "strings" + "syscall" + "unsafe" "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" ) var cachedConfigFileDir string @@ -20,16 +27,16 @@ func tunnelConfigurationsDirectory() (string, error) { if cachedConfigFileDir != "" { return cachedConfigFileDir, nil } - root, err := RootDirectory() + root, err := RootDirectory(true) if err != nil { return "", err } c := filepath.Join(root, "Configurations") - maybeMigrate(c) - err = os.MkdirAll(c, os.ModeDir|0700) - if err != nil { + err = os.Mkdir(c, os.ModeDir|0700) + if err != nil && !os.IsExist(err) { return "", err } + maybeMigrateConfiguration(c) cachedConfigFileDir = c return cachedConfigFileDir, nil } @@ -42,19 +49,129 @@ func PresetRootDirectory(root string) { disableAutoMigration = true } -func RootDirectory() (string, error) { +// TODO: replace with x/sys/windows upstreamed function +func setKernelObjectSecurity(handle windows.Handle, securityInformation windows.SECURITY_INFORMATION, securityDescriptor *windows.SECURITY_DESCRIPTOR) (err error) { + r1, _, e1 := syscall.Syscall(windows.NewLazySystemDLL("advapi32.dll").NewProc("SetKernelObjectSecurity").Addr(), 3, uintptr(handle), uintptr(securityInformation), uintptr(unsafe.Pointer(securityDescriptor))) + if r1 == 0 { + err = errnoErr(e1) + } + return +} +func getFinalPathNameByHandle(file windows.Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall6(windows.NewLazySystemDLL("kernel32.dll").NewProc("GetFinalPathNameByHandleW").Addr(), 4, uintptr(file), uintptr(unsafe.Pointer(filePath)), uintptr(filePathSize), uintptr(flags), 0, 0) + n = uint32(r0) + if n == 0 { + err = errnoErr(e1) + } + return +} + +func RootDirectory(create bool) (string, error) { if cachedRootDir != "" { return cachedRootDir, nil } - root, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, windows.KF_FLAG_CREATE) + var isWow bool + var processMachine, nativeMachine uint16 + err := windows.IsWow64Process2(windows.CurrentProcess(), &processMachine, &nativeMachine) + if err == nil { + isWow = processMachine != pe.IMAGE_FILE_MACHINE_UNKNOWN + } else { + if !errors.Is(err, windows.ERROR_PROC_NOT_FOUND) { + return "", err + } + err = windows.IsWow64Process(windows.CurrentProcess(), &isWow) + if err != nil { + return "", err + } + } + var root string + if !isWow { + root, err = windows.KnownFolderPath(windows.FOLDERID_ProgramFiles, windows.KF_FLAG_CREATE) + if err != nil { + return "", err + } + } else { + key, err := registry.OpenKey(windows.HKEY_LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows\CurrentVersion`, registry.READ|registry.WOW64_64KEY) + if err != nil { + return "", err + } + var typ uint32 + root, typ, err = key.GetStringValue("ProgramFilesDir") + key.Close() + if err != nil { + return "", err + } + if typ != registry.SZ { + return "", registry.ErrUnexpectedType + } + } + root = filepath.Join(root, "WireGuard") + if !create { + return filepath.Join(root, "Data"), nil + } + root16, err := windows.UTF16PtrFromString(root) + if err != nil { + return "", err + } + + // The root directory inherits its ACL from Program Files; we don't want to mess with that + err = windows.CreateDirectory(root16, nil) + if err != nil && err != windows.ERROR_ALREADY_EXISTS { + return "", err + } + + dataDirectorySd, err := windows.SecurityDescriptorFromString("O:SYG:SYD:PAI(A;OICI;FA;;;SY)(A;OICI;FR;;;BA)") if err != nil { return "", err } - c := filepath.Join(root, "WireGuard") - err = os.MkdirAll(c, os.ModeDir|0700) + dataDirectorySa := &windows.SecurityAttributes{ + Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), + SecurityDescriptor: dataDirectorySd, + } + + data := filepath.Join(root, "Data") + data16, err := windows.UTF16PtrFromString(data) + if err != nil { + return "", err + } + var dataHandle windows.Handle + for { + err = windows.CreateDirectory(data16, dataDirectorySa) + if err != nil && err != windows.ERROR_ALREADY_EXISTS { + return "", err + } + dataHandle, err = windows.CreateFile(data16, windows.READ_CONTROL|windows.WRITE_OWNER|windows.WRITE_DAC, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OPEN_REPARSE_POINT, 0) + if err != nil && err != windows.ERROR_FILE_NOT_FOUND { + return "", err + } + if err == nil { + break + } + } + defer windows.CloseHandle(dataHandle) + var fileInfo windows.ByHandleFileInformation + err = windows.GetFileInformationByHandle(dataHandle, &fileInfo) + if err != nil { + return "", err + } + if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { + return "", errors.New("Data directory is actually a file") + } + if fileInfo.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { + return "", errors.New("Data directory is reparse point") + } + var buf [windows.MAX_PATH * 4]uint16 + _, err = getFinalPathNameByHandle(dataHandle, &buf[0], uint32(len(buf)), 0) + if err != nil { + return "", err + } + if !strings.EqualFold(`\\?\`+data, windows.UTF16ToString(buf[:])) { + return "", fmt.Errorf("Data directory jumped to unexpected location: got %q; want %q", windows.UTF16ToString(buf[:]), `\\?\`+data) + } + err = setKernelObjectSecurity(dataHandle, windows.DACL_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, dataDirectorySd) if err != nil { return "", err } - cachedRootDir = c + cachedRootDir = data return cachedRootDir, nil } |