diff --git a/Advapi32.cs b/Advapi32.cs index a9a4fc7..caaa662 100755 --- a/Advapi32.cs +++ b/Advapi32.cs @@ -81,6 +81,162 @@ namespace winsw } } + static class LogonAsAService + { + public static void AddLogonAsAServiceRight(string Username) + { + //Needs to be at least XP or 2003 server + //https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832%28v=vs.85%29.aspx + System.OperatingSystem osInfo = System.Environment.OSVersion; + + if (osInfo.Version.Major >= 5 && osInfo.Version.Minor >= 1) + { + var newuser = GetLocalAccountIfLocalAccount(Username); + //Trace.WriteLine("Username for Logon as A Service: " + newuser); + long rightexitcode = SetRight(newuser, PrivlegeRights.SeServiceLogonRight.ToString()); + if (rightexitcode != 0) + { + Console.WriteLine("Failed to set logon as a service right"); + Environment.Exit(1); + } + } + else + { + Console.WriteLine("Cannot set Logon as a Service right. Unsupported operating system detected"); + } + } + + private static string GetDomain(string s) + { + int stop = s.IndexOf("\\"); + if (stop >= 0) + return s.Substring(0, stop); + else + return null; + } + private static string GetLogin(string s) + { + int stop = s.IndexOf("\\"); + return (stop > -1) ? s.Substring(stop + 1, s.Length - stop - 1) : s; + } + private static string GetLocalAccountIfLocalAccount(string username) + { + var machinename = Environment.MachineName; + string domain = GetDomain(username); + if (domain == null || domain.ToLower() == machinename.ToLower() || domain == ".") + { + return GetLogin(username); + } + return username; + } + + /// Adds a privilege to an account + /// Name of an account - "domain\account" or only "account" + /// Name ofthe privilege + /// The windows error code returned by LsaAddAccountRights + private static long SetRight(String accountName, String privilegeName) + { + long winErrorCode = 0; //contains the last error + + //pointer an size for the SID + IntPtr sid = IntPtr.Zero; + int sidSize = 0; + //StringBuilder and size for the domain name + StringBuilder domainName = new StringBuilder(); + int nameSize = 0; + //account-type variable for lookup + int accountType = 0; + + //get required buffer size + Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType); + + //allocate buffers + domainName = new StringBuilder(nameSize); + sid = Marshal.AllocHGlobal(sidSize); + + //lookup the SID for the account + bool result = Advapi32.LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, + ref accountType); + + //say what you're doing + //Console.WriteLine("LookupAccountName result = " + result); + //Console.WriteLine("IsValidSid: " + Advapi32.IsValidSid(sid)); + //Console.WriteLine("LookupAccountName domainName: " + domainName.ToString()); + + if (!result) + { + winErrorCode = Kernel32.GetLastError(); + Console.WriteLine("LookupAccountName failed: " + winErrorCode); + } + else + { + + //initialize an empty unicode-string + LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING(); + //combine all policies + int access = (int)( + LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN | + LSA_AccessPolicy.POLICY_CREATE_ACCOUNT | + LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE | + LSA_AccessPolicy.POLICY_CREATE_SECRET | + LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION | + LSA_AccessPolicy.POLICY_LOOKUP_NAMES | + LSA_AccessPolicy.POLICY_NOTIFICATION | + LSA_AccessPolicy.POLICY_SERVER_ADMIN | + LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS | + LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS | + LSA_AccessPolicy.POLICY_TRUST_ADMIN | + LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION | + LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION + ); + //initialize a pointer for the policy handle + IntPtr policyHandle = IntPtr.Zero; + + //these attributes are not used, but LsaOpenPolicy wants them to exists + LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES(); + ObjectAttributes.Length = 0; + ObjectAttributes.RootDirectory = IntPtr.Zero; + ObjectAttributes.Attributes = 0; + ObjectAttributes.SecurityDescriptor = IntPtr.Zero; + ObjectAttributes.SecurityQualityOfService = IntPtr.Zero; + + //get a policy handle + uint resultPolicy = Advapi32.LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle); + winErrorCode = Advapi32.LsaNtStatusToWinError(resultPolicy); + + if (winErrorCode != 0) + { + Console.WriteLine("OpenPolicy failed: " + winErrorCode); + } + else + { + //Now that we have the SID an the policy, + //we can add rights to the account. + + //initialize an unicode-string for the privilege name + LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1]; + userRights[0] = new LSA_UNICODE_STRING(); + userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName); + userRights[0].Length = (UInt16)(privilegeName.Length * UnicodeEncoding.CharSize); + userRights[0].MaximumLength = (UInt16)((privilegeName.Length + 1) * UnicodeEncoding.CharSize); + + //add the right to the account + uint res = Advapi32.LsaAddAccountRights(policyHandle, sid, userRights, 1); + winErrorCode = Advapi32.LsaNtStatusToWinError(res); + if (winErrorCode != 0) + { + Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode); + } + + Advapi32.LsaClose(policyHandle); + } + Advapi32.FreeSid(sid); + } + + return winErrorCode; + } + } + /// /// Advapi32.dll wrapper for performing additional service related operations that are not /// available in WMI. @@ -105,10 +261,85 @@ namespace winsw [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseServiceHandle(IntPtr hSCObject); - [DllImport("ADVAPI32.DLL")] + [DllImport("advapi32.DLL")] internal static extern bool SetServiceStatus(IntPtr hServiceStatus, ref SERVICE_STATUS lpServiceStatus); + + [DllImport("advapi32.dll", PreserveSig = true)] + internal static extern UInt32 LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, Int32 DesiredAccess, + out IntPtr PolicyHandle); + + [DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)] + internal static extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING[] UserRights, uint CountOfRights); + + [DllImport("advapi32")] + internal static extern void FreeSid(IntPtr pSid); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)] + internal static extern bool LookupAccountName(string lpSystemName, string lpAccountName, IntPtr psid, ref int cbsid, StringBuilder domainName, + ref int cbdomainLength, ref int use); + + [DllImport("advapi32.dll")] + internal static extern bool IsValidSid(IntPtr pSid); + + [DllImport("advapi32.dll", SetLastError = true)] + internal static extern uint LsaClose(IntPtr ObjectHandle); + + [DllImport("advapi32.dll", SetLastError = false)] + internal static extern uint LsaNtStatusToWinError(uint status); + } + //http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx + internal enum PrivlegeRights + { + SeServiceLogonRight, //Required for an account to log on using the service logon type. + SeRemoteInteractiveLogonRight, //Required for an account to log on remotely using the interactive logon type. + SeNetworkLogonRight, //Required for an account to log on using the network logon type. + SeInteractiveLogonRight, //Required for an account to log on using the interactive logon type. + SeDenyServiceLogonRight, //Explicitly denies an account the right to log on using the service logon type. + SeDenyRemoteInteractiveLogonRight, //Explicitly denies an account the right to log on remotely using the interactive logon type. + SeDenyNetworkLogonRight, //Explicitly denies an account the right to log on using the network logon type. + SeDenyInteractiveLogonRight, //Explicitly denies an account the right to log on using the interactive logon type. + SeDenyBatchLogonRight, //Explicitly denies an account the right to log on using the batch logon type. + SeBatchLogonRight //Required for an account to log on using the batch logon type. + } + + [StructLayout(LayoutKind.Sequential)] + struct LSA_UNICODE_STRING + { + public UInt16 Length; + public UInt16 MaximumLength; + public IntPtr Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + struct LSA_OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public LSA_UNICODE_STRING ObjectName; + public UInt32 Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + // enum all policies + enum LSA_AccessPolicy : long + { + POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L, + POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L, + POLICY_GET_PRIVATE_INFORMATION = 0x00000004L, + POLICY_TRUST_ADMIN = 0x00000008L, + POLICY_CREATE_ACCOUNT = 0x00000010L, + POLICY_CREATE_SECRET = 0x00000020L, + POLICY_CREATE_PRIVILEGE = 0x00000040L, + POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L, + POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L, + POLICY_AUDIT_LOG_ADMIN = 0x00000200L, + POLICY_SERVER_ADMIN = 0x00000400L, + POLICY_LOOKUP_NAMES = 0x00000800L, + POLICY_NOTIFICATION = 0x00001000L + } internal enum SCM_ACCESS : uint { diff --git a/Kernel32.cs b/Kernel32.cs index 56c3e5e..5fad8fa 100755 --- a/Kernel32.cs +++ b/Kernel32.cs @@ -21,6 +21,8 @@ namespace winsw [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); + [DllImport("kernel32.dll")] + internal static extern int GetLastError(); } [StructLayout(LayoutKind.Sequential)] diff --git a/Main.cs b/Main.cs index 1ce231e..e8adf1e 100644 --- a/Main.cs +++ b/Main.cs @@ -549,6 +549,7 @@ namespace winsw if (args[0] == "install") { string username=null, password=null; + bool setallowlogonasaserviceright = false; if (args.Count > 1 && args[1] == "/p") { // we expected username/password on stdin @@ -556,6 +557,14 @@ namespace winsw username = Console.ReadLine(); Console.Write("Password: "); password = ReadPassword(); + Console.WriteLine(); + Console.Write("Set Account rights to allow log on as a service (y/n)?: "); + var keypressed = Console.ReadKey(); + Console.WriteLine(); + if (keypressed.Key == ConsoleKey.Y) + { + setallowlogonasaserviceright = true; + } } else { @@ -563,8 +572,14 @@ namespace winsw { username = d.ServiceAccountUser; password = d.ServiceAccountPassword; + setallowlogonasaserviceright = d.AllowServiceAcountLogonRight; } } + + if (setallowlogonasaserviceright) + { + LogonAsAService.AddLogonAsAServiceRight(username); + } svc.Create ( d.Id, @@ -699,6 +714,5 @@ namespace winsw } } } - } } diff --git a/README.markdown b/README.markdown index 82510d1..ee36f76 100644 --- a/README.markdown +++ b/README.markdown @@ -278,8 +278,11 @@ It is possible to specify the useraccount (and password) that the service will r YOURDOMAIN useraccount Pa55w0rd + true +The is optional. If set to true, will automatically set the "Allow Log On As A Service" right to the listed account. + ### Working directory Some services need to run with a working directory specified. To do this, specify a `` element like this: diff --git a/ServiceDescriptor.cs b/ServiceDescriptor.cs index 51c45bf..37bcd5c 100755 --- a/ServiceDescriptor.cs +++ b/ServiceDescriptor.cs @@ -540,6 +540,14 @@ namespace winsw } + protected string AllowServiceLogon + { + get + { + return GetServiceAccountPart("allowservicelogon"); + } + } + protected string serviceAccountDomain { get{ @@ -573,6 +581,22 @@ namespace winsw return !string.IsNullOrEmpty(serviceAccountDomain) && !string.IsNullOrEmpty(serviceAccountName); } + public bool AllowServiceAcountLogonRight + { + get + { + if (AllowServiceLogon != null) + { + bool parsedvalue = false; + if (Boolean.TryParse(AllowServiceLogon, out parsedvalue)) + { + return parsedvalue; + } + } + return false; + } + } + /// /// Time to wait for the service to gracefully shutdown before we forcibly kill it /// diff --git a/Tests/winswTests/ServiceDescriptorTests.cs b/Tests/winswTests/ServiceDescriptorTests.cs index 0418850..764bdfb 100644 --- a/Tests/winswTests/ServiceDescriptorTests.cs +++ b/Tests/winswTests/ServiceDescriptorTests.cs @@ -17,6 +17,7 @@ namespace winswTests private const string Username = "User"; private const string Password = "Password"; private const string Domain = "Domain"; + private const string AllowServiceAccountLogonRight = "true"; [SetUp] public void SetUp() @@ -32,6 +33,7 @@ namespace winswTests + "" + Domain + "" + "" + Username + "" + "" + Password + "" + + "" + AllowServiceAccountLogonRight + "" + "" + "" + ExpectedWorkingDirectory @@ -48,6 +50,12 @@ namespace winswTests Assert.That(extendedServiceDescriptor.WorkingDirectory, Is.EqualTo(ExpectedWorkingDirectory)); } + [Test] + public void VerifyServiceLogonRight() + { + Assert.That(extendedServiceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(true)); + } + [Test] public void VerifyUsername() { @@ -153,5 +161,33 @@ namespace winswTests Assert.That(logHandler.Period, Is.EqualTo(7)); Assert.That(logHandler.Pattern, Is.EqualTo("log pattern")); } + + [Test] + public void VerifyServiceLogonRightGraceful() + { + const string SeedXml="" + + "" + + "" + Domain + "" + + "" + Username + "" + + "" + Password + "" + + "true1" + + "" + + ""; + var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml); + Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false)); + } + [Test] + public void VerifyServiceLogonRightOmitted() + { + const string SeedXml = "" + + "" + + "" + Domain + "" + + "" + Username + "" + + "" + Password + "" + + "" + + ""; + var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml); + Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false)); + } } }