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
DllEntryPoint(HANDLE dll,DWORD reason,PVOID reserved)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
NPGetCaps(DWORD index)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
UnicodeStringToANSI(UNICODE_STRING uInputString,LPSTR lpszOutputString,int nOutStringLen)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
is_windows_vista(void)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
ConfigureLogonScript(LPWSTR * lpLogonScript,char * filename)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
NPLogonNotify(PLUID lpLogonId,LPCWSTR lpAuthentInfoType,LPVOID lpAuthentInfo,LPCWSTR lpPreviousAuthentInfoType,LPVOID lpPreviousAuthentInfo,LPWSTR lpStationName,LPVOID StationHandle,LPWSTR * lpLogonScript)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
NPPasswordChangeNotify(LPCWSTR lpAuthentInfoType,LPVOID lpAuthentInfo,LPCWSTR lpPreviousAuthentInfoType,LPVOID lpPreviousAuthentInfo,LPWSTR lpStationName,LPVOID StationHandle,DWORD dwChangeInfo)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
KFW_Startup_Event(PWLX_NOTIFICATION_INFO pInfo)428 VOID KFW_Startup_Event( PWLX_NOTIFICATION_INFO pInfo )
429 {
430 DebugEvent0("KFW_Startup_Event");
431 }
432
433 static BOOL
GetSecurityLogonSessionData(HANDLE hToken,PSECURITY_LOGON_SESSION_DATA * ppSessionData)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
KFW_Logon_Event(PWLX_NOTIFICATION_INFO pInfo)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
LogonEventHandlerA(HWND hwnd,HINSTANCE hinst,LPSTR lpszCmdLine,int nCmdShow)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