Browse Source

Adding logon as a service right to user account specified in configuration

Nicholas Carpenter 11 years ago
parent
commit
4ce20f5533
6 changed files with 312 additions and 2 deletions
  1. 232 1
      Advapi32.cs
  2. 2 0
      Kernel32.cs
  3. 15 1
      Main.cs
  4. 3 0
      README.markdown
  5. 24 0
      ServiceDescriptor.cs
  6. 36 0
      Tests/winswTests/ServiceDescriptorTests.cs

+ 232 - 1
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;
+        }
+
+        /// <summary>Adds a privilege to an account</summary>
+        /// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
+        /// <param name="privilegeName">Name ofthe privilege</param>
+        /// <returns>The windows error code returned by LsaAddAccountRights</returns>
+        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;
+        }
+    }
+
     /// <summary>
     /// 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
     {

+ 2 - 0
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)]

+ 15 - 1
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
                 }
             }
         }
-
     }
 }

+ 3 - 0
README.markdown

@@ -278,8 +278,11 @@ It is possible to specify the useraccount (and password) that the service will r
        <domain>YOURDOMAIN</domain>
        <user>useraccount</user>
        <password>Pa55w0rd</password>
+       <allowservicelogon>true</allowservicelogon>
     </serviceaccount>
 
+The <allowservicelogon> 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 `<workingdirectory>` element like this:
 

+ 24 - 0
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;
+            }
+        }
+
          /// <summary>
          /// Time to wait for the service to gracefully shutdown before we forcibly kill it
          /// </summary>

+ 36 - 0
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>" + Domain + "</domain>"
                                    +   "<user>" + Username + "</user>"
                                    +   "<password>" + Password + "</password>"
+                                   + "<allowservicelogon>" + AllowServiceAccountLogonRight + "</allowservicelogon>"
                                    + "</serviceaccount>"
                                    + "<workingdirectory>"
                                    + 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="<service>"
+                                   + "<serviceaccount>"
+                                   +   "<domain>" + Domain + "</domain>"
+                                   +   "<user>" + Username + "</user>"
+                                   +   "<password>" + Password + "</password>"
+                                   + "<allowservicelogon>true1</allowservicelogon>"
+                                   +  "</serviceaccount>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
+            Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
+        }
+        [Test]
+        public void VerifyServiceLogonRightOmitted()
+        {
+            const string SeedXml = "<service>"
+                                   + "<serviceaccount>"
+                                   + "<domain>" + Domain + "</domain>"
+                                   + "<user>" + Username + "</user>"
+                                   + "<password>" + Password + "</password>"
+                                   + "</serviceaccount>"
+                                   + "</service>";
+            var serviceDescriptor = ServiceDescriptor.FromXML(SeedXml);
+            Assert.That(serviceDescriptor.AllowServiceAcountLogonRight, Is.EqualTo(false));
+        }
     }
 }