From 840f33de326233d5fee1144334db41bf5c82a8fa Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Mon, 25 Feb 2019 18:45:32 +0100 Subject: conf: introduce configuration management --- conf/store.go | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 conf/store.go (limited to 'conf/store.go') diff --git a/conf/store.go b/conf/store.go new file mode 100644 index 00000000..7c110865 --- /dev/null +++ b/conf/store.go @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package conf + +import ( + "errors" + "golang.zx2c4.com/wireguard/windows/conf/dpapi" + "io/ioutil" + "os" + "path/filepath" + "strings" +) + +const configFileSuffix = ".conf.dpapi" +const configFileUnencryptedSuffix = ".conf" + +func ListConfigNames() ([]string, error) { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return nil, err + } + files, err := ioutil.ReadDir(configFileDir) + if err != nil { + return nil, err + } + configs := make([]string, len(files)) + i := 0 + for _, file := range files { + name := filepath.Base(file.Name()) + if len(name) <= len(configFileSuffix) || !strings.HasSuffix(name, configFileSuffix) { + continue + } + if !file.Mode().IsRegular() || file.Mode().Perm()&0444 == 0 { + continue + } + configs[i] = strings.TrimSuffix(name, configFileSuffix) + i++ + } + return configs[:i], nil +} + +func MigrateUnencryptedConfigs() (int, []error) { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return 0, []error{err} + } + files, err := ioutil.ReadDir(configFileDir) + if err != nil { + return 0, []error{err} + } + errs := make([]error, len(files)) + i := 0 + e := 0 + 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.Mode().IsRegular() || file.Mode().Perm()&0444 == 0 { + continue + } + + // We don't use ioutil's 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 { + errs[e] = err + e++ + continue + } + bytes, err := ioutil.ReadAll(f) + f.Close() + if err != nil { + errs[e] = err + e++ + continue + } + _, err = FromWgQuick(string(bytes), "input") + if err != nil { + errs[e] = err + e++ + continue + } + + bytes, err = dpapi.Encrypt(bytes, strings.TrimSuffix(name, configFileUnencryptedSuffix)) + if err != nil { + errs[e] = err + e++ + continue + } + dstFile := strings.TrimSuffix(path, configFileUnencryptedSuffix) + configFileSuffix + if _, err = os.Stat(dstFile); err != nil && !os.IsNotExist(err) { + errs[e] = errors.New("Unable to migrate to " + dstFile + " as it already exists") + e++ + continue + } + err = ioutil.WriteFile(dstFile, bytes, 0600) + if err != nil { + errs[e] = err + e++ + continue + } + err = os.Remove(path) + if err != nil && os.Remove(dstFile) == nil { + errs[e] = err + e++ + continue + } + i++ + } + return i, errs[:e] +} + +func LoadFromName(name string) (*Config, error) { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return nil, err + } + return LoadFromPath(filepath.Join(configFileDir, name+configFileSuffix)) +} + +func LoadFromPath(path string) (*Config, error) { + name, err := NameFromPath(path) + if err != nil { + return nil, err + } + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + if strings.HasSuffix(path, configFileSuffix) { + bytes, err = dpapi.Decrypt(bytes, name) + if err != nil { + return nil, err + } + } + return FromWgQuick(string(bytes), name) +} + +func NameFromPath(path string) (string, error) { + name := filepath.Base(path) + if !((len(name) > len(configFileSuffix) && strings.HasSuffix(name, configFileSuffix)) || + (len(name) > len(configFileUnencryptedSuffix) && strings.HasSuffix(name, configFileUnencryptedSuffix))) { + return "", errors.New("Path must end in either " + configFileSuffix + " or " + configFileUnencryptedSuffix) + } + if strings.HasSuffix(path, configFileSuffix) { + name = strings.TrimSuffix(name, configFileSuffix) + } else { + name = strings.TrimSuffix(name, configFileUnencryptedSuffix) + } + return name, nil +} + +func (config *Config) Save() error { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return err + } + filename := filepath.Join(configFileDir, config.Name+configFileSuffix) + bytes := []byte(config.ToWgQuick()) + bytes, err = dpapi.Encrypt(bytes, config.Name) + if err != nil { + return err + } + err = ioutil.WriteFile(filename+".tmp", bytes, 0600) + if err != nil { + return err + } + err = os.Rename(filename+".tmp", filename) + if err != nil { + os.Remove(filename + ".tmp") + return err + } + return nil +} + +func (config *Config) Path() (string, error) { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return "", err + } + return filepath.Join(configFileDir, config.Name+configFileSuffix), nil +} + +func DeleteName(name string) error { + configFileDir, err := resolveConfigFileDir() + if err != nil { + return err + } + return os.Remove(filepath.Join(configFileDir, name+configFileSuffix)) +} + +func (config *Config) Delete() error { + return DeleteName(config.Name) +} -- cgit v1.2.3-59-g8ed1b