From afe25d1d825160236ed374b54f78f0fd113717ff Mon Sep 17 00:00:00 2001 From: NextTurn <45985406+NextTurn@users.noreply.github.com> Date: Wed, 28 Nov 2018 00:00:00 +0800 Subject: [PATCH] Allow prompting for credentials --- src/Core/ServiceWrapper/Program.cs | 131 +++++++++++++++++++- src/Core/WinSWCore/Native/CredentialApis.cs | 52 ++++++++ src/Core/WinSWCore/Native/Errors.cs | 1 + src/Core/WinSWCore/Native/Libraries.cs | 1 + src/Core/WinSWCore/ServiceDescriptor.cs | 4 +- 5 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 src/Core/WinSWCore/Native/CredentialApis.cs diff --git a/src/Core/ServiceWrapper/Program.cs b/src/Core/ServiceWrapper/Program.cs index 0b58156..1dee4a8 100644 --- a/src/Core/ServiceWrapper/Program.cs +++ b/src/Core/ServiceWrapper/Program.cs @@ -207,13 +207,24 @@ namespace winsw allowServiceLogonRight = true; } } - else + else if (descriptor.HasServiceAccount()) { - if (descriptor.HasServiceAccount()) + username = descriptor.ServiceAccountUserName; + password = descriptor.ServiceAccountPassword; + allowServiceLogonRight = descriptor.AllowServiceAcountLogonRight; + + if (username is null || password is null) { - username = descriptor.ServiceAccountUserName; - password = descriptor.ServiceAccountPassword; - allowServiceLogonRight = descriptor.AllowServiceAcountLogonRight; + switch (descriptor.ServiceAccountPrompt) + { + case "dialog": + PropmtForCredentialsDialog(); + break; + + case "console": + PromptForCredentialsConsole(); + break; + } } } @@ -257,6 +268,116 @@ namespace winsw { EventLog.CreateEventSource(eventLogSource, "Application"); } + + void PropmtForCredentialsDialog() + { + username ??= string.Empty; + password ??= string.Empty; + + int inBufferSize = 0; + _ = CredentialApis.CredPackAuthenticationBuffer( + 0, + username, + password, + IntPtr.Zero, + ref inBufferSize); + + IntPtr inBuffer = Marshal.AllocCoTaskMem(inBufferSize); + try + { + if (!CredentialApis.CredPackAuthenticationBuffer( + 0, + username, + password, + inBuffer, + ref inBufferSize)) + { + Throw.Win32Exception("Failed to pack auth buffer."); + } + + CredentialApis.CREDUI_INFO info = new CredentialApis.CREDUI_INFO + { + Size = Marshal.SizeOf(typeof(CredentialApis.CREDUI_INFO)), + CaptionText = "Windows Service Wrapper", // TODO + MessageText = "service account credentials", // TODO + }; + uint authPackage = 0; + bool save = false; + int error = CredentialApis.CredUIPromptForWindowsCredentials( + info, + 0, + ref authPackage, + inBuffer, + inBufferSize, + out IntPtr outBuffer, + out uint outBufferSize, + ref save, + CredentialApis.CREDUIWIN_GENERIC); + + if (error != Errors.ERROR_SUCCESS) + { + throw new Win32Exception(error); + } + + try + { + int userNameLength = 0; + int passwordLength = 0; + _ = CredentialApis.CredUnPackAuthenticationBuffer( + 0, + outBuffer, + outBufferSize, + null, + ref userNameLength, + default, + default, + null, + ref passwordLength); + + username = userNameLength == 0 ? null : new string('\0', userNameLength - 1); + password = passwordLength == 0 ? null : new string('\0', passwordLength - 1); + + if (!CredentialApis.CredUnPackAuthenticationBuffer( + 0, + outBuffer, + outBufferSize, + username, + ref userNameLength, + default, + default, + password, + ref passwordLength)) + { + Throw.Win32Exception("Failed to unpack auth buffer."); + } + } + finally + { + Marshal.FreeCoTaskMem(outBuffer); + } + } + finally + { + Marshal.FreeCoTaskMem(inBuffer); + } + } + + void PromptForCredentialsConsole() + { + if (username is null) + { + Console.Write("Username: "); + username = Console.ReadLine(); + } + + if (password is null) + { + Console.Write("Password: "); + password = ReadPassword(); + } + + Console.WriteLine(); + } } void Uninstall() diff --git a/src/Core/WinSWCore/Native/CredentialApis.cs b/src/Core/WinSWCore/Native/CredentialApis.cs new file mode 100644 index 0000000..db02e72 --- /dev/null +++ b/src/Core/WinSWCore/Native/CredentialApis.cs @@ -0,0 +1,52 @@ +using System; +using System.Runtime.InteropServices; + +namespace winsw.Native +{ + internal static class CredentialApis + { + internal const uint CREDUIWIN_GENERIC = 0x00000001; + + [DllImport(Libraries.CredUI, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CredPackAuthenticationBufferW")] + internal static extern bool CredPackAuthenticationBuffer( + uint flags, + string userName, + string password, + IntPtr packedCredentials, + ref int packedCredentialsSize); + + [DllImport(Libraries.CredUI, SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "CredUIPromptForWindowsCredentialsW")] + internal static extern int CredUIPromptForWindowsCredentials( + in CREDUI_INFO uiInfo, + uint authError, + ref uint authPackage, + IntPtr inAuthBuffer, + int inAuthBufferSize, + out IntPtr outAuthBuffer, + out uint outAuthBufferSize, + ref bool save, + uint flags); + + [DllImport(Libraries.CredUI, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CredUnPackAuthenticationBufferW")] + internal static extern bool CredUnPackAuthenticationBuffer( + uint flags, + IntPtr authBuffer, + uint authBufferSize, + string? userName, + ref int maxUserName, + string? domainName, + IntPtr maxDomainName, + string? password, + ref int maxPassword); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct CREDUI_INFO + { + internal int Size; + internal IntPtr ParentWindow; + internal string MessageText; + internal string CaptionText; + internal IntPtr BannerBitmap; + } + } +} diff --git a/src/Core/WinSWCore/Native/Errors.cs b/src/Core/WinSWCore/Native/Errors.cs index 0f23279..1dce14a 100644 --- a/src/Core/WinSWCore/Native/Errors.cs +++ b/src/Core/WinSWCore/Native/Errors.cs @@ -2,6 +2,7 @@ { internal static class Errors { + internal const int ERROR_SUCCESS = 0; internal const int ERROR_ACCESS_DENIED = 5; internal const int ERROR_INVALID_HANDLE = 6; internal const int ERROR_INVALID_PARAMETER = 7; diff --git a/src/Core/WinSWCore/Native/Libraries.cs b/src/Core/WinSWCore/Native/Libraries.cs index 4d01c0a..2b54307 100644 --- a/src/Core/WinSWCore/Native/Libraries.cs +++ b/src/Core/WinSWCore/Native/Libraries.cs @@ -3,6 +3,7 @@ internal static class Libraries { internal const string Advapi32 = "advapi32.dll"; + internal const string CredUI = "credui.dll"; internal const string Kernel32 = "kernel32.dll"; internal const string NtDll = "ntdll.dll"; } diff --git a/src/Core/WinSWCore/ServiceDescriptor.cs b/src/Core/WinSWCore/ServiceDescriptor.cs index 7ea3500..bcf877d 100755 --- a/src/Core/WinSWCore/ServiceDescriptor.cs +++ b/src/Core/WinSWCore/ServiceDescriptor.cs @@ -643,6 +643,8 @@ namespace winsw return null; } + public string? ServiceAccountPrompt => GetServiceAccountPart("prompt")?.ToLowerInvariant(); + protected string? AllowServiceLogon => GetServiceAccountPart("allowservicelogon"); public string? ServiceAccountPassword => GetServiceAccountPart("password"); @@ -651,7 +653,7 @@ namespace winsw public bool HasServiceAccount() { - return !string.IsNullOrEmpty(ServiceAccountUserName); + return this.dom.SelectSingleNode("//serviceaccount") != null; } public bool AllowServiceAcountLogonRight