672 lines
17 KiB
C
672 lines
17 KiB
C
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1997.
|
|
//
|
|
// File: whackspn.c
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 7-30-98 RichardW Created
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#define LDAP_UNICODE 1
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#define SECURITY_WIN32
|
|
#include <rpc.h>
|
|
#include <sspi.h>
|
|
#include <secext.h>
|
|
#include <lm.h>
|
|
#include <winsock2.h>
|
|
#include <winldap.h>
|
|
#include <ntldap.h>
|
|
#include <dsgetdc.h>
|
|
#include <dsgetdcp.h>
|
|
#include <ntdsapi.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define NlPrint(x) DPrint x;
|
|
#define NL_CRITICAL 0
|
|
#define NL_MISC 0
|
|
|
|
HANDLE hGC ;
|
|
|
|
BOOL AddDollar = TRUE;
|
|
|
|
VOID
|
|
DPrint(
|
|
DWORD Level,
|
|
PCHAR FormatString,
|
|
...
|
|
)
|
|
{
|
|
va_list ArgList;
|
|
|
|
va_start(ArgList, FormatString);
|
|
vprintf( FormatString, ArgList );
|
|
va_end( ArgList );
|
|
}
|
|
|
|
BOOL
|
|
FindDomainForServer(
|
|
PWSTR Server,
|
|
PWSTR Domain
|
|
)
|
|
{
|
|
ULONG NetStatus ;
|
|
WCHAR LocalServerName[ 64 ];
|
|
PDOMAIN_CONTROLLER_INFOW DcInfo ;
|
|
|
|
wcsncpy( LocalServerName, Server, 63 );
|
|
|
|
if ( AddDollar ) {
|
|
wcscat( LocalServerName, L"$" );
|
|
}
|
|
|
|
|
|
NetStatus = DsGetDcNameWithAccountW(
|
|
NULL,
|
|
LocalServerName,
|
|
( AddDollar ? UF_MACHINE_ACCOUNT_MASK : UF_NORMAL_ACCOUNT ),
|
|
L"",
|
|
NULL,
|
|
NULL,
|
|
DS_DIRECTORY_SERVICE_REQUIRED |
|
|
DS_RETURN_DNS_NAME,
|
|
&DcInfo );
|
|
|
|
if ( NetStatus == 0 ) {
|
|
wcscpy( Domain, DcInfo->DomainName );
|
|
NetApiBufferFree( DcInfo );
|
|
|
|
return TRUE ;
|
|
} else {
|
|
printf("can't find DC for %ws - %u\n", LocalServerName, NetStatus );
|
|
}
|
|
|
|
return FALSE ;
|
|
}
|
|
|
|
DWORD
|
|
AddDnsHostNameAttribute(
|
|
HANDLE hDs,
|
|
PWCHAR Server,
|
|
PWCHAR DnsDomain
|
|
)
|
|
|
|
{
|
|
NET_API_STATUS NetStatus = NO_ERROR;
|
|
ULONG CrackStatus = DS_NAME_NO_ERROR;
|
|
|
|
LPWSTR DnsHostNameValues[2];
|
|
LPWSTR SpnArray[3];
|
|
LPWSTR DnsSpn = NULL;
|
|
LPWSTR NetbiosSpn = NULL;
|
|
|
|
LDAPModW DnsHostNameAttr;
|
|
LDAPModW SpnAttr;
|
|
LDAPModW *Mods[3] = {NULL};
|
|
|
|
LDAP *LdapHandle = NULL;
|
|
LDAPMessage *LdapMessage = NULL;
|
|
PDS_NAME_RESULTW CrackedName = NULL;
|
|
LPWSTR DnOfAccount = NULL;
|
|
|
|
LPWSTR NameToCrack;
|
|
DWORD SamNameSize;
|
|
WCHAR SamName[ DNLEN + 1 + CNLEN + 1 + 1];
|
|
WCHAR nameBuffer[ 256 ];
|
|
|
|
ULONG LdapStatus;
|
|
LONG LdapOption;
|
|
|
|
LDAP_TIMEVAL LdapTimeout;
|
|
ULONG MessageNumber;
|
|
|
|
//
|
|
// Ldap modify control needed to indicate that the
|
|
// existing values of the modified attributes should
|
|
// be left intact and the missing ones should be added.
|
|
// Without this control, a modification of an attribute
|
|
// that results in an addition of a value that already
|
|
// exists will fail.
|
|
//
|
|
|
|
LDAPControl ModifyControl = {
|
|
LDAP_SERVER_PERMISSIVE_MODIFY_OID_W,
|
|
{
|
|
0, NULL
|
|
},
|
|
FALSE
|
|
};
|
|
|
|
PLDAPControl ModifyControlArray[2] =
|
|
{
|
|
&ModifyControl,
|
|
NULL
|
|
};
|
|
|
|
//
|
|
// Prepare DnsHostName modification entry
|
|
//
|
|
|
|
wcsncpy( nameBuffer, Server, sizeof( nameBuffer ) / sizeof( WCHAR ));
|
|
wcscat( nameBuffer, L"." );
|
|
wcscat( nameBuffer, DnsDomain );
|
|
|
|
DnsHostNameValues[0] = nameBuffer;
|
|
DnsHostNameValues[1] = NULL;
|
|
|
|
//
|
|
// If we set both DnsHostName and SPN, then DnsHostName is
|
|
// missing, so add it. If we set DnsHostName only, then
|
|
// DnsHostName already exists (but incorrect), so replace it.
|
|
//
|
|
if ( TRUE ) {
|
|
DnsHostNameAttr.mod_op = LDAP_MOD_ADD;
|
|
} else {
|
|
DnsHostNameAttr.mod_op = LDAP_MOD_REPLACE;
|
|
}
|
|
DnsHostNameAttr.mod_type = L"DnsHostName";
|
|
DnsHostNameAttr.mod_values = DnsHostNameValues;
|
|
|
|
Mods[0] = &DnsHostNameAttr;
|
|
Mods[1] = NULL;
|
|
|
|
//
|
|
// The name of the computer object is
|
|
// <NetbiosDomainName>\<NetbiosComputerName>$
|
|
//
|
|
|
|
wcscpy( SamName, DnsDomain );
|
|
{
|
|
PWCHAR p;
|
|
|
|
p = wcschr( SamName, L'.' );
|
|
*p = UNICODE_NULL;
|
|
}
|
|
|
|
wcscat( SamName, L"\\" );
|
|
wcscat( SamName, Server );
|
|
wcscat( SamName, L"$" );
|
|
|
|
//
|
|
// Crack the sam account name into a DN:
|
|
//
|
|
|
|
NameToCrack = SamName;
|
|
NetStatus = DsCrackNamesW(
|
|
hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&NameToCrack,
|
|
&CrackedName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: CrackNames failed on %ws for %ws: %ld\n",
|
|
DnsDomain,
|
|
SamName,
|
|
NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
if ( CrackedName->cItems != 1 ) {
|
|
CrackStatus = DS_NAME_ERROR_NOT_UNIQUE;
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: Cracked Name is not unique on %ws for %ws: %ld\n",
|
|
DnsDomain,
|
|
SamName,
|
|
NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
if ( CrackedName->rItems[ 0 ].status != DS_NAME_NO_ERROR ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: CrackNames failed on %ws for %ws: substatus %ld\n",
|
|
DnsDomain,
|
|
SamName,
|
|
CrackedName->rItems[ 0 ].status ));
|
|
CrackStatus = CrackedName->rItems[ 0 ].status;
|
|
goto Cleanup ;
|
|
}
|
|
DnOfAccount = CrackedName->rItems[0].pName;
|
|
|
|
//
|
|
// Open an LDAP connection to the DC and set useful options
|
|
//
|
|
|
|
LdapHandle = ldap_init( DnsDomain, LDAP_PORT );
|
|
|
|
if ( LdapHandle == NULL ) {
|
|
NetStatus = GetLastError();
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_init failed on %ws for %ws: %ld\n",
|
|
DnsDomain,
|
|
SamName,
|
|
NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// 30 second timeout
|
|
LdapOption = 30;
|
|
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_TIMELIMIT, &LdapOption );
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_set_option LDAP_OPT_TIMELIMIT failed on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapStatus,
|
|
ldap_err2stringA( LdapStatus )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Don't chase referals
|
|
LdapOption = PtrToLong(LDAP_OPT_OFF);
|
|
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_REFERRALS, &LdapOption );
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_set_option LDAP_OPT_REFERRALS failed on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapStatus,
|
|
ldap_err2stringA( LdapStatus )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
|
|
#if 0
|
|
// Set the option telling LDAP that I passed it an explicit DC name and
|
|
// that it can avoid the DsGetDcName.
|
|
LdapOption = PtrToLong(LDAP_OPT_ON);
|
|
LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_AREC_EXCLUSIVE, &LdapOption );
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_set_option LDAP_OPT_AREC_EXCLUSIVE failed on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapStatus,
|
|
ldap_err2stringA( LdapStatus )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Bind to the DC
|
|
//
|
|
|
|
LdapStatus = ldap_bind_s( LdapHandle,
|
|
NULL, // No DN of account to authenticate as
|
|
NULL, // Default credentials
|
|
LDAP_AUTH_NEGOTIATE );
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: Cannot ldap_bind to %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapStatus,
|
|
ldap_err2stringA( LdapStatus )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Write the modifications
|
|
//
|
|
|
|
LdapStatus = ldap_modify_extW( LdapHandle,
|
|
DnOfAccount,
|
|
Mods,
|
|
(PLDAPControl *) &ModifyControlArray,
|
|
NULL, // No client controls
|
|
&MessageNumber );
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: Cannot ldap_modify on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
DnOfAccount,
|
|
LdapStatus,
|
|
ldap_err2stringA( LdapStatus )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Wait for the modify to complete
|
|
LdapTimeout.tv_sec = 30;
|
|
LdapTimeout.tv_usec = 0;
|
|
LdapStatus = ldap_result( LdapHandle,
|
|
MessageNumber,
|
|
LDAP_MSG_ALL,
|
|
&LdapTimeout,
|
|
&LdapMessage );
|
|
|
|
switch ( LdapStatus ) {
|
|
case -1:
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapHandle->ld_errno,
|
|
ldap_err2stringA( LdapHandle->ld_errno )));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
|
|
case 0:
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_result timeout on %ws for %ws.\n",
|
|
DnsDomain,
|
|
SamName ));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
|
|
case LDAP_RES_MODIFY:
|
|
if ( LdapMessage->lm_returncode != 0 ) {
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapMessage->lm_returncode,
|
|
ldap_err2stringA( LdapMessage->lm_returncode )));
|
|
NetStatus = LdapMapErrorToWin32(LdapMessage->lm_returncode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
NlPrint(( NL_MISC,
|
|
"SPN: Set successfully on DC %ws\n",
|
|
DnsDomain ));
|
|
break; // This is what we expect
|
|
|
|
default:
|
|
NlPrint(( NL_CRITICAL,
|
|
"SPN: ldap_result unexpected result on %ws for %ws: %ld\n",
|
|
DnsDomain,
|
|
SamName,
|
|
LdapStatus ));
|
|
NetStatus = LdapMapErrorToWin32(LdapStatus);
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
|
|
//
|
|
// Log the failure in the event log, if requested.
|
|
// Try to output the most specific error.
|
|
//
|
|
|
|
if ( CrackedName ) {
|
|
DsFreeNameResultW( CrackedName );
|
|
}
|
|
|
|
if ( LdapMessage != NULL ) {
|
|
ldap_msgfree( LdapMessage );
|
|
}
|
|
|
|
if ( LdapHandle != NULL ) {
|
|
ldap_unbind_s( LdapHandle );
|
|
}
|
|
|
|
if ( DnsSpn ) {
|
|
LocalFree( DnsSpn );
|
|
}
|
|
|
|
if ( NetbiosSpn ) {
|
|
LocalFree( NetbiosSpn );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
BOOL
|
|
AddHostSpn(
|
|
PWSTR Server
|
|
)
|
|
{
|
|
WCHAR HostSpn[ 64 ];
|
|
WCHAR Domain[ MAX_PATH ];
|
|
WCHAR FlatName[ 64 ];
|
|
HANDLE hDs ;
|
|
ULONG NetStatus ;
|
|
PWSTR Dot ;
|
|
PDS_NAME_RESULTW Result ;
|
|
LPWSTR Flat = FlatName;
|
|
LPWSTR Spn = HostSpn ;
|
|
|
|
wcscpy( HostSpn, L"HOST/" );
|
|
wcscat( HostSpn, Server );
|
|
|
|
#if 1
|
|
if ( !FindDomainForServer( Server, Domain )) {
|
|
printf(" No domain controller for %ws found\n", Server );
|
|
return FALSE ;
|
|
}
|
|
#else
|
|
wcscpy( Domain, L"ntdev.microsoft.com" );
|
|
#endif
|
|
|
|
Dot = wcschr( Domain, L'.' );
|
|
if ( Dot ) {
|
|
*Dot = L'\0';
|
|
}
|
|
|
|
wcscpy( FlatName, Domain );
|
|
|
|
if ( Dot ) {
|
|
*Dot = L'.' ;
|
|
}
|
|
|
|
wcscat( FlatName, L"\\" );
|
|
wcscat( FlatName, Server );
|
|
if ( AddDollar ) {
|
|
wcscat( FlatName, L"$" );
|
|
}
|
|
|
|
NetStatus = DsBindW( NULL, Domain, &hDs );
|
|
|
|
if ( NetStatus != 0 ) {
|
|
printf("Failed to bind to DC of domain %ws, %#x\n",
|
|
Domain, NetStatus );
|
|
return FALSE ;
|
|
}
|
|
|
|
NetStatus = DsCrackNamesW(
|
|
hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&Flat,
|
|
&Result );
|
|
|
|
if ( NetStatus != 0 ) {
|
|
printf("Failed to crack name %ws into the FQDN, %#x\n",
|
|
FlatName, NetStatus );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return FALSE ;
|
|
} else {
|
|
DWORD i;
|
|
BOOL foundFailure = FALSE;
|
|
|
|
//
|
|
// check the stat-i inside the struct
|
|
//
|
|
for ( i = 0; i < Result->cItems; ++i ) {
|
|
if ( Result->rItems[i].status == DS_NAME_NO_ERROR ) {
|
|
printf("Found domain\\name '%ws\\%ws' for Flatname '%ws' \n",
|
|
Result->rItems[i].pDomain,
|
|
Result->rItems[i].pName,
|
|
FlatName);
|
|
} else {
|
|
printf("Couldn't crack name for '%ws' - %u\n",
|
|
FlatName, Result->rItems[i].status);
|
|
foundFailure = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( foundFailure ) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// add DnsHostName attribute to this object
|
|
//
|
|
NetStatus = AddDnsHostNameAttribute( hDs, Server, Domain );
|
|
if ( NetStatus != 0 ) {
|
|
printf( "Failed to add DnsHostName attrib to '%ws.%ws', %#x\n",
|
|
Server, Domain, NetStatus );
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// write the netbios based SPN
|
|
//
|
|
NetStatus = DsServerRegisterSpnW(
|
|
DS_SPN_ADD_SPN_OP,
|
|
L"HOST",
|
|
Result->rItems[0].pName);
|
|
|
|
if ( NetStatus != 0 ) {
|
|
printf("DsServerRegisterSpn Failed to assign SPN to '%ws', %#x\n",
|
|
Result->rItems[0].pName, NetStatus );
|
|
}
|
|
|
|
NetStatus = DsWriteAccountSpnW(
|
|
hDs,
|
|
DS_SPN_ADD_SPN_OP,
|
|
Result->rItems[0].pName,
|
|
1,
|
|
&Spn );
|
|
|
|
|
|
if ( NetStatus != 0 ) {
|
|
printf("Failed to assign SPN '%ws' to account '%ws', %#x\n",
|
|
HostSpn, Result->rItems[0].pName, NetStatus );
|
|
}
|
|
else {
|
|
DWORD i;
|
|
BOOL foundFailure = FALSE;
|
|
|
|
//
|
|
// check the stat-i inside the struct
|
|
//
|
|
for ( i = 0; i < Result->cItems; ++i ) {
|
|
if ( Result->rItems[i].status == DS_NAME_NO_ERROR ) {
|
|
printf("Assigned SPN '%ws' to account '%ws' \n",
|
|
HostSpn, Result->rItems[i].pName );
|
|
} else {
|
|
printf("WriteAccountSpn failed for '%ws' - %u\n",
|
|
HostSpn, Result->rItems[i].status);
|
|
foundFailure = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( foundFailure ) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// do it again with the DNS domain as well
|
|
//
|
|
wcscat( HostSpn, L"." );
|
|
wcscat( HostSpn, Domain );
|
|
|
|
NetStatus = DsWriteAccountSpnW(hDs,
|
|
DS_SPN_ADD_SPN_OP,
|
|
Result->rItems[0].pName,
|
|
1,
|
|
&Spn );
|
|
|
|
|
|
if ( NetStatus != 0 ) {
|
|
printf("Failed to assign SPN '%ws' to account '%ws', %#x\n",
|
|
HostSpn, Result->rItems[0].pName, NetStatus );
|
|
}
|
|
else {
|
|
DWORD i;
|
|
BOOL foundFailure = FALSE;
|
|
|
|
//
|
|
// check the stat-i inside the struct
|
|
//
|
|
for ( i = 0; i < Result->cItems; ++i ) {
|
|
if ( Result->rItems[i].status == DS_NAME_NO_ERROR ) {
|
|
printf("Assigned SPN '%ws' to account '%ws' \n",
|
|
HostSpn, Result->rItems[i].pName );
|
|
} else {
|
|
printf("WriteAccountSpn failed for '%ws' - %u\n",
|
|
HostSpn, Result->rItems[i].status);
|
|
foundFailure = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( foundFailure ) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
DsFreeNameResultW( Result );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
|
|
return NetStatus == 0 ;
|
|
|
|
}
|
|
|
|
void __cdecl wmain (int argc, wchar_t *argv[])
|
|
{
|
|
|
|
PWCHAR machineName;
|
|
|
|
if ( argc < 2 ) {
|
|
printf("usage: %s [-$] machinename\n", argv[0] );
|
|
exit(0);
|
|
}
|
|
|
|
if ( argv[1][0] == '-') {
|
|
switch (argv[1][1]) {
|
|
case '$':
|
|
AddDollar = FALSE ;
|
|
break;
|
|
default:
|
|
printf(" unrecognized option, '%s'\n", argv[1] );
|
|
exit(0);
|
|
|
|
}
|
|
|
|
machineName = argv[2];
|
|
|
|
}
|
|
else {
|
|
machineName = argv[1];
|
|
}
|
|
|
|
|
|
if ( AddHostSpn( machineName ) ) {
|
|
printf("Updated object\n" );
|
|
}
|
|
}
|