/*++ 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 #include "keytab.h" #include "keytypes.h" #include "secprinc.h" #include #include #include "options.h" #include "delegtools.h" #include "delegation.h" #include 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; } BOOL UserWantsToDoItAnyway( IN LPSTR fmt, ... ) { va_list va; CHAR Buffer[ 5 ] = { '\0' }; /* == %c\r\n\0 */ INT Response; BOOL ret = FALSE; BOOL keepGoing = TRUE; do { va_start( va, fmt ); fprintf( stderr, "\n" ); vfprintf( stderr, fmt, va ); fprintf( stderr, " [y/n]? " ); if ( !fgets( Buffer, sizeof( Buffer ), stdin ) ) { fprintf( stderr, "EOF on stdin. Assuming you mean no.\n" ); return FALSE; } 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; } 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; K5_OCTET kvno = 1; 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; 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", &kvno, OPT_INT, "Key Version Number (def:1)"}, { "ktvno", &ktvno, OPT_INT, "keytab version (def 0x502)" }, // { "Debug", &DebugFlag, OPT_BOOL | OPT_HIDDEN }, { "RawSalt", &RawHash, OPT_WSTRING | OPT_HIDDEN, "raw MIT salt. For use when generated salt is suspect (1877)." }, { "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, "Tacking on to existing keytab: \n\n" ); DisplayKeytab( stderr, pktFile, 0xFFFFFFFF ); } else { fprintf( stderr, "Keytab read failed!\n" ); return 5; } } if ( Principal ) { LPSTR realm, cp; CHAR tempBuffer[ 255 ]; realm = strchr( Principal, '@' ); if ( realm ) { ULONG length; realm++; length = lstrlenA( realm ); memcpy( tempBuffer, realm, ( length +1 ) * sizeof( realm[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 ) { // connect to the DSA. if ( pLdap || ConnectAndBindToDefaultDsa( &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 ( 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 ); gets( TempPassword ); fprintf( stderr, "\nType the password again to confirm:" ); gets( ConfirmPassword ); 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. */ wsprintfW( wUserName, L"%hs", UserName ); /* 372818: must first detect if we're not on a DC and connect to one prior to calling NetUserGetInfo */ err = DsGetDcNameW( NULL, NULL, NULL, NULL, DS_RETURN_DNS_NAME, &DomainControllerInfo ); if ( err != NO_ERROR ) { err = DsGetDcNameW( NULL, NULL, NULL, NULL, DS_RETURN_FLAT_NAME, &DomainControllerInfo ); } if ( err != NO_ERROR ) { fprintf( stderr, "ERROR: Can not locate a domain controller, " "error %d).\n", err ); return -1; } nas = NetUserGetInfo( DomainControllerInfo->DomainControllerName, wUserName, 1, // level 1 (PBYTE *) &pUserInfo ); NetApiBufferFree( DomainControllerInfo ); 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; } } wsprintfW( wPassword, L"%hs", Password ); pUserInfo->usri1_password = wPassword; nas = NetUserSetInfo( NULL, // local wUserName, 1, // level 1 (LPBYTE) pUserInfo, NULL ); if ( nas == NERR_Success ) { skipSetPassword: NetApiBufferFree( pUserInfo ); 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' ); if ( KtCreateKey( &pktEntry, Principal, Password, realm, kvno, 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\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" " foo@BAR.COM or xyz/foo@BAR.COM \n" " ^ ^\n" " | |\n" " +--------------------+---- I didn't find these.\n", Principal ); return 1; } } else { printf("Type \"%s /help\" for usage\n", argv[0]); } return 0; }