How can I see which shared folders my program has access to? - c#

My program needs to read and write to folders on other machines that might be in another domain. So I used the System.Runtime.InteropServices to add shared folders. This worked fine when it was hard coded in the main menu of my windows service. But since then something went wrong and I don't know if it is a coding error or configuration error.
What is the scope of a shared folder? If a thread in my program adds a shared folder, can the entire local machine see it?
Is there a way to view what shared folders has been added? Or is there a way to see when a folder is added?
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern System.UInt32 NetUseAdd(string UncServerName, int Level, ref USE_INFO_2 Buf, out uint ParmError);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct USE_INFO_2
{
internal LPWSTR ui2_local;
internal LPWSTR ui2_remote;
internal LPWSTR ui2_password;
internal DWORD ui2_status;
internal DWORD ui2_asg_type;
internal DWORD ui2_refcount;
internal DWORD ui2_usecount;
internal LPWSTR ui2_username;
internal LPWSTR ui2_domainname;
}
private void AddSharedFolder(string name, string domain, string username, string password)
{
if (name == null || domain == null || username == null || password == null)
return;
USE_INFO_2 useInfo = new USE_INFO_2();
useInfo.ui2_remote = name;
useInfo.ui2_password = password;
useInfo.ui2_asg_type = 0; //disk drive
useInfo.ui2_usecount = 1;
useInfo.ui2_username = username;
useInfo.ui2_domainname = domain;
uint paramErrorIndex;
uint returnCode = NetUseAdd(String.Empty, 2, ref useInfo, out paramErrorIndex);
if (returnCode != 0)
{
throw new Win32Exception((int)returnCode);
}
}

Each thread in a computer runs under a specific user account. Shared folder have security settings, i.e. they are subject to ACL-based access control, so that some users may have access permission and others may not. This means that the thread in your program may be able to "see" some shared folders, while other threads in the same computer (including the interactive user using the desktop) might be unable to "see" those folders.
In summary: you should assume very little.

Related

Get the PID of a Windows service

Could anyone help me to know how to get the PID of a Windows service?
I need to get the PID in order to run the following command:
Process.Start(new ProcessStartInfo
{
Filename = "cmd.exe",
CreateNoWindow = true,
UseShellExecute = false,
Arguments = string.Format("/c taskkill /pid {0} /f", pidnumber)
});
What the other answers neglect is the fact that a single process can also host multiple, autonomous services. The multiple instances of the svchost.exe process, each hosting a couple of services, is the best example.
So in general, it is absolutely unsafe to try to kill an arbitrary service by killing it's hosting process (I assume that is what you attempt to do, since you refer to taskkill.exe). You might take down several unrelated services in the process.
If you do know that the service's process only hosts the service you care about, than you can choose the strategy as suggested by #M C in his/her answer.
Alternatively, you can also use the ServiceController class to open a handle to your service and then use it (via the ServiceHandle property) to P/Invoke the QueryServiceStatusEx function to find out the Process ID you want to know.
If you need more details, you should clarify what it is that you're actually trying to achieve. It is not clear from your question.
Update Here is some code I ripped out of an existing project that should do what you want, given you have a ServiceController instance. _As said above, use with care!__
[StructLayout(LayoutKind.Sequential)]
internal sealed class SERVICE_STATUS_PROCESS
{
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceType;
[MarshalAs(UnmanagedType.U4)]
public uint dwCurrentState;
[MarshalAs(UnmanagedType.U4)]
public uint dwControlsAccepted;
[MarshalAs(UnmanagedType.U4)]
public uint dwWin32ExitCode;
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceSpecificExitCode;
[MarshalAs(UnmanagedType.U4)]
public uint dwCheckPoint;
[MarshalAs(UnmanagedType.U4)]
public uint dwWaitHint;
[MarshalAs(UnmanagedType.U4)]
public uint dwProcessId;
[MarshalAs(UnmanagedType.U4)]
public uint dwServiceFlags;
}
internal const int ERROR_INSUFFICIENT_BUFFER = 0x7a;
internal const int SC_STATUS_PROCESS_INFO = 0;
[DllImport("advapi32.dll", SetLastError = true)]
internal static extern bool QueryServiceStatusEx(SafeHandle hService, int infoLevel, IntPtr lpBuffer, uint cbBufSize, out uint pcbBytesNeeded);
public static int GetServiceProcessId(this ServiceController sc)
{
if (sc == null)
throw new ArgumentNullException("sc");
IntPtr zero = IntPtr.Zero;
try
{
UInt32 dwBytesNeeded;
// Call once to figure the size of the output buffer.
QueryServiceStatusEx(sc.ServiceHandle, SC_STATUS_PROCESS_INFO, zero, 0, out dwBytesNeeded);
if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER)
{
// Allocate required buffer and call again.
zero = Marshal.AllocHGlobal((int)dwBytesNeeded);
if (QueryServiceStatusEx(sc.ServiceHandle, SC_STATUS_PROCESS_INFO, zero, dwBytesNeeded, out dwBytesNeeded))
{
var ssp = new SERVICE_STATUS_PROCESS();
Marshal.PtrToStructure(zero, ssp);
return (int)ssp.dwProcessId;
}
}
}
finally
{
if (zero != IntPtr.Zero)
{
Marshal.FreeHGlobal(zero);
}
}
return -1;
}
Assuming you know the name of the EXE the service uses and there is exactly one of them:
int procID = Process.GetProcessesByName("yourservice")[0].Id;
The method Process.GetProcessesByName("yourservice") returns an Array of Processes with your specified name, so in case you don't know how much of "yourservice.exe" runs simultaneously you might need a foreach loop.
See this answer on a similar question:
Finding out Windows service's running process name
Using a WMI query you can -
Find all services related to a single exe (a single exe can host multiple services):
select Name from Win32_Service where ProcessId = 588
Or, to answer this question, you can get the PID of the process that a service is running in:
select ProcessId from Win32_Service where Name = 'wuauserv'

