// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.
using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Xml;
namespace net.windward.utils.xml
{
///
/// Provides the capability to open and test an xml file regardless of it's filename/url and the
/// security mechanism. This includes most combinations where the calling program is on a domain
/// or workgroup and the xml file is on a domain (same or different), workgroup, http, ftp, or a
/// system with no uname/password required.
/// It does not handle every combination because Windows does not provide the capabilities to do so
/// in an API (even though "net use" can do so).
/// bugbug - need to add schema support for open and test; return an XPathDocument also.
///
public class XmlFileOpen
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private extern static bool CloseHandle(IntPtr handle);
/*
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern UInt32 NetUseAdd(string UncServerName, int Level, ref USE_INFO_2 Buf, out uint ParmError);
[DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint NetUseDel(string UncServerName, string UseName, uint ForceCond);
*/
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct USE_INFO_2
{
public string ui2_local;
public string ui2_remote;
public string ui2_password;
public uint ui2_status;
public uint ui2_asg_type;
public uint ui2_refcount;
public uint ui2_usecount;
public string ui2_username;
public string ui2_domainname;
}
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_NEW_CREDENTIALS = 9;
///
/// Open the XML document using the passed in credentials.
///
/// The name of the file. Can be http(s) or ftp as well as a share or plain old filename.
/// Username to open under. If null then anonymous user.
/// Password to open under.
/// the loaded XML document.
public static XmlDocument OpenXmlDocument(string filename, string username, string password)
{
// handle http(s) and ftp using credentials
string lcName = filename.ToLower();
if (lcName.StartsWith("http:") || lcName.StartsWith("https:") || lcName.StartsWith("ftp:"))
{
XmlDocument xmlDoc = new XmlDocument();
if (username != null)
{
XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = new NetworkCredential(username, password);
xmlDoc.XmlResolver = resolver;
}
xmlDoc.Load(filename);
return xmlDoc;
}
// if anon - try it
if (username == null)
{
// can't do this -- return OpenWithIdentity(filename, WindowsIdentity.GetAnonymous());
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filename);
return xmlDoc;
}
// get the username & domain
string domain, user;
int pos = username.IndexOf('\\');
if (pos == -1)
pos = username.IndexOf('/');
bool onlyDomain;
if (pos != -1)
{
onlyDomain = true;
domain = username.Substring(0, pos);
user = username.Substring(pos + 1);
}
else
{
onlyDomain = false;
WindowsIdentity ident = WindowsIdentity.GetCurrent();
if (ident == null)
domain = null;
else
{
domain = ident.Name;
pos = domain.IndexOf('\\');
if (pos != -1)
domain = domain.Substring(0, pos);
}
user = username;
}
// first we try a domain set of credentials
if (domain != null)
{
try
{
XmlDocument doc = OpenAsDomainUser(filename, domain, user, password);
if (doc != null)
return doc;
}
catch (Exception)
{
if (onlyDomain)
throw;
}
}
if (onlyDomain)
throw new IOException("Could not open: " + filename + " as user: " + domain + "\\" + user);
// get the share name (may not be one) - we don't handle z:\file.xml using local user on Z: system
/* only needed if we have remote local user access
string share = null;
if (filename.StartsWith("\\\\"))
{
pos = filename.IndexOf('\\', 3);
if (pos != -1)
{
pos = filename.IndexOf('\\', pos + 2);
if (pos != -1)
share = filename.Substring(0, pos);
}
}
*/
// second we try as a local user on this system
return OpenAsDomainUser(filename, ".", user, password);
/* No good way to open as a local user on a remote machine
try
{
XmlDocument doc = OpenAsDomainUser(filename, ".", user, password);
if (doc != null)
return doc;
}
catch (Exception)
{
// if not a share - we're done
if (share == null)
throw;
}
// if no share, we're done
if (share == null)
throw new ApplicationException("bugbug");
// third try local user on remote system
return OpenAsRemoteUser(share, filename, username, password);
*/
}
///
/// Open the XML document using the passed in credentials.
///
/// The name of the file. Can be http(s) or ftp as well as a share or plain old filename.
/// Username to open under. If null then anonymous user.
/// Password to open under.
public static void TestXmlFile(string filename, string username, string password)
{
// handle http(s) and ftp using credentials
string lcName = filename.ToLower();
if (lcName.StartsWith("http:") || lcName.StartsWith("https:") || lcName.StartsWith("ftp:"))
{
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.ProhibitDtd = false;
readerSettings.ValidationType = ValidationType.DTD;
if (username != null)
{
XmlUrlResolver resolver = new XmlUrlResolver();
resolver.Credentials = new NetworkCredential(username, password);
readerSettings.XmlResolver = resolver;
}
XmlReader xmlReader = XmlReader.Create(filename, readerSettings);
while (xmlReader.Read())
// nothing - just make sure can read all
;
return;
}
// if anon - try it
if (username == null)
{
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.ProhibitDtd = false;
readerSettings.ValidationType = ValidationType.DTD;
XmlReader xmlReader = XmlReader.Create(filename, readerSettings);
while (xmlReader.Read())
// nothing - just make sure can read all
;
return;
}
// get the username & domain
string domain, user;
int pos = username.IndexOf('\\');
if (pos == -1)
pos = username.IndexOf('/');
bool onlyDomain;
if (pos != -1)
{
onlyDomain = true;
domain = username.Substring(0, pos);
user = username.Substring(pos + 1);
}
else
{
onlyDomain = false;
WindowsIdentity ident = WindowsIdentity.GetCurrent();
Trap.trap(ident == null);
if (ident == null)
domain = null;
else
{
domain = ident.Name;
pos = domain.IndexOf('\\');
if (pos != -1)
domain = domain.Substring(0, pos);
}
user = username;
}
// first we try a domain set of credentials
if (domain != null)
{
try
{
TestAsDomainUser(filename, domain, user, password);
return;
}
catch (Exception)
{
if (onlyDomain)
throw;
}
}
if (onlyDomain)
throw new IOException("Could not test: " + filename + " as user: " + domain + "\\" + user);
// second we try as a local user on this system
TestAsDomainUser(filename, ".", user, password);
}
private static XmlDocument OpenWithIdentity(string filename, WindowsIdentity identity)
{
WindowsImpersonationContext impersonationContext = identity.Impersonate();
try
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filename);
return xmlDoc;
}
finally
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
}
private static XmlDocument OpenAsDomainUser(string filename, string domain, string username, string password)
{
IntPtr tokenHandle = new IntPtr(0);
bool returnValue = LogonUser(username, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref tokenHandle);
if (returnValue == false)
return null;
try
{
WindowsIdentity identity = new WindowsIdentity(tokenHandle);
return OpenWithIdentity(filename, identity);
}
finally
{
CloseHandle(tokenHandle);
}
}
private static void TestAsDomainUser(string filename, string domain, string username, string password)
{
IntPtr tokenHandle = new IntPtr(0);
bool returnValue = LogonUser(username, domain, password, LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref tokenHandle);
if (returnValue == false)
throw new IOException("Could not login as user: " + domain + "\\" + username);
try
{
WindowsIdentity identity = new WindowsIdentity(tokenHandle);
WindowsImpersonationContext impersonationContext = identity.Impersonate();
try
{
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.ProhibitDtd = false;
readerSettings.ValidationType = ValidationType.DTD;
XmlReader xmlReader = XmlReader.Create(filename, readerSettings);
while (xmlReader.Read())
// nothing - just make sure can read all
;
}
finally
{
impersonationContext.Undo();
impersonationContext.Dispose();
}
}
finally
{
CloseHandle(tokenHandle);
}
}
/*
private static XmlDocument OpenAsRemoteUser(string share, string filename, string username, string password)
{
USE_INFO_2 useInfo = new USE_INFO_2();
useInfo.ui2_remote = share;
useInfo.ui2_password = password;
useInfo.ui2_asg_type = 0; //disk drive
useInfo.ui2_usecount = 1;
useInfo.ui2_username = username;
useInfo.ui2_domainname = ".";
uint paramErrorIndex;
uint returnCode = NetUseAdd(String.Empty, 2, ref useInfo, out paramErrorIndex);
if (returnCode != 0)
throw new Win32Exception((int)returnCode);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(filename);
// NetUseDel()
return xmlDoc;
}
*/
}
}