diff --git a/eventlog/install.go b/eventlog/install.go index e2912bb..79ccd5a 100644 --- a/eventlog/install.go +++ b/eventlog/install.go @@ -24,9 +24,10 @@ const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` // Install modifies PC registry to allow logging with event source src. // It adds all required keys/values to event log key. Install uses msgFile -// as event message file. Use bitwise of log.Error, log.Warning and log.Info -// to specify events supported. -func Install(src, msgFile string, eventsSupported uint32) error { +// as event message file, creating key as REG_EXPAND_SZ, if useExpandKey +// is true, otherwise as REG_SZ. Use bitwise of log.Error, log.Warning +// and log.Info to specify events supported. +func Install(src, msgFile string, useExpandKey bool, eventsSupported uint32) error { appkey, err := registry.OpenKey(syscall.HKEY_LOCAL_MACHINE, addKeyName) if err != nil { return err @@ -44,7 +45,11 @@ func Install(src, msgFile string, eventsSupported uint32) error { if err != nil { return err } - err = sk.SetString("EventMessageFile", msgFile) + if useExpandKey { + err = sk.SetStringExpand("EventMessageFile", msgFile) + } else { + err = sk.SetString("EventMessageFile", msgFile) + } if err != nil { return err } @@ -58,7 +63,7 @@ func Install(src, msgFile string, eventsSupported uint32) error { // InstallAsEventCreate is the same as Install, but uses // %SystemRoot%\System32\EventCreate.exe as event message file. func InstallAsEventCreate(src string, eventsSupported uint32) error { - return Install(src, "%SystemRoot%\\System32\\EventCreate.exe", eventsSupported) + return Install(src, "%SystemRoot%\\System32\\EventCreate.exe", true, eventsSupported) } // Remove deletes all registry elements installed by correspondent Install. diff --git a/mgr/config.go b/mgr/config.go index 96b010c..777861c 100644 --- a/mgr/config.go +++ b/mgr/config.go @@ -9,6 +9,7 @@ package mgr import ( "github.com/multiplay/winsvc/winapi" "syscall" + "unicode/utf16" "unsafe" ) @@ -35,7 +36,7 @@ type Config struct { BinaryPathName string LoadOrderGroup string TagId uint32 - Dependencies string + Dependencies []string ServiceStartName string // name of the account under which the service should run DisplayName string Password string @@ -49,6 +50,24 @@ func toString(p *uint16) string { return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:]) } +func toStringSlice(ps *uint16) []string { + if ps == nil { + return nil + } + r := make([]string, 0) + for from, i, p := 0, 0, (*[1 << 24]uint16)(unsafe.Pointer(ps)); true; i++ { + if p[i] == 0 { + // empty string marks the end + if i <= from { + break + } + r = append(r, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return r +} + func (s *Service) Config() (Config, error) { b := make([]byte, 1024) p := (*winapi.QUERY_SERVICE_CONFIG)(unsafe.Pointer(&b[0])) @@ -87,7 +106,7 @@ func (s *Service) Config() (Config, error) { BinaryPathName: toString(p.BinaryPathName), LoadOrderGroup: toString(p.LoadOrderGroup), TagId: p.TagId, - Dependencies: toString(p.Dependencies), + Dependencies: toStringSlice(p.Dependencies), ServiceStartName: toString(p.ServiceStartName), DisplayName: toString(p.DisplayName), Description: toString(p2.Description), @@ -107,7 +126,7 @@ func updateDescription(handle syscall.Handle, desc string) error { func (s *Service) UpdateConfig(c Config) error { err := winapi.ChangeServiceConfig(s.Handle, c.ServiceType, c.StartType, c.ErrorControl, toPtr(c.BinaryPathName), toPtr(c.LoadOrderGroup), - nil, toPtr(c.Dependencies), toPtr(c.ServiceStartName), + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password), toPtr(c.DisplayName)) if err != nil { return err diff --git a/mgr/mgr.go b/mgr/mgr.go index fa27c63..4d8c1f4 100644 --- a/mgr/mgr.go +++ b/mgr/mgr.go @@ -14,6 +14,7 @@ package mgr import ( "github.com/multiplay/winsvc/winapi" "syscall" + "unicode/utf16" ) // Mgr is used to manage Windows service. @@ -52,6 +53,25 @@ func toPtr(s string) *uint16 { return syscall.StringToUTF16Ptr(s) } +// toStringBlock terminates strings in ss with 0, and then +// concatenates them together. It also adds extra 0 at the end. +func toStringBlock(ss []string) *uint16 { + if len(ss) == 0 { + return nil + } + t := "" + for _, s := range ss { + if s != "" { + t += s + "\x00" + } + } + if t == "" { + return nil + } + t += "\x00" + return &utf16.Encode([]rune(t))[0] +} + // CreateService installs new service name on the system. // The service will be executed by running exepath binary, // while service settings are specified in config c. @@ -66,7 +86,7 @@ func (m *Mgr) CreateService(name, exepath string, c Config) (*Service, error) { h, err := winapi.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName), winapi.SERVICE_ALL_ACCESS, winapi.SERVICE_WIN32_OWN_PROCESS, c.StartType, c.ErrorControl, toPtr(exepath), toPtr(c.LoadOrderGroup), - nil, toPtr(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password)) + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password)) if err != nil { return nil, err } diff --git a/mgr/mgr_test.go b/mgr/mgr_test.go index b3ad57c..9145e6b 100644 --- a/mgr/mgr_test.go +++ b/mgr/mgr_test.go @@ -10,6 +10,8 @@ import ( "github.com/multiplay/winsvc/mgr" "os" "path/filepath" + "sort" + "strings" "testing" ) @@ -19,7 +21,7 @@ func TestOpenLanManServer(t *testing.T) { t.Fatalf("SCM connection failed: %s", err) } defer m.Disconnect() - s, err := m.OpenService("lanmanserver") + s, err := m.OpenService("LanmanServer") if err != nil { t.Fatalf("OpenService(lanmanserver) failed: %s", err) } @@ -44,6 +46,18 @@ func install(t *testing.T, m *mgr.Mgr, name, exepath string, c mgr.Config) { defer s.Close() } +func depString(d []string) string { + if len(d) == 0 { + return "" + } + for i := range d { + d[i] = strings.ToLower(d[i]) + } + ss := sort.StringSlice(d) + ss.Sort() + return strings.Join([]string(ss), " ") +} + func testConfig(t *testing.T, s *mgr.Service, should mgr.Config) mgr.Config { is, err := s.Config() if err != nil { @@ -58,6 +72,9 @@ func testConfig(t *testing.T, s *mgr.Service, should mgr.Config) mgr.Config { if should.Description != is.Description { t.Fatalf("config mismatch: Description is %q, but should have %q", is.Description, should.Description) } + if depString(should.Dependencies) != depString(is.Dependencies) { + t.Fatalf("config mismatch: Dependencies is %v, but should have %v", is.Dependencies, should.Dependencies) + } return is } @@ -78,9 +95,10 @@ func TestMyService(t *testing.T) { defer m.Disconnect() c := mgr.Config{ - StartType: mgr.StartDisabled, - DisplayName: "my service", - Description: "my service is just a test", + StartType: mgr.StartDisabled, + DisplayName: "my service", + Description: "my service is just a test", + Dependencies: []string{"LanmanServer", "W32Time"}, } exename := os.Args[0] diff --git a/registry/registry.go b/registry/registry.go index 46aa0d0..df68086 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -57,10 +57,18 @@ func (k *Key) SetUInt32(name string, value uint32) error { (*byte)(unsafe.Pointer(&value)), uint32(unsafe.Sizeof(value))) } -func (k *Key) SetString(name string, value string) error { +func (k *Key) setString(name string, value string, valtype uint32) error { buf := syscall.StringToUTF16(value) return winapi.RegSetValueEx( k.Handle, syscall.StringToUTF16Ptr(name), - 0, syscall.REG_SZ, + 0, valtype, (*byte)(unsafe.Pointer(&buf[0])), uint32(len(buf)*2)) } + +func (k *Key) SetString(name string, value string) error { + return k.setString(name, value, syscall.REG_SZ) +} + +func (k *Key) SetStringExpand(name string, value string) error { + return k.setString(name, value, syscall.REG_EXPAND_SZ) +} diff --git a/svc/security.go b/svc/security.go index e7b1103..47ed35a 100644 --- a/svc/security.go +++ b/svc/security.go @@ -79,7 +79,7 @@ func IsAnInteractiveSession() (bool, error) { return false, err } p := unsafe.Pointer(&gs.Groups[0]) - groups := (*[256]syscall.SIDAndAttributes)(p)[:gs.GroupCount] + groups := (*[2 << 20]syscall.SIDAndAttributes)(p)[:gs.GroupCount] for _, g := range groups { if winapi.EqualSid(g.Sid, interSid) { return true, nil