This commit is contained in:
MichaelGrafnetter 2016-10-01 21:58:38 +02:00
parent e21c85812f
commit 1c8d6dbde8
11 changed files with 168 additions and 74 deletions

View File

@ -1,3 +1,7 @@
Version 2.18
- [Module] The Get-ADReplAccount cmdlet now correctly reports the access denied error.
- [Framework] Replication errors are now reported using more suitable exception types.
Version 2.17.1
- [Module] Fixed a bug in progress reporting of the Get-ADReplAccount cmdlet.

View File

@ -4,6 +4,9 @@
[Serializable]
public abstract class DirectoryException : Exception
{
public DirectoryException(Exception innerException = null) : base(null, innerException)
{
}
}
}

View File

@ -13,7 +13,7 @@ namespace DSInternals.Common.Exceptions
private set;
}
public DirectoryObjectException(object objectIdentifier)
public DirectoryObjectException(object objectIdentifier, Exception innerException = null) : base(innerException)
{
this.ObjectIdentifier = objectIdentifier;
}

View File

@ -7,8 +7,8 @@
[Serializable]
public sealed class DirectoryObjectNotFoundException : DirectoryObjectException
{
public DirectoryObjectNotFoundException(object objectIdentifier)
: base(objectIdentifier)
public DirectoryObjectNotFoundException(object objectIdentifier = null, Exception innerExcetion = null)
: base(objectIdentifier, innerExcetion)
{
}
@ -16,7 +16,9 @@
{
get
{
return String.Format(Resources.ObjectNotFoundMessageFormat,this.ObjectIdentifier);
return this.ObjectIdentifier != null ?
String.Format(Resources.ObjectWithIdentityNotFoundMessageFormat, this.ObjectIdentifier) :
Resources.ObjectNotFoundMessage;
}
}
}

View File

@ -11,11 +11,12 @@
get;
private set;
}
public SchemaAttributeNotFoundException(string attributeName)
public SchemaAttributeNotFoundException(string attributeName) : base(null)
{
this.AttributeIdentifier = attributeName;
}
public SchemaAttributeNotFoundException(int attributeId)
: base(null)
{
this.AttributeIdentifier = attributeId;
}

View File