Send multiple directories to the Recycle Bin with FileSystem.DeleteDirectory

Making a file browser with loads of functions, coming back to fine tune some of my methods to find this:
foreach (ListViewItem item in listView1.SelectedItems)
{
FileSystem.DeleteDirectory(item.SubItems[3].Text,
UIOption.AllDialogs,
RecycleOption.SendToRecycleBin,
UICancelOption.ThrowException);
}
which works great to send a SINGLE directory or file to the recycle bin, but it will prompt for every selected item. Not great for deleting a pile of files and folders.
Any way to achieve this without the excess prompts? Or do I have to delve into SHFILEOPSTRUCT?
Thanks for your help, so far 90% of my questions were already answered here, best website ever.
If you don't want the prompts, you could use Directory.Delete instead of the FileSystem method. This will delete the directory and files and subdirectories (provided you specify that you want it to do so).
This seems to be the only way to do what you have required
Moving the files and directory to the recycle bin without prompt
using System.Runtime.InteropServices;
class Win32ApiUtils
{
// Don't declare a value for the Pack size. If you omit it, the correct value is used when
// marshaling and a single SHFILEOPSTRUCT can be used for both 32-bit and 64-bit operation.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHFILEOPSTRUCT
{
public IntPtr hwnd;
[MarshalAs(UnmanagedType.U4)]
public int wFunc;
[MarshalAs(UnmanagedType.LPWStr)]
public string pFrom;
[MarshalAs(UnmanagedType.LPWStr)]
public string pTo;
public ushort fFlags;
[MarshalAs(UnmanagedType.Bool)]
public bool fAnyOperationsAborted;
public IntPtr hNameMappings;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszProgressTitle;
}
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp);
const int FO_DELETE = 3;
const int FOF_ALLOWUNDO = 0x40;
const int FOF_NOCONFIRMATION = 0x10; //Don't prompt the user.;
public static int DeleteFilesToRecycleBin(string filename)
{
SHFILEOPSTRUCT shf = new SHFILEOPSTRUCT();
shf.wFunc = FO_DELETE;
shf.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;
shf.pFrom = filename + "\0"; // <--- this "\0" is critical !!!!!
int result = SHFileOperation(ref shf);
// Any value different from zero is an error to lookup
return result;
}
}
foreach (ListViewItem item in listView1.SelectedItems)
{
int result = Win32ApiUtils.DeleteFilesToRecycleBin(item.SubItems[3].Text);
if(result != 0) ...... // ??? throw ??? message to user and contine ???
}
-- Warning --
This code needs to be tested. I have found the layout of SHFILEOPSTRUCT on PInvoke site and on that link there are some notes about the declaration of the strings used.
Well. tested on my Win7 64bit with a single directory to delete. Works like a charm....

