623 lines
14 KiB
C
623 lines
14 KiB
C
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1997.
|
|
//
|
|
// File: setspn.c
|
|
//
|
|
// Contents:
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 7-30-98 RichardW Created
|
|
// 8-10-99 JBrezak Turned into setspn added list capability
|
|
// 09-22-99 Jaroslad support for adding/removing arbitrary SPNs
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#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 <dsgetdc.h>
|
|
#include <dsgetdcp.h>
|
|
#include <ntdsapi.h>
|
|
#include <ntdsapip.h> // private DS_NAME_FORMATS
|
|
#include <stdio.h>
|
|
#include <winldap.h>
|
|
#include <shlwapi.h>
|
|
|
|
DWORD debug = 0;
|
|
|
|
BOOL AddSpn(PWSTR Service, PWSTR Server);
|
|
|
|
BOOL
|
|
FindDomainForAccount(
|
|
PWSTR Server,
|
|
PWSTR DomainToCheck,
|
|
PWSTR Domain,
|
|
PWSTR DC
|
|
)
|
|
{
|
|
ULONG NetStatus ;
|
|
PDOMAIN_CONTROLLER_INFO DcInfo ;
|
|
WCHAR LocalServerName[ 64 ];
|
|
|
|
wcsncpy( LocalServerName, Server, 63 );
|
|
wcscat( LocalServerName, L"$" );
|
|
|
|
NetStatus = DsGetDcNameWithAccountW(
|
|
NULL,
|
|
LocalServerName,
|
|
UF_ACCOUNT_TYPE_MASK,
|
|
DomainToCheck,
|
|
NULL,
|
|
NULL,
|
|
DS_DIRECTORY_SERVICE_REQUIRED |
|
|
DS_RETURN_FLAT_NAME,
|
|
&DcInfo );
|
|
|
|
if ( NetStatus == 0 )
|
|
{
|
|
wcscat( Server, L"$" );
|
|
wcscpy( Domain, DcInfo->DomainName );
|
|
|
|
if (DC)
|
|
{
|
|
wcscpy( DC, &DcInfo->DomainControllerName[2] );
|
|
|
|
}
|
|
|
|
NetApiBufferFree( DcInfo );
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
NetStatus = DsGetDcNameWithAccountW(
|
|
NULL,
|
|
Server,
|
|
UF_ACCOUNT_TYPE_MASK,
|
|
DomainToCheck,
|
|
NULL,
|
|
NULL,
|
|
DS_DIRECTORY_SERVICE_REQUIRED |
|
|
DS_RETURN_FLAT_NAME,
|
|
&DcInfo );
|
|
|
|
if ( NetStatus == 0 )
|
|
{
|
|
wcscpy( Domain, DcInfo->DomainName );
|
|
|
|
if (DC)
|
|
{
|
|
wcscpy( DC, &DcInfo->DomainControllerName[2] );
|
|
|
|
}
|
|
|
|
NetApiBufferFree( DcInfo );
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
return FALSE ;
|
|
|
|
}
|
|
|
|
BOOL
|
|
AddHostSpn(
|
|
PWSTR Server
|
|
)
|
|
{
|
|
return (AddSpn(L"HOST", Server));
|
|
}
|
|
|
|
|
|
BOOL
|
|
AddSpn(
|
|
PWSTR Service,
|
|
PWSTR Server
|
|
)
|
|
{
|
|
WCHAR HostSpn[ 64 ];
|
|
WCHAR FlatSpn[ 64 ];
|
|
WCHAR Domain[ MAX_PATH ];
|
|
WCHAR FlatName[ 64 ];
|
|
HANDLE hDs ;
|
|
ULONG NetStatus ;
|
|
PDS_NAME_RESULT Result ;
|
|
LPWSTR Flat = FlatName;
|
|
LPWSTR Spns[2];
|
|
WCHAR LocalServerName[ 64 ];
|
|
|
|
wcsncpy( LocalServerName, Server, sizeof(LocalServerName)/sizeof(LocalServerName[0]) );
|
|
|
|
if ( !FindDomainForAccount( LocalServerName, L"", Domain, NULL ))
|
|
{
|
|
fprintf(stderr,
|
|
"Could not find account %ws\n", LocalServerName );
|
|
return FALSE;
|
|
}
|
|
|
|
wcscpy( FlatName, Domain );
|
|
wcscat( FlatName, L"\\" );
|
|
wcscat( FlatName, LocalServerName );
|
|
|
|
NetStatus = DsBind( NULL, Domain, &hDs );
|
|
|
|
if ( NetStatus != 0 )
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to bind to DC of domain %ws, %#x\n",
|
|
Domain, NetStatus );
|
|
return FALSE ;
|
|
}
|
|
|
|
NetStatus = DsCrackNames(
|
|
hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&Flat,
|
|
&Result );
|
|
|
|
if ( NetStatus != 0 ||
|
|
Result->rItems[0].status != DS_NAME_NO_ERROR ||
|
|
Result->cItems != 1)
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to crack name %ws into the FQDN, (%d) %d %#x\n",
|
|
FlatName, NetStatus, Result->cItems,
|
|
(Result->cItems==1)?Result->rItems[0].status:-1 );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return FALSE ;
|
|
}
|
|
|
|
wsprintf( HostSpn, L"%s/%s.%s", Service, Server, Domain );
|
|
wsprintf( FlatSpn, L"%s/%s", Service, Server );
|
|
Spns[0] = HostSpn;
|
|
Spns[1] = FlatSpn;
|
|
|
|
printf("Registering ServicePrincipalNames for %ws\n",
|
|
Result->rItems[0].pName);
|
|
printf("\t%ws\n", HostSpn);
|
|
printf("\t%ws\n", FlatSpn);
|
|
|
|
#if 0
|
|
printf("DsWriteAccountSpn: Commented out\n");
|
|
#else
|
|
NetStatus = DsWriteAccountSpn(
|
|
hDs,
|
|
DS_SPN_ADD_SPN_OP,
|
|
Result->rItems[0].pName,
|
|
2,
|
|
Spns );
|
|
|
|
|
|
if ( NetStatus != 0 )
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to assign SPN to account '%ws', %#x\n",
|
|
Result->rItems[0].pName, NetStatus );
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
DsFreeNameResult( Result );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return NetStatus == 0 ;
|
|
}
|
|
|
|
// added by jaroslad on 09/22/99
|
|
BOOL
|
|
AddRemoveSpn(
|
|
PWSTR HostSpn,
|
|
PWSTR HostDomain,
|
|
PWSTR Server,
|
|
DS_SPN_WRITE_OP Operation
|
|
|
|
)
|
|
{
|
|
WCHAR FlatSpn[ MAX_PATH ];
|
|
WCHAR Domain[ MAX_PATH ];
|
|
WCHAR FlatName[ MAX_PATH ];
|
|
HANDLE hDs ;
|
|
ULONG NetStatus ;
|
|
PDS_NAME_RESULT Result ;
|
|
LPWSTR Flat = FlatName;
|
|
LPWSTR Spns[2];
|
|
|
|
if ( !FindDomainForAccount( Server, HostDomain, Domain, NULL ))
|
|
{
|
|
fprintf(stderr,
|
|
"Unable to locate account %ws\n", Server);
|
|
return FALSE ;
|
|
}
|
|
|
|
wcscpy( FlatName, Domain );
|
|
wcscat( FlatName, L"\\" );
|
|
wcscat( FlatName, Server );
|
|
|
|
NetStatus = DsBind( NULL, Domain, &hDs );
|
|
if ( NetStatus != 0 )
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to bind to DC of domain %ws, %#x\n",
|
|
Domain, NetStatus );
|
|
return FALSE ;
|
|
}
|
|
|
|
NetStatus = DsCrackNames(
|
|
hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&Flat,
|
|
&Result );
|
|
|
|
if ( NetStatus != 0 ||
|
|
Result->rItems[0].status != DS_NAME_NO_ERROR ||
|
|
Result->cItems != 1)
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to crack name %ws into the FQDN, (%d) %d %#x\n",
|
|
FlatName, NetStatus, Result->cItems,
|
|
(Result->cItems==1)?Result->rItems[0].status:-1 );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return FALSE ;
|
|
}
|
|
|
|
Spns[0] = HostSpn;
|
|
|
|
printf("%s ServicePrincipalNames for %ws\n",
|
|
(Operation==DS_SPN_DELETE_SPN_OP)?"Unregistering":"Registering", Result->rItems[0].pName);
|
|
printf("\t%ws\n", HostSpn);
|
|
|
|
#if 0
|
|
printf("DsWriteAccountSpn: Commented out\n");
|
|
#else
|
|
NetStatus = DsWriteAccountSpn(
|
|
hDs,
|
|
Operation,
|
|
Result->rItems[0].pName,
|
|
1,
|
|
Spns );
|
|
|
|
|
|
if ( NetStatus != 0 )
|
|
{
|
|
fprintf(stderr,
|
|
"Failed to %s SPN on account '%ws', %#x\n",
|
|
(Operation==DS_SPN_DELETE_SPN_OP)?"remove":"assign",
|
|
Result->rItems[0].pName, NetStatus );
|
|
return FALSE;
|
|
}
|
|
#endif
|
|
|
|
DsFreeNameResult( Result );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return NetStatus == 0 ;
|
|
}
|
|
|
|
|
|
BOOL
|
|
LookupHostSpn(
|
|
PWSTR ServerDomain,
|
|
PWSTR Server
|
|
)
|
|
{
|
|
WCHAR FlatName[ 128 ];
|
|
HANDLE hDs ;
|
|
ULONG NetStatus ;
|
|
PDS_NAME_RESULT Result ;
|
|
LPWSTR Flat = FlatName;
|
|
LDAP *ld;
|
|
int rc;
|
|
LDAPMessage *e, *res;
|
|
WCHAR *base_dn;
|
|
WCHAR *search_dn, search_ava[256];
|
|
WCHAR Domain[ MAX_PATH ];
|
|
WCHAR DC[ MAX_PATH ];
|
|
|
|
if ( !FindDomainForAccount( Server, ServerDomain, Domain, DC ))
|
|
{
|
|
fprintf(stderr, "Cannot find account %S\n", Server);
|
|
return FALSE ;
|
|
}
|
|
|
|
if (debug)
|
|
printf("Domain=%S DC=%S\n", Domain, DC);
|
|
|
|
ld = ldap_open(DC, LDAP_PORT);
|
|
if (ld == NULL) {
|
|
fprintf(stderr, "ldap_init failed = %x", LdapGetLastError());
|
|
return FALSE;
|
|
}
|
|
|
|
rc = ldap_bind_s(ld, NULL, NULL, LDAP_AUTH_NEGOTIATE);
|
|
if (rc != LDAP_SUCCESS) {
|
|
fprintf(stderr, "ldap_bind failed = %x", LdapGetLastError());
|
|
ldap_unbind(ld);
|
|
return FALSE;
|
|
}
|
|
|
|
NetStatus = DsBind( NULL, Domain, &hDs );
|
|
if ( NetStatus != 0 )
|
|
{
|
|
fprintf(stderr, "Failed to bind to DC of domain %ws, %#x\n",
|
|
Domain, NetStatus );
|
|
return FALSE ;
|
|
}
|
|
|
|
wcscpy( FlatName, Domain );
|
|
wcscat( FlatName, L"\\" );
|
|
wcscat( FlatName, Server );
|
|
|
|
NetStatus = DsCrackNames(
|
|
hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&Flat,
|
|
&Result );
|
|
|
|
if ( NetStatus != 0 ||
|
|
Result->rItems[0].status != DS_NAME_NO_ERROR ||
|
|
Result->cItems != 1)
|
|
{
|
|
if (Result->rItems[0].status == DS_NAME_ERROR_NOT_FOUND)
|
|
{
|
|
fprintf(stderr,
|
|
"Requested name \"%ws\" not found in directory.\n",
|
|
FlatName);
|
|
}
|
|
else if (Result->rItems[0].status == DS_NAME_ERROR_NOT_UNIQUE)
|
|
{
|
|
fprintf(stderr,
|
|
"Requested name \"%ws\" not unique in directory.\n",
|
|
FlatName);
|
|
}
|
|
else
|
|
fprintf(stderr,
|
|
"Failed to crack name %ws into the FQDN, (%d) %d %#x\n",
|
|
FlatName, NetStatus, Result->cItems,
|
|
(Result->cItems==1)?Result->rItems[0].status:-1 );
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
return FALSE ;
|
|
}
|
|
|
|
search_dn = Server;
|
|
|
|
base_dn = StrChr(Result->rItems[0].pName, L',');
|
|
if (!base_dn)
|
|
base_dn = Result->rItems[0].pName;
|
|
else
|
|
base_dn++;
|
|
|
|
if (debug) {
|
|
printf("BASE_DN=%S\n", base_dn);
|
|
printf("SEARCH_DN=%S\n", search_dn);
|
|
}
|
|
|
|
DsUnBind( &hDs );
|
|
|
|
wsprintf(search_ava, L"(sAMAccountName=%s)", search_dn);
|
|
if (debug)
|
|
printf("FILTER=\"%S\"\n", search_ava);
|
|
rc = ldap_search_s(ld, base_dn, LDAP_SCOPE_SUBTREE,
|
|
search_ava, NULL, 0, &res);
|
|
|
|
DsFreeNameResult( Result );
|
|
|
|
if (rc != LDAP_SUCCESS) {
|
|
fprintf(stderr, "ldap_search_s failed: %S", ldap_err2string(rc));
|
|
ldap_unbind(ld);
|
|
return 1;
|
|
}
|
|
|
|
for (e = ldap_first_entry(ld, res);
|
|
e;
|
|
e = ldap_next_entry(ld, e)) {
|
|
BerElement *b;
|
|
WCHAR *attr;
|
|
WCHAR *dn = ldap_get_dn(ld, res);
|
|
|
|
printf("Registered ServicePrincipalNames");
|
|
if (dn)
|
|
printf(" for %S", dn);
|
|
printf(":\n");
|
|
|
|
ldap_memfree(dn);
|
|
|
|
for (attr = ldap_first_attribute(ld, e, &b);
|
|
attr;
|
|
attr = ldap_next_attribute(ld, e, b)) {
|
|
WCHAR **values, **p;
|
|
values = ldap_get_values(ld, e, attr);
|
|
for (p = values; *p; p++) {
|
|
if (StrCmp(attr, L"servicePrincipalName") == 0)
|
|
printf(" %S\n", *p);
|
|
}
|
|
ldap_value_free(values);
|
|
ldap_memfree(attr);
|
|
}
|
|
|
|
//ber_free(b, 1);
|
|
}
|
|
|
|
ldap_msgfree(res);
|
|
ldap_unbind(ld);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void Usage( PWSTR name)
|
|
{
|
|
printf("\
|
|
Usage: %S [switches data] computername \n\
|
|
Where \"computername\" can be the name or domain\\name\n\
|
|
\n\
|
|
Switches:\n\
|
|
-R = reset HOST ServicePrincipalName\n\
|
|
Usage: setspn -R computername\n\
|
|
-A = add arbitrary SPN \n\
|
|
Usage: setspn -A SPN computername\n\
|
|
-D = delete arbitrary SPN \n\
|
|
Usage: setspn -D SPN computername\n\
|
|
-L = list registered SPNs \n\
|
|
Usage: setspn [-L] computername \n\
|
|
Examples: \n\
|
|
setspn -R daserver1 \n\
|
|
It will register SPN \"HOST/daserver1\" and \"HOST/{DNS of daserver1}\" \n\
|
|
setspn -A http/daserver daserver1 \n\
|
|
It will register SPN \"http/daserver\" for computer \"daserver1\" \n\
|
|
setspn -D http/daserver daserver1 \n\
|
|
It will delete SPN \"http/daserver\" for computer \"daserver1\" \n\
|
|
", name);
|
|
ExitProcess(0);
|
|
}
|
|
|
|
void __cdecl wmain (int argc, wchar_t *argv[])
|
|
{
|
|
int resetSPN = FALSE, addSPN = FALSE, deleteSPN = FALSE, listSPN = TRUE;
|
|
UNICODE_STRING Service, Host,HostSpn, HostDomain ;
|
|
wchar_t *ptr;
|
|
int i;
|
|
DS_SPN_WRITE_OP Operation;
|
|
PWSTR Scan;
|
|
DWORD Status = 1;
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if ((argv[i][0] == L'-') || (argv[i][0] == L'/'))
|
|
{
|
|
for (ptr = (argv[i] + 1); *ptr; ptr++)
|
|
{
|
|
switch(towupper(*ptr))
|
|
{
|
|
case L'R':
|
|
resetSPN = TRUE;
|
|
break;
|
|
case L'A':
|
|
addSPN = TRUE;
|
|
break;
|
|
case L'D':
|
|
deleteSPN = TRUE;
|
|
break;
|
|
case L'L':
|
|
listSPN = TRUE;
|
|
break;
|
|
case L'V':
|
|
debug = TRUE;
|
|
break;
|
|
case L'?':
|
|
default:
|
|
Usage(argv[0]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
|
|
if ( resetSPN )
|
|
{
|
|
if ( ( argc - i ) != 1 )
|
|
{
|
|
Usage( argv[0] );
|
|
}
|
|
|
|
RtlInitUnicodeString( &Host, argv[i] );
|
|
|
|
if ( AddHostSpn( Host.Buffer ) )
|
|
{
|
|
printf("Updated object\n");
|
|
Status = 0;
|
|
}
|
|
}
|
|
else if ( addSPN || deleteSPN )
|
|
{
|
|
if ( ( argc - i ) != 2 )
|
|
{
|
|
Usage( argv[0] );
|
|
}
|
|
|
|
RtlInitUnicodeString( &HostSpn, argv[i] );
|
|
|
|
Scan = argv[ i + 1 ];
|
|
|
|
if ( Scan = wcschr( Scan, L'\\' ) )
|
|
{
|
|
*Scan++ = L'\0';
|
|
RtlInitUnicodeString( &HostDomain, argv[i+1] );
|
|
RtlInitUnicodeString( &Host, Scan );
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString( &HostDomain, L"" );
|
|
RtlInitUnicodeString( &Host, argv[ i + 1 ] );
|
|
}
|
|
|
|
if ( addSPN )
|
|
Operation = DS_SPN_ADD_SPN_OP;
|
|
|
|
if ( deleteSPN )
|
|
Operation = DS_SPN_DELETE_SPN_OP;
|
|
|
|
if ( AddRemoveSpn( HostSpn.Buffer, HostDomain.Buffer, Host.Buffer, Operation) )
|
|
{
|
|
printf("Updated object\n");
|
|
Status = 0;
|
|
}
|
|
}
|
|
else if ( listSPN )
|
|
{
|
|
if ( ( argc - i ) != 1 )
|
|
{
|
|
Usage( argv[0] );
|
|
}
|
|
|
|
Scan = argv[ i ];
|
|
|
|
if ( Scan = wcschr( Scan, L'\\' ) )
|
|
{
|
|
*Scan++ = L'\0';
|
|
RtlInitUnicodeString( &HostDomain, argv[ i ] );
|
|
RtlInitUnicodeString( &Host, Scan );
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString( &HostDomain, L"" );
|
|
RtlInitUnicodeString( &Host, argv[ i ] );
|
|
}
|
|
|
|
if (LookupHostSpn( HostDomain.Buffer, Host.Buffer ))
|
|
{
|
|
Status = 0;
|
|
}
|
|
}
|
|
|
|
ExitProcess(Status);
|
|
}
|