/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved.
The source code in this file is covered under a dual-license scenario:
- RCL: for OPC Foundation members in good-standing
- GPL V2: everybody else
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
GNU General Public License as published by the Free Software Foundation;
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
This source code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using System.Net;
using System.Security.Cryptography.X509Certificates;
namespace Opc.Ua.Configuration
{
///
/// An access rule for an HTTP URL used by a WCF service.
///
[DataContract(Namespace=Namespaces.OpcUaConfig)]
public class HttpAccessRule
{
#region Public Properties
///
/// The access right affected by the rule.
///
[DataMember(Order = 0)]
public string UrlPrefix
{
get { return m_urlPrefix; }
set { m_urlPrefix = value; }
}
///
/// The access right affected by the rule.
///
[DataMember(Order = 1)]
public ApplicationAccessRight Right
{
get { return m_right; }
set { m_right = value; }
}
///
/// The name of the NT account principal which the access rule applies to.
///
[DataMember(Order = 3)]
public String IdentityName
{
get { return m_identityName; }
set { m_identityName = value; }
}
#endregion
#region WIN32 Declarations
private enum HTTP_SERVICE_CONFIG_QUERY_TYPE : int
{
HttpServiceConfigQueryExact = 0,
HttpServiceConfigQueryNext = 1
}
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_URLACL_KEY
{
[MarshalAs(UnmanagedType.LPWStr)]
public string pUrlPrefix;
}
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_URLACL_QUERY
{
public HTTP_SERVICE_CONFIG_QUERY_TYPE QueryDesc;
public HTTP_SERVICE_CONFIG_URLACL_KEY KeyDesc;
public int dwToken;
}
private struct HTTP_SERVICE_CONFIG_SSL_QUERY
{
public HTTP_SERVICE_CONFIG_QUERY_TYPE QueryDesc;
public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
public int dwToken;
};
[StructLayout(LayoutKind.Sequential)]
private struct sockaddr_in
{
public short sin_family;
public ushort sin_port;
public uint sin_addr;
public ulong sin_zero;
};
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_SSL_KEY
{
public IntPtr pIpPort;
}
const Int16 AF_INET = 2;
const Int16 AF_INET6 = 23;
[StructLayout(LayoutKind.Sequential)]
struct SOCKADDR_IN
{
public Int16 family;
public UInt16 port;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U1)]
public Byte[] addr;
public Int32 nothing;
}
[StructLayout(LayoutKind.Sequential)]
struct SOCKADDR_IN6
{
public Int16 family;
public UInt16 port;
public Int32 flowInfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16, ArraySubType = UnmanagedType.U1)]
public Byte[] addr;
public Int32 scopeID;
}
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
private struct HTTP_SERVICE_CONFIG_SSL_PARAM
{
public int SslHashLength;
public IntPtr pSslHash;
public Guid AppId;
[MarshalAs(UnmanagedType.LPWStr)]
public string pSslCertStoreName;
public uint DefaultCertCheckMode;
public int DefaultRevocationFreshnessTime;
public int DefaultRevocationUrlRetrievalTimeout;
[MarshalAs(UnmanagedType.LPWStr)]
public string pDefaultSslCtlIdentifier;
[MarshalAs(UnmanagedType.LPWStr)]
public string pDefaultSslCtlStoreName;
public uint DefaultFlags;
}
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_SSL_SET
{
public HTTP_SERVICE_CONFIG_SSL_KEY KeyDesc;
public HTTP_SERVICE_CONFIG_SSL_PARAM ParamDesc;
}
private enum HttpError : int
{
NO_ERROR = 0,
ERROR_FILE_NOT_FOUND = 2,
ERROR_INVALID_DATA = 13,
ERROR_HANDLE_EOF = 38,
ERROR_INVALID_PARAMETER = 87,
ERROR_INSUFFICIENT_BUFFER = 122,
ERROR_ALREADY_EXISTS = 183,
ERROR_NO_MORE_ITEMS = 259,
ERROR_INVALID_DLL = 1154,
ERROR_NOT_FOUND = 1168
}
private const int HTTP_SERVICE_CONFIG_SSL_FLAG_USE_DS_MAPPER = 0x00000001;
private const int HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT = 0x00000002;
private const int HTTP_SERVICE_CONFIG_SSL_FLAG_NO_RAW_FILTER = 0x00000004;
private enum HTTP_SERVICE_CONFIG_ID : int
{
HttpServiceConfigIPListenList = 0,
HttpServiceConfigSSLCertInfo = 1,
HttpServiceConfigUrlAclInfo = 2
}
///
/// Declares the native methods used by the class.
///
private static class NativeMethods
{
[DllImport("Httpapi.dll", SetLastError = true)]
public static extern HttpError HttpQueryServiceConfiguration(
IntPtr ServiceHandle,
HTTP_SERVICE_CONFIG_ID ConfigId,
IntPtr pInputConfigInfo,
int InputConfigInfoLength,
IntPtr pOutputConfigInfo,
int OutputConfigInfoLength,
out int pReturnLength,
IntPtr pOverlapped);
[DllImport("Httpapi.dll", SetLastError = true)]
public static extern HttpError HttpSetServiceConfiguration(
IntPtr ServiceHandle,
HTTP_SERVICE_CONFIG_ID ConfigId,
IntPtr pConfigInformation,
int ConfigInformationLength,
IntPtr pOverlapped);
[DllImport("Httpapi.dll", SetLastError = true)]
public static extern HttpError HttpDeleteServiceConfiguration(
IntPtr ServiceHandle,
HTTP_SERVICE_CONFIG_ID ConfigId,
IntPtr pConfigInformation,
int ConfigInformationLength,
IntPtr pOverlapped);
[DllImport("Kernel32.dll", EntryPoint = "RtlZeroMemory", SetLastError = true)]
public static extern void ZeroMemory(IntPtr dest, int size);
[DllImport("Httpapi.dll", SetLastError = true)]
public static extern HttpError HttpInitialize(
HTTPAPI_VERSION version,
HttpInitFlag flags,
IntPtr reserved);
[DllImport("Httpapi.dll", SetLastError = true)]
public static extern HttpError HttpTerminate(
HttpInitFlag flags,
IntPtr reserved);
}
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_URLACL_PARAM
{
[MarshalAs(UnmanagedType.LPWStr)]
public string pStringSecurityDescriptor;
}
[StructLayout(LayoutKind.Sequential)]
private struct HTTP_SERVICE_CONFIG_URLACL_SET
{
public HTTP_SERVICE_CONFIG_URLACL_KEY KeyDesc;
public HTTP_SERVICE_CONFIG_URLACL_PARAM ParamDesc;
}
private enum HttpInitFlag : int
{
HTTP_INITIALIZE_SERVER = 1,
HTTP_INITIALIZE_CONFIG = 2
}
[StructLayout(LayoutKind.Sequential, Pack=2)]
private struct HTTPAPI_VERSION
{
public HTTPAPI_VERSION(ushort majorVersion, ushort minorVersion)
{
major = majorVersion;
minor = minorVersion;
}
public ushort major;
public ushort minor;
}
#endregion
///
/// Fetches the current SSL certificate configuration.
///
public static List GetSslCertificateBindings()
{
List bindings = new List();
// initialize library.
HttpError error = NativeMethods.HttpInitialize(
new HTTPAPI_VERSION(1,0),
HttpInitFlag.HTTP_INITIALIZE_CONFIG,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not initialize HTTP library.\r\nError={0}",
error);
}
// set up the iterator.
HTTP_SERVICE_CONFIG_SSL_QUERY query = new HTTP_SERVICE_CONFIG_SSL_QUERY();
query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryNext;
query.KeyDesc.pIpPort = IntPtr.Zero;
IntPtr pInput = Marshal.AllocHGlobal(Marshal.SizeOf());
NativeMethods.ZeroMemory(pInput, Marshal.SizeOf());
IntPtr pOutput = IntPtr.Zero;
try
{
// loop through each record.
for (query.dwToken = 0; error == HttpError.NO_ERROR; query.dwToken++)
{
// get the size of buffer to allocate.
Marshal.StructureToPtr(query, pInput, true);
int requiredBufferLength = 0;
error = NativeMethods.HttpQueryServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pInput,
Marshal.SizeOf(),
pOutput,
requiredBufferLength,
out requiredBufferLength,
IntPtr.Zero);
if (error == HttpError.ERROR_NO_MORE_ITEMS)
{
break;
}
if (error != HttpError.ERROR_INSUFFICIENT_BUFFER)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not read SSL configuration information.\r\nError={0}",
error);
}
// allocate the buffer.
pOutput = Marshal.AllocHGlobal(requiredBufferLength);
NativeMethods.ZeroMemory(pOutput, requiredBufferLength);
// get the actual data.
error = NativeMethods.HttpQueryServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pInput,
Marshal.SizeOf(),
pOutput,
requiredBufferLength,
out requiredBufferLength,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not read SSL configuration information.\r\nError={0}",
error);
}
HTTP_SERVICE_CONFIG_SSL_SET sslSet = (HTTP_SERVICE_CONFIG_SSL_SET)Marshal.PtrToStructure< HTTP_SERVICE_CONFIG_SSL_SET>(pOutput);
short family = Marshal.ReadInt16(sslSet.KeyDesc.pIpPort);
SslCertificateBinding binding = new SslCertificateBinding();
if (family == AF_INET)
{
SOCKADDR_IN inet = (SOCKADDR_IN)Marshal.PtrToStructure(sslSet.KeyDesc.pIpPort);
binding.IPAddress = new IPAddress(inet.addr);
binding.Port = inet.port;
}
if (family == AF_INET6)
{
SOCKADDR_IN6 inet = (SOCKADDR_IN6)Marshal.PtrToStructure(sslSet.KeyDesc.pIpPort);
binding.IPAddress = new IPAddress(inet.addr, inet.scopeID);
binding.Port = inet.port;
}
binding.Port = (ushort)(((binding.Port & 0xFF00) >> 8) | ((binding.Port & 0x00FF) << 8));
byte[] bytes = new byte[sslSet.ParamDesc.SslHashLength];
Marshal.Copy(sslSet.ParamDesc.pSslHash, bytes, 0, bytes.Length);
binding.Thumbprint = Utils.ToHexString(bytes);
binding.ApplicationId = sslSet.ParamDesc.AppId;
binding.StoreName = sslSet.ParamDesc.pSslCertStoreName;
binding.DefaultCertCheckMode = sslSet.ParamDesc.DefaultCertCheckMode;
binding.DefaultRevocationFreshnessTime = sslSet.ParamDesc.DefaultRevocationFreshnessTime;
binding.DefaultRevocationUrlRetrievalTimeout = sslSet.ParamDesc.DefaultRevocationUrlRetrievalTimeout;
binding.DefaultSslCtlIdentifier = sslSet.ParamDesc.pDefaultSslCtlIdentifier;
binding.DefaultSslCtlStoreName = sslSet.ParamDesc.pDefaultSslCtlStoreName;
binding.DefaultFlags = sslSet.ParamDesc.DefaultFlags;
bindings.Add(binding);
Marshal.FreeHGlobal(pOutput);
pOutput = IntPtr.Zero;
}
}
finally
{
if (pInput != IntPtr.Zero)
{
Marshal.DestroyStructure(pInput);
Marshal.FreeHGlobal(pInput);
}
if (pOutput != IntPtr.Zero)
{
Marshal.FreeHGlobal(pOutput);
}
NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
}
return bindings;
}
///
/// Serializes an IPAddress.
///
private static IntPtr ToIntPtr(IPAddress address, ushort port)
{
IntPtr pAddress = IntPtr.Zero;
if (address == null)
{
return pAddress;
}
if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
SOCKADDR_IN inet = new SOCKADDR_IN();
inet.family = AF_INET;
inet.addr = address.GetAddressBytes();
inet.port = (ushort)(((port & 0xFF00) >> 8) | ((port & 0x00FF) << 8));
pAddress = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(inet, pAddress, false);
}
else if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
{
SOCKADDR_IN6 inet = new SOCKADDR_IN6();
inet.family = AF_INET6;
inet.addr = address.GetAddressBytes();
inet.port = (ushort)(((port & 0xFF00) >> 8) | ((port & 0x00FF) << 8));
inet.scopeID = (int)address.ScopeId;
pAddress = Marshal.AllocHGlobal(Marshal.SizeOf());
Marshal.StructureToPtr(inet, pAddress, false);
}
return pAddress;
}
///
/// Creates a new SSL certificate binding.
///
public static void SetSslCertificateBinding(SslCertificateBinding binding)
{
if (binding == null) throw new ArgumentNullException("binding");
// initialize library.
HttpError error = NativeMethods.HttpInitialize(
new HTTPAPI_VERSION(1,0),
HttpInitFlag.HTTP_INITIALIZE_CONFIG,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not initialize HTTP library.\r\nError={0}",
error);
}
IntPtr pAddress = IntPtr.Zero;
IntPtr pThumprint = IntPtr.Zero;
IntPtr pConfigInfo = IntPtr.Zero;
try
{
pAddress = ToIntPtr(binding.IPAddress, binding.Port);
byte[] thumbprint = Utils.FromHexString(binding.Thumbprint);
pThumprint = Marshal.AllocCoTaskMem(thumbprint.Length);
Marshal.Copy(thumbprint, 0, pThumprint, thumbprint.Length);
HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET();
configSslSet.KeyDesc.pIpPort = pAddress;
configSslSet.ParamDesc.pSslHash = pThumprint;
configSslSet.ParamDesc.SslHashLength = thumbprint.Length;
configSslSet.ParamDesc.AppId = binding.ApplicationId;
configSslSet.ParamDesc.pSslCertStoreName = binding.StoreName;
configSslSet.ParamDesc.DefaultCertCheckMode = binding.DefaultCertCheckMode;
configSslSet.ParamDesc.DefaultFlags = binding.DefaultFlags;
configSslSet.ParamDesc.DefaultRevocationFreshnessTime = binding.DefaultRevocationFreshnessTime;
configSslSet.ParamDesc.DefaultRevocationUrlRetrievalTimeout = binding.DefaultRevocationUrlRetrievalTimeout;
configSslSet.ParamDesc.pDefaultSslCtlIdentifier = binding.DefaultSslCtlIdentifier;
configSslSet.ParamDesc.pDefaultSslCtlStoreName = binding.DefaultSslCtlStoreName;
int size = Marshal.SizeOf();
pConfigInfo = Marshal.AllocCoTaskMem(size);
Marshal.StructureToPtr(configSslSet, pConfigInfo, false);
error = NativeMethods.HttpSetServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pConfigInfo,
size,
IntPtr.Zero);
if (error == HttpError.ERROR_ALREADY_EXISTS)
{
error = NativeMethods.HttpDeleteServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pConfigInfo,
size,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not delete existing SSL certificate binding.\r\nError={0}",
error);
}
error = NativeMethods.HttpSetServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pConfigInfo,
size,
IntPtr.Zero);
}
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not create SSL certificate binding.\r\nError={0}",
error);
}
}
finally
{
if (pConfigInfo != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pConfigInfo);
}
if (pAddress != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pAddress);
}
if (pThumprint != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pThumprint);
}
NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
}
}
///
/// Deletes a new SSL certificate binding.
///
public static void DeleteSslCertificateBinding(IPAddress address, ushort port)
{
if (address == null) throw new ArgumentNullException("address");
// initialize library.
HttpError error = NativeMethods.HttpInitialize(
new HTTPAPI_VERSION(1, 0),
HttpInitFlag.HTTP_INITIALIZE_CONFIG,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not initialize HTTP library.\r\nError={0}",
error);
}
IntPtr pAddress = IntPtr.Zero;
IntPtr pConfigInfo = IntPtr.Zero;
try
{
pAddress = ToIntPtr(address, port);
HTTP_SERVICE_CONFIG_SSL_SET configSslSet = new HTTP_SERVICE_CONFIG_SSL_SET();
configSslSet.KeyDesc.pIpPort = pAddress;
int size = Marshal.SizeOf();
pConfigInfo = Marshal.AllocCoTaskMem(size);
Marshal.StructureToPtr(configSslSet, pConfigInfo, false);
error = NativeMethods.HttpDeleteServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigSSLCertInfo,
pConfigInfo,
size,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not delete existing SSL certificate binding.\r\nError={0}",
error);
}
}
finally
{
if (pConfigInfo != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pConfigInfo);
}
if (pAddress != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pAddress);
}
NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
}
}
#region Static Methods
///
/// Gets the application access rules for the specified URL.
///
public static IList GetAccessRules(string url)
{
List accessRules = new List();
HttpError error = NativeMethods.HttpInitialize(
new HTTPAPI_VERSION(1, 0),
HttpInitFlag.HTTP_INITIALIZE_CONFIG,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not initialize HTTP library.\r\nError={0}",
error);
}
HTTP_SERVICE_CONFIG_URLACL_QUERY query = new HTTP_SERVICE_CONFIG_URLACL_QUERY();
query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryNext;
if (!String.IsNullOrEmpty(url))
{
query.QueryDesc = HTTP_SERVICE_CONFIG_QUERY_TYPE.HttpServiceConfigQueryExact;
query.KeyDesc.pUrlPrefix = url;
}
IntPtr pInput = Marshal.AllocHGlobal(Marshal.SizeOf());
NativeMethods.ZeroMemory(pInput, Marshal.SizeOf());
IntPtr pOutput = IntPtr.Zero;
try
{
for (query.dwToken = 0; error == HttpError.NO_ERROR; query.dwToken++)
{
Marshal.StructureToPtr(query, pInput, true);
int requiredBufferLength = 0;
error = NativeMethods.HttpQueryServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
pInput,
Marshal.SizeOf(),
pOutput,
requiredBufferLength,
out requiredBufferLength,
IntPtr.Zero);
if (error == HttpError.ERROR_NO_MORE_ITEMS)
{
break;
}
if (!String.IsNullOrEmpty(url))
{
if (error == HttpError.ERROR_FILE_NOT_FOUND)
{
break;
}
}
if (error != HttpError.ERROR_INSUFFICIENT_BUFFER)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not read access rules for HTTP url.\r\nError={1}, Url={0}",
url,
error);
}
pOutput = Marshal.AllocHGlobal(requiredBufferLength);
NativeMethods.ZeroMemory(pOutput, requiredBufferLength);
error = NativeMethods.HttpQueryServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
pInput,
Marshal.SizeOf(),
pOutput,
requiredBufferLength,
out requiredBufferLength,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not read access rules for HTTP url.\r\nError={1}, Url={0}",
url,
error);
}
HTTP_SERVICE_CONFIG_URLACL_SET result = (HTTP_SERVICE_CONFIG_URLACL_SET)Marshal.PtrToStructure(pOutput);
// parse the SDDL and update the access list.
ParseSddl(result.KeyDesc.pUrlPrefix, result.ParamDesc.pStringSecurityDescriptor, accessRules);
Marshal.FreeHGlobal(pOutput);
pOutput = IntPtr.Zero;
// all done if requesting the results for a single url.
if (!String.IsNullOrEmpty(url))
{
break;
}
}
}
finally
{
if (pInput != IntPtr.Zero)
{
Marshal.DestroyStructure(pInput);
Marshal.FreeHGlobal(pInput);
}
if (pOutput != IntPtr.Zero)
{
Marshal.FreeHGlobal(pOutput);
}
NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
}
return accessRules;
}
///
/// Sets the application access rules for the specified URL.
///
public static void SetAccessRules(string url, IList rules, bool replaceExisting)
{
HttpError error = NativeMethods.HttpInitialize(
new HTTPAPI_VERSION(1, 0),
HttpInitFlag.HTTP_INITIALIZE_CONFIG,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not initialize HTTP library.\r\nError={0}",
error);
}
// fetch existing rules if not replacing them.
if (!replaceExisting)
{
IList existingRules = GetAccessRules(url);
if (existingRules.Count > 0)
{
List mergedRules = new List(existingRules);
mergedRules.AddRange(rules);
rules = mergedRules;
}
}
HTTP_SERVICE_CONFIG_URLACL_SET update = new HTTP_SERVICE_CONFIG_URLACL_SET();
update.KeyDesc.pUrlPrefix = url;
update.ParamDesc.pStringSecurityDescriptor = FormatSddl(rules);
IntPtr pStruct = IntPtr.Zero;
int updateSize = Marshal.SizeOf();
try
{
pStruct = Marshal.AllocHGlobal(updateSize);
NativeMethods.ZeroMemory(pStruct, updateSize);
Marshal.StructureToPtr(update, pStruct, false);
error = NativeMethods.HttpDeleteServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
pStruct,
updateSize,
IntPtr.Zero);
if (error != HttpError.ERROR_FILE_NOT_FOUND && error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not delete existing access rules for HTTP url.\r\nError={1}, Url={0}",
url,
error);
}
if (rules.Count > 0)
{
error = NativeMethods.HttpSetServiceConfiguration(
IntPtr.Zero,
HTTP_SERVICE_CONFIG_ID.HttpServiceConfigUrlAclInfo,
pStruct,
updateSize,
IntPtr.Zero);
if (error != HttpError.NO_ERROR)
{
throw ServiceResultException.Create(
StatusCodes.BadUnexpectedError,
"Could not set the access rules for HTTP url.\r\nError={1}, Url={0}",
url,
error);
}
}
}
finally
{
if (pStruct != IntPtr.Zero)
{
Marshal.DestroyStructure(pStruct);
Marshal.FreeHGlobal(pStruct);
}
NativeMethods.HttpTerminate(HttpInitFlag.HTTP_INITIALIZE_CONFIG, IntPtr.Zero);
}
}
///
/// Sets the application access rules for the specified URL (replaces the hostname with a wildcard).
///
public static void SetAccessRules(Uri url, IList accessRules, bool replaceExisting)
{
StringBuilder wildcard = new StringBuilder();
wildcard.Append(url.Scheme);
wildcard.Append("://+:");
wildcard.Append(url.Port);
wildcard.Append(url.PathAndQuery);
List httpRules = new List();
foreach (ApplicationAccessRule accessRule in accessRules)
{
// urls do not support deny rules.
if (accessRule.RuleType == AccessControlType.Deny)
{
continue;
}
string identityName = accessRule.IdentityName;
if (accessRule.IdentityName.StartsWith("S-"))
{
Utils.Trace("Could not translate SID: {0}", accessRule.IdentityName);
continue;
}
HttpAccessRule httpRule = new HttpAccessRule();
httpRule.Right = accessRule.Right;
httpRule.IdentityName = identityName;
httpRules.Add(httpRule);
}
SetAccessRules(wildcard.ToString(), httpRules, replaceExisting);
}
#endregion
#region Private Methods
///
/// Extracts the access rules from the SDDL string.
///
private static void ParseSddl(string url, string sddl, List accessRules)
{
IList entities = AccessControlEntity.Parse(sddl);
for (int ii = 0; ii < entities.Count; ii++)
{
AccessControlEntity entity = entities[ii];
if (entity.AccessType != "A")
{
continue;
}
ApplicationAccessRight rights = ApplicationAccessRight.None;
switch (entity.Rights)
{
case "GA":
case "GXGW":
case "GWGX":
{
rights = ApplicationAccessRight.Configure;
break;
}
case "GX":
{
rights = ApplicationAccessRight.Run;
break;
}
}
if (rights == ApplicationAccessRight.None)
{
continue;
}
}
}
///
/// Extracts the access rules from the SDDL string.
///
private static string FormatSddl(IList accessRules)
{
StringBuilder builder = new StringBuilder();
builder.Append("D:");
for (int ii = 0; ii < accessRules.Count; ii++)
{
builder.Append("("); // start of ACE
builder.Append("A;"); // access type
builder.Append(";"); // flags
switch (accessRules[ii].Right)
{
case ApplicationAccessRight.Configure:
{
builder.Append("GXGW;"); // rights
break;
}
case ApplicationAccessRight.Run:
case ApplicationAccessRight.Update:
{
builder.Append("GX;"); // rights
break;
}
}
builder.Append(";"); // object guid
builder.Append(";"); // inherited object guid
builder.Append(')'); // end of ace.
}
return builder.ToString();
}
#endregion
#region Private Fields
private string m_urlPrefix;
private ApplicationAccessRight m_right;
private string m_identityName;
#endregion
}
#region AccessControlEntity Class
///
/// A class that stores the components of ACE within a DACL.
///
public class AccessControlEntity
{
///
/// The access type granted by the ACE.
///
public string AccessType
{
get { return m_accessType; }
set { m_accessType = value; }
}
///
/// Any flags associated with the ACE.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1726:UsePreferredTerms", MessageId = "Flags")]
public string Flags
{
get { return m_flags; }
set { m_flags = value; }
}
///
/// The rights allowed/restricted by the ACE.
///
public string Rights
{
get { return m_rights; }
set { m_rights = value; }
}
///
/// The object associated with the ACE.
///
public string ObjectGuid
{
get { return m_objectGuid; }
set { m_objectGuid = value; }
}
///
/// The inherited object associated with the ACE.
///
public string InheritObjectGuid
{
get { return m_inheritObjectGuid; }
set { m_inheritObjectGuid = value; }
}
///
/// The SID for the account which is affected by the ACE.
///
public string AccountSid
{
get { return m_accountSid; }
set { m_accountSid = value; }
}
///
/// Extracts a list of ACEs from a SDDL string.
///
public static IList Parse(string sddl)
{
List entities = new List();
if (!sddl.StartsWith("D:", StringComparison.Ordinal))
{
throw new ArgumentException(Utils.Format("Could not parse SDDL string: {0}", sddl));
}
sddl = sddl.Substring(2, sddl.Length-2);
string[] aces = sddl.Split('(', ')');
for (int ii = 0; ii < aces.Length; ii++)
{
if (String.IsNullOrEmpty(aces[ii]))
{
continue;
}
AccessControlEntity entity = new AccessControlEntity();
entity.Initialize(aces[ii]);
entities.Add(entity);
}
return entities;
}
///
/// Extracts a single ACE from a SDDL string fragment.
///
public void Initialize(string sddl)
{
string[] fields = sddl.Split(';');
if (fields.Length != 6)
{
throw new ArgumentException(Utils.Format("Could not parse SDDL ACE string: {0}", sddl));
}
m_accessType = fields[0];
m_flags = fields[1];
m_rights = fields[2];
m_objectGuid = fields[3];
m_inheritObjectGuid = fields[4];
m_accountSid = fields[5];
}
private string m_accessType;
private string m_flags;
private string m_rights;
private string m_objectGuid;
private string m_inheritObjectGuid;
private string m_accountSid;
}
#endregion
#region SslCertificateBinding Class
///
/// Stores the details of an SSL certification configuration binding.
///
public class SslCertificateBinding
{
///
/// The IP Address.
///
public IPAddress IPAddress { get; set; }
///
/// The port number.
///
public ushort Port { get; set; }
///
/// The certificate thumbprint.
///
public string Thumbprint { get; set; }
///
/// The application id.
///
public Guid ApplicationId { get; set; }
///
/// The names of the store to use.
///
public string StoreName { get; set; }
///
/// The default revocation check mode.
///
public uint DefaultCertCheckMode { get; set; }
///
/// The default revocation freshness time.
///
public int DefaultRevocationFreshnessTime { get; set; }
///
/// The default revocation URL timeout.
///
public int DefaultRevocationUrlRetrievalTimeout { get; set; }
///
/// The default certificate trust list identifier.
///
public string DefaultSslCtlIdentifier { get; set; }
///
/// The default certificate trust list store.
///
public string DefaultSslCtlStoreName { get; set; }
///
/// The default flags.
///
public uint DefaultFlags { get; set; }
}
#endregion
}