How to p/invoke getpwnam() from libc in C#? - c#

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}");

Related

P/invoking the NtQueryVolumeInformationFile function returns 0xC0000003 error

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;
}

C# PInvoke issue

I have been trying to replicate the following C++ function in my C# application but I am getting an AccessViolationException at the time the method "ObjSearchObject2" is invoked. Pretty sure I am doing a lot of things wrong. The C++ function and the corresponding headers looks like this,
bool DocumentSearch(char *pFileId)
{
const int NUM_CONDITIONS = 1;
const int NUM_TYPE_DEFNS = 1;
ObjSearchObjectParm rSearchObject;
ObjSearchCondition rSearchCondition;
ObjObjectResults rSearchResults;
char *pTypeDefnIdArray[NUM_TYPE_DEFNS];
bool bReturnValue = false;
memset(&rSearchObject, 0, sizeof(ObjSearchObjectParm));
memset(&rSearchCondition, 0, sizeof(ObjSearchCondition));
memset(&rSearchResults, 0, sizeof(ObjObjectResults));
pTypeDefnIdArray[0] = "dotdA9";//Documents
rSearchObject.obj_defn_ids = pTypeDefnIdArray;
rSearchObject.obj_defn_count = NUM_TYPE_DEFNS;
rSearchObject.num_conditions = NUM_CONDITIONS;
rSearchObject.max_results = 5000;
rSearchObject.search_cond = &rSearchCondition;
rSearchCondition.field = "ancestor";
rSearchCondition.op = "is";
rSearchCondition.value = pFileId;
if (ObjSearchObject2(&rSearchObject, &rSearchResults))
{
printf("ERROR: ObjSearchObject2 returned: %s \n", ObjGetErrorMsg());
}
else
{
printf("INFO : Number of returned results = %d \n", rSearchResults.num_results);
if (rSearchResults.num_results > 0)
{
for (int iIndex = 0; iIndex < rSearchResults.num_results; iIndex++)
{
if (rSearchResults.object_handle[iIndex])
{
printf("INFO : Object Id returned: %s (name=%s, updated=%s, type=%s, state=%s) \n",
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "id_object"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "name"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "date_update"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "id_type_definition"),
ObjGetObjectAttr(rSearchResults.object_handle[iIndex], "num_state")
);
ObjFreeObjectHdl(rSearchResults.object_handle[iIndex]);
}
}
bReturnValue = true;
}
}
return bReturnValue;
}
int ObjSearchObject2(ObjSearchObjectParm *, ObjObjectResults *);
char * ObjGetObjectAttr(ObjApiObjectHdl objectHdl, char *attr_name);
char * ObjGetErrorMsg();
void ObjFreeObjectHdl(ObjApiObjectHdl objectHdl);
typedef struct _ObjSearchObjectParm {
int obj_defn_count; // mandatory
char **obj_defn_ids; // mandatory
char *text_server_alias; // optional.
char *query_string; // optional text search string
ObjApiBoolean include_deleted; // defaults to OBJAPI_FALSE
int max_results; // default = 200
int num_conditions; // mandatory
ObjSearchCondition *search_cond; // mandatory
ObjApiBoolean include_content; // mandatory for COMPLEX searches, translated to Y/N in API
ObjApiBoolean include_metadata; // mandatory for COMPLEX searches, translated to Y/N in API
} ObjSearchObjectParm;
enum ObjApiSearchOp
{
OBJAPI_MATCH_ALL=1,
OBJAPI_MATCH_ANY=2
};
enum ObjApiBoolean
{
OBJAPI_FALSE,
OBJAPI_TRUE
};
typedef struct _ObjSearchCondition {
ObjApiSearchOp boolop; // only for complex searches
char *join_relation; // only for complex searches
int num_opening_brackets; // only for complex searches
int num_closing_brackets; // only for complex searches
char *field;
char *op;
char *value;
} ObjSearchCondition;
typedef void* ObjApiObjectHdl;
typedef struct _ObjObjectResults {
ObjApiObjectHdl *object_handle;
int num_results;
} ObjObjectResults;
My take on this is as follows, (UPDATE: Code updated)
public class ObjectiveNativeAPI
{
private const string dllPath = #"objapi.dll";
public enum ObjApiBoolean
{
OBJAPI_FALSE,
OBJAPI_TRUE
};
public enum ObjApiSearchOp
{
OBJAPI_MATCH_ALL = 1,
OBJAPI_MATCH_ANY = 2
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ObjSearchObjectParm
{
public int obj_defn_count;
public string[] obj_defn_ids;
public string text_server_alias;
public string query_string;
public ObjApiBoolean include_deleted;
public int max_results;
public int num_conditions;
public IntPtr search_cond;
public ObjApiBoolean include_content;
public ObjApiBoolean include_metadata;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct ObjSearchCondition
{
public ObjApiSearchOp boolop;
public string join_relation;
public int num_opening_brackets;
public int num_closing_brackets;
public string field;
public string op;
public string value;
}
[StructLayout(LayoutKind.Sequential)]
public struct ObjObjectResults
{
public IntPtr object_handle;
public int num_results;
}
[DllImport(dllPath, EntryPoint = "ObjSearchObject2")]
public static extern int ObjSearchObject2(ref ObjSearchObjectParm objSearchObjectParm, ref ObjObjectResults objObjectResults);
[DllImport(dllPath, EntryPoint = "ObjGetObjectAttr")]
public static extern string ObjGetObjectAttr(IntPtr object_handle, string attr_name);
[DllImport(dllPath, EntryPoint = "ObjFreeObjectHdl")]
public static extern void ObjFreeObjectHdl(IntPtr object_handle);
}
public void Run()
{
ObjectiveNativeAPI.ObjSearchObjectParm rSearchObject = new ObjectiveNativeAPI.ObjSearchObjectParm();
ObjectiveNativeAPI.ObjSearchCondition rSearchCondition = new ObjectiveNativeAPI.ObjSearchCondition();
ObjectiveNativeAPI.ObjObjectResults rSearchResults = new ObjectiveNativeAPI.ObjObjectResults();
rSearchCondition.field = "ancestor";
rSearchCondition.op = "is";
rSearchCondition.value = txtCotainerId.Text;
rSearchObject.obj_defn_ids = new[] {"dotdA9"};
rSearchObject.obj_defn_count = 1;
rSearchObject.num_conditions = 1;
rSearchObject.max_results = 5000;
IntPtr search_cond = Marshal.AllocCoTaskMem(Marshal.SizeOf(rSearchCondition));
Marshal.StructureToPtr(rSearchCondition, search_cond, false);
rSearchObject.search_cond = search_cond;
int result = ObjectiveNativeAPI.ObjSearchObject2(ref rSearchObject, ref rSearchResults);
MessageBox.Show(string.Format("FunctionResult: {0}", result));
Marshal.FreeCoTaskMem(search_cond);
}
I am a bit lost on this. I have managed to translate some other segments of this project but this is causing me grief. Any help around this would be appreciated.

Windows-1252 encoding through byte[]

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.)

How can I get other domains from one domain

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);
}
}

Has anyone used the Win32 API function CredWrite in .NET?

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.

Categories

Resources