I'm trying to use CredWrite, but get an ERROR_INVALID_PARAMETER 87 (0x57) error. The intent is to have a secure place to save the user's password for my .net WPF application.
And my code:
public class CredMan
{
private const string TARGET_PREFIX = "myappname:";
public static void SavePassword(string username, string password)
{
Win32CredMan.Credential cred = new Win32CredMan.Credential();
cred.Flags = 0;
cred.Type = Win32CredMan.CRED_TYPE.GENERIC;
cred.TargetName = TARGET_PREFIX + username;
var encoding = new System.Text.UTF8Encoding();
cred.CredentialBlob = encoding.GetBytes(password);
cred.Persist = Win32CredMan.CRED_PERSIST.LOCAL_MACHINE;
cred.UserName = username;
bool isGood = Win32CredMan.CredWrite(cred, 0);
int lastError = Marshal.GetLastWin32Error();
}
}
This is the win32 wrapper: (mostly grabbed from pinvoke.net)
internal class Win32CredMan
{
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(CredentialInMarshaler))]out Credential credential);
[DllImport("Advapi32.dll", EntryPoint = "CredFreeW", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern void CredFree(IntPtr buffer);
[DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
public static extern bool CredWrite([In] Credential userCredential, [In] UInt32 flags);
public enum CRED_TYPE : uint
{
GENERIC = 1,
DOMAIN_PASSWORD = 2,
DOMAIN_CERTIFICATE = 3,
DOMAIN_VISIBLE_PASSWORD = 4,
GENERIC_CERTIFICATE = 5,
DOMAIN_EXTENDED = 6,
MAXIMUM = 7, // Maximum supported cred type
MAXIMUM_EX = (MAXIMUM + 1000), // Allow new applications to run on old OSes
}
public enum CRED_PERSIST : uint
{
SESSION = 1,
LOCAL_MACHINE = 2,
ENTERPRISE = 3,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CREDENTIAL_ATTRIBUTE
{
string Keyword;
uint Flags;
uint ValueSize;
IntPtr Value;
}
//This type is deliberately not designed to be marshalled.
public class Credential
{
public UInt32 Flags;
public CRED_TYPE Type;
public string TargetName;
public string Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public byte[] CredentialBlob;
public CRED_PERSIST Persist;
public CREDENTIAL_ATTRIBUTE[] Attributes;
public string TargetAlias;
public string UserName;
}
}
I ran into this same problem now.
I found that this issue occurred using the DOMAIN_PASSWORD option as credential type.
It turns out the TargetName contained an incorrect value.
you should only specify the dns or ip address (wildcard optional),
but NOT containing a full url or protocol.
e.g. "*.microsoft.com" is correct, but "http://www.microsoft.com/" is INVALID
I'll just post this here in case other people run into this issue. took me a while to find it.
Related
Let's start with documentation:
https://man7.org/linux/man-pages/man3/getpwnam.3.html
Having this, I made a following C# code:
using System;
using System.Runtime.InteropServices;
if (args.Length < 1) {
Console.Error.WriteLine("Provide user name.");
Environment.Exit(-1);
}
var name = args[0];
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
Syscall.getpwnam(name, out var passwd);
Console.WriteLine($"User = {name}, UID = {passwd.Uid}, GID = {passwd.Gid}");
passwd = GetPasswd(name);
Console.WriteLine($"User = {name}, UID = {passwd.Uid}, GID = {passwd.Gid}");
}
else {
Console.WriteLine("It supposed to be run on Linux.");
}
static Passwd GetPasswd(string name) {
var bufsize = 16384;
var buf = new byte[bufsize];
var passwd = new Passwd();
Syscall.getpwnam_r(name, passwd, buf, (uint)bufsize, out var result);
return result;
}
public struct Passwd {
public string Name;
public string Password;
public uint Uid;
public uint Gid;
public string Gecos;
public string Directory;
public string Shell;
}
static class Syscall {
[DllImport("libc", SetLastError = true)]
public static extern void getpwnam(string name, out Passwd passwd);
[DllImport("libc", SetLastError = true)]
public static extern void getpwnam_r(string name, Passwd passwd, byte[] buf, uint bufsize, out Passwd result);
}
It doesn't work.
Here's what I get:
User = service, UID = 0, GID = 0
Segmentation fault (core dumped)
What am I doing wrong?
How should I call it to get the actual structure? I'm not interested in returned strings. All I care of are Uid and Gid values.
As linked documentation mentions - this function accepts one parameter - name, and returns pointer to structure with data. So signature should be:
[DllImport("libc", SetLastError = true)]
public static extern IntPtr getpwnam(string name);
And then:
// we have pointer here
var passwdPtr = Syscall.getpwnam(name);
// don't forget to check if pointer is not IntPtr.Zero.
// interpret data at pointer as structure
var passwd = Marshal.PtrToStructure<Passwd>(passwdPtr);
Console.WriteLine($"User = {passwd.Name}, UID = {passwd.Uid}, GID = {passwd.Gid}");
The C# import code:
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = true)]
public static extern long NtQueryVolumeInformationFile(IntPtr FileHandle, out IO_STATUS_BLOCK IoStatusBlock, IntPtr FsInformation, uint Length, FSINFOCLASS FsInformationClass);
The FSINFOCLASS enum:
public enum FSINFOCLASS
{
FileFsVolumeInformation,
FileFsLabelInformation,
FileFsSizeInformation,
FileFsDeviceInformation,
FileFsAttributeInformation,
FileFsControlInformation,
FileFsFullSizeInformation,
FileFsObjectIdInformation,
FileFsDriverPathInformation,
FileFsVolumeFlagsInformation,
FileFsSectorSizeInformation,
FileFsDataCopyInformation,
FileFsMetadataSizeInformation,
FileFsFullSizeInformationEx,
FileFsMaximumInformation
}
The way it is called:
var handlePtr = Win32.CreateFile(#"\\.\c:", Win32.GENERIC_READ, Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE | Win32.FILE_SHARE_DELETE, 0, Win32.OPEN_EXISTING, 0, 0);
if (handlePtr == IntPtr.Zero - 1)
return 0;
var iosb = new Win32.IO_STATUS_BLOCK();
var buffer = Marshal.AllocHGlobal(273);
var result = Win32.NtQueryVolumeInformationFile(handlePtr, out iosb, buffer, 273,
Win32.FSINFOCLASS.FileFsVolumeInformation);
// result=0xC0000003 , should be zero
The code runs with the administrative privilege.
The error code's description is: STATUS_INVALID_INFO_CLASS
But as far as I can tell, the info class enum is correct.
What am I missing?
Thanks to Jimi's comment, I assigned the value 1 to the enum member FileFsVolumeInformation and that fixed that error.
However it needed more fixes to fully work. The complete working code for the interop imports follows:
[DllImport("ntdll.dll", ExactSpelling = true, SetLastError = true)]
public static extern long NtQueryVolumeInformationFile(IntPtr FileHandle, out IO_STATUS_BLOCK IoStatusBlock, IntPtr FsInformation,
uint Length, FSINFOCLASS FsInformationClass);
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct FILE_FS_VOLUME_INFORMATION
{
public long VolumeCreationTime;
public uint VolumeSerialNumber;
public uint VolumeLabelLength;
public bool SupportsObjects;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
public string VolumeLabel;
}
public enum FSINFOCLASS
{
FileFsVolumeInformation = 1,
FileFsLabelInformation,
FileFsSizeInformation,
FileFsDeviceInformation,
FileFsAttributeInformation,
FileFsControlInformation,
FileFsFullSizeInformation,
FileFsObjectIdInformation,
FileFsDriverPathInformation,
FileFsVolumeFlagsInformation,
FileFsSectorSizeInformation,
FileFsDataCopyInformation,
FileFsMetadataSizeInformation,
FileFsFullSizeInformationEx,
FileFsMaximumInformation
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct IO_STATUS_BLOCK
{
public uint status;
public IntPtr information;
}
public uint GetVolumeSerialNumber(string volumePath)
{
var handlePtr = Win32.CreateFile($#"\\.\{volumePath.TrimEnd('\\')}", Win32.GENERIC_READ, Win32.FILE_SHARE_READ | Win32.FILE_SHARE_WRITE | Win32.FILE_SHARE_DELETE,
0, Win32.OPEN_EXISTING, 0, 0);
if (handlePtr == IntPtr.Zero - 1)
return 0;
var iosb = new Win32.IO_STATUS_BLOCK();
var fsInfo = new Win32.FILE_FS_VOLUME_INFORMATION();
var bufferSize = Marshal.SizeOf(fsInfo) + 32; //32 =maximum ntfs volume label length
var buffer = Marshal.AllocHGlobal(bufferSize);
var result = Win32.NtQueryVolumeInformationFile(handlePtr, out iosb, buffer, (uint)bufferSize,
Win32.FSINFOCLASS.FileFsVolumeInformation);
if (result != 0)
{
Marshal.FreeHGlobal(buffer);
Win32.CloseHandle(handlePtr);
return 0;
}
fsInfo = Marshal.PtrToStructure<Win32.FILE_FS_VOLUME_INFORMATION>(buffer);
Marshal.FreeHGlobal(buffer);
return fsInfo.VolumeSerialNumber;
}
I'm having problems trying to impersonate an active directory user in a desktop application. Every time I use the LogOn API the result is false.
The user and domain do exist since I can also authenticate the user over the DirectoryServices.AccountManagement on the same application.
Have read the documentation about impersonation in the Microsoft site and even some post here on the stack. Also, have used the SimpleImpersonation library with the same results.
public class Demo
{
private WindowsImpersonationContext impersonationContext = null;
[DllImport("advapi32.dll", SetLastError = true)]
private static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int DuplicateToken(IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken);
private void Enter()
{
try
{
IntPtr token = IntPtr.Zero;
IntPtr tokenDuplicate = IntPtr.Zero;
string userName = "myValidUser";
string domain = "my.domain.example";
string password = "myValidPassword";
if (LogonUser(userName, domain, password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_WINNT35, ref token) != 0)
{
WindowsIdentity WindowsIdentityPrincipal = new WindowsIdentity(token);
if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
{
WindowsIdentity tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
impersonationContext = tempWindowsIdentity.Impersonate();
}
else
{
throw new Win32Exception(new Win32Exception(Marshal.GetLastWin32Error()).Message);
}
}
else
{
//throws username or pass incorrect
throw new Win32Exception(new Win32Exception(Marshal.GetLastWin32Error()).Message);
}
}
catch (Exception exc)
{
throw exc;
}
}
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
}
private enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
LOGON32_LOGON_NEW_CREDENTIALS = 9,
}
}
I don't know if the reason it isn't working is that my computer is running on an outside network and connecting/authenticating to the company network over a VPN.
Edit 1. The resulting error code is 1326 (unknown user name or bad
password)
Edit 2. The method is trying to obtain the identity token for later
use on thread impersonation.
You might want to check the documentation for the LogonUser function.
If your username is in the format user#domain.example then you need to pass in:
lpszUserName = "user#domain.example"
lpszDomain = null
If your username is in the format domain\user then you need to pass in:
lpszUserName = "user"
lpszDomain = "domain"
Treating the fully qualified username the wrong way will result in the error code you're seeing.
You cannot use LogonUser to log on to a remote computer. You need to use WNetAddConnection2 api function. Please refer to the msdn documentation.
for LogonUser:
https://learn.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-logonuserw
for WNetAddConnection2 :
https://learn.microsoft.com/en-us/windows/desktop/api/winnetwk/nf-winnetwk-wnetaddconnection2w
here is a class that I have written :
public class RemoteNetworkConnector : IDisposable
{
readonly string _networkName;
public RemoteNetworkConnector(string networkName, NetworkCredential credentials)
{
_networkName = networkName;
NetResource netResource = new NetResource
{
Scope = ResourceScope.GlobalNetwork,
ResourceType = ResourceType.Disk,
DisplayType = ResourceDisplaytype.Share,
RemoteName = networkName
};
var userName = string.IsNullOrEmpty(credentials.Domain)
? credentials.UserName
: string.Format(#"{0}\{1}", credentials.Domain, credentials.UserName);
var connectionResult = WNetAddConnection2(
netResource,
credentials.Password,
userName,
0);
if (connectionResult != 0)
{
throw new Win32Exception(connectionResult, "Error connecting to remote share");
}
}
~RemoteNetworkConnector()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
WNetCancelConnection2(_networkName, 0, true);
}
[DllImport("mpr.dll")]
private static extern int WNetAddConnection2(NetResource netResource,
string password, string username, int flags);
[DllImport("mpr.dll")]
private static extern int WNetCancelConnection2(string name, int flags,
bool force);
[StructLayout(LayoutKind.Sequential)]
public class NetResource
{
public ResourceScope Scope;
public ResourceType ResourceType;
public ResourceDisplaytype DisplayType;
public int Usage;
public string LocalName;
public string RemoteName;
public string Comment;
public string Provider;
}
public enum ResourceScope : int
{
Connected = 1,
GlobalNetwork,
Remembered,
Recent,
Context
};
public enum ResourceType : int
{
Any = 0,
Disk = 1,
Print = 2,
Reserved = 8,
}
public enum ResourceDisplaytype : int
{
Generic = 0x0,
Domain = 0x01,
Server = 0x02,
Share = 0x03,
File = 0x04,
Group = 0x05,
Network = 0x06,
Root = 0x07,
Shareadmin = 0x08,
Directory = 0x09,
Tree = 0x0a,
Ndscontainer = 0x0b
}
}
I hope this will help.
I'm making use of the Setup... methods (such as SetupGetLineText) to read some content from inf files (I need this, not interested in a generic ini parser). These methods use Windows-1252 encoding and I need this to be converted to Unicode. I got this working with a string, such as this (input is of type string):
Encoding.UTF8.GetString(Encoding.GetEncoding(1252).GetBytes(input));
Even though this works fine, you can also immediately retrieve the bytes from the SetupGetLineText method (and others). However, I'm not entirely sure on how to convert the bytes now as they are different from what Encoding.GetEncoding(1252) returns. To make this a bit more clear I've uploaded a screenshot of the current situation. As you can see, the majority of the characters match (ignore the 0s), but there are a couple of situations with differences. For example, [4] and [5] are 26 and 32, whereas the string variant has just 130 listed. How can I get from 26 and 32 to 130? Or perhaps better, how can I directly go from the byte array to a UTF-8 string?
Some code:
public static readonly IntPtr INVALID_HANDLE = new IntPtr(-1);
public const int INF_STYLE_OLDNT = 0x00000001;
public const int INF_STYLE_WIN4 = 0x00000002;
[StructLayout(LayoutKind.Sequential)]
public struct InfContext
{
IntPtr Inf;
IntPtr CurrentInf;
uint Section;
uint Line;
}
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetupGetLineText([MarshalAs(UnmanagedType.Struct)] ref InfContext context, IntPtr infHandle, string section, string key, string returnBuffer, int returnBufferSize, out int requiredSize);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr SetupOpenInfFile([MarshalAs(UnmanagedType.LPWStr)] string fileName, [MarshalAs(UnmanagedType.LPWStr)] string infClass, Int32 infStyle, out uint errorLine);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetupEnumInfSections(IntPtr infHandle, uint index, string returnBuffer, int returnBufferSize, out int requiredSize);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetupFindFirstLine(IntPtr infHandle, string section, string key, [MarshalAs(UnmanagedType.Struct)]ref InfContext context);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetupFindNextLine([MarshalAs(UnmanagedType.Struct)] ref InfContext contextIn, [MarshalAs(UnmanagedType.Struct)] ref InfContext contextOut);
[DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool SetupFindNextMatchLine([MarshalAs(UnmanagedType.Struct)] ref InfContext contextIn, string key, [MarshalAs(UnmanagedType.Struct)] ref InfContext contextOut);
// InfFile class
public InfFile(string path)
{
_file = path;
}
public bool Open()
{
uint errorLineNumber;
_handle = NativeMethodsInf.SetupOpenInfFile(_file, null, INF_STYLE_OLDNT | INF_STYLE_WIN4, out errorLineNumber);
return _handle != INVALID_HANDLE;
}
public string EnumSection(uint index)
{
int requiredSize;
string result = String.Empty.PadLeft(75-1);
bool success = SetupEnumInfSections(_handle, index, result, 75, out requiredSize);
if (requiredSize > 75)
{
result = result.PadLeft(requiredSize - 1);
success = SetupEnumInfSections(_handle, index, result, requiredSize, out requiredSize);
}
return !success ? null : result.Substring(0, requiredSize - 1); // Still needs to be converted to proper encoding.
}
public InfLine FindFirstLine(string section)
{
return FindFirstKey(section, null);
}
public InfLine FindFirstKey(string section, string key)
{
InfContext infContext = new InfContext();
return !SetupFindFirstLine(_handle, section, key, ref infContext) ? null : new InfLine(infContext);
}
// InfLine class
public bool FindNextLine()
{
return SetupFindNextLine(ref _context, ref _context);
}
public bool FindNextMatchLine(string key)
{
return SetupFindNextMatchLine(ref _context, key, ref _context);
}
public string GetCompleteValue()
{
int requiredSize;
string result = String.Empty.PadLeft(250-1);
bool success = SetupGetLineText(ref _context, IntPtr.Zero, null, null, result, 250, out requiredSize);
if (requiredSize > 250)
{
result = result.PadLeft(requiredSize - 1);
success = SetupGetLineText(ref _context, IntPtr.Zero, null, null, result, requiredSize, out requiredSize);
}
return !success ? null : result.Substring(0, requiredSize - 1);
}
// And then use with something like:
using (InfFile file = new InfFile(#"..\..\..\test.inf"))
{
if (file.Open())
{
uint currentSection = 0;
string section;
while ((section = file.EnumSection(currentSection++)) != null)
{
Console.WriteLine("Section: " + section);
var x = file.FindFirstKey(section, null);
if (x != null)
while (true)
{
string key = x.GetFieldValue(0);
string value = x.GetCompleteValue();
Console.WriteLine("Key: " + key + " || Value: " + value);
if (!x.FindNextLine())
break;
}
}
}
}
Example inf:
; German Specific
[Strings.0007] ; German
Provider="Hewlett-Packard"
Mfg="Hewlett-Packard"
CD="hp cd"
BUTTON_SCAN="Taste "Scannen" gedrückt"
LAUNCH_APPLICATION_SCAN="HP Scansoftware"
; Japanese Specific
[Strings.0411] ; Japanese
Provider="Hewlett-Packard"
Mfg="Hewlett-Packard"
CD="hp cd"
BUTTON_SCAN="[スキャン] ボタンを押す"
LAUNCH_APPLICATION_SCAN="hp スキャニング ソフトウェア"
I need to convert section, key and value using:
public static string ConvertToUTF8(string input)
{
try
{
return Encoding.UTF8.GetString(Encoding.GetEncoding(1252).GetBytes(input)).Trim().Trim('\0');
}
catch
{
return input;
}
}
To get the proper values, otherwise you'll see that they aren't the original characters.
For example:
Taste "Scannen" gedrückt
becomes
Taste Scannen gedrückt
Without calling ConvertToUTF8 first.
You're currently converting a string to Windows-1252 and then converting it back to a string by interpreting those bytes as UTF-8.
That is not working fine - that's broken, basically.
If you've already got a string, it's not in Windows-1252... it's in UTF-16 internally, but you can think of it as a character sequence. If you've actually started with a byte array, then you should use Encoding.GetEncoding(1252).GetString(bytes) to convert that byte array to a string.
(If you can use SetupGetLineTextW instead, you may be able to avoid all this ANSI business entirely.)
I try to get all domains in a forest.
I can connect to one specific domain and get its DirectoryEntry like this:
DirectoryContext dc =
new DirectoryContext(DirectoryContextType.DirectoryServer, "xx.x.xxx.40", "w28\\administrator", "pwd");
Domain domain = Domain.GetDomain(dc);
DirectoryEntry entry = domain.GetDirectoryEntry();
foreach (DirectoryEntry child in entry.Children)
{
Console.WriteLine(" - " + child.Name);
}
However, when I try to get other domains via the Forest properity.
Forest forest = domain.Forest;
Console.WriteLine("Count: " + forest.Domains.Count); //It crashes here
DomainCollection domains = forest.Domains;
My app crashes and the exception message is shown below:
System.DirectoryServices.ActiveDirectory.ActiveDirectoryServerDownException:
The specified domain either does not exist or could not be contacted.
at
System.DirectoryServices.ActiveDirectory.Locator.GetDomainControllerInfo(String
computerName, String domainName, String siteName, Int64 flags) at
System.DirectoryServices.ActiveDirectory.DirectoryContext.isCurrentForest()
at
System.DirectoryServices.ActiveDirectory.DirectoryContext.GetServerName()
at
System.DirectoryServices.ActiveDirectory.DirectoryEntryManager.GetNewDirectoryEntry(String
dn) at
System.DirectoryServices.ActiveDirectory.DirectoryEntryManager.GetCachedDirectoryEntry(String
distinguishedName) at
System.DirectoryServices.ActiveDirectory.DirectoryEntryManager.ExpandWellKnownDN(WellKnownDN
dn) at
System.DirectoryServices.ActiveDirectory.DirectoryEntryManager.ExpandWellKnownDN(WellKnownDN
dn) at System.DirectoryServices.ActiveDirectory.Forest.GetDomains()
at System.DirectoryServices.ActiveDirectory.Forest.get_Domains()
Please help me.
Thanks in advance.
I ran similar code in my forest (by GetCurrentDomain() and query its Forest) and they worked well. I think the problem was just as the exception and callstack presented - it tries to get info about your forest by querying the forest root server which is a DC, and it cannot be contacted. I think you need to check your topology and then look at the server's status.
I have this same problem. I'm outside the domain and I always will be because we're network security testers.
I found this is a good way to work around
class PInvoke {
[DllImport("Netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DsGetDcName
(
[MarshalAs(UnmanagedType.LPTStr)]
string ComputerName,
[MarshalAs(UnmanagedType.LPTStr)]
string DomainName,
[In] int DomainGuid,
[MarshalAs(UnmanagedType.LPTStr)]
string SiteName,
[MarshalAs(UnmanagedType.U4)]
DSGETDCNAME_FLAGS flags,
out IntPtr pDOMAIN_CONTROLLER_INFO
);
[StructLayout(LayoutKind.Sequential)]
public class GuidClass
{
public Guid TheGuid;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DOMAIN_CONTROLLER_INFO
{
[MarshalAs(UnmanagedType.LPTStr)]
public string DomainControllerName;
[MarshalAs(UnmanagedType.LPTStr)]
public string DomainControllerAddress;
public uint DomainControllerAddressType;
public Guid DomainGuid;
[MarshalAs(UnmanagedType.LPTStr)]
public string DomainName;
[MarshalAs(UnmanagedType.LPTStr)]
public string DnsForestName;
public uint Flags;
[MarshalAs(UnmanagedType.LPTStr)]
public string DcSiteName;
[MarshalAs(UnmanagedType.LPTStr)]
public string ClientSiteName;
}
[DllImport("Netapi32.dll", SetLastError = true)]
public static extern int NetApiBufferFree(IntPtr Buffer);
[Flags]
public enum DSGETDCNAME_FLAGS : uint
{
DS_FORCE_REDISCOVERY = 0x00000001,
DS_DIRECTORY_SERVICE_REQUIRED = 0x00000010,
DS_DIRECTORY_SERVICE_PREFERRED = 0x00000020,
DS_GC_SERVER_REQUIRED = 0x00000040,
DS_PDC_REQUIRED = 0x00000080,
DS_BACKGROUND_ONLY = 0x00000100,
DS_IP_REQUIRED = 0x00000200,
DS_KDC_REQUIRED = 0x00000400,
DS_TIMESERV_REQUIRED = 0x00000800,
DS_WRITABLE_REQUIRED = 0x00001000,
DS_GOOD_TIMESERV_PREFERRED = 0x00002000,
DS_AVOID_SELF = 0x00004000,
DS_ONLY_LDAP_NEEDED = 0x00008000,
DS_IS_FLAT_NAME = 0x00010000,
DS_IS_DNS_NAME = 0x00020000,
DS_RETURN_DNS_NAME = 0x40000000,
DS_RETURN_FLAT_NAME = 0x80000000
}
}
class domain
{
public static void DetectDc(string domain, string username, string password, out string dc, out string dcAddress, out string path)
{
PInvoke.DOMAIN_CONTROLLER_INFO domainInfo;
const int errorSuccess = 0;
var pDci = IntPtr.Zero;
try
{
var val = PInvoke.DsGetDcName(null, domain, 0, "", 0, out pDci);
//check return value for error
if (errorSuccess == val)
{
domainInfo = (PInvoke.DOMAIN_CONTROLLER_INFO)Marshal.PtrToStructure(pDci, typeof(PInvoke.DOMAIN_CONTROLLER_INFO));
}
else
{
dc = "";
dcAddress = "";
path = "";
namingContext = "";
return;
}
}
finally
{
PInvoke.NetApiBufferFree(pDci);
}
dc = domainInfo.DomainControllerName;
dc = dc.Replace("\\\\", "");
dcAddress = domainInfo.DomainControllerAddress;
dcAddress = dcAddress.Replace("\\\\", "");
var ldap = new Ldap(domain, dcAddress, username, password);
}
}