// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // +build windows package service import ( "bytes" "crypto/sha256" "encoding/base64" "strconv" "syscall" "time" "unicode/utf16" "unsafe" "github.com/pkg/errors" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/winlogbeat/sys" "github.com/elastic/gosigar" ) // Windows API calls //sys _OpenSCManager(machineName *uint16, databaseName *uint16, desiredAcces ServiceSCMAccessRight) (handle ServiceDatabaseHandle, err error) = advapi32.OpenSCManagerW //sys _EnumServicesStatusEx(handle ServiceDatabaseHandle, infoLevel ServiceInfoLevel, serviceType ServiceType, serviceState ServiceEnumState, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uintptr, groupName *uintptr) (err error) [failretval==0] = advapi32.EnumServicesStatusExW //sys _OpenService(handle ServiceDatabaseHandle, serviceName *uint16, desiredAccess ServiceAccessRight) (serviceHandle ServiceHandle, err error) = advapi32.OpenServiceW //sys _QueryServiceConfig(serviceHandle ServiceHandle, serviceConfig *byte, bufSize uint32, bytesNeeded *uint32) (err error) [failretval==0] = advapi32.QueryServiceConfigW //sys _QueryServiceConfig2(serviceHandle ServiceHandle, infoLevel ServiceConfigInformation, configBuffer *byte, bufSize uint32, bytesNeeded *uint32) (err error) [failretval==0] = advapi32.QueryServiceConfig2W //sys _CloseServiceHandle(handle uintptr) (err error) = advapi32.CloseServiceHandle var ( sizeofEnumServiceStatusProcess = (int)(unsafe.Sizeof(EnumServiceStatusProcess{})) ) type ServiceDatabaseHandle uintptr type ServiceHandle uintptr type ProcessHandle uintptr type ServiceConfigInformation uint32 const ( ServiceConfigDelayedAutoStartInfo ServiceConfigInformation = 3 ServiceConfigDescription ServiceConfigInformation = 1 ServiceConfigFailureActions ServiceConfigInformation = 2 ServiceConfigFailureActionsFlag ServiceConfigInformation = 4 ServiceConfigPreferredNode ServiceConfigInformation = 9 ServiceConfigPreshutdownInfo ServiceConfigInformation = 7 ServiceConfigRequiredPrivilegesInfo ServiceConfigInformation = 6 ServiceConfigServiceSidInfo ServiceConfigInformation = 5 ServiceConfigTriggerInfo ServiceConfigInformation = 8 ServiceConfigLaunchProtected ServiceConfigInformation = 12 ) type serviceDelayedAutoStartInfo struct { delayedAutoStart bool } type serviceTriggerInfo struct { cTriggers uint32 pTriggers uintptr pReserved uintptr } var serviceStates = map[ServiceState]string{ ServiceContinuePending: "Continuing", ServicePausePending: "Pausing", ServicePaused: "Paused", ServiceRunning: "Running", ServiceStartPending: "Starting", ServiceStopPending: "Stopping", ServiceStopped: "Stopped", } const ( StartTypeBoot ServiceStartType = iota StartTypeSystem StartTypeAutomatic StartTypeManual StartTypeDisabled StartTypeAutomaticDelayed StartTypeAutomaticTriggered StartTypeAutomaticDelayedTriggered StartTypeManualTriggered ) var serviceStartTypes = map[ServiceStartType]string{ StartTypeBoot: "Boot", StartTypeSystem: "System", StartTypeAutomatic: "Automatic", StartTypeManual: "Manual", StartTypeDisabled: "Disabled", StartTypeAutomaticDelayed: "Automatic (Delayed)", StartTypeAutomaticTriggered: "Automatic (Triggered)", StartTypeAutomaticDelayedTriggered: "Automatic (Delayed, Triggered)", StartTypeManualTriggered: "Manual (Triggered)", } func (startType ServiceStartType) String() string { return serviceStartTypes[startType] } func (state ServiceState) String() string { if val, ok := serviceStates[state]; ok { return val } return "" } // errorNames is mapping of errno values to names. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681383(v=vs.85).aspx var errorNames = map[uint32]string{ 1077: "ERROR_SERVICE_NEVER_STARTED", } type ServiceStatus struct { DisplayName string ServiceName string CurrentState string StartType ServiceStartType PID uint32 // ID of the associated process. Uptime time.Duration ExitCode uint32 // Exit code for stopped services. } type ServiceReader struct { handle ServiceDatabaseHandle state ServiceEnumState guid string // Host's MachineGuid value (a unique ID for the host). ids map[string]string // Cache of service IDs. protectedServices map[string]struct{} } var InvalidServiceDatabaseHandle = ^ServiceDatabaseHandle(0) var InvalidServiceHandle = ^ServiceHandle(0) func OpenSCManager(machineName string, databaseName string, desiredAccess ServiceSCMAccessRight) (ServiceDatabaseHandle, error) { var machineNamePtr *uint16 if machineName != "" { var err error machineNamePtr, err = syscall.UTF16PtrFromString(machineName) if err != nil { return InvalidServiceDatabaseHandle, err } } var databaseNamePtr *uint16 if databaseName != "" { var err error databaseNamePtr, err = syscall.UTF16PtrFromString(databaseName) if err != nil { return InvalidServiceDatabaseHandle, err } } handle, err := _OpenSCManager(machineNamePtr, databaseNamePtr, desiredAccess) if err != nil { return InvalidServiceDatabaseHandle, ServiceErrno(err.(syscall.Errno)) } return handle, nil } func OpenService(handle ServiceDatabaseHandle, serviceName string, desiredAccess ServiceAccessRight) (ServiceHandle, error) { var serviceNamePtr *uint16 if serviceName != "" { var err error serviceNamePtr, err = syscall.UTF16PtrFromString(serviceName) if err != nil { return InvalidServiceHandle, err } } serviceHandle, err := _OpenService(handle, serviceNamePtr, desiredAccess) if err != nil { return InvalidServiceHandle, ServiceErrno(err.(syscall.Errno)) } return serviceHandle, nil } func QueryServiceConfig2(serviceHandle ServiceHandle, infoLevel ServiceConfigInformation) ([]byte, error) { var buffer []byte for { var bytesNeeded uint32 var bufPtr *byte if len(buffer) > 0 { bufPtr = &buffer[0] } if err := _QueryServiceConfig2(serviceHandle, infoLevel, bufPtr, uint32(len(buffer)), &bytesNeeded); err != nil { if ServiceErrno(err.(syscall.Errno)) == SERVICE_ERROR_INSUFFICIENT_BUFFER { // Increase buffer size and retry. buffer = make([]byte, len(buffer)+int(bytesNeeded)) continue } return nil, err } break } return buffer, nil } func getServiceStates(handle ServiceDatabaseHandle, state ServiceEnumState, protectedServices map[string]struct{}) ([]ServiceStatus, error) { var servicesReturned uint32 var servicesBuffer []byte for { var bytesNeeded uint32 var buf *byte if len(servicesBuffer) > 0 { buf = &servicesBuffer[0] } if err := _EnumServicesStatusEx(handle, ScEnumProcessInfo, ServiceWin32, state, buf, uint32(len(servicesBuffer)), &bytesNeeded, &servicesReturned, nil, nil); err != nil { if ServiceErrno(err.(syscall.Errno)) == SERVICE_ERROR_MORE_DATA { // Increase buffer size and retry. servicesBuffer = make([]byte, len(servicesBuffer)+int(bytesNeeded)) continue } return nil, ServiceErrno(err.(syscall.Errno)) } break } // Windows appears to tack on a single byte null terminator to the UTF-16 // strings, but we are expecting either no null terminator or \u0000 (an // even number of bytes). if len(servicesBuffer)%2 != 0 && servicesBuffer[len(servicesBuffer)-1] == 0 { servicesBuffer = servicesBuffer[:len(servicesBuffer)-1] } var services []ServiceStatus for i := 0; i < int(servicesReturned); i++ { serviceTemp := (*EnumServiceStatusProcess)(unsafe.Pointer(&servicesBuffer[i*sizeofEnumServiceStatusProcess])) service, err := getServiceInformation(serviceTemp, servicesBuffer, handle, protectedServices) if err != nil { return nil, err } services = append(services, service) } return services, nil } func getServiceInformation(rawService *EnumServiceStatusProcess, servicesBuffer []byte, handle ServiceDatabaseHandle, protectedServices map[string]struct{}) (ServiceStatus, error) { service := ServiceStatus{ PID: rawService.ServiceStatusProcess.DwProcessId, } // Read null-terminated UTF16 strings from the buffer. serviceNameOffset := uintptr(unsafe.Pointer(rawService.LpServiceName)) - (uintptr)(unsafe.Pointer(&servicesBuffer[0])) displayNameOffset := uintptr(unsafe.Pointer(rawService.LpDisplayName)) - (uintptr)(unsafe.Pointer(&servicesBuffer[0])) strBuf := new(bytes.Buffer) if err := sys.UTF16ToUTF8Bytes(servicesBuffer[displayNameOffset:], strBuf); err != nil { return service, err } service.DisplayName = strBuf.String() strBuf.Reset() if err := sys.UTF16ToUTF8Bytes(servicesBuffer[serviceNameOffset:], strBuf); err != nil { return service, err } service.ServiceName = strBuf.String() var state string if stat, ok := serviceStates[ServiceState(rawService.ServiceStatusProcess.DwCurrentState)]; ok { state = stat } else { state = "Can not define State" } service.CurrentState = state // Exit code. service.ExitCode = rawService.ServiceStatusProcess.DwWin32ExitCode if service.ExitCode == uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) { service.ExitCode = rawService.ServiceStatusProcess.DwServiceSpecificExitCode } serviceHandle, err := OpenService(handle, service.ServiceName, ServiceQueryConfig) if err != nil { return service, err } defer CloseServiceHandle(serviceHandle) // Get detailed information if err := getAdditionalServiceInfo(serviceHandle, &service); err != nil { return service, err } // Get optional information if err := getOptionalServiceInfo(serviceHandle, &service); err != nil { return service, err } //Get uptime for service if ServiceState(rawService.ServiceStatusProcess.DwCurrentState) != ServiceStopped { processUpTime, err := getServiceUptime(rawService.ServiceStatusProcess.DwProcessId) if err != nil { if _, ok := protectedServices[service.ServiceName]; errors.Cause(err) == syscall.ERROR_ACCESS_DENIED && !ok { protectedServices[service.ServiceName] = struct{}{} logp.Warn("Uptime for service %v is not available because of insufficient rights", service.ServiceName) } else { return service, err } } service.Uptime = processUpTime / time.Millisecond } return service, nil } // getServiceUptime returns the uptime for process func getServiceUptime(processID uint32) (time.Duration, error) { var processCreationTime gosigar.ProcTime err := processCreationTime.Get(int(processID)) if err != nil { return time.Duration(processCreationTime.StartTime), err } uptime := time.Since(time.Unix(0, int64(processCreationTime.StartTime)*int64(time.Millisecond))) return uptime, nil } func getAdditionalServiceInfo(serviceHandle ServiceHandle, service *ServiceStatus) error { var buffer []byte for { var bytesNeeded uint32 var bufPtr *byte if len(buffer) > 0 { bufPtr = &buffer[0] } if err := _QueryServiceConfig(serviceHandle, bufPtr, uint32(len(buffer)), &bytesNeeded); err != nil { if ServiceErrno(err.(syscall.Errno)) == SERVICE_ERROR_INSUFFICIENT_BUFFER { // Increase buffer size and retry. buffer = make([]byte, len(buffer)+int(bytesNeeded)) continue } return ServiceErrno(err.(syscall.Errno)) } serviceQueryConfig := (*QueryServiceConfig)(unsafe.Pointer(&buffer[0])) service.StartType = ServiceStartType(serviceQueryConfig.DwStartType) break } return nil } func getOptionalServiceInfo(serviceHandle ServiceHandle, service *ServiceStatus) error { // Get information if the service is started delayed or triggered. Only valid for automatic or manual services. So filter them first. if service.StartType == StartTypeAutomatic || service.StartType == StartTypeManual { var delayedInfo *serviceDelayedAutoStartInfo if service.StartType == StartTypeAutomatic { delayedInfoBuffer, err := QueryServiceConfig2(serviceHandle, ServiceConfigDelayedAutoStartInfo) if err != nil { return err } delayedInfo = (*serviceDelayedAutoStartInfo)(unsafe.Pointer(&delayedInfoBuffer[0])) } // Get information if the service is triggered. triggeredInfoBuffer, err := QueryServiceConfig2(serviceHandle, ServiceConfigTriggerInfo) if err != nil { return err } triggeredInfo := (*serviceTriggerInfo)(unsafe.Pointer(&triggeredInfoBuffer[0])) if service.StartType == StartTypeAutomatic { if triggeredInfo.cTriggers > 0 && delayedInfo.delayedAutoStart { service.StartType = StartTypeAutomaticDelayedTriggered } else if triggeredInfo.cTriggers > 0 { service.StartType = StartTypeAutomaticTriggered } else if delayedInfo.delayedAutoStart { service.StartType = StartTypeAutomaticDelayed } return nil } if service.StartType == StartTypeManual && triggeredInfo.cTriggers > 0 { service.StartType = StartTypeManualTriggered } } return nil } func (reader *ServiceReader) Close() error { return CloseServiceDatabaseHandle(reader.handle) } func CloseServiceDatabaseHandle(handle ServiceDatabaseHandle) error { if err := _CloseServiceHandle(uintptr(handle)); err != nil { return ServiceErrno(err.(syscall.Errno)) } return nil } func CloseServiceHandle(handle ServiceHandle) error { if err := _CloseServiceHandle(uintptr(handle)); err != nil { return ServiceErrno(err.(syscall.Errno)) } return nil } func NewServiceReader() (*ServiceReader, error) { hndl, err := OpenSCManager("", "", ScManagerEnumerateService|ScManagerConnect) if err != nil { return nil, errors.Wrap(err, "initialization failed") } guid, err := getMachineGUID() if err != nil { return nil, err } r := &ServiceReader{ handle: hndl, state: ServiceStateAll, guid: guid, ids: map[string]string{}, protectedServices: map[string]struct{}{}, } return r, nil } func (reader *ServiceReader) Read() ([]common.MapStr, error) { services, err := getServiceStates(reader.handle, reader.state, reader.protectedServices) if err != nil { return nil, err } result := make([]common.MapStr, 0, len(services)) for _, service := range services { ev := common.MapStr{ "id": reader.getServiceID(service.ServiceName), "display_name": service.DisplayName, "name": service.ServiceName, "state": service.CurrentState, "start_type": service.StartType.String(), } if service.CurrentState == "Stopped" { ev.Put("exit_code", getErrorCode(service.ExitCode)) } if service.PID > 0 { ev.Put("pid", service.PID) } if service.Uptime > 0 { if _, err = ev.Put("uptime.ms", service.Uptime); err != nil { return nil, err } } result = append(result, ev) } return result, nil } // getServiceID returns a unique ID for the service that is derived from the // machine's GUID and the service's name. func (reader *ServiceReader) getServiceID(name string) string { // hash returns a base64 encoded sha256 hash that is truncated to 10 chars. hash := func(v string) string { sum := sha256.Sum256([]byte(v)) base64Hash := base64.RawURLEncoding.EncodeToString(sum[:]) return base64Hash[:10] } id, found := reader.ids[name] if !found { id = hash(reader.guid + name) reader.ids[name] = id } return id } func (e ServiceErrno) Error() string { // If the value is not one of the known Service errors then assume its a // general windows error. if _, found := serviceErrors[e]; !found { return syscall.Errno(e).Error() } // Use FormatMessage to convert the service errno to a string. var flags uint32 = syscall.FORMAT_MESSAGE_FROM_SYSTEM | syscall.FORMAT_MESSAGE_ARGUMENT_ARRAY | syscall.FORMAT_MESSAGE_IGNORE_INSERTS b := make([]uint16, 300) n, err := windows.FormatMessage(flags, modadvapi32.Handle(), uint32(e), 0, b, nil) if err != nil { return "service error #" + strconv.Itoa(int(e)) } // Trim terminating \r and \n for ; n > 0 && (b[n-1] == '\n' || b[n-1] == '\r'); n-- { } return string(utf16.Decode(b[:n])) } // getMachineGUID returns the machine's GUID value which is unique to a Windows // installation. func getMachineGUID() (string, error) { const key = registry.LOCAL_MACHINE const path = `SOFTWARE\Microsoft\Cryptography` const name = "MachineGuid" k, err := registry.OpenKey(key, path, registry.READ|registry.WOW64_64KEY) if err != nil { return "", errors.Wrapf(err, `failed to open HKLM\%v`, path) } guid, _, err := k.GetStringValue(name) if err != nil { return "", errors.Wrapf(err, `failed to get value of HKLM\%v\%v`, path, name) } return guid, nil } func getErrorCode(errno uint32) string { name, found := errorNames[errno] if found { return name } return strconv.Itoa(int(errno)) }