Is it possible to determine the file name of a certificate's private key on a remote Windows server?

I'm trying to determine the file name of a certificate's private key stored on a remote Windows (2k3/2k8) machine and am having some difficulty. I'm also not that familiar with Microsoft's CryptAPI, so I'm looking for any help you can provide.
The purpose of this exercise is to find certificates with private keys installed on a remote computer that meet a specific criteria and ensure the correct rights are assigned to their private key files. Although I could assign rights at the folder level, I'd prefer to only assign rights at the private key file level where it's necessary (for obvious reasons).
Here's the scenario, assume a service account with administrative-like permissions is accessing the certificate store:
I retreive the remote certificate store using the following call from C# using p/invoke:
[DllImport("CRYPT32", EntryPoint = "CertOpenStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertOpenStore(int storeProvider, int encodingType, int hcryptProv, int flags, string pvPara);
IntPtr storeHandle = CertOpenStore(CERT_STORE_PROV_SYSTEM,
0,
0,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
string.Format(#"\{0}{1}", serverName, name));
I then use CertEnumCertificatesInStore to retreive certificates that I want to evaluate.
[DllImport("CRYPT32", EntryPoint = "CertEnumCertificatesInStore", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CertEnumCertificatesInStore(IntPtr storeProvider, IntPtr prevCertContext);
IntPtr certCtx = IntPtr.Zero;
certCtx = CertEnumCertificatesInStore(storeHandle, certCtx);
If a certificate matches my criteria, I create an X509Certificate2 instance from the IntPtr returned from the CertEnumCertificatesInStore call like:
X509Certificate2 current = new X509Certificate2(certCtx);
Once I have the X509Certificate2 instances for certificates I'm interested in, I call CryptAcquireCertificatePrivateKey to get the private key provider:
[DllImport("crypt32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptAcquireCertificatePrivateKey(IntPtr pCert, uint dwFlags, IntPtr pvReserved, ref IntPtr phCryptProv, ref int pdwKeySpec, ref bool pfCallerFreeProv);
//cert is an X509Certificate2
CryptAcquireCertificatePrivateKey(cert.Handle,
0,
IntPtr.Zero,
ref hProvider,
ref _keyNumber,
ref freeProvider);
To retreive the private key file name, I try to request the Unique Container Name from the hProvider as pData like:
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
internal extern static bool CryptGetProvParam(IntPtr hCryptProv, CryptGetProvParamType dwParam, IntPtr pvData, ref int pcbData, uint dwFlags);
IntPtr pData = IntPtr.Zero;
CryptGetProvParam(hProvider, PP_UNIQUE_CONTAINER, pData, ref cbBytes, 0));
So far all of the above steps work great locally (servername == local machine name); however, the unique container name (private key filename) that's returned for a certificate stored in a remote computer's local machine certificate store doesn't render as the actual private key filename I'm seeing under:
w2k3: \Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys
ws08: \ProgramData\Microsoft\Crypto\RSA\MachineKeys
For example, if I run the above steps directly on the remote machine, I get a private key file name AAAAAAA-111111, but if I run them remotely, I get a private key BBBBBBBB-2222222. Also, if I install the remote certificate locally and run the steps against my local machine I get the same private key name BBBBBBBB-2222222.
Most likely I feel that I may be missing a caveat in step 4, calling CryptAcquireCertificatePrivateKey. It may be that this call relies on the local machine's identity to generate the name of the unique container that would be used to store the private key blob.
Updated
After some further research, I found a blog that details out exactly how the filenames for private key containers are created here.
Instead of using CryptAcquireCertificatePrivateKey, you can use the methods described on that blog to get the private key container name on any machine once you have the name of the container obtained by CertGetCertificateContextProperty. The code here shows how to get the private key container name so you can generate the private key filename. * disclaimer - I'm pretty sure this is subject to change and may not even be complete, but am posting it in case it helps someone else in the future *
Structs and P/Invoke:
[StructLayout(LayoutKind.Sequential)]
public struct CryptKeyProviderInfo
{
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)]
public String pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
public const uint CERT_KEY_PROV_INFO_PROP_ID = 0x00000002;
[DllImport("crypt32.dll", SetLastError = true)]
internal extern static bool CertGetCertificateContextProperty(IntPtr pCertContext, uint dwPropId, IntPtr pvData, ref uint pcbData);
IntPtr providerInfo = IntPtr.Zero;
string containerName = string.Empty;
try
{
//Win32 call w/IntPtr.Zero will get the size of our Cert_Key_Prov_Info_Prop_ID struct
uint pcbProviderInfo = 0;
if (!Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, IntPtr.Zero, ref pcbProviderInfo))
{
//if we can't get the certificate context, return string.empty
return string.Empty;
}
//Allocate heap for Cert_Key_Prov_Info_Prop_ID struct
providerInfo = Marshal.AllocHGlobal((int)pcbProviderInfo);
//Request actual Cert_Key_Prov_Info_Prop_ID struct with populated data using our allocated heap
if (Win32.CertGetCertificateContextProperty(certificate.Handle, Win32.CERT_KEY_PROV_INFO_PROP_ID, providerInfo, ref pcbProviderInfo))
{
//Cast returned pointer into managed structure so we can refer to it by it's structure layout
Win32.CryptKeyProviderInfo keyInfo = (Win32.CryptKeyProviderInfo)Marshal.PtrToStructure(providerInfo, typeof(Win32.CryptKeyProviderInfo));
//Get the container name
containerName = keyInfo.pwszContainerName;
}
//Do clean-up immediately if possible
if (providerInfo != IntPtr.Zero)
{
Marshal.FreeHGlobal(providerInfo);
providerInfo = IntPtr.Zero;
}
}
finally
{
//Do clean-up on finalizer if an exception cause early terminiation of try - after alloc, before cleanup
if (providerInfo != IntPtr.Zero)
Marshal.FreeHGlobal(providerInfo);
}
Using the CertGetCertificateContextProperty above, I was able to solve this question. So it is possible to determine the file name of a certificate's private key on a remote computer using the steps mentioned in the update.

Impersonation and CurrentUser Registry Access

Environment: Windows XP SP3, C#, .Net 4.0
Problem:
I'm attempting to add access to an impersonated users registry hive in an impersonation class and I'm running into issues based on the type of user being impersonated (or more accurately the limitation seems to be on the impersonating user).
I was originally following an impersonation example from CodeProject which showed a call to LoadUserProfile() taking place after impersonation was started using a duplicate token generated througha call to DuplcateToken() from the original token obtained from LogonUser(). I was not able to get this example working in my environment impersonating a limited user from an administrator account (From the screen shots included in the example it appears as though it was done on a windows Vista\7 system and no details were given about the account types involved).
The call to LoadUserProfile() was throwing an error of "Access Denied". Looking at userenv.log showed the line "LoadUserProfile: failed to enable the restore privilege. error c0000022". The LoadUserProfile documentation on MSDN shows that the calling process must posses the SE_RESTORE_NAME and SE_BACKUP_NAME privileges which by default only members of the Administrators and Backup Operators groups have. (As a side note when I attempted to add these two privileges later on to the Users group i still received Access Denied but the userenv.log showed "DropClientContext: Client [number] does not have enough permission. error 5" which I couldn't find any information on)
Given that the user I was impersonating did not have these privileges I moved the call to LoadUserProfile() up to before starting the impersonation and this time it loaded without a problem and I was able to read and write to it in this test. Thinking that I had discovered my answer I created a conditional check for the type of account so that LoadUserProfile() would be called before impersonation if the current user was a member of Administrators or wait until after impersonation if the member was not a member of Administrators (in the later instance I would be relying on the impersonated user having these privileges). Unfortunately I was wrong; I had not discovered my answer. When I tested the call with the role reversed (User > Administrator) The call to LoadUserProfile() still failed again with the Access Denied error and userenv.log showed the same "LoadUserProfile: failed to enable the restore privilege. error c0000061" but with a different error number this time.
Thinking that the privileges may not be enabled by default on the tokens returned from LogonUser() and\or DuplicateToken() I added two calls to AdjustTokenPrivilege() on the current users token (taking place after impersonation) obtained from WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token.
TokenAccessLevels.AdjustPrivileges and TokenAccessLevels.Query were specified because the documentation for AdjustTokenPrivilege on MSDN specifies that they are needed on the token being adjusted (I also tried obtaining the token through a call to OpenProcessToken() using a handle retrieved from System.Diagnostics.Process.GetCurrentProcess().Handle but that failed when called from the User both inside and outside of impersonation with GetCurrentProcess() being the function that threw access denied)
AdjustTokenPrivilege() returned successfully when used with the WindowsIdentity...Token but LoadUserProfile() still resulted in Access Denied (restore privilege).
At this point I was not convinced that AdjustTokenPrivilege() was doing it's job so I set out to determine what privileges were available and what state they were in for a particular token with GetTokenInformation() which resulted in it's own little bag of fun. After learning some new things I was able to call GetTokenInformation() and print out a list of privileges and their current status but the results were somewhat inconclusive since both Restore and Backup showed an attribute of 0 before and after calling AdjustTokenPrivilege() both as the administrator and while impersonating the administrator (Strangely three other privileges changed from 2 to 1 on the token when calling AdjustTokenPrivilege() but not the ones actually being adjusted which remained at a value of 0)
I removed the call to DuplicateToken() and replaced all of the places it was being used with the token returned from LogonUser() to see if that would help though in testing the privileges on the tokens the LogonUser() and DuplicateToken() tokens were identical. When I initially wrote the impersonation class I had been using the primary token in my call to WindowsImpersonationContext.Impersonate() without any problems and figured it was worth a try.
In the code example I've provided below I am able to impersonate and access the registry of a User when run as an Administrator but not the other way around. Any help would be greatly appreciated.
Pre-Post Edit:
I've also tried using the RegOpenCurrentUser() API in place of LoadUserProfile() and had success with Administrator > self and Administrator > User impersonation but when impersonating an Administrator from either another Administrator account or a User RegOpenCurrentUser() returns a pointer to HKEY_USERS\S-1-5-18 (what ever that is) instead of the actual account hive. I'm guessing because it is not actually loaded which brings me back to square one with needing to use LoadUserProfile()
From RegOpenCurrentUser documentation (MSDN):
RegOpenCurrentUser uses the thread's token to access the appropriate key, or the default if the profile is not loaded.
Code Snippet:
// Private variables used by class
private IntPtr tokenHandle;
private PROFILEINFO pInfo;
private WindowsImpersonationContext thisUser;
private string sDomain = string.Empty;
private string sUsername = string.Empty;
private string sPassword = string.Empty;
private bool bDisposed = false;
private RegistryKey rCurrentUser = null;
private SafeRegistryHandle safeHandle = null;
//Constants used for privilege adjustment
private const string SE_RESTORE_NAME = "SeRestorePrivilege";
private const string SE_BACKUP_NAME = "SeBackupPrivilege";
private const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001;
private const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002;
private const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004;
private const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000;
[StructLayout(LayoutKind.Sequential)]
protected struct PROFILEINFO {
public int dwSize;
public int dwFlags;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpProfilePath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpDefaultPath;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpServerName;
[MarshalAs(UnmanagedType.LPTStr)]
public String lpPolicyPath;
public IntPtr hProfile;
}
protected struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public LUID_AND_ATTRIBUTES[] Privileges;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
[StructLayout(LayoutKind.Sequential)]
protected struct LUID {
public uint LowPart;
public int HighPart;
}
// Private API calls used by class
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
protected static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
[DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)]
protected static extern bool UnloadUserProfile(IntPtr hToken, IntPtr hProfile);
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, [MarshalAs(UnmanagedType.Bool)]bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 Zero, IntPtr Null1, IntPtr Null2);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)][return: MarshalAs(UnmanagedType.Bool)]
protected static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);
[PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
public void Start() {
tokenHandle = IntPtr.Zero; // set the pointer to nothing
if (!LogonUser(sUsername, sDomain, sPassword, 2, 0, ref tokenHandle)) {
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
} // end if !LogonUser returned false
try { //All of this is for loading the registry and is not required for impersonation to start
LUID LuidRestore = new LUID();
LUID LuidBackup = new LUID();
if(LookupPrivilegeValue(null, SE_RESTORE_NAME, ref LuidRestore) && LookupPrivilegeValue(null, SE_BACKUP_NAME, ref LuidBackup)) {
//Create the TokenPrivileges array to pass to AdjustTokenPrivileges
LUID_AND_ATTRIBUTES[] LuidAndAttributes = new LUID_AND_ATTRIBUTES[2];
LuidAndAttributes[0].Luid = LuidRestore;
LuidAndAttributes[0].Attributes = SE_PRIVILEGE_ENABLED;
LuidAndAttributes[1].Luid = LuidBackup;
LuidAndAttributes[1].Attributes = SE_PRIVILEGE_ENABLED;
TOKEN_PRIVILEGES TokenPrivileges = new TOKEN_PRIVILEGES();
TokenPrivileges.PrivilegeCount = 2;
TokenPrivileges.Privileges = LuidAndAttributes;
IntPtr procHandle = WindowsIdentity.GetCurrent(TokenAccessLevels.AdjustPrivileges | TokenAccessLevels.Query).Token;
if(AdjustTokenPrivileges(procHandle, false, ref TokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) {
pInfo = new PROFILEINFO();
pInfo.dwSize = Marshal.SizeOf(pInfo);
pInfo.lpUserName = sUsername;
pInfo.dwFlags = 1;
LoadUserProfile(tokenHandle, ref pInfo); //this is not required to take place
if(pInfo.hProfile != IntPtr.Zero) {
safeHandle = new SafeRegistryHandle(pInfo.hProfile, true);
rCurrentUser = RegistryKey.FromHandle(safeHandle);
}//end if pInfo.hProfile
}//end if AdjustTokenPrivileges
}//end if LookupPrivilegeValue 1 & 2
}catch{
//We don't really care that this didn't work but we don't want to throw any errors at this point as it would stop impersonation
}//end try
WindowsIdentity thisId = new WindowsIdentity(tokenHandle);
thisUser = thisId.Impersonate();
} // end function Start
From the LoadUserProfile docs:
Starting with Windows XP Service Pack 2 (SP2) and Windows Server 2003, the caller must be an administrator or the LocalSystem account. It is not sufficient for the caller to merely impersonate the administrator or LocalSystem account.
If your process starts as a regular user you're out of luck. You could possibly launch a new process (under the admin credentials) to load the profile.
I found that the logon type set in the call to LogonUser() can be a factor. Even when running as an administrator I couldn't get past the error unless I switched from LOGON32_LOGON_INTERACTIVE to LOGON32_LOGON_BATCH. You would need to be sure the "Log on as a batch job" group policy doesn't interfere though.

Searching for Windows User SIDs in C#

Context
Context first - issues I'm trying to resolve are below.
[EDIT] The application in questions is built against .NET 3.5 SP1.
One of our clients has asked as to quote how long it would take for us to improve one of our applications. This application currently provides basic user authentication in the form of username/password combinations. This client would like the ability for their employees to log-in using the details of whatever Windows User account is currently logged in at the time of running the application.
It's not a deal-breaker if I tell them no - but the client might be willing to pay the costs of development to add this feature to the application. It's worth looking into.
Based on my hunting around, it seems like storing the user login details against Domain\Username will be problematic if those details are changed. But Windows User SID's aren't supposed to change at all. I've got the impression that it would be best to record Windows Users by SID - feel free to relieve me of that if I'm wrong.
I've been having a fiddle with some Windows API calls. From within C#, grabbing the current user's SID is easy enough. I can already take any user's SID and process it using LookupAccountSid to get username and domain for display purposes. For the interested, my code for this is at the end of this post.
That's just the tip of the iceberg, however. The two issues below are completely outside my experience. Not only do I not know how to implement them - I don't even known how to find out how to implement them, or what the pitfalls are on various systems.
Any help getting myself aimed in the right direction would be very much appreciated.
Issue 1)
Getting hold of the local user at runtime is meaningless if that user hasn't been granted access to the application. We will need to add a new section to our application's 'administrator console' for adding Windows Users (or groups) and assigning within-app permissions against those users.
Something like an 'Add Windows User Login' button that will raise a pop-up window that will allow the user to search for available Windows User accounts on the network (not just the local machine) to be added to the list of available application logins.
If there's already a component in .NET or Windows that I can shanghai into doing this for me, it would make me a very happy man.
Issue 2)
I also want to know how to take a given Windows User SID and check it against a given Windows User Group (probably taken from a database). I'm not sure how to get started with this one either, though I expect it to be easier than the issue above.
For the Interested
[STAThread]
static void Main(string[] args)
{
MessageBox.Show(WindowsUserManager.GetAccountNameFromSID(WindowsIdentity.GetCurrent().User.Value));
MessageBox.Show(WindowsUserManager.GetAccountNameFromSID("S-1-5-21-57989841-842925246-1957994488-1003"));
}
public static class WindowsUserManager
{
public static string GetAccountNameFromSID(string SID)
{
try
{
StringBuilder name = new StringBuilder();
uint cchName = (uint)name.Capacity;
StringBuilder referencedDomainName = new StringBuilder();
uint cchReferencedDomainName = (uint)referencedDomainName.Capacity;
WindowsUserManager.SID_NAME_USE sidUse;
int err = (int)ESystemError.ERROR_SUCCESS;
if (!WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse))
{
err = Marshal.GetLastWin32Error();
if (err == (int)ESystemError.ERROR_INSUFFICIENT_BUFFER)
{
name.EnsureCapacity((int)cchName);
referencedDomainName.EnsureCapacity((int)cchReferencedDomainName);
err = WindowsUserManager.LookupAccountSid(null, SID, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse) ?
(int)ESystemError.ERROR_SUCCESS :
Marshal.GetLastWin32Error();
}
}
if (err != (int)ESystemError.ERROR_SUCCESS)
throw new ApplicationException(String.Format("Could not retrieve acount name from SID. {0}", SystemExceptionManager.GetDescription(err)));
return String.Format(#"{0}\{1}", referencedDomainName.ToString(), name.ToString());
}
catch (Exception ex)
{
if (ex is ApplicationException)
throw ex;
throw new ApplicationException("Could not retrieve acount name from SID", ex);
}
}
private enum SID_NAME_USE
{
SidTypeUser = 1,
SidTypeGroup,
SidTypeDomain,
SidTypeAlias,
SidTypeWellKnownGroup,
SidTypeDeletedAccount,
SidTypeInvalid,
SidTypeUnknown,
SidTypeComputer
}
[DllImport("advapi32.dll", EntryPoint = "GetLengthSid", CharSet = CharSet.Auto)]
private static extern int GetLengthSid(IntPtr pSID);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool ConvertStringSidToSid(
string StringSid,
out IntPtr ptrSid);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool LookupAccountSid(
string lpSystemName,
[MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
StringBuilder lpName,
ref uint cchName,
StringBuilder ReferencedDomainName,
ref uint cchReferencedDomainName,
out SID_NAME_USE peUse);
private static bool LookupAccountSid(
string lpSystemName,
string stringSid,
StringBuilder lpName,
ref uint cchName,
StringBuilder ReferencedDomainName,
ref uint cchReferencedDomainName,
out SID_NAME_USE peUse)
{
byte[] SID = null;
IntPtr SID_ptr = IntPtr.Zero;
try
{
WindowsUserManager.ConvertStringSidToSid(stringSid, out SID_ptr);
int err = SID_ptr == IntPtr.Zero ? Marshal.GetLastWin32Error() : (int)ESystemError.ERROR_SUCCESS;
if (SID_ptr == IntPtr.Zero ||
err != (int)ESystemError.ERROR_SUCCESS)
throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}", stringSid, SystemExceptionManager.GetDescription(err)));
int size = (int)GetLengthSid(SID_ptr);
SID = new byte[size];
Marshal.Copy(SID_ptr, SID, 0, size);
}
catch (Exception ex)
{
if (ex is ApplicationException)
throw ex;
throw new ApplicationException(String.Format("'{0}' could not be converted to a SID byte array. {1}.", stringSid, ex.Message), ex);
}
finally
{
// Always want to release the SID_ptr (if it exists) to avoid memory leaks.
if (SID_ptr != IntPtr.Zero)
Marshal.FreeHGlobal(SID_ptr);
}
return WindowsUserManager.LookupAccountSid(lpSystemName, SID, lpName, ref cchName, ReferencedDomainName, ref cchReferencedDomainName, out peUse);
}
}
If you're using the 3.5 version of the framework, you really want to look into System.DirectoryServices.AccountManagement. I've used it before to provide lookups of AD accounts, and it's much simpler to deal with than writing your own class. It will also solve your #2 question. I don't have the code at hand, but if you need it I can always look it up.

Categories