1 /* 2 Copyright 2005,2006 by the Massachusetts Institute of Technology 3 Copyright 2007 by Secure Endpoints Inc. 4 5 All rights reserved. 6 7 Permission to use, copy, modify, and distribute this software and its 8 documentation for any purpose and without fee is hereby granted, 9 provided that the above copyright notice appear in all copies and that 10 both that copyright notice and this permission notice appear in 11 supporting documentation, and that the name of the Massachusetts 12 Institute of Technology (M.I.T.) not be used in advertising or publicity 13 pertaining to distribution of the software without specific, written 14 prior permission. 15 16 M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 17 ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 18 M.I.T. BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 19 ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 20 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 21 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 22 SOFTWARE. 23 24 */ 25 26 #include "kfwlogon.h" 27 28 #include <io.h> 29 #include <stdio.h> 30 #include <sys/stat.h> 31 #include <sys/types.h> 32 #include <fcntl.h> 33 34 #include <winsock2.h> 35 #include <lm.h> 36 #include <nb30.h> 37 38 static HANDLE hDLL; 39 40 static HANDLE hInitMutex = NULL; 41 static BOOL bInit = FALSE; 42 43 44 BOOLEAN APIENTRY DllEntryPoint(HANDLE dll, DWORD reason, PVOID reserved) 45 { 46 hDLL = dll; 47 switch (reason) { 48 case DLL_PROCESS_ATTACH: 49 /* Initialization Mutex */ 50 hInitMutex = CreateMutex(NULL, FALSE, NULL); 51 break; 52 53 case DLL_PROCESS_DETACH: 54 CloseHandle(hInitMutex); 55 break; 56 57 case DLL_THREAD_ATTACH: 58 case DLL_THREAD_DETACH: 59 default: 60 /* Everything else succeeds but does nothing. */ 61 break; 62 } 63 64 return TRUE; 65 } 66 67 DWORD APIENTRY NPGetCaps(DWORD index) 68 { 69 switch (index) { 70 case WNNC_NET_TYPE: 71 /* We aren't a file system; We don't have our own type; use somebody else's. */ 72 return WNNC_NET_SUN_PC_NFS; 73 case WNNC_START: 74 /* Say we are already started, even though we might wait after we receive NPLogonNotify */ 75 return 1; 76 77 default: 78 return 0; 79 } 80 } 81 82 83 static BOOL 84 WINAPI 85 UnicodeStringToANSI(UNICODE_STRING uInputString, LPSTR lpszOutputString, int nOutStringLen) 86 { 87 CPINFO CodePageInfo; 88 89 GetCPInfo(CP_ACP, &CodePageInfo); 90 91 if (CodePageInfo.MaxCharSize > 1) 92 // Only supporting non-Unicode strings 93 return FALSE; 94 95 if (uInputString.Buffer && ((LPBYTE) uInputString.Buffer)[1] == '\0') 96 { 97 // Looks like unicode, better translate it 98 // UNICODE_STRING specifies the length of the buffer string in Bytes not WCHARS 99 WideCharToMultiByte(CP_ACP, 0, (LPCWSTR) uInputString.Buffer, uInputString.Length/2, 100 lpszOutputString, nOutStringLen-1, NULL, NULL); 101 lpszOutputString[min(uInputString.Length/2,nOutStringLen-1)] = '\0'; 102 return TRUE; 103 } 104 105 lpszOutputString[0] = '\0'; 106 return FALSE; 107 } // UnicodeStringToANSI 108 109 110 static BOOL 111 is_windows_vista(void) 112 { 113 static BOOL fChecked = FALSE; 114 static BOOL fIsWinVista = FALSE; 115 116 if (!fChecked) 117 { 118 OSVERSIONINFO Version; 119 120 memset (&Version, 0x00, sizeof(Version)); 121 Version.dwOSVersionInfoSize = sizeof(Version); 122 123 if (GetVersionEx (&Version)) 124 { 125 if (Version.dwPlatformId == VER_PLATFORM_WIN32_NT && 126 Version.dwMajorVersion >= 6) 127 fIsWinVista = TRUE; 128 } 129 fChecked = TRUE; 130 } 131 132 return fIsWinVista; 133 } 134 135 136 /* Construct a Logon Script that will cause the LogonEventHandler to be executed 137 * under in the logon session 138 */ 139 140 #define RUNDLL32_CMDLINE "rundll32.exe kfwlogon.dll,LogonEventHandler " 141 VOID 142 ConfigureLogonScript(LPWSTR *lpLogonScript, char * filename) { 143 DWORD dwLogonScriptLen; 144 LPWSTR lpScript; 145 LPSTR lpTemp; 146 147 if (!lpLogonScript) 148 return; 149 *lpLogonScript = NULL; 150 151 if (!filename) 152 return; 153 154 dwLogonScriptLen = strlen(RUNDLL32_CMDLINE) + strlen(filename) + 2; 155 lpTemp = (LPSTR) malloc(dwLogonScriptLen); 156 if (!lpTemp) 157 return; 158 159 _snprintf(lpTemp, dwLogonScriptLen, "%s%s", RUNDLL32_CMDLINE, filename); 160 161 SetLastError(0); 162 dwLogonScriptLen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpTemp, -1, NULL, 0); 163 DebugEvent("ConfigureLogonScript %s requires %d bytes gle=0x%x", lpTemp, dwLogonScriptLen, GetLastError()); 164 165 lpScript = LocalAlloc(LMEM_ZEROINIT, dwLogonScriptLen * 2); 166 if (lpScript) { 167 if (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpTemp, -1, lpScript, 2 * dwLogonScriptLen)) 168 *lpLogonScript = lpScript; 169 else { 170 DebugEvent("ConfigureLogonScript - MultiByteToWideChar failed gle = 0x%x", GetLastError()); 171 LocalFree(lpScript); 172 } 173 } else { 174 DebugEvent("LocalAlloc failed gle=0x%x", GetLastError()); 175 } 176 free(lpTemp); 177 } 178 179 180 DWORD APIENTRY NPLogonNotify( 181 PLUID lpLogonId, 182 LPCWSTR lpAuthentInfoType, 183 LPVOID lpAuthentInfo, 184 LPCWSTR lpPreviousAuthentInfoType, 185 LPVOID lpPreviousAuthentInfo, 186 LPWSTR lpStationName, 187 LPVOID StationHandle, 188 LPWSTR *lpLogonScript) 189 { 190 char uname[MAX_USERNAME_LENGTH+1]=""; 191 char password[MAX_PASSWORD_LENGTH+1]=""; 192 char logonDomain[MAX_DOMAIN_LENGTH+1]=""; 193 194 MSV1_0_INTERACTIVE_LOGON *IL; 195 196 DWORD code = 0; 197 198 char *reason; 199 char *ctemp; 200 201 BOOLEAN interactive = TRUE; 202 HWND hwndOwner = (HWND)StationHandle; 203 BOOLEAN lowercased_name = TRUE; 204 205 /* Can we load KFW binaries? */ 206 if ( !KFW_is_available() ) 207 return 0; 208 209 DebugEvent0("NPLogonNotify start"); 210 211 /* Remote Desktop / Terminal Server connections to existing sessions 212 * are interactive logons. Unfortunately, because the session already 213 * exists the logon script does not get executed and this prevents 214 * us from being able to execute the rundll32 entrypoint 215 * LogonEventHandlerA which would process the credential cache this 216 * routine will produce. Therefore, we must cleanup orphaned cache 217 * files from this routine. We will take care of it before doing 218 * anything else. 219 */ 220 KFW_cleanup_orphaned_caches(); 221 222 /* Are we interactive? */ 223 if (lpStationName) 224 interactive = (wcsicmp(lpStationName, L"WinSta0") == 0); 225 226 if ( !interactive ) { 227 char station[64]="station"; 228 DWORD rv; 229 230 SetLastError(0); 231 rv = WideCharToMultiByte(CP_UTF8, 0, lpStationName, -1, 232 station, sizeof(station), NULL, NULL); 233 DebugEvent("Skipping NPLogonNotify- LoginId(%d,%d) - Interactive(%d:%s) - gle %d", 234 lpLogonId->HighPart, lpLogonId->LowPart, interactive, rv != 0 ? station : "failure", GetLastError()); 235 return 0; 236 } else 237 DebugEvent("NPLogonNotify - LoginId(%d,%d)", lpLogonId->HighPart, lpLogonId->LowPart); 238 239 /* Initialize Logon Script to none */ 240 *lpLogonScript=NULL; 241 242 /* MSV1_0_INTERACTIVE_LOGON and KERB_INTERACTIVE_LOGON are equivalent for 243 * our purposes */ 244 245 if ( wcsicmp(lpAuthentInfoType,L"MSV1_0:Interactive") && 246 wcsicmp(lpAuthentInfoType,L"Kerberos:Interactive") ) 247 { 248 char msg[64]; 249 WideCharToMultiByte(CP_ACP, 0, lpAuthentInfoType, -1, 250 msg, sizeof(msg), NULL, NULL); 251 msg[sizeof(msg)-1]='\0'; 252 DebugEvent("NPLogonNotify - Unsupported Authentication Info Type: %s", msg); 253 return 0; 254 } 255 256 IL = (MSV1_0_INTERACTIVE_LOGON *) lpAuthentInfo; 257 258 /* Convert from Unicode to ANSI */ 259 260 /*TODO: Use SecureZeroMemory to erase passwords */ 261 if (!UnicodeStringToANSI(IL->UserName, uname, MAX_USERNAME_LENGTH) || 262 !UnicodeStringToANSI(IL->Password, password, MAX_PASSWORD_LENGTH) || 263 !UnicodeStringToANSI(IL->LogonDomainName, logonDomain, MAX_DOMAIN_LENGTH)) 264 return 0; 265 266 /* Make sure AD-DOMAINS sent from login that is sent to us is stripped */ 267 ctemp = strchr(uname, '@'); 268 if (ctemp) *ctemp = 0; 269 270 /* is the name all lowercase? */ 271 for ( ctemp = uname; *ctemp ; ctemp++) { 272 if ( !islower(*ctemp) ) { 273 lowercased_name = FALSE; 274 break; 275 } 276 } 277 278 code = KFW_get_cred(uname, password, 0, &reason); 279 DebugEvent("NPLogonNotify - KFW_get_cred uname=[%s] code=[%d]",uname, code); 280 281 /* remove any kerberos 5 tickets currently held by the SYSTEM account 282 * for this user 283 */ 284 if (!code) { 285 char filename[MAX_PATH+1] = ""; 286 char acctname[MAX_USERNAME_LENGTH+MAX_DOMAIN_LENGTH+3]=""; 287 PSID pUserSid = NULL; 288 LPTSTR pReferencedDomainName = NULL; 289 DWORD dwSidLen = 0, dwDomainLen = 0, count; 290 SID_NAME_USE eUse; 291 292 if (_snprintf(acctname, sizeof(acctname), "%s\\%s", logonDomain, uname) < 0) { 293 code = -1; 294 goto cleanup; 295 } 296 297 count = GetTempPath(sizeof(filename), filename); 298 if (count == 0 || count > (sizeof(filename)-1)) { 299 code = -1; 300 goto cleanup; 301 } 302 303 if (_snprintf(filename, sizeof(filename), "%s\\kfwlogon-%x.%x", 304 filename, lpLogonId->HighPart, lpLogonId->LowPart) < 0) 305 { 306 code = -1; 307 goto cleanup; 308 } 309 310 KFW_copy_cache_to_system_file(uname, filename); 311 312 /* Need to determine the SID */ 313 314 /* First get the size of the required buffers */ 315 LookupAccountName (NULL, 316 acctname, 317 pUserSid, 318 &dwSidLen, 319 pReferencedDomainName, 320 &dwDomainLen, 321 &eUse); 322 if(dwSidLen){ 323 pUserSid = (PSID) malloc (dwSidLen); 324 memset(pUserSid,0,dwSidLen); 325 } 326 327 if(dwDomainLen){ 328 pReferencedDomainName = (LPTSTR) malloc (dwDomainLen * sizeof(TCHAR)); 329 memset(pReferencedDomainName,0,dwDomainLen * sizeof(TCHAR)); 330 } 331 332 //Now get the SID and the domain name 333 if (pUserSid && LookupAccountName( NULL, 334 acctname, 335 pUserSid, 336 &dwSidLen, 337 pReferencedDomainName, 338 &dwDomainLen, 339 &eUse)) 340 { 341 DebugEvent("LookupAccountName obtained user %s sid in domain %s", acctname, pReferencedDomainName); 342 code = KFW_set_ccache_dacl_with_user_sid(filename, pUserSid); 343 344 #ifdef USE_WINLOGON_EVENT 345 /* If we are on Vista, setup a LogonScript 346 * that will execute the LogonEventHandler entry point via rundll32.exe 347 */ 348 if (is_windows_vista()) { 349 ConfigureLogonScript(lpLogonScript, filename); 350 if (*lpLogonScript) 351 DebugEvent0("LogonScript assigned"); 352 else 353 DebugEvent0("No Logon Script"); 354 } 355 #else 356 ConfigureLogonScript(lpLogonScript, filename); 357 if (*lpLogonScript) 358 DebugEvent0("LogonScript assigned"); 359 else 360 DebugEvent0("No Logon Script"); 361 #endif 362 } else { 363 DebugEvent0("LookupAccountName failed"); 364 DeleteFile(filename); 365 code = -1; 366 } 367 368 cleanup: 369 if (pUserSid) 370 free(pUserSid); 371 if (pReferencedDomainName) 372 free(pReferencedDomainName); 373 } 374 375 KFW_destroy_tickets_for_principal(uname); 376 377 if (code) { 378 char msg[128]; 379 HANDLE h; 380 char *ptbuf[1]; 381 382 StringCbPrintf(msg, sizeof(msg), "Kerberos ticket acquisition failed: %s", reason); 383 384 h = RegisterEventSource(NULL, KFW_LOGON_EVENT_NAME); 385 ptbuf[0] = msg; 386 ReportEvent(h, EVENTLOG_WARNING_TYPE, 0, 1008, NULL, 1, 0, ptbuf, NULL); 387 DeregisterEventSource(h); 388 SetLastError(code); 389 } 390 391 if (code) 392 DebugEvent0("NPLogonNotify failure"); 393 else 394 DebugEvent0("NPLogonNotify success"); 395 396 return code; 397 } 398 399 400 DWORD APIENTRY NPPasswordChangeNotify( 401 LPCWSTR lpAuthentInfoType, 402 LPVOID lpAuthentInfo, 403 LPCWSTR lpPreviousAuthentInfoType, 404 LPVOID lpPreviousAuthentInfo, 405 LPWSTR lpStationName, 406 LPVOID StationHandle, 407 DWORD dwChangeInfo) 408 { 409 return 0; 410 } 411 412 #include <userenv.h> 413 #include <Winwlx.h> 414 415 #ifdef COMMENT 416 typedef struct _WLX_NOTIFICATION_INFO { 417 ULONG Size; 418 ULONG Flags; 419 PWSTR UserName; 420 PWSTR Domain; 421 PWSTR WindowStation; 422 HANDLE hToken; 423 HDESK hDesktop; 424 PFNMSGECALLBACK pStatusCallback; 425 } WLX_NOTIFICATION_INFO, *PWLX_NOTIFICATION_INFO; 426 #endif 427 428 VOID KFW_Startup_Event( PWLX_NOTIFICATION_INFO pInfo ) 429 { 430 DebugEvent0("KFW_Startup_Event"); 431 } 432 433 static BOOL 434 GetSecurityLogonSessionData(HANDLE hToken, PSECURITY_LOGON_SESSION_DATA * ppSessionData) 435 { 436 NTSTATUS Status = 0; 437 TOKEN_STATISTICS Stats; 438 DWORD ReqLen; 439 BOOL Success; 440 441 if (!ppSessionData) 442 return FALSE; 443 *ppSessionData = NULL; 444 445 446 Success = GetTokenInformation( hToken, TokenStatistics, &Stats, sizeof(TOKEN_STATISTICS), &ReqLen ); 447 if ( !Success ) 448 return FALSE; 449 450 Status = LsaGetLogonSessionData( &Stats.AuthenticationId, ppSessionData ); 451 if ( FAILED(Status) || !ppSessionData ) 452 return FALSE; 453 454 return TRUE; 455 } 456 457 VOID KFW_Logon_Event( PWLX_NOTIFICATION_INFO pInfo ) 458 { 459 #ifdef USE_WINLOGON_EVENT 460 WCHAR szUserW[128] = L""; 461 char szUserA[128] = ""; 462 char szPath[MAX_PATH] = ""; 463 char szLogonId[128] = ""; 464 DWORD count; 465 char filename[MAX_PATH] = ""; 466 char newfilename[MAX_PATH] = ""; 467 char commandline[MAX_PATH+256] = ""; 468 STARTUPINFO startupinfo; 469 PROCESS_INFORMATION procinfo; 470 HANDLE hf = NULL; 471 472 LUID LogonId = {0, 0}; 473 PSECURITY_LOGON_SESSION_DATA pLogonSessionData = NULL; 474 475 HKEY hKey1 = NULL, hKey2 = NULL; 476 477 DebugEvent0("KFW_Logon_Event - Start"); 478 479 GetSecurityLogonSessionData( pInfo->hToken, &pLogonSessionData ); 480 481 if ( pLogonSessionData ) { 482 LogonId = pLogonSessionData->LogonId; 483 DebugEvent("KFW_Logon_Event - LogonId(%d,%d)", LogonId.HighPart, LogonId.LowPart); 484 485 _snprintf(szLogonId, sizeof(szLogonId), "kfwlogon-%d.%d",LogonId.HighPart, LogonId.LowPart); 486 LsaFreeReturnBuffer( pLogonSessionData ); 487 } else { 488 DebugEvent0("KFW_Logon_Event - Unable to determine LogonId"); 489 return; 490 } 491 492 count = GetEnvironmentVariable("TEMP", filename, sizeof(filename)); 493 if ( count > sizeof(filename) || count == 0 ) { 494 GetWindowsDirectory(filename, sizeof(filename)); 495 } 496 497 if ( strlen(filename) + strlen(szLogonId) + 2 > sizeof(filename) ) { 498 DebugEvent0("KFW_Logon_Event - filename too long"); 499 return; 500 } 501 502 strcat(filename, "\\"); 503 strcat(filename, szLogonId); 504 505 hf = CreateFile(filename, FILE_ALL_ACCESS, 0, NULL, OPEN_EXISTING, 506 FILE_ATTRIBUTE_NORMAL, NULL); 507 if (hf == INVALID_HANDLE_VALUE) { 508 DebugEvent0("KFW_Logon_Event - file cannot be opened"); 509 return; 510 } 511 CloseHandle(hf); 512 513 if (KFW_set_ccache_dacl(filename, pInfo->hToken)) { 514 DebugEvent0("KFW_Logon_Event - unable to set dacl"); 515 DeleteFile(filename); 516 return; 517 } 518 519 if (KFW_obtain_user_temp_directory(pInfo->hToken, newfilename, sizeof(newfilename))) { 520 DebugEvent0("KFW_Logon_Event - unable to obtain temp directory"); 521 return; 522 } 523 524 if ( strlen(newfilename) + strlen(szLogonId) + 2 > sizeof(newfilename) ) { 525 DebugEvent0("KFW_Logon_Event - new filename too long"); 526 return; 527 } 528 529 strcat(newfilename, "\\"); 530 strcat(newfilename, szLogonId); 531 532 if (!MoveFileEx(filename, newfilename, 533 MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { 534 DebugEvent("KFW_Logon_Event - MoveFileEx failed GLE = 0x%x", GetLastError()); 535 return; 536 } 537 538 _snprintf(commandline, sizeof(commandline), "kfwcpcc.exe \"%s\"", newfilename); 539 540 GetStartupInfo(&startupinfo); 541 if (CreateProcessAsUser( pInfo->hToken, 542 "kfwcpcc.exe", 543 commandline, 544 NULL, 545 NULL, 546 FALSE, 547 CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS, 548 NULL, 549 NULL, 550 &startupinfo, 551 &procinfo)) 552 { 553 DebugEvent("KFW_Logon_Event - CommandLine %s", commandline); 554 555 WaitForSingleObject(procinfo.hProcess, 30000); 556 557 CloseHandle(procinfo.hThread); 558 CloseHandle(procinfo.hProcess); 559 } else { 560 DebugEvent0("KFW_Logon_Event - CreateProcessFailed"); 561 } 562 563 DeleteFile(newfilename); 564 565 DebugEvent0("KFW_Logon_Event - End"); 566 #endif /* USE_WINLOGON_EVENT */ 567 } 568 569 570 /* Documentation on the use of RunDll32 entrypoints can be found 571 * at https://support.microsoft.com/kb/164787 572 */ 573 void CALLBACK 574 LogonEventHandlerA(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow) 575 { 576 HANDLE hf = NULL; 577 char commandline[MAX_PATH+256] = ""; 578 STARTUPINFO startupinfo; 579 PROCESS_INFORMATION procinfo; 580 581 DebugEvent0("LogonEventHandler - Start"); 582 583 /* Validate lpszCmdLine as a file */ 584 hf = CreateFile(lpszCmdLine, GENERIC_READ | DELETE, 0, NULL, OPEN_EXISTING, 585 FILE_ATTRIBUTE_NORMAL, NULL); 586 if (hf == INVALID_HANDLE_VALUE) { 587 DebugEvent("LogonEventHandler - \"%s\" cannot be opened", lpszCmdLine); 588 return; 589 } 590 CloseHandle(hf); 591 592 593 _snprintf(commandline, sizeof(commandline), "kfwcpcc.exe \"%s\"", lpszCmdLine); 594 595 GetStartupInfo(&startupinfo); 596 SetLastError(0); 597 if (CreateProcess( NULL, 598 commandline, 599 NULL, 600 NULL, 601 FALSE, 602 CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS, 603 NULL, 604 NULL, 605 &startupinfo, 606 &procinfo)) 607 { 608 DebugEvent("KFW_Logon_Event - CommandLine %s", commandline); 609 610 WaitForSingleObject(procinfo.hProcess, 30000); 611 612 CloseHandle(procinfo.hThread); 613 CloseHandle(procinfo.hProcess); 614 } else { 615 DebugEvent("KFW_Logon_Event - CreateProcessFailed \"%s\" GLE 0x%x", 616 commandline, GetLastError()); 617 DebugEvent("KFW_Logon_Event PATH %s", getenv("PATH")); 618 } 619 620 DeleteFile(lpszCmdLine); 621 622 DebugEvent0("KFW_Logon_Event - End"); 623 } 624