xref: /freebsd/crypto/krb5/src/windows/kfwlogon/kfwlogon.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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