diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 87e0d8baa..9b99a11e9 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -78,7 +78,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+ - W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled + W 💣 github.com/dblohm7/wingoes/com from tailscale.com/cmd/tailscaled+ + W 💣 github.com/dblohm7/wingoes/com/automation from tailscale.com/util/osdiag/internal/wsc W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+ github.com/fxamacker/cbor/v2 from tailscale.com/tka @@ -335,6 +336,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/multierr from tailscale.com/control/controlclient+ tailscale.com/util/must from tailscale.com/logpolicy 💣 tailscale.com/util/osdiag from tailscale.com/cmd/tailscaled+ + W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+ W tailscale.com/util/pidowner from tailscale.com/ipn/ipnauth tailscale.com/util/racebuild from tailscale.com/logpolicy diff --git a/util/osdiag/internal/wsc/wsc_windows.go b/util/osdiag/internal/wsc/wsc_windows.go new file mode 100644 index 000000000..6fb6e5400 --- /dev/null +++ b/util/osdiag/internal/wsc/wsc_windows.go @@ -0,0 +1,313 @@ +// Copyright (c) Tailscale Inc & AUTHORS +// SPDX-License-Identifier: BSD-3-Clause + +// Code generated by 'go generate'; DO NOT EDIT. + +package wsc + +import ( + "runtime" + "syscall" + "unsafe" + + "github.com/dblohm7/wingoes" + "github.com/dblohm7/wingoes/com" + "github.com/dblohm7/wingoes/com/automation" +) + +var ( + CLSID_WSCProductList = &com.CLSID{0x17072F7B, 0x9ABE, 0x4A74, [8]byte{0xA2, 0x61, 0x1E, 0xB7, 0x6B, 0x55, 0x10, 0x7A}} +) + +var ( + IID_IWSCProductList = &com.IID{0x722A338C, 0x6E8E, 0x4E72, [8]byte{0xAC, 0x27, 0x14, 0x17, 0xFB, 0x0C, 0x81, 0xC2}} + IID_IWscProduct = &com.IID{0x8C38232E, 0x3A45, 0x4A27, [8]byte{0x92, 0xB0, 0x1A, 0x16, 0xA9, 0x75, 0xF6, 0x69}} +) + +type WSC_SECURITY_PRODUCT_STATE int32 + +const ( + WSC_SECURITY_PRODUCT_STATE_ON = WSC_SECURITY_PRODUCT_STATE(0) + WSC_SECURITY_PRODUCT_STATE_OFF = WSC_SECURITY_PRODUCT_STATE(1) + WSC_SECURITY_PRODUCT_STATE_SNOOZED = WSC_SECURITY_PRODUCT_STATE(2) + WSC_SECURITY_PRODUCT_STATE_EXPIRED = WSC_SECURITY_PRODUCT_STATE(3) +) + +type WSC_SECURITY_SIGNATURE_STATUS int32 + +const ( + WSC_SECURITY_PRODUCT_OUT_OF_DATE = WSC_SECURITY_SIGNATURE_STATUS(0) + WSC_SECURITY_PRODUCT_UP_TO_DATE = WSC_SECURITY_SIGNATURE_STATUS(1) +) + +type WSC_SECURITY_PROVIDER int32 + +const ( + WSC_SECURITY_PROVIDER_FIREWALL = WSC_SECURITY_PROVIDER(1) + WSC_SECURITY_PROVIDER_AUTOUPDATE_SETTINGS = WSC_SECURITY_PROVIDER(2) + WSC_SECURITY_PROVIDER_ANTIVIRUS = WSC_SECURITY_PROVIDER(4) + WSC_SECURITY_PROVIDER_ANTISPYWARE = WSC_SECURITY_PROVIDER(8) + WSC_SECURITY_PROVIDER_INTERNET_SETTINGS = WSC_SECURITY_PROVIDER(16) + WSC_SECURITY_PROVIDER_USER_ACCOUNT_CONTROL = WSC_SECURITY_PROVIDER(32) + WSC_SECURITY_PROVIDER_SERVICE = WSC_SECURITY_PROVIDER(64) + WSC_SECURITY_PROVIDER_NONE = WSC_SECURITY_PROVIDER(0) + WSC_SECURITY_PROVIDER_ALL = WSC_SECURITY_PROVIDER(127) +) + +type SECURITY_PRODUCT_TYPE int32 + +const ( + SECURITY_PRODUCT_TYPE_ANTIVIRUS = SECURITY_PRODUCT_TYPE(0) + SECURITY_PRODUCT_TYPE_FIREWALL = SECURITY_PRODUCT_TYPE(1) + SECURITY_PRODUCT_TYPE_ANTISPYWARE = SECURITY_PRODUCT_TYPE(2) +) + +type IWscProductABI struct { + com.IUnknownABI // Technically IDispatch, but we're bypassing all of that atm +} + +func (abi *IWscProductABI) GetProductName() (pVal string, err error) { + var t0 automation.BSTR + + method := unsafe.Slice(abi.Vtbl, 14)[7] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + pVal = t0.String() + t0.Close() + return +} + +func (abi *IWscProductABI) GetProductState() (val WSC_SECURITY_PRODUCT_STATE, err error) { + method := unsafe.Slice(abi.Vtbl, 14)[8] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&val))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + } + return +} + +func (abi *IWscProductABI) GetSignatureStatus() (val WSC_SECURITY_SIGNATURE_STATUS, err error) { + method := unsafe.Slice(abi.Vtbl, 14)[9] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&val))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + } + return +} + +func (abi *IWscProductABI) GetRemediationPath() (pVal string, err error) { + var t0 automation.BSTR + + method := unsafe.Slice(abi.Vtbl, 14)[10] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + pVal = t0.String() + t0.Close() + return +} + +func (abi *IWscProductABI) GetProductStateTimestamp() (pVal string, err error) { + var t0 automation.BSTR + + method := unsafe.Slice(abi.Vtbl, 14)[11] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + pVal = t0.String() + t0.Close() + return +} + +func (abi *IWscProductABI) GetProductGuid() (pVal string, err error) { + var t0 automation.BSTR + + method := unsafe.Slice(abi.Vtbl, 14)[12] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + pVal = t0.String() + t0.Close() + return +} + +func (abi *IWscProductABI) GetProductIsDefault() (pVal bool, err error) { + var t0 int32 + + method := unsafe.Slice(abi.Vtbl, 14)[13] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + pVal = t0 != 0 + return +} + +type WscProduct struct { + com.GenericObject[IWscProductABI] +} + +func (o WscProduct) GetProductName() (pVal string, err error) { + p := *(o.Pp) + return p.GetProductName() +} + +func (o WscProduct) GetProductState() (val WSC_SECURITY_PRODUCT_STATE, err error) { + p := *(o.Pp) + return p.GetProductState() +} + +func (o WscProduct) GetSignatureStatus() (val WSC_SECURITY_SIGNATURE_STATUS, err error) { + p := *(o.Pp) + return p.GetSignatureStatus() +} + +func (o WscProduct) GetRemediationPath() (pVal string, err error) { + p := *(o.Pp) + return p.GetRemediationPath() +} + +func (o WscProduct) GetProductStateTimestamp() (pVal string, err error) { + p := *(o.Pp) + return p.GetProductStateTimestamp() +} + +func (o WscProduct) GetProductGuid() (pVal string, err error) { + p := *(o.Pp) + return p.GetProductGuid() +} + +func (o WscProduct) GetProductIsDefault() (pVal bool, err error) { + p := *(o.Pp) + return p.GetProductIsDefault() +} + +func (o WscProduct) IID() *com.IID { + return IID_IWscProduct +} + +func (o WscProduct) Make(r com.ABIReceiver) any { + if r == nil { + return WscProduct{} + } + + runtime.SetFinalizer(r, com.ReleaseABI) + + pp := (**IWscProductABI)(unsafe.Pointer(r)) + return WscProduct{com.GenericObject[IWscProductABI]{Pp: pp}} +} + +func (o WscProduct) MakeFromKnownABI(r **IWscProductABI) WscProduct { + if r == nil { + return WscProduct{} + } + + runtime.SetFinalizer(r, func(r **IWscProductABI) { (*r).Release() }) + return WscProduct{com.GenericObject[IWscProductABI]{Pp: r}} +} + +func (o WscProduct) UnsafeUnwrap() *IWscProductABI { + return *(o.Pp) +} + +type IWSCProductListABI struct { + com.IUnknownABI // Technically IDispatch, but we're bypassing all of that atm +} + +func (abi *IWSCProductListABI) Initialize(provider WSC_SECURITY_PROVIDER) (err error) { + method := unsafe.Slice(abi.Vtbl, 10)[7] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(provider)) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + } + return +} + +func (abi *IWSCProductListABI) GetCount() (val int32, err error) { + method := unsafe.Slice(abi.Vtbl, 10)[8] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(unsafe.Pointer(&val))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + } + return +} + +func (abi *IWSCProductListABI) GetItem(index uint32) (val WscProduct, err error) { + var t0 *IWscProductABI + + method := unsafe.Slice(abi.Vtbl, 10)[9] + hr, _, _ := syscall.SyscallN(method, uintptr(unsafe.Pointer(abi)), uintptr(index), uintptr(unsafe.Pointer(&t0))) + if e := wingoes.ErrorFromHRESULT(wingoes.HRESULT(hr)); !e.IsOK() { + err = e + if e.Failed() { + return + } + } + + var r0 WscProduct + val = r0.MakeFromKnownABI(&t0) + return +} + +type WSCProductList struct { + com.GenericObject[IWSCProductListABI] +} + +func (o WSCProductList) Initialize(provider WSC_SECURITY_PROVIDER) (err error) { + p := *(o.Pp) + return p.Initialize(provider) +} + +func (o WSCProductList) GetCount() (val int32, err error) { + p := *(o.Pp) + return p.GetCount() +} + +func (o WSCProductList) GetItem(index uint32) (val WscProduct, err error) { + p := *(o.Pp) + return p.GetItem(index) +} + +func (o WSCProductList) IID() *com.IID { + return IID_IWSCProductList +} + +func (o WSCProductList) Make(r com.ABIReceiver) any { + if r == nil { + return WSCProductList{} + } + + runtime.SetFinalizer(r, com.ReleaseABI) + + pp := (**IWSCProductListABI)(unsafe.Pointer(r)) + return WSCProductList{com.GenericObject[IWSCProductListABI]{Pp: pp}} +} + +func (o WSCProductList) UnsafeUnwrap() *IWSCProductListABI { + return *(o.Pp) +} diff --git a/util/osdiag/osdiag_windows.go b/util/osdiag/osdiag_windows.go index 12ea366b1..89ba49216 100644 --- a/util/osdiag/osdiag_windows.go +++ b/util/osdiag/osdiag_windows.go @@ -14,10 +14,12 @@ import ( "unicode/utf16" "unsafe" + "github.com/dblohm7/wingoes/com" "github.com/dblohm7/wingoes/pe" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" "tailscale.com/types/logger" + "tailscale.com/util/osdiag/internal/wsc" "tailscale.com/util/winutil" "tailscale.com/util/winutil/authenticode" ) @@ -44,6 +46,7 @@ func logSupportInfo(logf logger.Logf, reason LogSupportInfoReason) { const ( supportInfoKeyModules = "modules" supportInfoKeyRegistry = "registry" + supportInfoKeySecurity = "securitySoftware" supportInfoKeyWinsockLSP = "winsockLSP" ) @@ -65,6 +68,8 @@ func getSupportInfo(w io.Writer, reason LogSupportInfoReason) error { output[supportInfoKeyModules] = err } + output[supportInfoKeySecurity] = getSecurityInfo() + lspInfo, err := getWinsockLSPInfo() if err == nil { output[supportInfoKeyWinsockLSP] = lspInfo @@ -482,3 +487,105 @@ func enumWinsockProtocols() ([]wsaProtocolInfo, error) { return buf, nil } + +type providerKey struct { + provType wsc.WSC_SECURITY_PROVIDER + provKey string +} + +var providerKeys = []providerKey{ + providerKey{ + wsc.WSC_SECURITY_PROVIDER_ANTIVIRUS, + "av", + }, + providerKey{ + wsc.WSC_SECURITY_PROVIDER_ANTISPYWARE, + "antispy", + }, + providerKey{ + wsc.WSC_SECURITY_PROVIDER_FIREWALL, + "firewall", + }, +} + +const ( + maxProvCount = 100 +) + +type secProductInfo struct { + Name string `json:"name,omitempty"` + NameErr error `json:"nameErr,omitempty"` + State string `json:"state,omitempty"` + StateErr error `json:"stateErr,omitempty"` +} + +func getSecurityInfo() map[string]any { + result := make(map[string]any) + + for _, prov := range providerKeys { + // Note that we need to obtain a new product list for each provider type; + // the docs clearly state that we cannot reuse objects. + productList, err := com.CreateInstance[wsc.WSCProductList](wsc.CLSID_WSCProductList) + if err != nil { + result[prov.provKey] = err + continue + } + + err = productList.Initialize(prov.provType) + if err != nil { + result[prov.provKey] = err + continue + } + + n, err := productList.GetCount() + if err != nil { + result[prov.provKey] = err + continue + } + if n == 0 { + continue + } + + n = min(n, maxProvCount) + values := make([]any, 0, n) + + for i := int32(0); i < n; i++ { + product, err := productList.GetItem(uint32(i)) + if err != nil { + values = append(values, err) + continue + } + + var value secProductInfo + + value.Name, err = product.GetProductName() + if err != nil { + value.NameErr = err + } + + state, err := product.GetProductState() + if err == nil { + switch state { + case wsc.WSC_SECURITY_PRODUCT_STATE_ON: + value.State = "on" + case wsc.WSC_SECURITY_PRODUCT_STATE_OFF: + value.State = "off" + case wsc.WSC_SECURITY_PRODUCT_STATE_SNOOZED: + value.State = "snoozed" + case wsc.WSC_SECURITY_PRODUCT_STATE_EXPIRED: + value.State = "expired" + default: + value.State = fmt.Sprintf("", state) + } + } else { + value.StateErr = err + } + + values = append(values, value) + } + + result[prov.provKey] = values + } + + return result +}