Merge aicom-network-branch (without NDIS changes for now)
[reactos.git] / reactos / dll / win32 / msgina / msgina.c
1 /*
2 * ReactOS GINA
3 * Copyright (C) 2003-2004, 2006 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 /*
20 * PROJECT: ReactOS msgina.dll
21 * FILE: dll/win32/msgina/msgina.c
22 * PURPOSE: ReactOS Logon GINA DLL
23 * PROGRAMMER: Thomas Weidenmueller (w3seek@users.sourceforge.net)
24 * Hervé Poussineau (hpoussin@reactos.org)
25 */
26
27 #include "msgina.h"
28
29 #include <wine/debug.h>
30
31 WINE_DEFAULT_DEBUG_CHANNEL(msgina);
32
33 HINSTANCE hDllInstance;
34
35 extern GINA_UI GinaGraphicalUI;
36 extern GINA_UI GinaTextUI;
37 static PGINA_UI pGinaUI;
38
39 /*
40 * @implemented
41 */
42 BOOL WINAPI
43 WlxNegotiate(
44 IN DWORD dwWinlogonVersion,
45 OUT PDWORD pdwDllVersion)
46 {
47 TRACE("WlxNegotiate(%lx, %p)\n", dwWinlogonVersion, pdwDllVersion);
48
49 if(!pdwDllVersion || (dwWinlogonVersion < WLX_VERSION_1_3))
50 return FALSE;
51
52 *pdwDllVersion = WLX_VERSION_1_3;
53
54 return TRUE;
55 }
56
57 static LONG
58 ReadRegSzKey(
59 IN HKEY hKey,
60 IN LPCWSTR pszKey,
61 OUT LPWSTR* pValue)
62 {
63 LONG rc;
64 DWORD dwType;
65 DWORD cbData = 0;
66 LPWSTR Value;
67
68 if (!pValue)
69 return ERROR_INVALID_PARAMETER;
70
71 *pValue = NULL;
72 rc = RegQueryValueExW(hKey, pszKey, NULL, &dwType, NULL, &cbData);
73 if (rc != ERROR_SUCCESS)
74 return rc;
75 if (dwType != REG_SZ)
76 return ERROR_FILE_NOT_FOUND;
77 Value = HeapAlloc(GetProcessHeap(), 0, cbData + sizeof(WCHAR));
78 if (!Value)
79 return ERROR_NOT_ENOUGH_MEMORY;
80 rc = RegQueryValueExW(hKey, pszKey, NULL, NULL, (LPBYTE)Value, &cbData);
81 if (rc != ERROR_SUCCESS)
82 {
83 HeapFree(GetProcessHeap(), 0, Value);
84 return rc;
85 }
86 /* NULL-terminate the string */
87 Value[cbData / sizeof(WCHAR)] = '\0';
88
89 *pValue = Value;
90 return ERROR_SUCCESS;
91 }
92
93 static VOID
94 ChooseGinaUI(VOID)
95 {
96 HKEY ControlKey = NULL;
97 LPWSTR SystemStartOptions = NULL;
98 LPWSTR CurrentOption, NextOption; /* Pointers into SystemStartOptions */
99 BOOL ConsoleBoot = FALSE;
100 LONG rc;
101
102 rc = RegOpenKeyExW(
103 HKEY_LOCAL_MACHINE,
104 L"SYSTEM\\CurrentControlSet\\Control",
105 0,
106 KEY_QUERY_VALUE,
107 &ControlKey);
108
109 rc = ReadRegSzKey(ControlKey, L"SystemStartOptions", &SystemStartOptions);
110 if (rc != ERROR_SUCCESS)
111 goto cleanup;
112
113 /* Check for CMDCONS in SystemStartOptions */
114 CurrentOption = SystemStartOptions;
115 while (CurrentOption)
116 {
117 NextOption = wcschr(CurrentOption, L' ');
118 if (NextOption)
119 *NextOption = L'\0';
120 if (wcsicmp(CurrentOption, L"CONSOLE") == 0)
121 {
122 TRACE("Found %S. Switching to console boot\n", CurrentOption);
123 ConsoleBoot = TRUE;
124 goto cleanup;
125 }
126 CurrentOption = NextOption ? NextOption + 1 : NULL;
127 }
128
129 cleanup:
130 if (ConsoleBoot)
131 pGinaUI = &GinaTextUI;
132 else
133 pGinaUI = &GinaGraphicalUI;
134
135 if (ControlKey != NULL)
136 RegCloseKey(ControlKey);
137 HeapFree(GetProcessHeap(), 0, SystemStartOptions);
138 }
139
140 /*
141 * @implemented
142 */
143 BOOL WINAPI
144 WlxInitialize(
145 LPWSTR lpWinsta,
146 HANDLE hWlx,
147 PVOID pvReserved,
148 PVOID pWinlogonFunctions,
149 PVOID *pWlxContext)
150 {
151 PGINA_CONTEXT pgContext;
152
153 UNREFERENCED_PARAMETER(pvReserved);
154
155 pgContext = (PGINA_CONTEXT)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(GINA_CONTEXT));
156 if(!pgContext)
157 {
158 WARN("LocalAlloc() failed\n");
159 return FALSE;
160 }
161
162 /* Return the context to winlogon */
163 *pWlxContext = (PVOID)pgContext;
164 pgContext->hDllInstance = hDllInstance;
165
166 /* Save pointer to dispatch table */
167 pgContext->pWlxFuncs = (PWLX_DISPATCH_VERSION_1_3)pWinlogonFunctions;
168
169 /* Save the winlogon handle used to call the dispatch functions */
170 pgContext->hWlx = hWlx;
171
172 /* Save window station */
173 pgContext->station = lpWinsta;
174
175 /* Clear status window handle */
176 pgContext->hStatusWindow = 0;
177
178 /* Notify winlogon that we will use the default SAS */
179 pgContext->pWlxFuncs->WlxUseCtrlAltDel(hWlx);
180
181 /* Locates the authentification package */
182 //LsaRegisterLogonProcess(...);
183
184 /* Check autologon settings the first time */
185 pgContext->AutoLogonState = AUTOLOGON_CHECK_REGISTRY;
186
187 ChooseGinaUI();
188 return pGinaUI->Initialize(pgContext);
189 }
190
191 /*
192 * @implemented
193 */
194 BOOL WINAPI
195 WlxStartApplication(
196 PVOID pWlxContext,
197 PWSTR pszDesktopName,
198 PVOID pEnvironment,
199 PWSTR pszCmdLine)
200 {
201 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
202 STARTUPINFOW StartupInfo;
203 PROCESS_INFORMATION ProcessInformation;
204 WCHAR CurrentDirectory[MAX_PATH];
205 HANDLE hAppToken;
206 UINT len;
207 BOOL ret;
208
209 len = GetWindowsDirectoryW(CurrentDirectory, MAX_PATH);
210 if (len == 0 || len > MAX_PATH)
211 {
212 WARN("GetWindowsDirectoryW() failed\n");
213 return FALSE;
214 }
215
216 ret = DuplicateTokenEx(pgContext->UserToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hAppToken);
217 if (!ret)
218 {
219 WARN("DuplicateTokenEx() failed with error %lu\n", GetLastError());
220 return FALSE;
221 }
222
223 ZeroMemory(&StartupInfo, sizeof(STARTUPINFOW));
224 ZeroMemory(&ProcessInformation, sizeof(PROCESS_INFORMATION));
225 StartupInfo.cb = sizeof(STARTUPINFOW);
226 StartupInfo.lpTitle = pszCmdLine;
227 StartupInfo.dwX = StartupInfo.dwY = StartupInfo.dwXSize = StartupInfo.dwYSize = 0L;
228 StartupInfo.dwFlags = 0;
229 StartupInfo.wShowWindow = SW_SHOW;
230 StartupInfo.lpDesktop = pszDesktopName;
231
232 len = GetWindowsDirectoryW(CurrentDirectory, MAX_PATH);
233 if (len == 0 || len > MAX_PATH)
234 {
235 WARN("GetWindowsDirectoryW() failed\n");
236 return FALSE;
237 }
238 ret = CreateProcessAsUserW(
239 hAppToken,
240 pszCmdLine,
241 NULL,
242 NULL,
243 NULL,
244 FALSE,
245 CREATE_UNICODE_ENVIRONMENT,
246 pEnvironment,
247 CurrentDirectory,
248 &StartupInfo,
249 &ProcessInformation);
250 CloseHandle(ProcessInformation.hProcess);
251 CloseHandle(ProcessInformation.hThread);
252 CloseHandle(hAppToken);
253 if (!ret)
254 WARN("CreateProcessAsUserW() failed with error %lu\n", GetLastError());
255 return ret;
256 }
257
258 /*
259 * @implemented
260 */
261 BOOL WINAPI
262 WlxActivateUserShell(
263 PVOID pWlxContext,
264 PWSTR pszDesktopName,
265 PWSTR pszMprLogonScript,
266 PVOID pEnvironment)
267 {
268 HKEY hKey;
269 DWORD BufSize, ValueType;
270 WCHAR pszUserInitApp[MAX_PATH + 1];
271 WCHAR pszExpUserInitApp[MAX_PATH];
272 DWORD len;
273 LONG rc;
274
275 TRACE("WlxActivateUserShell()\n");
276
277 UNREFERENCED_PARAMETER(pszMprLogonScript);
278
279 /* Get the path of userinit */
280 rc = RegOpenKeyExW(
281 HKEY_LOCAL_MACHINE,
282 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon",
283 0,
284 KEY_QUERY_VALUE,
285 &hKey);
286 if (rc != ERROR_SUCCESS)
287 {
288 WARN("RegOpenKeyExW() failed with error %lu\n", rc);
289 return FALSE;
290 }
291
292 /* Query userinit application */
293 BufSize = sizeof(pszUserInitApp) - sizeof(UNICODE_NULL);
294 rc = RegQueryValueExW(
295 hKey,
296 L"Userinit",
297 NULL,
298 &ValueType,
299 (LPBYTE)pszUserInitApp,
300 &BufSize);
301 RegCloseKey(hKey);
302 if (rc != ERROR_SUCCESS || (ValueType != REG_SZ && ValueType != REG_EXPAND_SZ))
303 {
304 WARN("RegQueryValueExW() failed with error %lu\n", rc);
305 return FALSE;
306 }
307 pszUserInitApp[MAX_PATH] = UNICODE_NULL;
308
309 len = ExpandEnvironmentStringsW(pszUserInitApp, pszExpUserInitApp, MAX_PATH);
310 if (len > MAX_PATH)
311 {
312 WARN("ExpandEnvironmentStringsW() failed. Required size %lu\n", len);
313 return FALSE;
314 }
315
316 /* Start userinit app */
317 return WlxStartApplication(pWlxContext, pszDesktopName, pEnvironment, pszExpUserInitApp);
318 }
319
320 /*
321 * @implemented
322 */
323 int WINAPI
324 WlxLoggedOnSAS(
325 PVOID pWlxContext,
326 DWORD dwSasType,
327 PVOID pReserved)
328 {
329 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
330 INT SasAction = WLX_SAS_ACTION_NONE;
331
332 TRACE("WlxLoggedOnSAS(0x%lx)\n", dwSasType);
333
334 UNREFERENCED_PARAMETER(pReserved);
335
336 switch (dwSasType)
337 {
338 case WLX_SAS_TYPE_CTRL_ALT_DEL:
339 case WLX_SAS_TYPE_TIMEOUT:
340 {
341 SasAction = pGinaUI->LoggedOnSAS(pgContext, dwSasType);
342 break;
343 }
344 case WLX_SAS_TYPE_SC_INSERT:
345 {
346 FIXME("WlxLoggedOnSAS: SasType WLX_SAS_TYPE_SC_INSERT not supported!\n");
347 break;
348 }
349 case WLX_SAS_TYPE_SC_REMOVE:
350 {
351 FIXME("WlxLoggedOnSAS: SasType WLX_SAS_TYPE_SC_REMOVE not supported!\n");
352 break;
353 }
354 default:
355 {
356 WARN("WlxLoggedOnSAS: Unknown SasType: 0x%x\n", dwSasType);
357 break;
358 }
359 }
360
361 return SasAction;
362 }
363
364 /*
365 * @implemented
366 */
367 BOOL WINAPI
368 WlxDisplayStatusMessage(
369 IN PVOID pWlxContext,
370 IN HDESK hDesktop,
371 IN DWORD dwOptions,
372 IN PWSTR pTitle,
373 IN PWSTR pMessage)
374 {
375 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
376
377 TRACE("WlxDisplayStatusMessage(\"%S\")\n", pMessage);
378
379 return pGinaUI->DisplayStatusMessage(pgContext, hDesktop, dwOptions, pTitle, pMessage);
380 }
381
382 /*
383 * @implemented
384 */
385 BOOL WINAPI
386 WlxRemoveStatusMessage(
387 IN PVOID pWlxContext)
388 {
389 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
390
391 TRACE("WlxRemoveStatusMessage()\n");
392
393 return pGinaUI->RemoveStatusMessage(pgContext);
394 }
395
396 static PWSTR
397 DuplicationString(PWSTR Str)
398 {
399 DWORD cb;
400 PWSTR NewStr;
401
402 if (Str == NULL) return NULL;
403
404 cb = (wcslen(Str) + 1) * sizeof(WCHAR);
405 if ((NewStr = LocalAlloc(LMEM_FIXED, cb)))
406 memcpy(NewStr, Str, cb);
407 return NewStr;
408 }
409
410 BOOL
411 DoLoginTasks(
412 IN OUT PGINA_CONTEXT pgContext,
413 IN PWSTR UserName,
414 IN PWSTR Domain,
415 IN PWSTR Password)
416 {
417 LPWSTR ProfilePath = NULL;
418 TOKEN_STATISTICS Stats;
419 PWLX_PROFILE_V1_0 pProfile = NULL;
420 DWORD cbStats, cbSize;
421 BOOL bResult;
422
423 if (!LogonUserW(UserName, Domain, Password,
424 LOGON32_LOGON_INTERACTIVE,
425 LOGON32_PROVIDER_DEFAULT,
426 &pgContext->UserToken))
427 {
428 WARN("LogonUserW() failed\n");
429 goto cleanup;
430 }
431
432 /* Get profile path */
433 cbSize = 0;
434 bResult = GetProfilesDirectoryW(NULL, &cbSize);
435 if (!bResult && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
436 {
437 ProfilePath = HeapAlloc(GetProcessHeap(), 0, cbSize * sizeof(WCHAR));
438 if (!ProfilePath)
439 {
440 WARN("HeapAlloc() failed\n");
441 goto cleanup;
442 }
443 bResult = GetProfilesDirectoryW(ProfilePath, &cbSize);
444 }
445 if (!bResult)
446 {
447 WARN("GetUserProfileDirectoryW() failed\n");
448 goto cleanup;
449 }
450
451 /* Allocate memory for profile */
452 pProfile = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WLX_PROFILE_V1_0));
453 if (!pProfile)
454 {
455 WARN("HeapAlloc() failed\n");
456 goto cleanup;
457 }
458 pProfile->dwType = WLX_PROFILE_TYPE_V1_0;
459 pProfile->pszProfile = ProfilePath;
460
461 if (!GetTokenInformation(pgContext->UserToken,
462 TokenStatistics,
463 (PVOID)&Stats,
464 sizeof(TOKEN_STATISTICS),
465 &cbStats))
466 {
467 WARN("Couldn't get Authentication id from user token!\n");
468 goto cleanup;
469 }
470 *pgContext->pAuthenticationId = Stats.AuthenticationId;
471 pgContext->pMprNotifyInfo->pszUserName = DuplicationString(UserName);
472 pgContext->pMprNotifyInfo->pszDomain = DuplicationString(Domain);
473 pgContext->pMprNotifyInfo->pszPassword = DuplicationString(Password);
474 pgContext->pMprNotifyInfo->pszOldPassword = NULL;
475 *pgContext->pdwOptions = 0;
476 *pgContext->pProfile = pProfile;
477 return TRUE;
478
479 cleanup:
480 HeapFree(GetProcessHeap(), 0, pProfile);
481 HeapFree(GetProcessHeap(), 0, ProfilePath);
482 return FALSE;
483 }
484
485 static BOOL
486 DoAutoLogon(
487 IN PGINA_CONTEXT pgContext)
488 {
489 HKEY WinLogonKey = NULL;
490 LPWSTR AutoLogon = NULL;
491 LPWSTR AutoCount = NULL;
492 LPWSTR IgnoreShiftOverride = NULL;
493 LPWSTR UserName = NULL;
494 LPWSTR DomainName = NULL;
495 LPWSTR Password = NULL;
496 BOOL result = FALSE;
497 LONG rc;
498
499 TRACE("DoAutoLogon(): AutoLogonState = %lu\n",
500 pgContext->AutoLogonState);
501
502 if (pgContext->AutoLogonState == AUTOLOGON_DISABLED)
503 return FALSE;
504
505 rc = RegOpenKeyExW(
506 HKEY_LOCAL_MACHINE,
507 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\WinLogon",
508 0,
509 KEY_QUERY_VALUE,
510 &WinLogonKey);
511 if (rc != ERROR_SUCCESS)
512 goto cleanup;
513
514 if (pgContext->AutoLogonState == AUTOLOGON_CHECK_REGISTRY)
515 {
516 /* Set it by default to disabled, we might reenable it again later */
517 pgContext->AutoLogonState = AUTOLOGON_DISABLED;
518
519 rc = ReadRegSzKey(WinLogonKey, L"AutoAdminLogon", &AutoLogon);
520 if (rc != ERROR_SUCCESS)
521 goto cleanup;
522 if (wcscmp(AutoLogon, L"1") != 0)
523 goto cleanup;
524
525 rc = ReadRegSzKey(WinLogonKey, L"AutoLogonCount", &AutoCount);
526 if (rc == ERROR_SUCCESS && wcscmp(AutoCount, L"0") == 0)
527 goto cleanup;
528 else if (rc != ERROR_FILE_NOT_FOUND)
529 goto cleanup;
530
531 rc = ReadRegSzKey(WinLogonKey, L"IgnoreShiftOverride", &UserName);
532 if (rc == ERROR_SUCCESS)
533 {
534 if (wcscmp(AutoLogon, L"1") != 0 && GetKeyState(VK_SHIFT) < 0)
535 goto cleanup;
536 }
537 else if (GetKeyState(VK_SHIFT) < 0)
538 {
539 /* User pressed SHIFT */
540 goto cleanup;
541 }
542
543 pgContext->AutoLogonState = AUTOLOGON_ONCE;
544 result = TRUE;
545 }
546 else /* pgContext->AutoLogonState == AUTOLOGON_ONCE */
547 {
548 pgContext->AutoLogonState = AUTOLOGON_DISABLED;
549
550 rc = ReadRegSzKey(WinLogonKey, L"DefaultUserName", &UserName);
551 if (rc != ERROR_SUCCESS)
552 goto cleanup;
553 rc = ReadRegSzKey(WinLogonKey, L"DefaultDomainName", &DomainName);
554 if (rc != ERROR_SUCCESS && rc != ERROR_FILE_NOT_FOUND)
555 goto cleanup;
556 rc = ReadRegSzKey(WinLogonKey, L"DefaultPassword", &Password);
557 if (rc != ERROR_SUCCESS)
558 goto cleanup;
559
560 result = DoLoginTasks(pgContext, UserName, DomainName, Password);
561 }
562
563 cleanup:
564 if (WinLogonKey != NULL)
565 RegCloseKey(WinLogonKey);
566 HeapFree(GetProcessHeap(), 0, AutoLogon);
567 HeapFree(GetProcessHeap(), 0, AutoCount);
568 HeapFree(GetProcessHeap(), 0, IgnoreShiftOverride);
569 HeapFree(GetProcessHeap(), 0, UserName);
570 HeapFree(GetProcessHeap(), 0, DomainName);
571 HeapFree(GetProcessHeap(), 0, Password);
572 TRACE("DoAutoLogon(): AutoLogonState = %lu, returning %d\n",
573 pgContext->AutoLogonState, result);
574 return result;
575 }
576
577 /*
578 * @implemented
579 */
580 VOID WINAPI
581 WlxDisplaySASNotice(
582 IN PVOID pWlxContext)
583 {
584 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
585
586 TRACE("WlxDisplaySASNotice(%p)\n", pWlxContext);
587
588 if (GetSystemMetrics(SM_REMOTESESSION))
589 {
590 /* User is remotely logged on. Don't display a notice */
591 pgContext->pWlxFuncs->WlxSasNotify(pgContext->hWlx, WLX_SAS_TYPE_CTRL_ALT_DEL);
592 return;
593 }
594
595 if (DoAutoLogon(pgContext))
596 {
597 /* Don't display the window, we want to do an automatic logon */
598 pgContext->AutoLogonState = AUTOLOGON_ONCE;
599 pgContext->pWlxFuncs->WlxSasNotify(pgContext->hWlx, WLX_SAS_TYPE_CTRL_ALT_DEL);
600 return;
601 }
602 else
603 pgContext->AutoLogonState = AUTOLOGON_DISABLED;
604
605 pGinaUI->DisplaySASNotice(pgContext);
606
607 TRACE("WlxDisplaySASNotice() done\n");
608 }
609
610 /*
611 * @implemented
612 */
613 INT WINAPI
614 WlxLoggedOutSAS(
615 IN PVOID pWlxContext,
616 IN DWORD dwSasType,
617 OUT PLUID pAuthenticationId,
618 IN OUT PSID pLogonSid,
619 OUT PDWORD pdwOptions,
620 OUT PHANDLE phToken,
621 OUT PWLX_MPR_NOTIFY_INFO pMprNotifyInfo,
622 OUT PVOID *pProfile)
623 {
624 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
625 INT res;
626
627 TRACE("WlxLoggedOutSAS()\n");
628
629 UNREFERENCED_PARAMETER(dwSasType);
630 UNREFERENCED_PARAMETER(pLogonSid);
631
632 pgContext->pAuthenticationId = pAuthenticationId;
633 pgContext->pdwOptions = pdwOptions;
634 pgContext->pMprNotifyInfo = pMprNotifyInfo;
635 pgContext->pProfile = pProfile;
636
637 if (0 == GetSystemMetrics(SM_REMOTESESSION) &&
638 DoAutoLogon(pgContext))
639 {
640 /* User is local and registry contains information
641 * to log on him automatically */
642 *phToken = pgContext->UserToken;
643 return WLX_SAS_ACTION_LOGON;
644 }
645
646 res = pGinaUI->LoggedOutSAS(pgContext);
647 *phToken = pgContext->UserToken;
648 return res;
649 }
650
651 /*
652 * @implemented
653 */
654 int WINAPI
655 WlxWkstaLockedSAS(
656 PVOID pWlxContext,
657 DWORD dwSasType)
658 {
659 PGINA_CONTEXT pgContext = (PGINA_CONTEXT)pWlxContext;
660
661 TRACE("WlxWkstaLockedSAS()\n");
662
663 UNREFERENCED_PARAMETER(dwSasType);
664
665 return pGinaUI->LockedSAS(pgContext);
666 }
667
668 BOOL WINAPI
669 DllMain(
670 IN HINSTANCE hinstDLL,
671 IN DWORD dwReason,
672 IN LPVOID lpvReserved)
673 {
674 UNREFERENCED_PARAMETER(lpvReserved);
675
676 if (dwReason == DLL_PROCESS_ATTACH)
677 hDllInstance = hinstDLL;
678
679 return TRUE;
680 }