diff --git a/cmd/cone/login.go b/cmd/cone/login.go index 0d2ed7fb..cf237564 100644 --- a/cmd/cone/login.go +++ b/cmd/cone/login.go @@ -13,27 +13,51 @@ import ( conductoroneapi "github.com/conductorone/conductorone-sdk-go" "github.com/conductorone/cone/pkg/client" + "github.com/conductorone/cone/pkg/managedconfig" ) func loginCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "login ", + Use: "login [tenant-name or tenant-url]", Short: fmt.Sprintf("Authenticate to ConductorOne, creating config.yaml in %s if it doesn't exist.", defaultConfigPath()), - RunE: loginRun, + Long: fmt.Sprintf("Authenticate to ConductorOne, creating config.yaml in %s if it doesn't exist.\n\n"+ + "If a managed device configuration is present, the tenant is discovered from it automatically "+ + "and the tenant argument may be omitted.", defaultConfigPath()), + RunE: loginRun, } cmd.Flags().String("profile", "default", "Config profile to create or update.") return cmd } +// resolveLoginTenant determines the tenant (name or URL) to authenticate +// against. Managed device configuration pushed by an administrator takes +// precedence over an argument supplied on the command line, allowing a bare +// "cone login" to discover its tenant automatically. When no managed +// configuration is present the behavior is unchanged: the tenant must be passed +// as an argument. The returned bool reports whether the tenant was sourced from +// managed configuration. +func resolveLoginTenant(cmd *cobra.Command, args []string) (string, bool, error) { + if serverURL := managedconfig.Read().ControlPlaneURL(); serverURL != "" { + return serverURL, true, nil + } + if err := validateArgLenth(1, args, cmd); err != nil { + return "", false, err + } + return args[0], false, nil +} + func loginRun(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - if err := validateArgLenth(1, args, cmd); err != nil { + tenant, fromManaged, err := resolveLoginTenant(cmd, args) + if err != nil { return err } - tenant := args[0] + if fromManaged { + pterm.Info.Printfln("Using tenant %q from managed device configuration.", tenant) + } spinner, err := pterm.DefaultSpinner.Start("Logging in...") if err != nil { diff --git a/cmd/cone/secret_test.go b/cmd/cone/secret_test.go index 78568552..035c2ced 100644 --- a/cmd/cone/secret_test.go +++ b/cmd/cone/secret_test.go @@ -127,6 +127,7 @@ func TestCreateInputFormat(t *testing.T) { got := createInputFormat(tt.name) if got == nil { t.Fatal("createInputFormat() returned nil") + return } if *got != tt.want { t.Errorf("createInputFormat(%q) = %q, want %q", tt.name, *got, tt.want) @@ -208,6 +209,7 @@ func TestCreateSecretInternalText(t *testing.T) { r := f.internalReq if r == nil { t.Fatal("internal request was not built") + return } if r.SecretType == nil || *r.SecretType != shared.PaperSecretServiceCreateInternalRequestSecretTypeSecretTypeText { t.Errorf("SecretType = %v, want Text", r.SecretType) @@ -246,6 +248,7 @@ func TestCreateSecretInternalFile(t *testing.T) { r := f.internalReq if r == nil { t.Fatal("internal request was not built") + return } if r.SecretType == nil || *r.SecretType != shared.PaperSecretServiceCreateInternalRequestSecretTypeSecretTypeFile { t.Errorf("SecretType = %v, want File", r.SecretType) @@ -283,6 +286,7 @@ func TestCreateSecretExternalText(t *testing.T) { r := f.externalReq if r == nil { t.Fatal("external request was not built") + return } if r.SecretType == nil || *r.SecretType != shared.PaperSecretServiceCreateExternalRequestSecretTypeSecretTypeText { t.Errorf("SecretType = %v, want Text", r.SecretType) @@ -311,6 +315,7 @@ func TestCreateSecretExternalFile(t *testing.T) { r := f.externalReq if r == nil { t.Fatal("external request was not built") + return } if r.SecretType == nil || *r.SecretType != shared.PaperSecretServiceCreateExternalRequestSecretTypeSecretTypeFile { t.Errorf("SecretType = %v, want File", r.SecretType) @@ -347,6 +352,7 @@ func TestCreateExternalInputFormat(t *testing.T) { got := createExternalInputFormat(tt.name) if got == nil { t.Fatal("createExternalInputFormat() returned nil") + return } if *got != tt.want { t.Errorf("createExternalInputFormat(%q) = %q, want %q", tt.name, *got, tt.want) @@ -405,6 +411,7 @@ func TestSetContentInputFormat(t *testing.T) { got := setContentInputFormat(tt.name) if got == nil { t.Fatal("setContentInputFormat() returned nil") + return } if *got != tt.want { t.Errorf("setContentInputFormat(%q) = %q, want %q", tt.name, *got, tt.want) diff --git a/go.mod b/go.mod index 1fa49940..4a56637b 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 github.com/pquerna/xjwt v0.3.0 github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.9.2 // indirect @@ -56,7 +56,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/oauth2 v0.30.0 - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sys v0.33.0 golang.org/x/text v0.26.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/pkg/managedconfig/managedconfig.go b/pkg/managedconfig/managedconfig.go new file mode 100644 index 00000000..10f125b0 --- /dev/null +++ b/pkg/managedconfig/managedconfig.go @@ -0,0 +1,113 @@ +// Package managedconfig reads the ConductorOne managed device configuration: +// administrator-defined policy delivered to a device through an MDM. It lets any +// ConductorOne client auto-discover the tenant it belongs to without per-machine +// manual setup. +// +// The configuration lives in a company-level, read-only managed store addressed +// by the namespace "ai.c1". Reads are best-effort by design: an absent, +// unreadable, or malformed store yields a zero Config. Nothing in this package +// returns an error or panics, so callers can consult it unconditionally at the +// top of their configuration-resolution chain. +package managedconfig + +import ( + "strings" + + "github.com/pelletier/go-toml/v2" +) + +const ( + // Namespace is the managed-config store the client reads from. + Namespace = "ai.c1" + + // KeyTenantDomain is the key holding the full DNS host of the tenant's + // control plane, for example "acme.conductor.one". + KeyTenantDomain = "TenantDomain" +) + +// Config holds the managed device configuration values the client understands. +// Keys the client does not recognize are ignored. +type Config struct { + // TenantDomain is the full DNS host of the tenant's control plane, for + // example "acme.conductor.one". It is empty when unset or invalid. + TenantDomain string +} + +// ControlPlaneURL returns the tenant control-plane URL ("https://" + TenantDomain), +// or an empty string when no valid TenantDomain is configured. +func (c Config) ControlPlaneURL() string { + if c.TenantDomain == "" { + return "" + } + return "https://" + c.TenantDomain +} + +// Read returns the managed device configuration for the current operating +// system. It never returns an error: an absent, unreadable, or malformed store +// yields a zero Config. +func Read() Config { + return configFromMap(readManagedConfig()) +} + +// configFromMap extracts the recognized keys from a raw key/value view of the +// store, ignoring unknown keys and dropping values that do not satisfy the +// contract. +func configFromMap(m map[string]string) Config { + var c Config + if domain := strings.TrimSpace(m[KeyTenantDomain]); isValidTenantDomain(domain) { + c.TenantDomain = domain + } + return c +} + +// isValidTenantDomain reports whether s is a full DNS host suitable for use as a +// control-plane locator: at least three dot-separated labels (for example +// "acme.conductor.one" or "acme.eu.c1.ai") with no scheme, path, port, or +// whitespace. A bare tenant slug is intentionally rejected. +func isValidTenantDomain(s string) bool { + if strings.ContainsAny(s, "/:@ \t\r\n") { + return false + } + labels := strings.Split(s, ".") + if len(labels) < 3 { + return false + } + for _, l := range labels { + if l == "" { + return false + } + } + return true +} + +// parseManagedTOML parses the Linux managed-config TOML into a flat map of +// top-level string values. Nested tables and non-string values are ignored. +// Malformed input yields a nil map. +func parseManagedTOML(data []byte) map[string]string { + raw := map[string]any{} + if err := toml.Unmarshal(data, &raw); err != nil { + return nil + } + return stringValues(raw) +} + +// stringValues returns only the top-level string entries of raw. +func stringValues(raw map[string]any) map[string]string { + out := make(map[string]string, len(raw)) + for k, v := range raw { + if s, ok := v.(string); ok { + out[k] = s + } + } + return out +} + +// parseDefaultsValue converts the raw output of `defaults read ` +// into a single-key map. Empty output yields a nil map. +func parseDefaultsValue(key string, out []byte) map[string]string { + v := strings.TrimSpace(string(out)) + if v == "" { + return nil + } + return map[string]string{key: v} +} diff --git a/pkg/managedconfig/managedconfig_darwin_cgo.go b/pkg/managedconfig/managedconfig_darwin_cgo.go new file mode 100644 index 00000000..fce92967 --- /dev/null +++ b/pkg/managedconfig/managedconfig_darwin_cgo.go @@ -0,0 +1,104 @@ +//go:build darwin && cgo + +package managedconfig + +/* +#cgo LDFLAGS: -framework CoreFoundation +#include +#include +*/ +import "C" + +import "unsafe" + +// readManagedConfig reads the managed device configuration from the macOS +// managed-preferences store using CoreFoundation directly. It calls +// CFPreferencesCopyAppValue, which resolves the managed-preferences layer +// (administrator-pushed policy) for the "ai.c1" application domain, exactly as +// the `defaults read` fallback does — but without shelling out. +// +// A missing key, a value that is not a string, or any lookup failure yields a +// nil map. This function never returns an error and never panics. +func readManagedConfig() map[string]string { + v := copyManagedString(Namespace, KeyTenantDomain) + if v == "" { + return nil + } + return map[string]string{KeyTenantDomain: v} +} + +// copyManagedString reads a single string value from the managed-preferences +// layer for the given application domain, returning "" when the key is absent, +// is not a string, or cannot be read. +// +// Memory ownership follows the CoreFoundation "Create/Copy" rule mirrored from +// crypto/x509/internal/macos: every ref obtained from a Create*/Copy* call is +// owned (+1 retain) and must be released; refs obtained from Get* calls are not +// owned and must not be released. +func copyManagedString(appID, key string) string { + // C.CString allocates C memory that must be freed explicitly. + cKey := C.CString(key) + defer C.free(unsafe.Pointer(cKey)) + cApp := C.CString(appID) + defer C.free(unsafe.Pointer(cApp)) + + // CFStringCreateWithCString returns an owned (+1) CFString, or NULL if the + // bytes are not valid for the encoding. Release both when done. + keyRef := C.CFStringCreateWithCString(C.kCFAllocatorDefault, cKey, C.kCFStringEncodingUTF8) + if keyRef == nil { + return "" + } + defer C.CFRelease(C.CFTypeRef(keyRef)) + + appRef := C.CFStringCreateWithCString(C.kCFAllocatorDefault, cApp, C.kCFStringEncodingUTF8) + if appRef == nil { + return "" + } + defer C.CFRelease(C.CFTypeRef(appRef)) + + // CFPreferencesCopyAppValue returns an owned (+1) value, or NULL when the + // key is absent. Release it whether or not it is the type we want. + val := C.CFPreferencesCopyAppValue(keyRef, appRef) + if val == nil { + return "" + } + defer C.CFRelease(val) + + // The value may be any property-list type. Confirm it is actually a + // CFString before extracting; a non-string value yields "" rather than a + // crash. + if C.CFGetTypeID(val) != C.CFStringGetTypeID() { + return "" + } + + return cfStringToGoString(C.CFStringRef(val)) +} + +// cfStringToGoString extracts a Go string from a CFString without assuming a +// fixed buffer size, mirroring the robust extraction in +// crypto/x509/internal/macos: try the zero-copy fast path first, then fall back +// to CFStringGetCString with a buffer sized by CFStringGetMaximumSizeForEncoding. +// The caller retains ownership of s; this function releases nothing it does not +// create. +func cfStringToGoString(s C.CFStringRef) string { + // Fast path: CFStringGetCStringPtr may return a pointer to the CFString's + // internal UTF-8 storage. It is owned by the CFString (not by us), so it is + // copied by C.GoString and never released. It returns NULL when no such + // direct representation exists, in which case we fall back below. + if p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8); p != nil { + return C.GoString(p) + } + + // Fallback: size a buffer large enough for the worst-case UTF-8 encoding of + // the string's length, plus one for the NUL terminator, then copy into it. + length := C.CFStringGetLength(s) + maxSize := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8) + 1 + buf := (*C.char)(C.malloc(C.size_t(maxSize))) + defer C.free(unsafe.Pointer(buf)) + + // CFStringGetCString returns a Boolean (0 on failure). + if C.CFStringGetCString(s, buf, maxSize, C.kCFStringEncodingUTF8) == 0 { + return "" + } + return C.GoString(buf) +} diff --git a/pkg/managedconfig/managedconfig_darwin_defaults.go b/pkg/managedconfig/managedconfig_darwin_defaults.go new file mode 100644 index 00000000..73dcb6a2 --- /dev/null +++ b/pkg/managedconfig/managedconfig_darwin_defaults.go @@ -0,0 +1,28 @@ +//go:build darwin && !cgo + +package managedconfig + +import "os/exec" + +// defaultsRead reads a single managed-preferences value through the `defaults` +// tool. Reading via the managed-preferences layer (rather than the on-disk +// plist) ensures only administrator-pushed policy is honored. It is a variable +// so tests can stub the lookup. +// +// This CGO-off fallback exists so cone still cross-compiles for darwin from a +// pure-Go toolchain (for example darwin/arm64 built on Linux with +// CGO_ENABLED=0). The darwin && cgo build uses the native CoreFoundation reader +// in managedconfig_darwin_cgo.go instead. +var defaultsRead = func(domain, key string) ([]byte, error) { + return exec.Command("defaults", "read", domain, key).Output() +} + +// readManagedConfig reads the managed device configuration from the macOS +// managed-preferences domain. A missing key or lookup error yields a nil map. +func readManagedConfig() map[string]string { + out, err := defaultsRead(Namespace, KeyTenantDomain) + if err != nil { + return nil + } + return parseDefaultsValue(KeyTenantDomain, out) +} diff --git a/pkg/managedconfig/managedconfig_linux.go b/pkg/managedconfig/managedconfig_linux.go new file mode 100644 index 00000000..48a2ab86 --- /dev/null +++ b/pkg/managedconfig/managedconfig_linux.go @@ -0,0 +1,19 @@ +//go:build linux + +package managedconfig + +import "os" + +// linuxConfigPath is the location of the Linux managed device configuration +// file. It is a variable so tests can point it at a temporary file. +var linuxConfigPath = "/etc/c1/managed.toml" + +// readManagedConfig reads the managed device configuration from the Linux +// managed-config file. A missing or unreadable file yields a nil map. +func readManagedConfig() map[string]string { + data, err := os.ReadFile(linuxConfigPath) + if err != nil { + return nil + } + return parseManagedTOML(data) +} diff --git a/pkg/managedconfig/managedconfig_linux_test.go b/pkg/managedconfig/managedconfig_linux_test.go new file mode 100644 index 00000000..790bcc5d --- /dev/null +++ b/pkg/managedconfig/managedconfig_linux_test.go @@ -0,0 +1,70 @@ +//go:build linux + +package managedconfig + +import ( + "os" + "path/filepath" + "testing" +) + +// withLinuxConfigPath points the package at path for the duration of the test. +func withLinuxConfigPath(t *testing.T, path string) { + t.Helper() + prev := linuxConfigPath + linuxConfigPath = path + t.Cleanup(func() { linuxConfigPath = prev }) +} + +func writeTempConfig(t *testing.T, contents string) string { + t.Helper() + dir := t.TempDir() + path := filepath.Join(dir, "managed.toml") + if err := os.WriteFile(path, []byte(contents), 0600); err != nil { + t.Fatalf("writing temp config: %v", err) + } + return path +} + +func TestReadManagedConfigLinux_Parse(t *testing.T) { + withLinuxConfigPath(t, writeTempConfig(t, "TenantDomain = \"acme.conductor.one\"\n")) + + if got := Read().TenantDomain; got != "acme.conductor.one" { + t.Errorf("TenantDomain = %q, want %q", got, "acme.conductor.one") + } + if got := Read().ControlPlaneURL(); got != "https://acme.conductor.one" { + t.Errorf("ControlPlaneURL() = %q, want %q", got, "https://acme.conductor.one") + } +} + +func TestReadManagedConfigLinux_UnknownKeyIgnored(t *testing.T) { + withLinuxConfigPath(t, writeTempConfig(t, "Unknown = \"junk\"\nTenantDomain = \"acme.eu.c1.ai\"\nExtra = 42\n")) + + if got := Read().TenantDomain; got != "acme.eu.c1.ai" { + t.Errorf("TenantDomain = %q, want %q", got, "acme.eu.c1.ai") + } +} + +func TestReadManagedConfigLinux_Absent(t *testing.T) { + withLinuxConfigPath(t, filepath.Join(t.TempDir(), "does-not-exist.toml")) + + if got := Read(); got != (Config{}) { + t.Errorf("Read() = %+v, want zero Config", got) + } +} + +func TestReadManagedConfigLinux_Malformed(t *testing.T) { + withLinuxConfigPath(t, writeTempConfig(t, "this is not = = valid toml")) + + if got := Read(); got != (Config{}) { + t.Errorf("Read() = %+v, want zero Config", got) + } +} + +func TestReadManagedConfigLinux_BareSlugRejected(t *testing.T) { + withLinuxConfigPath(t, writeTempConfig(t, "TenantDomain = \"acme\"\n")) + + if got := Read().TenantDomain; got != "" { + t.Errorf("TenantDomain = %q, want empty (bare slug should be rejected)", got) + } +} diff --git a/pkg/managedconfig/managedconfig_other.go b/pkg/managedconfig/managedconfig_other.go new file mode 100644 index 00000000..64e9b7b8 --- /dev/null +++ b/pkg/managedconfig/managedconfig_other.go @@ -0,0 +1,9 @@ +//go:build !linux && !darwin && !windows + +package managedconfig + +// readManagedConfig returns no configuration on platforms without a managed +// device configuration store. +func readManagedConfig() map[string]string { + return nil +} diff --git a/pkg/managedconfig/managedconfig_test.go b/pkg/managedconfig/managedconfig_test.go new file mode 100644 index 00000000..725b8e0a --- /dev/null +++ b/pkg/managedconfig/managedconfig_test.go @@ -0,0 +1,97 @@ +package managedconfig + +import "testing" + +func TestConfigControlPlaneURL(t *testing.T) { + tests := []struct { + name string + config Config + want string + }{ + {name: "empty", config: Config{}, want: ""}, + {name: "commercial", config: Config{TenantDomain: "acme.conductor.one"}, want: "https://acme.conductor.one"}, + {name: "eu", config: Config{TenantDomain: "acme.eu.c1.ai"}, want: "https://acme.eu.c1.ai"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.config.ControlPlaneURL(); got != tt.want { + t.Errorf("ControlPlaneURL() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestConfigFromMap(t *testing.T) { + tests := []struct { + name string + in map[string]string + want string + }{ + {name: "nil map", in: nil, want: ""}, + {name: "empty map", in: map[string]string{}, want: ""}, + {name: "valid commercial", in: map[string]string{KeyTenantDomain: "acme.conductor.one"}, want: "acme.conductor.one"}, + {name: "valid eu", in: map[string]string{KeyTenantDomain: "acme.eu.c1.ai"}, want: "acme.eu.c1.ai"}, + {name: "trims whitespace", in: map[string]string{KeyTenantDomain: " acme.conductor.one\n"}, want: "acme.conductor.one"}, + {name: "unknown keys ignored", in: map[string]string{"SomethingElse": "value", KeyTenantDomain: "acme.conductor.one"}, want: "acme.conductor.one"}, + {name: "bare slug rejected", in: map[string]string{KeyTenantDomain: "acme"}, want: ""}, + {name: "two-label rejected", in: map[string]string{KeyTenantDomain: "conductor.one"}, want: ""}, + {name: "scheme rejected", in: map[string]string{KeyTenantDomain: "https://acme.conductor.one"}, want: ""}, + {name: "path rejected", in: map[string]string{KeyTenantDomain: "acme.conductor.one/foo"}, want: ""}, + {name: "empty value", in: map[string]string{KeyTenantDomain: ""}, want: ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := configFromMap(tt.in).TenantDomain; got != tt.want { + t.Errorf("configFromMap(%v).TenantDomain = %q, want %q", tt.in, got, tt.want) + } + }) + } +} + +func TestIsValidTenantDomain(t *testing.T) { + valid := []string{"acme.conductor.one", "acme.eu.c1.ai", "a.b.c"} + for _, s := range valid { + if !isValidTenantDomain(s) { + t.Errorf("isValidTenantDomain(%q) = false, want true", s) + } + } + invalid := []string{"", "acme", "conductor.one", "acme.conductor.one/x", "https://acme.conductor.one", "acme.conductor.one:8080", "acme..one", "acme conductor one"} + for _, s := range invalid { + if isValidTenantDomain(s) { + t.Errorf("isValidTenantDomain(%q) = true, want false", s) + } + } +} + +func TestParseManagedTOML(t *testing.T) { + tests := []struct { + name string + in string + want string // expected TenantDomain after configFromMap; "" means absent/ignored + }{ + {name: "valid", in: "TenantDomain = \"acme.conductor.one\"\n", want: "acme.conductor.one"}, + {name: "unknown keys ignored", in: "TenantDomain = \"acme.conductor.one\"\nUnknown = \"x\"\nCount = 3\n", want: "acme.conductor.one"}, + {name: "absent key", in: "SomethingElse = \"x\"\n", want: ""}, + {name: "empty file", in: "", want: ""}, + {name: "malformed", in: "TenantDomain = = = broken", want: ""}, + {name: "non-string value ignored", in: "TenantDomain = 123\n", want: ""}, + {name: "nested table ignored", in: "[section]\nTenantDomain = \"acme.conductor.one\"\n", want: ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := configFromMap(parseManagedTOML([]byte(tt.in))).TenantDomain + if got != tt.want { + t.Errorf("parseManagedTOML(%q) TenantDomain = %q, want %q", tt.in, got, tt.want) + } + }) + } +} + +func TestParseDefaultsValue(t *testing.T) { + if m := parseDefaultsValue(KeyTenantDomain, []byte("acme.conductor.one\n")); m[KeyTenantDomain] != "acme.conductor.one" { + t.Errorf("parseDefaultsValue got %v", m) + } + if m := parseDefaultsValue(KeyTenantDomain, []byte(" \n")); m != nil { + t.Errorf("parseDefaultsValue(empty) = %v, want nil", m) + } +} diff --git a/pkg/managedconfig/managedconfig_windows.go b/pkg/managedconfig/managedconfig_windows.go new file mode 100644 index 00000000..548702bf --- /dev/null +++ b/pkg/managedconfig/managedconfig_windows.go @@ -0,0 +1,24 @@ +//go:build windows + +package managedconfig + +import "golang.org/x/sys/windows/registry" + +// registryPath is the HKLM policy key holding the managed device configuration. +const registryPath = `SOFTWARE\Policies\ConductorOne\C1` + +// readManagedConfig reads the managed device configuration from the Windows +// registry policy key. A missing key or read error yields a nil map. +func readManagedConfig() map[string]string { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, registryPath, registry.QUERY_VALUE) + if err != nil { + return nil + } + defer func() { _ = k.Close() }() + + val, _, err := k.GetStringValue(KeyTenantDomain) + if err != nil { + return nil + } + return map[string]string{KeyTenantDomain: val} +} diff --git a/vendor/golang.org/x/sys/windows/registry/key.go b/vendor/golang.org/x/sys/windows/registry/key.go new file mode 100644 index 00000000..39aeeb64 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/key.go @@ -0,0 +1,214 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows + +// Package registry provides access to the Windows registry. +// +// Here is a simple example, opening a registry key and reading a string value from it. +// +// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) +// if err != nil { +// log.Fatal(err) +// } +// defer k.Close() +// +// s, _, err := k.GetStringValue("SystemRoot") +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Windows system root is %q\n", s) +package registry + +import ( + "io" + "runtime" + "syscall" + "time" +) + +const ( + // Registry key security and access rights. + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx + // for details. + ALL_ACCESS = 0xf003f + CREATE_LINK = 0x00020 + CREATE_SUB_KEY = 0x00004 + ENUMERATE_SUB_KEYS = 0x00008 + EXECUTE = 0x20019 + NOTIFY = 0x00010 + QUERY_VALUE = 0x00001 + READ = 0x20019 + SET_VALUE = 0x00002 + WOW64_32KEY = 0x00200 + WOW64_64KEY = 0x00100 + WRITE = 0x20006 +) + +// Key is a handle to an open Windows registry key. +// Keys can be obtained by calling OpenKey; there are +// also some predefined root keys such as CURRENT_USER. +// Keys can be used directly in the Windows API. +type Key syscall.Handle + +const ( + // Windows defines some predefined root keys that are always open. + // An application can use these keys as entry points to the registry. + // Normally these keys are used in OpenKey to open new keys, + // but they can also be used anywhere a Key is required. + CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT) + CURRENT_USER = Key(syscall.HKEY_CURRENT_USER) + LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE) + USERS = Key(syscall.HKEY_USERS) + CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG) + PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA) +) + +// Close closes open key k. +func (k Key) Close() error { + return syscall.RegCloseKey(syscall.Handle(k)) +} + +// OpenKey opens a new key with path name relative to key k. +// It accepts any open key, including CURRENT_USER and others, +// and returns the new key and an error. +// The access parameter specifies desired access rights to the +// key to be opened. +func OpenKey(k Key, path string, access uint32) (Key, error) { + p, err := syscall.UTF16PtrFromString(path) + if err != nil { + return 0, err + } + var subkey syscall.Handle + err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey) + if err != nil { + return 0, err + } + return Key(subkey), nil +} + +// OpenRemoteKey opens a predefined registry key on another +// computer pcname. The key to be opened is specified by k, but +// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS. +// If pcname is "", OpenRemoteKey returns local computer key. +func OpenRemoteKey(pcname string, k Key) (Key, error) { + var err error + var p *uint16 + if pcname != "" { + p, err = syscall.UTF16PtrFromString(`\\` + pcname) + if err != nil { + return 0, err + } + } + var remoteKey syscall.Handle + err = regConnectRegistry(p, syscall.Handle(k), &remoteKey) + if err != nil { + return 0, err + } + return Key(remoteKey), nil +} + +// ReadSubKeyNames returns the names of subkeys of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadSubKeyNames(n int) ([]string, error) { + // RegEnumKeyEx must be called repeatedly and to completion. + // During this time, this goroutine cannot migrate away from + // its current thread. See https://golang.org/issue/49320 and + // https://golang.org/issue/49466. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + names := make([]string, 0) + // Registry key size limit is 255 bytes and described there: + // https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx + buf := make([]uint16, 256) //plus extra room for terminating zero byte +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} + +// CreateKey creates a key named path under open key k. +// CreateKey returns the new key and a boolean flag that reports +// whether the key already existed. +// The access parameter specifies the access rights for the key +// to be created. +func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) { + var h syscall.Handle + var d uint32 + var pathPointer *uint16 + pathPointer, err = syscall.UTF16PtrFromString(path) + if err != nil { + return 0, false, err + } + err = regCreateKeyEx(syscall.Handle(k), pathPointer, + 0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d) + if err != nil { + return 0, false, err + } + return Key(h), d == _REG_OPENED_EXISTING_KEY, nil +} + +// DeleteKey deletes the subkey path of key k and its values. +func DeleteKey(k Key, path string) error { + pathPointer, err := syscall.UTF16PtrFromString(path) + if err != nil { + return err + } + return regDeleteKey(syscall.Handle(k), pathPointer) +} + +// A KeyInfo describes the statistics of a key. It is returned by Stat. +type KeyInfo struct { + SubKeyCount uint32 + MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte + ValueCount uint32 + MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte + MaxValueLen uint32 // longest data component among the key's values, in bytes + lastWriteTime syscall.Filetime +} + +// ModTime returns the key's last write time. +func (ki *KeyInfo) ModTime() time.Time { + return time.Unix(0, ki.lastWriteTime.Nanoseconds()) +} + +// Stat retrieves information about the open key k. +func (k Key) Stat() (*KeyInfo, error) { + var ki KeyInfo + err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil, + &ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount, + &ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime) + if err != nil { + return nil, err + } + return &ki, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/mksyscall.go b/vendor/golang.org/x/sys/windows/registry/mksyscall.go new file mode 100644 index 00000000..bbf86ccf --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/mksyscall.go @@ -0,0 +1,9 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build generate + +package registry + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall.go diff --git a/vendor/golang.org/x/sys/windows/registry/syscall.go b/vendor/golang.org/x/sys/windows/registry/syscall.go new file mode 100644 index 00000000..f533091c --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/syscall.go @@ -0,0 +1,32 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows + +package registry + +import "syscall" + +const ( + _REG_OPTION_NON_VOLATILE = 0 + + _REG_CREATED_NEW_KEY = 1 + _REG_OPENED_EXISTING_KEY = 2 + + _ERROR_NO_MORE_ITEMS syscall.Errno = 259 +) + +func LoadRegLoadMUIString() error { + return procRegLoadMUIStringW.Find() +} + +//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW +//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW +//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW +//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW +//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW +//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW +//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW + +//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW diff --git a/vendor/golang.org/x/sys/windows/registry/value.go b/vendor/golang.org/x/sys/windows/registry/value.go new file mode 100644 index 00000000..a1bcbb23 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/value.go @@ -0,0 +1,390 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows + +package registry + +import ( + "errors" + "io" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + // Registry value types. + NONE = 0 + SZ = 1 + EXPAND_SZ = 2 + BINARY = 3 + DWORD = 4 + DWORD_BIG_ENDIAN = 5 + LINK = 6 + MULTI_SZ = 7 + RESOURCE_LIST = 8 + FULL_RESOURCE_DESCRIPTOR = 9 + RESOURCE_REQUIREMENTS_LIST = 10 + QWORD = 11 +) + +var ( + // ErrShortBuffer is returned when the buffer was too short for the operation. + ErrShortBuffer = syscall.ERROR_MORE_DATA + + // ErrNotExist is returned when a registry key or value does not exist. + ErrNotExist = syscall.ERROR_FILE_NOT_FOUND + + // ErrUnexpectedType is returned by Get*Value when the value's type was unexpected. + ErrUnexpectedType = errors.New("unexpected key value type") +) + +// GetValue retrieves the type and data for the specified value associated +// with an open key k. It fills up buffer buf and returns the retrieved +// byte count n. If buf is too small to fit the stored value it returns +// ErrShortBuffer error along with the required buffer size n. +// If no buffer is provided, it returns true and actual buffer size n. +// If no buffer is provided, GetValue returns the value's type only. +// If the value does not exist, the error returned is ErrNotExist. +// +// GetValue is a low level function. If value's type is known, use the appropriate +// Get*Value function instead. +func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return 0, 0, err + } + var pbuf *byte + if len(buf) > 0 { + pbuf = (*byte)(unsafe.Pointer(&buf[0])) + } + l := uint32(len(buf)) + err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l) + if err != nil { + return int(l), valtype, err + } + return int(l), valtype, nil +} + +func (k Key) getValue(name string, buf []byte) (data []byte, valtype uint32, err error) { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return nil, 0, err + } + var t uint32 + n := uint32(len(buf)) + for { + err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n) + if err == nil { + return buf[:n], t, nil + } + if err != syscall.ERROR_MORE_DATA { + return nil, 0, err + } + if n <= uint32(len(buf)) { + return nil, 0, err + } + buf = make([]byte, n) + } +} + +// GetStringValue retrieves the string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringValue returns ErrNotExist. +// If value is not SZ or EXPAND_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return "", typ, err2 + } + switch typ { + case SZ, EXPAND_SZ: + default: + return "", typ, ErrUnexpectedType + } + if len(data) == 0 { + return "", typ, nil + } + u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2] + return syscall.UTF16ToString(u), typ, nil +} + +// GetMUIStringValue retrieves the localized string value for +// the specified value name associated with an open key k. +// If the value name doesn't exist or the localized string value +// can't be resolved, GetMUIStringValue returns ErrNotExist. +// GetMUIStringValue panics if the system doesn't support +// regLoadMUIString; use LoadRegLoadMUIString to check if +// regLoadMUIString is supported before calling this function. +func (k Key) GetMUIStringValue(name string) (string, error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return "", err + } + + buf := make([]uint16, 1024) + var buflen uint32 + var pdir *uint16 + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path + + // Try to resolve the string value using the system directory as + // a DLL search path; this assumes the string value is of the form + // @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320. + + // This approach works with tzres.dll but may have to be revised + // in the future to allow callers to provide custom search paths. + + var s string + s, err = ExpandString("%SystemRoot%\\system32\\") + if err != nil { + return "", err + } + pdir, err = syscall.UTF16PtrFromString(s) + if err != nil { + return "", err + } + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed + if buflen <= uint32(len(buf)) { + break // Buffer not growing, assume race; break + } + buf = make([]uint16, buflen) + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + if err != nil { + return "", err + } + + return syscall.UTF16ToString(buf), nil +} + +// ExpandString expands environment-variable strings and replaces +// them with the values defined for the current user. +// Use ExpandString to expand EXPAND_SZ strings. +func ExpandString(value string) (string, error) { + if value == "" { + return "", nil + } + p, err := syscall.UTF16PtrFromString(value) + if err != nil { + return "", err + } + r := make([]uint16, 100) + for { + n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r))) + if err != nil { + return "", err + } + if n <= uint32(len(r)) { + return syscall.UTF16ToString(r[:n]), nil + } + r = make([]uint16, n) + } +} + +// GetStringsValue retrieves the []string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringsValue returns ErrNotExist. +// If value is not MULTI_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != MULTI_SZ { + return nil, typ, ErrUnexpectedType + } + if len(data) == 0 { + return nil, typ, nil + } + p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[: len(data)/2 : len(data)/2] + if len(p) == 0 { + return nil, typ, nil + } + if p[len(p)-1] == 0 { + p = p[:len(p)-1] // remove terminating null + } + val = make([]string, 0, 5) + from := 0 + for i, c := range p { + if c == 0 { + val = append(val, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return val, typ, nil +} + +// GetIntegerValue retrieves the integer value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetIntegerValue returns ErrNotExist. +// If value is not DWORD or QWORD, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 8)) + if err2 != nil { + return 0, typ, err2 + } + switch typ { + case DWORD: + if len(data) != 4 { + return 0, typ, errors.New("DWORD value is not 4 bytes long") + } + var val32 uint32 + copy((*[4]byte)(unsafe.Pointer(&val32))[:], data) + return uint64(val32), DWORD, nil + case QWORD: + if len(data) != 8 { + return 0, typ, errors.New("QWORD value is not 8 bytes long") + } + copy((*[8]byte)(unsafe.Pointer(&val))[:], data) + return val, QWORD, nil + default: + return 0, typ, ErrUnexpectedType + } +} + +// GetBinaryValue retrieves the binary value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetBinaryValue returns ErrNotExist. +// If value is not BINARY, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != BINARY { + return nil, typ, ErrUnexpectedType + } + return data, typ, nil +} + +func (k Key) setValue(name string, valtype uint32, data []byte) error { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return err + } + if len(data) == 0 { + return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0) + } + return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data))) +} + +// SetDWordValue sets the data and type of a name value +// under key k to value and DWORD. +func (k Key) SetDWordValue(name string, value uint32) error { + return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:]) +} + +// SetQWordValue sets the data and type of a name value +// under key k to value and QWORD. +func (k Key) SetQWordValue(name string, value uint64) error { + return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:]) +} + +func (k Key) setStringValue(name string, valtype uint32, value string) error { + v, err := syscall.UTF16FromString(value) + if err != nil { + return err + } + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2] + return k.setValue(name, valtype, buf) +} + +// SetStringValue sets the data and type of a name value +// under key k to value and SZ. The value must not contain a zero byte. +func (k Key) SetStringValue(name, value string) error { + return k.setStringValue(name, SZ, value) +} + +// SetExpandStringValue sets the data and type of a name value +// under key k to value and EXPAND_SZ. The value must not contain a zero byte. +func (k Key) SetExpandStringValue(name, value string) error { + return k.setStringValue(name, EXPAND_SZ, value) +} + +// SetStringsValue sets the data and type of a name value +// under key k to value and MULTI_SZ. The value strings +// must not contain a zero byte. +func (k Key) SetStringsValue(name string, value []string) error { + ss := "" + for _, s := range value { + for i := 0; i < len(s); i++ { + if s[i] == 0 { + return errors.New("string cannot have 0 inside") + } + } + ss += s + "\x00" + } + v := utf16.Encode([]rune(ss + "\x00")) + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[: len(v)*2 : len(v)*2] + return k.setValue(name, MULTI_SZ, buf) +} + +// SetBinaryValue sets the data and type of a name value +// under key k to value and BINARY. +func (k Key) SetBinaryValue(name string, value []byte) error { + return k.setValue(name, BINARY, value) +} + +// DeleteValue removes a named value from the key k. +func (k Key) DeleteValue(name string) error { + namePointer, err := syscall.UTF16PtrFromString(name) + if err != nil { + return err + } + return regDeleteValue(syscall.Handle(k), namePointer) +} + +// ReadValueNames returns the value names of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadValueNames(n int) ([]string, error) { + ki, err := k.Stat() + if err != nil { + return nil, err + } + names := make([]string, 0, ki.ValueCount) + buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go new file mode 100644 index 00000000..fc1835d8 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go @@ -0,0 +1,117 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package registry + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW") + procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW") + procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW") + procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") + procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") + procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW") + procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") + procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") +) + +func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize)) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) + n = uint32(r0) + if n == 0 { + err = errnoErr(e1) + } + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index c4d65458..9671af4a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -193,6 +193,7 @@ golang.org/x/sys/cpu golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows +golang.org/x/sys/windows/registry # golang.org/x/term v0.32.0 ## explicit; go 1.23.0 golang.org/x/term