|
|
/*++
MAIN.C
main program for the ktPass program
Copyright (C) 1998 Microsoft Corporation, all rights reserved.
Created, Jun 18, 1998 by DavidCHR.
--*/
#include "master.h"
#include <winldap.h>
#include "keytab.h"
#include "keytypes.h"
#include "secprinc.h"
#include <kerbcon.h>
#include <lm.h>
#include "options.h"
#include "delegtools.h"
#include "delegation.h"
#include <rpc.h>
#include <ntdsapi.h>
#include <dsgetdc.h>
#include <windns.h>
LPSTR KvnoAttribute = "msDS-KeyVersionNumber"; #define KVNO_DETECT_AT_DC ( (ULONG) -1 )
PVOID MIDL_user_allocate( size_t size ) {
return malloc( size ); }
VOID MIDL_user_free( PVOID pvFree ) {
free( pvFree ); }
// this global is set by the command line options.
K5_INT16 ktvno = 0x0502; // kerberos 5, keytab v.2
PKTFILE NewKt() {
PKTFILE ret;
ret = (PKTFILE) malloc (sizeof(KTFILE));
if (!ret) {
return NULL; }
memset(ret, 0L, sizeof(KTFILE));
ret->Version = ktvno;
return ret; }
#define MAYBE 2
USHORT PromptResponse = MAYBE;
BOOL UserWantsToDoItAnyway( IN LPSTR fmt, ... ) { va_list va; CHAR Buffer[ 5 ] = { '\0' }; /* == %c\r\n\0 */ INT Response; BOOL ret = FALSE; BOOL keepGoing = TRUE; ULONG i;
do {
va_start( va, fmt ); fprintf( stderr, "\n" ); vfprintf( stderr, fmt, va );
fprintf( stderr, " [y/n]? " );
if ( PromptResponse != MAYBE ) {
fprintf( stderr, "auto: %hs\n", PromptResponse ? "YES" : "NO" );
return PromptResponse; }
if ( !fgets( Buffer, sizeof( Buffer ), stdin ) ) {
fprintf( stderr, "EOF on stdin. Assuming you mean no.\n" );
return FALSE; }
for ( i = 0; i < sizeof( Buffer ); i++ ) {
if ( Buffer[i] == '\n' ) {
Buffer[i] = '\0'; break; } } Response = Buffer[ 0 ];
switch( Response ) {
case 'Y': case 'y':
ret = TRUE; keepGoing = FALSE; break;
case EOF:
fprintf( stderr, "EOF at console. I assume you mean no.\n" );
// fallthrough
case 'N': case 'n':
ret = FALSE; keepGoing = FALSE; break;
default:
printf( "Your response, %02x ('%c'), doesn't make sense.\n" "'Y' and 'N' are the only acceptable responses.", Response, Response ); } } while ( keepGoing );
if ( !ret ) {
printf( "Exiting.\n" ); exit( -1 ); }
return ret; }
BOOL GetTargetDomainFromUser( IN LPSTR UserName, OUT LPSTR *ppRealUserName, OUT OPTIONAL LPWSTR *ppTargetDC ) {
HANDLE hDS; DWORD dwErr; DWORD StringLength; BOOL ret = FALSE; PDS_NAME_RESULTA pResults; LPWSTR DcName; /* BUGBUG: this implementation takes ANSI
parameters and converts them to unicode.
This is an artifact of this being a proof-of-concept app that later became a support tool.
Someday, we should use unicode throughout and convert to ANSI as needed. */
PDOMAIN_CONTROLLER_INFO pDCName; LPSTR DomainName; LPSTR Cursor;
ASSERT( ppRealUserName != NULL );
*ppRealUserName = UserName;
if (ppTargetDC) { *ppTargetDC = NULL; }
dwErr = DsBind( NULL, NULL, &hDS );
if ( dwErr != ERROR_SUCCESS ) {
fprintf( stderr, "Cannot bind to default domain: 0x%x\n", dwErr );
} else {
dwErr = DsCrackNamesA( hDS, DS_NAME_NO_FLAGS, DS_UNKNOWN_NAME, DS_NT4_ACCOUNT_NAME, 1, &UserName, &pResults ); DsUnBind( hDS ); if ( dwErr == ERROR_FILE_NOT_FOUND ) { fprintf( stderr, "Cannot locate the user %hs. Will try the local domain.\n", UserName ); ret = TRUE; } else if ( dwErr != ERROR_SUCCESS ) { fprintf( stderr, "Cannot DsCrackNames %hs: 0x%x\n", UserName, dwErr ); } else { if ( pResults->cItems != 1 ) { fprintf( stderr, "\"%hs\" has %ld matches -- it needs to be unique!\n", UserName, pResults->cItems ); } else if ( pResults->rItems[0].status != DS_NAME_NO_ERROR ) { fprintf( stderr, "DsCrackNames returned 0x%x in the name entry for %hs.\n", pResults->rItems[ 0 ].status, UserName );
} else {
DomainName = pResults->rItems[0].pDomain;
Cursor = strchr( pResults->rItems[ 0 ].pName, '\\' );
ASSERT( Cursor != NULL ); /* dscracknames wouldn't give back
an NT4_ACCOUNT_NAME that is not of the form DOMAIN\user */
Cursor++;
*ppRealUserName = _strdup( Cursor );
if ( !*ppRealUserName ) {
/* Note that I'm reading from the output parameter after
writing to it, which might be dangerous if this weren't just an app. */
fprintf( stderr, "Couldn't return username portion of \"%hs\"" " -- out of memory.\n", pResults->rItems[0].pName );
} else if ( !ppTargetDC ) {
// user has already selected a DC,
// so he doesn't need us to hunt for one.
ret = TRUE;
} else {
// next, hunt for a DC in that domain.
dwErr = DsGetDcNameA( NULL, // perform locally
DomainName, NULL, // domain GUID: don't care
NULL, // site name: use closest site
DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME | DS_WRITABLE_REQUIRED, &pDCName );
if ( dwErr != ERROR_SUCCESS ) {
fprintf( stderr, "Cannot DsGetDcName for \"%hs\": 0x%x\n", DomainName, dwErr );
} else {
while( pDCName->DomainControllerName[0] == '\\' ) {
pDCName->DomainControllerName++; }
/* Retrieve the string length, +1 for terminating null. */
StringLength = strlen( pDCName->DomainControllerName ) +1;
DcName = (LPWSTR) malloc( StringLength * sizeof( WCHAR ) );
if ( !DcName ) {
fprintf( stderr, "cannot allocate %ld WCHARs.", StringLength );
} else {
swprintf( DcName, L"%hs", pDCName->DomainControllerName );
*ppTargetDC = DcName;
printf( "Targeting domain controller: %ws\n", DcName );
ret = TRUE;
}
NetApiBufferFree( pDCName );
}
if ( !ret ) {
free( *ppRealUserName ); }
} }
DsFreeNameResult( pResults ); } }
if ( !ret ) {
*ppRealUserName = UserName; }
return ret; }
VOID GetKeyVersionFromDomain( IN PLDAP pLdap, IN LPSTR UserName, IN OUT PULONG pkvno ) {
ASSERT( pLdap != NULL );
if ( *pkvno == KVNO_DETECT_AT_DC ) {
if ( !LdapQueryUlongAttributeA( pLdap, NULL, // ignored
UserName, KvnoAttribute, pkvno ) ) {
// a win2k DC would fail with attribute not found.
if ( GetLastError() == LDAP_NO_SUCH_ATTRIBUTE ) {
fprintf( stderr, "The %hs attribute does not exist on the target DC.\n" " Assuming this is a Windows 2000 domain, and setting\n" " the Key Version Number in the Keytab to 1.\n" "\n" " Supply \"/kvno 1\" on the command line to skip this message.\n", KvnoAttribute );
*pkvno = 1;
} else {
fprintf( stderr, "Failed to query kvno attribute from the DC.\n" "Ktpass cannot continue.\n" );
exit( -1 ); } } } }
VOID CheckKeyVersion( IN ULONG BigVer ) {
BYTE LittleVer;
LittleVer = (BYTE) BigVer;
if ( LittleVer != BigVer ) {
if ( !UserWantsToDoItAnyway( "WARNING: The Key version used by Windows (%ld) is too big\n" " to be encoded in a keytab without truncating it to %ld.\n" " This is due to a limitation of the keytab file format\n" " and may lead to interoperability issues.\n" "\n" "Do you want to proceed and truncate the version number", BigVer, LittleVer ) ) {
exit( -1 );
} } }
extern BOOL KtDumpSalt; // in ..\lib\mkkey.c
extern LPWSTR RawHash; // in mkkey.c
// #include "globals.h"
// #include "commands.h"
int __cdecl main( int argc, PCHAR argv[] ) {
LPSTR Principal = NULL; LPSTR UserName = NULL; LPSTR Password = NULL; PLDAP pLdap = NULL; LPSTR UserDn = NULL;
BOOL SetUpn = TRUE; ULONG BigKvno = KVNO_DETECT_AT_DC; ULONG Crypto = KERB_ETYPE_DES_CBC_MD5; ULONG ptype = KRB5_NT_PRINCIPAL; ULONG uacFlags = 0; PKTFILE pktFile = NULL; PCHAR KtReadFile = NULL; PCHAR KtWriteFile = NULL; BOOL DesOnly = TRUE; ULONG LdapOperation = LDAP_MOD_ADD; HANDLE hConsole = NULL; BOOL SetPassword = TRUE; BOOL WarnedAboutAccountStrangeness = FALSE; PVOID pvTrash = NULL; DWORD dwConsoleMode; LPWSTR BindTarget = NULL; // local domain (see ldlib\delegtools.c)
optEnumStruct CryptoSystems[] = {
{ "DES-CBC-CRC", (PVOID) KERB_ETYPE_DES_CBC_CRC, "for compatibility" }, { "DES-CBC-MD5", (PVOID) KERB_ETYPE_DES_CBC_MD5, "default" },
TERMINATE_ARRAY };
#define DUPE( type, desc ) { "KRB5_NT_" # type, \
(PVOID) KRB5_NT_##type, \ desc }
optEnumStruct PrincTypes[] = {
DUPE( PRINCIPAL, "The general ptype-- recommended" ), DUPE( SRV_INST, "user service instance" ), DUPE( SRV_HST, "host service instance" ), DUPE( SRV_XHST, NULL ),
TERMINATE_ARRAY };
optEnumStruct MappingOperations[] = {
{ "add", (PVOID) LDAP_MOD_ADD, "add value (default)" }, { "set", (PVOID) LDAP_MOD_REPLACE, "set value" },
TERMINATE_ARRAY };
#if DBG
#undef OPT_HIDDEN
#define OPT_HIDDEN 0 /* no hidden options on debug builds. */
#endif
optionStruct Options[] = {
{ "?", NULL, OPT_HELP | OPT_HIDDEN }, { "h", NULL, OPT_HELP | OPT_HIDDEN }, { "help", NULL, OPT_HELP | OPT_HIDDEN }, { NULL, NULL, OPT_DUMMY, "most useful args" }, { "out", &KtWriteFile, OPT_STRING, "Keytab to produce" }, { "princ", &Principal, OPT_STRING, "Principal name (user@REALM)" }, { "pass", &Password, OPT_STRING, "password to use" }, { NULL, NULL, OPT_CONTINUE, "use \"*\" to prompt for password." }, { NULL, NULL, OPT_DUMMY, "less useful stuff" }, { "mapuser", &UserName, OPT_STRING, "map princ (above) to this user account (default: don't)" }, { "mapOp", &LdapOperation, OPT_ENUMERATED, "how to set the mapping attribute (default: add it)", MappingOperations }, { "DesOnly", &DesOnly, OPT_BOOL, "Set account for des-only encryption (default:do)" }, { "in", &KtReadFile, OPT_STRING, "Keytab to read/digest" }, { NULL, NULL, OPT_DUMMY, "options for key generation" }, { "crypto", &Crypto, OPT_ENUMERATED, "Cryptosystem to use", CryptoSystems }, { "ptype", &ptype, OPT_ENUMERATED, "principal type in question", PrincTypes }, { "kvno", &BigKvno, OPT_INT, "Override Key Version Number"}, { NULL, NULL, OPT_CONTINUE, "Default: query DC for kvno. Use /kvno 1 for Win2K compat." }, /* It is best NOT to mess with the keytab version number.
We use this for debugging only. */
/* Use /target to hit a specific DC. This is good if you just
created a user there, for example. It also eliminates the network traffic used to locate the DC */
{ "Answer", &PromptResponse, OPT_BOOL, "+Answer answers YES to prompts. -Answer answers NO." }, { "Target", &BindTarget, OPT_WSTRING, "Which DC to use. Default:detect" }, { "ktvno", &ktvno, OPT_INT | OPT_HIDDEN, "keytab version (def 0x502). Leave this alone." }, // { "Debug", &DebugFlag, OPT_BOOL | OPT_HIDDEN },
{ "RawSalt", &RawHash, OPT_WSTRING | OPT_HIDDEN, "raw salt to use when generating key (not needed)" }, { "DumpSalt", &KtDumpSalt, OPT_BOOL | OPT_HIDDEN, "show us the MIT salt being used to generate the key" }, { "SetUpn", &SetUpn, OPT_BOOL | OPT_HIDDEN, "Set the UPN in addition to the SPN. Default DO." }, { "SetPass", &SetPassword, OPT_BOOL | OPT_HIDDEN, "Set the user's password if supplied." },
TERMINATE_ARRAY };
FILE *f;
// DebugFlag = 0;
ParseOptionsEx( argc-1, argv+1, Options, OPT_FLAG_TERMINATE, &pvTrash, NULL, NULL );
if ( ( Principal ) && ( strlen( Principal ) > BUFFER_SIZE ) ) {
fprintf( stderr, "Please submit a shorter principal name.\n" ); return 1; }
if ( Password && ( strlen( Password ) > BUFFER_SIZE ) ) {
fprintf( stderr, "Please submit a shorter password.\n" );
return 1; }
if ( KtReadFile ) {
if ( ReadKeytabFromFile( &pktFile, KtReadFile ) ) {
fprintf( stderr, "Existing keytab: \n\n" );
DisplayKeytab( stderr, pktFile, 0xFFFFFFFF );
} else {
fprintf( stderr, "Keytab read failed!\n" ); return 5; } }
if ( !UserName && ( BigKvno == KVNO_DETECT_AT_DC ) ) {
//
// if the user doesn't pass /kvno, we want to
// detect the kvno at the DC. However, if no
// /mapuser is passed, there's no DC to do this
// at. Win2K ktpass provided '1' as the default,
// so this is what we do here.
//
BigKvno = 1; }
if ( Principal ) {
LPSTR realm, cp; CHAR tempBuffer[ DNS_MAX_NAME_BUFFER_LENGTH ]; realm = strchr( Principal, '@' ); if ( realm ) {
ULONG length;
realm++;
length = lstrlenA( realm );
if ( length >= sizeof( tempBuffer )) {
length = sizeof( tempBuffer ) - 1; }
memcpy( tempBuffer, realm, ( length + 1 ) * sizeof( realm[0] ) );
tempBuffer[sizeof( tempBuffer ) - 1] = '\0';
CharUpperBuffA( realm, length );
if ( lstrcmpA( realm, tempBuffer ) != 0 ) {
fprintf( stderr, "WARNING: realm \"%hs\" has lowercase characters in it.\n" " We only currently support realms in UPPERCASE.\n" " assuming you mean \"%hs\"...\n", tempBuffer, realm );
// now "realm" will be all uppercase.
}
*(realm-1) = '\0'; // separate the realm from the principal
if ( UserName ) {
/* Crack the domain name (507151). Without this call
the DC we target may not contain the user object. Note that UserName is modified by this operation. */
if ( !GetTargetDomainFromUser( UserName, &UserName, BindTarget ? NULL : &BindTarget ) ) {
return 1; }
// connect to the DSA.
if ( pLdap || ConnectAndBindToDefaultDsa( BindTarget, &pLdap ) ) {
// locate the User
if ( UserDn || FindUser( pLdap, UserName, &uacFlags, &UserDn ) ) {
if ( ( LdapOperation == LDAP_MOD_REPLACE ) & !( uacFlags & UF_NORMAL_ACCOUNT ) ) {
/* 97282: the user is not UF_NORMAL_ACCOUNT, so
check to see that the caller *really* wants to blow away the non-user's SPNs. */
if ( uacFlags ) {
fprintf( stderr, "WARNING: Account %hs is not a normal user " "account (uacFlags=0x%x).\n", UserName, uacFlags );
} else {
fprintf( stderr, "WARNING: Cannot determine the account type" " for %hs.\n", UserName ); }
WarnedAboutAccountStrangeness = TRUE;
if ( !UserWantsToDoItAnyway( "Do you really want to delete any previous " "servicePrincipalName values on %hs", UserName ) ) {
/* Abort the operation, but try to do whatever
else the user asked us to do. */
goto abortedMapping; } }
/* 97279: check to see if there are other SPNs
by the same name already registered. If so, we don't want to blow away those accounts. If/when we decide to do this, we'd do it here. */
// set/add the user property
if ( SetStringProperty( pLdap, UserDn, "servicePrincipalName", Principal, LdapOperation ) ) {
if ( SetUpn ) {
*(realm-1) = '@'; // UPN includes the '@'
if ( !SetStringProperty( pLdap, UserDn, "userPrincipalName", Principal, LDAP_MOD_REPLACE ) ) {
fprintf( stderr, "WARNING: Failed to set UPN %hs on %hs.\n" " kinits to '%hs' will fail.\n", Principal, UserDn, Principal ); }
*(realm -1 ) = '\0'; // where it was before
}
fprintf( stderr, "Successfully mapped %hs to %hs.\n", Principal, UserName );
abortedMapping:
; /* Need a semicolon so we can goto here. */
} else {
fprintf( stderr, "WARNING: Unable to set SPN mapping data.\n" " If %hs already has an SPN mapping installed for " " %hs, this is no cause for concern.\n", UserName, Principal ); } } // else a message will be printed.
} // else a message will be printed.
} // if ( UserName )
if ( Password ) {
PKTENT pktEntry; CHAR TempPassword[ 255 ], ConfirmPassword[ 255 ];
if ( lstrcmpA( Password, "*" ) == 0 ) {
hConsole = GetStdHandle( STD_INPUT_HANDLE );
if ( GetConsoleMode( hConsole, &dwConsoleMode ) ) {
if ( SetConsoleMode( hConsole, dwConsoleMode & ~ENABLE_ECHO_INPUT ) ) {
do {
fprintf( stderr, "Type the password for %hs: ", Principal );
if ( !fgets( TempPassword, sizeof( TempPassword ), stdin ) ) {
fprintf( stderr, "failed to read password.\n" );
exit( GetLastError() ); }
fprintf( stderr, "\nType the password again to confirm:" );
if ( !fgets( ConfirmPassword, sizeof( ConfirmPassword ), stdin ) ) {
fprintf( stderr, "failed to read confirmation password.\n" ); exit( GetLastError() ); }
if ( lstrcmpA( ConfirmPassword, TempPassword ) == 0 ) {
printf( "\n" );
break;
} else {
fprintf( stderr, "The passwords you type must match exactly.\n" ); } } while ( TRUE );
Password = TempPassword;
SetConsoleMode( hConsole, dwConsoleMode );
} else {
fprintf( stderr, "Failed to turn off echo input for password entry:" " 0x%x\n",
GetLastError() );
return -1; }
} else {
fprintf( stderr, "Failed to retrieve console mode settings: 0x%x.\n", GetLastError() );
return -1; } }
if ( SetPassword && UserName ) {
DWORD err; NET_API_STATUS nas; PUSER_INFO_1 pUserInfo; WCHAR wUserName[ MAX_PATH ]; DOMAIN_CONTROLLER_INFOW * DomainControllerInfo = NULL;
/* WASBUG 369: converting ascii to unicode
This is safe, because RFC1510 doesn't do UNICODE, and this tool is specifically for unix interop support; unix machines don't do unicode. */
if ( strlen( UserName ) >= MAX_PATH ) {
UserName[MAX_PATH] = '\0'; }
wsprintfW( wUserName, L"%hs", UserName );
nas = NetUserGetInfo( BindTarget, wUserName, 1, // level 1
(PBYTE *) &pUserInfo );
if ( nas == NERR_Success ) {
WCHAR wPassword[ PWLEN ];
uacFlags = pUserInfo->usri1_flags;
if ( !( uacFlags & UF_NORMAL_ACCOUNT ) ) {
/* 97282: For abnormal accounts (these include
workstation trust accounts, interdomain trust accounts, server trust accounts), ask the user if he/she really wants to perform this operation. */
if ( !WarnedAboutAccountStrangeness ) {
fprintf( stderr, "WARNING: Account %hs is not a user account" " (uacflags=0x%x).\n", UserName, uacFlags );
WarnedAboutAccountStrangeness = TRUE; }
fprintf( stderr, "WARNING: Resetting %hs's password may" " cause authentication problems if %hs" " is being used as a server.\n", UserName, UserName );
if ( !UserWantsToDoItAnyway( "Reset %hs's password", UserName ) ) {
/* Skip it, but try to do anything else the user
requested. */
goto skipSetPassword; } }
if ( strlen( Password ) >= PWLEN ) {
Password[PWLEN] = '\0'; }
wsprintfW( wPassword, L"%hs", Password );
pUserInfo->usri1_password = wPassword;
nas = NetUserSetInfo( BindTarget, wUserName, 1, // level 1
(LPBYTE) pUserInfo, NULL );
if ( nas == NERR_Success ) {
skipSetPassword:
NetApiBufferFree( pUserInfo );
GetKeyVersionFromDomain( pLdap, UserName, &BigKvno );
goto skipout;
} else {
fprintf( stderr, "Failed to set password for %ws: 0x%x.\n", wUserName, nas );
}
} else {
fprintf( stderr, "Failed to retrieve user info for %ws: 0x%x.\n", wUserName, nas ); }
fprintf( stderr, "Aborted.\n" );
return nas; }
skipout:
ASSERT( realm != NULL );
// physically separate the realm data.
ASSERT( *( realm -1 ) == '\0' );
CheckKeyVersion( BigKvno );
if ( KtCreateKey( &pktEntry, Principal, Password, realm, (K5_OCTET) BigKvno, ptype, Crypto, // this is the "fake" etype
Crypto ) ) {
if ( pktFile == NULL ) {
pktFile = NewKt();
if ( !pktFile ) {
fprintf( stderr, "Failed to allocate keytable.\n" );
return 4; } }
if ( AddEntryToKeytab( pktFile, pktEntry, FALSE ) ) {
fprintf( stderr, "Key created.\n" );
} else {
fprintf( stderr, "Failed to add entry to keytab.\n" ); return 2; }
if ( KtWriteFile ) {
fprintf( stderr, "Output keytab to %hs:\n", KtWriteFile );
DisplayKeytab( stderr, pktFile, 0xFFFFFFFF );
if ( !WriteKeytabToFile( pktFile, KtWriteFile ) ) {
fprintf( stderr, "\n\n" "Failed to write keytab file %hs.\n", KtWriteFile );
return 6; }
// write keytab.
}
} else {
fprintf( stderr, "Failed to create key for keytab. Quitting.\n" );
return 7; }
if ( UserName && DesOnly ) {
ASSERT( pLdap != NULL ); ASSERT( UserDn != NULL );
// set the DES_ONLY flag
// first, query the account's account flags.
if ( uacFlags /* If we already queried the user's
AccountControl flags, no need to do it again */ || QueryAccountControlFlagsA( pLdap, NULL, // domain name is ignored
UserName, &uacFlags ) ) {
uacFlags |= UF_USE_DES_KEY_ONLY;
if ( SetAccountControlFlagsA( pLdap, NULL, // domain name is ignored
UserName, uacFlags ) ) {
fprintf( stderr, "Account %hs has been set for DES-only encryption.\n", UserName );
if ( !SetPassword ) {
fprintf( stderr, "To make this take effect, you must change " "%hs's password manually.\n", UserName ); } } // else message printed.
} // else message printed
} } // else user doesn't want me to make a key
if ( !Password && !UserName ) {
fprintf( stderr, "doing nothing.\n" "specify /pass and/or /mapuser to either \n" "make a key with the given password or \n" "map a user to a particular SPN, respectively.\n" ); }
} else {
fprintf( stderr, "principal %hs doesn't contain an '@' symbol.\n" "Looking for something of the form:\n" " [email protected] or xyz/[email protected] \n" " ^ ^\n" " | |\n" " +--------------------+---- I didn't find these.\n", Principal );
return 1; }
} else {
//
// if no principal is specified, we should find a way to warn
// the user. The only real reason to do this is when importing
// a keytab and not saving a key; admittedly not a likely scenario.
//
printf( "\n" "WARNING: No principal name specified.\n" ); }
return 0; }
|