aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/conf/migration_windows.go
blob: ed288f3c67fa0f55521805e17a841ec2a44b5ce3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* SPDX-License-Identifier: MIT
 *
 * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
 */

package conf

import (
	"errors"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"golang.org/x/sys/windows"
)

var (
	migrating          sync.Mutex
	lastMigrationTimer *time.Timer
)

type MigrationCallback func(name, oldPath, newPath string)

func MigrateUnencryptedConfigs(migrated MigrationCallback) { migrateUnencryptedConfigs(3, migrated) }

func migrateUnencryptedConfigs(sharingBase int, migrated MigrationCallback) {
	if migrated == nil {
		migrated = func(_, _, _ string) {}
	}
	migrating.Lock()
	defer migrating.Unlock()
	configFileDir, err := tunnelConfigurationsDirectory()
	if err != nil {
		return
	}
	files, err := os.ReadDir(configFileDir)
	if err != nil {
		return
	}
	ignoreSharingViolations := false
	for _, file := range files {
		path := filepath.Join(configFileDir, file.Name())
		name := filepath.Base(file.Name())
		if len(name) <= len(configFileUnencryptedSuffix) || !strings.HasSuffix(name, configFileUnencryptedSuffix) {
			continue
		}
		if !file.Type().IsRegular() {
			continue
		}
		info, err := file.Info()
		if err != nil {
			continue
		}
		if info.Mode().Perm()&0o444 == 0 {
			continue
		}

		var bytes []byte
		var config *Config
		var newPath string
		// We don't use os.ReadFile, because we actually want RDWR, so that we can take advantage
		// of Windows file locking for ensuring the file is finished being written.
		f, err := os.OpenFile(path, os.O_RDWR, 0)
		if err != nil {
			if errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
				if ignoreSharingViolations {
					continue
				} else if sharingBase > 0 {
					if lastMigrationTimer != nil {
						lastMigrationTimer.Stop()
					}
					lastMigrationTimer = time.AfterFunc(time.Second/time.Duration(sharingBase*sharingBase), func() { migrateUnencryptedConfigs(sharingBase-1, migrated) })
					ignoreSharingViolations = true
					continue
				}
			}
			goto error
		}
		bytes, err = io.ReadAll(f)
		f.Close()
		if err != nil {
			goto error
		}
		config, err = FromWgQuickWithUnknownEncoding(string(bytes), strings.TrimSuffix(name, configFileUnencryptedSuffix))
		if err != nil {
			goto error
		}
		err = config.Save(false)
		if err != nil {
			goto error
		}
		err = os.Remove(path)
		if err != nil {
			goto error
		}
		newPath, err = config.Path()
		if err != nil {
			goto error
		}
		migrated(config.Name, path, newPath)
		continue
	error:
		log.Printf("Unable to ingest and encrypt %#q: %v", path, err)
	}
}