@ -620,7 +620,7 @@
/// <summary>
/// The volume label you entered exceeds the label character limit of the target file system.
/// </summary>
LABEL_TOO_Int32 = 154,
LABEL_TOO_LONG = 154,
/// <summary>
/// Cannot create another thread.
@ -811,7 +811,7 @@
/// <summary>
/// The global filename characters, * or ?, are entered incorrectly or too many global filename characters are specified.
/// </summary>
META_EXPANSION_TOO_Int32 = 208,
META_EXPANSION_TOO_LONG = 208,
/// <summary>
/// The signal being posted is not correct.
@ -1735,7 +1735,7 @@
/// <summary>
/// The format of the specified password is invalid.
/// </summary>
INVALID_PASSUInt16NAME = 1216,
INVALID_PASSWORDNAME = 1216,
/// <summary>
/// The format of the specified message name is invalid.
@ -2083,7 +2083,7 @@
/// <summary>
/// The password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a NULL string.
/// </summary>
NULL_LM_PASSUInt16 = 1304,
NULL_LM_PASSWORD = 1304,
/// <summary>
/// The revision level is unknown.
@ -2213,7 +2213,7 @@
/// <summary>
/// Logon failure: the specified account password has expired.
/// </summary>
PASSUInt16_EXPIRED = 1330,
PASSWORD_EXPIRED = 1330,
/// <summary>
/// Logon failure: account currently disabled.
@ -2468,7 +2468,7 @@
/// <summary>
/// The length of a secret exceeds the maximum length allowed.
/// </summary>
SECRET_TOO_Int32 = 1382,
SECRET_TOO_LONG = 1382,
/// <summary>
/// The local security authority database contains an internal inconsistency.
@ -3315,7 +3315,7 @@
/// <summary>
/// The string is too long.
/// </summary>
RPC_S_STRING_TOO_Int32 = 1743,
RPC_S_STRING_TOO_LONG = 1743,
/// <summary>
/// The RPC protocol sequence was not found.
@ -3470,7 +3470,7 @@
/// <summary>
/// The file containing the character translation table has fewer than 512 bytes.
/// </summary>
RPC_X_SS_CHAR_TRANS_Int16_FILE = 1774,
RPC_X_SS_CHAR_TRANS_LONG_FILE = 1774,
/// <summary>
/// A null context handle was passed from the client to the host during a remote procedure call.
@ -3805,7 +3805,7 @@
/// <summary>
/// The user's password must be changed before logging on the first time.
/// </summary>
PASSUInt16_MUST_CHANGE = 1907,
PASSWORD_MUST_CHANGE = 1907,
/// <summary>
/// Could not find the domain controller for this domain.
@ -4050,12 +4050,12 @@
/// <summary>
/// The network connection was made successfully, but the user had to be prompted for a password other than the one originally specified.
/// </summary>
CONNECTED_OTHER_PASSUInt16 = 2108,
CONNECTED_OTHER_PASSWORD = 2108,
/// <summary>
/// The network connection was made successfully using default credentials.
/// </summary>
CONNECTED_OTHER_PASSUInt16_DEFAULT = 2109,
CONNECTED_OTHER_PASSWORD_DEFAULT = 2109,
/// <summary>
/// The specified username is invalid.
@ -5984,12 +5984,12 @@
/// <summary>
/// The name is too long.
/// </summary>
DS_NAME_TOO_Int32 = 8348,
DS_NAME_TOO_LONG = 8348,
/// <summary>
/// The name value is too long.
/// </summary>
DS_NAME_VALUE_TOO_Int32 = 8349,
DS_NAME_VALUE_TOO_LONG = 8349,
/// <summary>
/// The directory service encountered an error parsing a name.
@ -6009,7 +6009,7 @@
/// <summary>
/// The security descriptor is too short.
/// </summary>
DS_SEC_DESC_TOO_Int16 = 8353,
DS_SEC_DESC_TOO_LONG = 8353,
/// <summary>
/// The security descriptor is invalid.
@ -6890,7 +6890,7 @@
/// <summary>
/// Security Account Manager needs to get the boot password.
/// </summary>
DS_SAM_NEED_BOOTKEY_PASSUInt16 = 8529,
DS_SAM_NEED_BOOTKEY_PASSWORD = 8529,
/// <summary>
/// Security Account Manager needs to get the boot key from floppy disk.
@ -8006,7 +8006,7 @@
/// <summary>
/// Name component or name was too long.
/// </summary>
WSAENAMETOOInt32 = 10063,
WSAENAMETOOLONG = 10063,
/// <summary>
/// A socket operation failed because the destination host was down.
@ -8657,7 +8657,7 @@
/// <summary>
/// Assembly Protection Error : The public key for an assembly was too short to be allowed.
/// </summary>
SXS_PROTECTION_PUBLIC_KEY_TOO_Int16 = 14075,
SXS_PROTECTION_PUBLIC_KEY_TOO_LONG = 14075,
/// <summary>
/// Assembly Protection Error : The catalog for an assembly is not valid, or does not match the assembly's manifest.

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Runtime Version:4.0.30319.34209
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -124,11 +124,11 @@ namespace DSInternals.Common.Properties {
}
/// <summary>
/// Looks up a localized string similar to Object with identity &apos;{0}&apos; has not been found..
/// Looks up a localized string similar to Could not find the requested object..
/// </summary>
public static string ObjectNotFoundMessageFormat {
public static string ObjectNotFoundMessage {
get {
return ResourceManager.GetString("ObjectNotFoundMessageFormat", resourceCulture);
return ResourceManager.GetString("ObjectNotFoundMessage", resourceCulture);
}
}
@ -141,6 +141,15 @@ namespace DSInternals.Common.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Object with identity &apos;{0}&apos; has not been found..
/// </summary>
public static string ObjectWithIdentityNotFoundMessageFormat {
get {
return ResourceManager.GetString("ObjectWithIdentityNotFoundMessageFormat", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} (Object identity: &apos;{1}&apos;).
/// </summary>

View File

@ -138,12 +138,15 @@
<data name="ObjectNotAccountMessage" xml:space="preserve">
<value>Object is not an account.</value>
</data>
<data name="ObjectNotFoundMessageFormat" xml:space="preserve">
<value>Object with identity '{0}' has not been found.</value>
<data name="ObjectNotFoundMessage" xml:space="preserve">
<value>Could not find the requested object.</value>
</data>
<data name="ObjectNotSecurityPrincipalMessage" xml:space="preserve">
<value>Object is not a security principal.</value>
</data>
<data name="ObjectWithIdentityNotFoundMessageFormat" xml:space="preserve">
<value>Object with identity '{0}' has not been found.</value>
</data>
<data name="OperationExceptionMessageFormat" xml:space="preserve">
<value>{0} (Object identity: '{1}')</value>
</data>

View File

@ -1,4 +1,5 @@
using DSInternals.Common.Cryptography;
using DSInternals.Common.Exceptions;
using DSInternals.Common.Interop;
using DSInternals.Common.Properties;
using System;
@ -36,6 +37,8 @@ namespace DSInternals.Common
break;
case Win32ErrorCode.ACCESS_DENIED:
case Win32ErrorCode.DS_DRA_ACCESS_DENIED:
case Win32ErrorCode.WRONG_PASSWORD:
case Win32ErrorCode.PASSWORD_EXPIRED:
exceptionToThrow = new UnauthorizedAccessException(genericException.Message, genericException);
break;
case Win32ErrorCode.NOT_ENOUGH_MEMORY:
@ -50,6 +53,11 @@ namespace DSInternals.Common
case Win32ErrorCode.RPC_S_CALL_FAILED:
exceptionToThrow = new ActiveDirectoryServerDownException(genericException.Message, genericException);
break;
case Win32ErrorCode.DS_OBJ_NOT_FOUND:
// This error code means either a non-existing DN or Access Denied.
case Win32ErrorCode.DS_DRA_BAD_DN:
exceptionToThrow = new DirectoryObjectNotFoundException(null, genericException);
break;
// TODO: Add translation for ActiveDirectoryOperationException and for other exception types.
default:
// We were not able to translate the Win32Exception to a more specific type.

View File

@ -3,6 +3,7 @@
#include "RpcTypeConverter.h"
using namespace DSInternals::Common;
using namespace DSInternals::Common::Exceptions;
using namespace DSInternals::Common::Interop;
using namespace DSInternals::Replication::Model;
@ -227,6 +228,8 @@ namespace DSInternals
}
ReplicaObject^ DrsConnection::ReplicateSingleObject(String^ distinguishedName, array<ATTRTYP>^ partialAttributeSet)
{
try
{
auto request = CreateReplicateSingleRequest(distinguishedName, partialAttributeSet);
auto reply = GetNCChanges(move(request));
@ -234,6 +237,22 @@ namespace DSInternals
// TODO: Assert objects.Count == 1; It is guaranteed that it is > 0
return objects[0];
}
catch (DirectoryObjectNotFoundException^)
{
// ReplicateSingleObject also exits with this error when access is denied, so we need to differentiate between these situations.
bool objectExists = this->TestObjectExistence(distinguishedName);
if (objectExists)
{
// Force the validator to throw the DRA access denied exception.
Validator::AssertSuccess(Win32ErrorCode::DS_DRA_ACCESS_DENIED);
}
else
{
// Rethrow the original exception, as the object really does not exists.
throw;
}
}
}
ReplicaObject^ DrsConnection::ReplicateSingleObject(Guid objectGuid)
{
@ -241,6 +260,8 @@ namespace DSInternals
}
ReplicaObject^ DrsConnection::ReplicateSingleObject(Guid objectGuid, array<ATTRTYP>^ partialAttributeSet)
{
try
{
auto request = CreateReplicateSingleRequest(objectGuid, partialAttributeSet);
auto reply = GetNCChanges(move(request));
@ -248,6 +269,22 @@ namespace DSInternals
// TODO: Assert objects.Count == 1; It is guaranteed that it is > 0
return objects[0];
}
catch (DirectoryObjectNotFoundException^)
{
// ReplicateSingleObject also exits with this error when access is denied, so we need to differentiate between these situations.
bool objectExists = this->TestObjectExistence(objectGuid);
if (objectExists)
{
// Force the validator to throw the DRA access denied exception.
Validator::AssertSuccess(Win32ErrorCode::DS_DRA_ACCESS_DENIED);
}
else
{
// Rethrow the original exception, as the object really does not exists.
throw;
}
}
}
midl_ptr<DRS_MSG_GETCHGREPLY_V6> DrsConnection::GetNCChanges(midl_ptr<DRS_MSG_GETCHGREQ_V8> &&request)
{
@ -262,15 +299,11 @@ namespace DSInternals
DWORD outVersion = 0;
auto reply = make_midl_ptr<DRS_MSG_GETCHGREPLY_V6>();
// Send message:
ULONG result = IDL_DRSGetNCChanges_NoSEH(handle, inVersion, (DRS_MSG_GETCHGREQ*)request.get(), &outVersion, (DRS_MSG_GETCHGREPLY*)reply.get());
Validator::AssertSuccess((Win32ErrorCode)result);
// TODO: Check the returned version
auto result = (Win32ErrorCode) IDL_DRSGetNCChanges_NoSEH(handle, inVersion, (DRS_MSG_GETCHGREQ*)request.get(), &outVersion, (DRS_MSG_GETCHGREPLY*)reply.get());
// Validate result
if (reply->cNumObjects == 0)
{
// TODO: DirectoryObjectNotFound ex.
throw gcnew Exception("Directory object not found.");
}
// TODO: Check the returned version
Validator::AssertSuccess(result);
// TODO: Test extended error code:
DWORD extendedError = reply->dwDRSError;
return reply;
@ -291,75 +324,105 @@ namespace DSInternals
DRS_HANDLE handle = this->handle.ToPointer();
auto result = IDL_DRSCrackNames_NoSEH(handle, inVersion, (DRS_MSG_CRACKREQ*)request.get(), &outVersion, (DRS_MSG_CRACKREPLY*)reply.get());
Validator::AssertSuccess((Win32ErrorCode)result);
if (reply->pResult->cItems != request->cNames)
{
// TODO: Exception type
throw gcnew Exception("Obj not found");
}
return reply;
}
String^ DrsConnection::ResolveDistinguishedName(NTAccount^ accountName)
{
auto stringAccountName = accountName->Value;
auto dn = this->ResolveName(stringAccountName, DS_NAME_FORMAT::DS_NT4_ACCOUNT_NAME, DS_NAME_FORMAT::DS_FQDN_1779_NAME);
auto dn = this->TryResolveName(stringAccountName, DS_NAME_FORMAT::DS_NT4_ACCOUNT_NAME, DS_NAME_FORMAT::DS_FQDN_1779_NAME);
if (dn == nullptr)
{
throw gcnew DirectoryObjectNotFoundException(stringAccountName, nullptr);
}
return dn;
}
String^ DrsConnection::ResolveDistinguishedName(SecurityIdentifier^ objectSid)
{
auto stringSid = objectSid->ToString();
auto dn = this->ResolveName(stringSid, DS_NAME_FORMAT::DS_SID_OR_SID_HISTORY_NAME, DS_NAME_FORMAT::DS_FQDN_1779_NAME);
auto dn = this->TryResolveName(stringSid, DS_NAME_FORMAT::DS_SID_OR_SID_HISTORY_NAME, DS_NAME_FORMAT::DS_FQDN_1779_NAME);
if (dn == nullptr)
{
throw gcnew DirectoryObjectNotFoundException(stringSid, nullptr);
}
return dn;
}
Guid DrsConnection::ResolveGuid(NTAccount^ accountName)
{
auto stringAccountName = accountName->Value;
auto stringGuid = this->ResolveName(stringAccountName, DS_NAME_FORMAT::DS_NT4_ACCOUNT_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
auto stringGuid = this->TryResolveName(stringAccountName, DS_NAME_FORMAT::DS_NT4_ACCOUNT_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
if (stringGuid == nullptr)
{
throw gcnew DirectoryObjectNotFoundException(stringAccountName, nullptr);
}
return Guid::Parse(stringGuid);
}
Guid DrsConnection::ResolveGuid(SecurityIdentifier^ objectSid)
{
auto stringSid = objectSid->ToString();
auto stringGuid = this->ResolveName(stringSid, DS_NAME_FORMAT::DS_SID_OR_SID_HISTORY_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
auto stringGuid = this->TryResolveName(stringSid, DS_NAME_FORMAT::DS_SID_OR_SID_HISTORY_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
if (stringGuid == nullptr)
{
throw gcnew DirectoryObjectNotFoundException(stringSid, nullptr);
}
return Guid::Parse(stringGuid);
}
Guid DrsConnection::ResolveGuid(String^ userPrincipalName)
{
auto stringGuid = this->ResolveName(userPrincipalName, DS_NAME_FORMAT::DS_USER_PRINCIPAL_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
auto stringGuid = this->TryResolveName(userPrincipalName, DS_NAME_FORMAT::DS_USER_PRINCIPAL_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
if (stringGuid == nullptr)
{
throw gcnew DirectoryObjectNotFoundException(userPrincipalName, nullptr);
}
return Guid::Parse(stringGuid);
}
String^ DrsConnection::ResolveName(String^ name, DS_NAME_FORMAT formatOffered, DS_NAME_FORMAT formatDesired)
String^ DrsConnection::TryResolveName(String^ name, DS_NAME_FORMAT formatOffered, DS_NAME_FORMAT formatDesired)
{
// We only want to resolve 1 name at a time:
const size_t numItems = 1;
// Prepare the request
auto request = make_midl_ptr<DRS_MSG_CRACKREQ_V1>(numItems);
request->formatOffered = formatOffered;
request->formatDesired = formatDesired;
request->rpNames[0] = RpcTypeConverter::ToNativeString(name).release();
auto result = this->ResolveName(move(request));
// TODO: Merge with ResolveName(midl_ptr<DRS_MSG_CRACKREQ_V1> &&request)?
return result;
}
String^ DrsConnection::ResolveName(midl_ptr<DRS_MSG_CRACKREQ_V1> &&request)
{
// Perform RPC call
auto reply = this->CrackNames(move(request));
// Process the response
auto item = reply->pResult->rItems[0];
// TODO: const 0
if (item.status != 0)
if (item.status == DS_NAME_ERROR::DS_NAME_NO_ERROR)
{
// TODO: Exception type
throw gcnew Exception("Object not found");
}
auto name = marshal_as<String^>(item.pName);
return name;
}
else
{
// No name translation has been found for some reason.
return nullptr;
}
}
bool DrsConnection::TestObjectExistence(String^ distinguishedName)
{
auto resolvedName = this->TryResolveName(distinguishedName, DS_NAME_FORMAT::DS_FQDN_1779_NAME, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME);
// Return true if and only if the object exists
return resolvedName != nullptr;
}
bool DrsConnection::TestObjectExistence(Guid objectGuid)
{
auto stringGuid = objectGuid.ToString("B");
auto resolvedName = this->TryResolveName(stringGuid, DS_NAME_FORMAT::DS_UNIQUE_ID_NAME, DS_NAME_FORMAT::DS_FQDN_1779_NAME);
// Return true if and only if the object exists
return resolvedName != nullptr;
}
bool DrsConnection::ReleaseHandle()
{

View File

@ -54,6 +54,8 @@ namespace DSInternals
Guid ResolveGuid(NTAccount^ accountName);
Guid ResolveGuid(SecurityIdentifier^ objectSid);
Guid ResolveGuid(String^ userPrincipalName);
bool TestObjectExistence(String^ distinguishedName);
bool TestObjectExistence(Guid objectGuid);
protected:
virtual bool ReleaseHandle() override;
private:
@ -61,8 +63,7 @@ namespace DSInternals
void Bind(IntPtr rpcHandle);
midl_ptr<DRS_MSG_GETCHGREPLY_V6> GetNCChanges(midl_ptr<DRS_MSG_GETCHGREQ_V8> &&request);
midl_ptr<DRS_MSG_CRACKREPLY_V1> CrackNames(midl_ptr<DRS_MSG_CRACKREQ_V1> &&request);
String^ ResolveName(String^ name, DS_NAME_FORMAT formatOffered, DS_NAME_FORMAT formatDesired);
String^ ResolveName(midl_ptr<DRS_MSG_CRACKREQ_V1> &&request);
String^ TryResolveName(String^ name, DS_NAME_FORMAT formatOffered, DS_NAME_FORMAT formatDesired);
midl_ptr<DRS_EXTENSIONS_INT> CreateClientInfo();
midl_ptr<DRS_MSG_GETCHGREQ_V8> CreateReplicateAllRequest(ReplicationCookie^ cookie, array<ATTRTYP>^ partialAttributeSet, ULONG maxBytes, ULONG maxObjects);
midl_ptr<DRS_MSG_GETCHGREQ_V8> CreateReplicateSingleRequest(String^ distinguishedName, array<ATTRTYP>^ partialAttributeSet);