Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

803 lines
21 KiB

  1. /*---------------------------------------------------------------------------
  2. File: LSAUtils.cpp
  3. Comments: Code to change the domain membership of a workstation.
  4. This file also contains some general helper functions, such as:
  5. GetDomainDCName
  6. EstablishNullSession
  7. EstablishSession
  8. EstablishShare // connects to a share
  9. InitLsaString
  10. GetDomainSid
  11. (c) Copyright 1999, Mission Critical Software, Inc., All Rights Reserved
  12. Proprietary and confidential to Mission Critical Software, Inc.
  13. REVISION LOG ENTRY
  14. Revision By: Christy Boles
  15. Revised on 02/03/99 12:37:51
  16. ---------------------------------------------------------------------------
  17. */
  18. //
  19. //#include "stdafx.h"
  20. #include <windows.h>
  21. #include <process.h>
  22. #ifndef UNICODE
  23. #define UNICODE
  24. #define _UNICODE
  25. #endif
  26. #include <lm.h> // for NetXxx API
  27. #include <RpcDce.h>
  28. #include <stdio.h>
  29. #include "LSAUtils.h"
  30. #include "ErrDct.hpp"
  31. #include "ResStr.h"
  32. #include "ealen.hpp"
  33. #define RTN_OK 0
  34. #define RTN_USAGE 1
  35. #define RTN_ERROR 13
  36. extern TErrorDct err;
  37. BOOL
  38. EstablishNullSession(
  39. LPCWSTR Server, // in - server name
  40. BOOL bEstablish // in - TRUE=establish, FALSE=disconnect
  41. )
  42. {
  43. return EstablishSession(Server,L"",L"",L"",bEstablish);
  44. }
  45. BOOL
  46. EstablishSession(
  47. LPCWSTR Server, // in - server name
  48. LPWSTR Domain, // in - domain name for user credentials
  49. LPWSTR UserName, // in - username for credentials to use
  50. LPWSTR Password, // in - password for credentials
  51. BOOL bEstablish // in - TRUE=establish, FALSE=disconnect
  52. )
  53. {
  54. LPCWSTR szIpc = L"\\IPC$";
  55. WCHAR RemoteResource[2 + LEN_Computer + 5 + 1]; // \\ + computername + \IPC$ + NULL
  56. DWORD cchServer;
  57. NET_API_STATUS nas;
  58. //
  59. // do not allow NULL or empty server name
  60. //
  61. if(Server == NULL || *Server == L'\0')
  62. {
  63. SetLastError(ERROR_INVALID_COMPUTERNAME);
  64. return FALSE;
  65. }
  66. cchServer = lstrlenW( Server );
  67. if( Server[0] != L'\\' && Server[1] != L'\\')
  68. {
  69. //
  70. // prepend slashes and NULL terminate
  71. //
  72. RemoteResource[0] = L'\\';
  73. RemoteResource[1] = L'\\';
  74. RemoteResource[2] = L'\0';
  75. }
  76. else
  77. {
  78. cchServer -= 2; // drop slashes from count
  79. RemoteResource[0] = L'\0';
  80. }
  81. if(cchServer > LEN_Computer)
  82. {
  83. SetLastError(ERROR_INVALID_COMPUTERNAME);
  84. return FALSE;
  85. }
  86. if(lstrcatW(RemoteResource, Server) == NULL)
  87. {
  88. return FALSE;
  89. }
  90. if(lstrcatW(RemoteResource, szIpc) == NULL)
  91. {
  92. return FALSE;
  93. }
  94. //
  95. // disconnect or connect to the resource, based on bEstablish
  96. //
  97. if(bEstablish)
  98. {
  99. USE_INFO_2 ui2;
  100. DWORD errParm;
  101. ZeroMemory(&ui2, sizeof(ui2));
  102. ui2.ui2_local = NULL;
  103. ui2.ui2_remote = RemoteResource;
  104. ui2.ui2_asg_type = USE_IPC;
  105. ui2.ui2_domainname = Domain;
  106. ui2.ui2_username = UserName;
  107. ui2.ui2_password = Password;
  108. // try establishing session for one minute
  109. // if computer is not accepting any more connections
  110. for (int i = 0; i < (60000 / 5000); i++)
  111. {
  112. nas = NetUseAdd(NULL, 2, (LPBYTE)&ui2, &errParm);
  113. if (nas != ERROR_REQ_NOT_ACCEP)
  114. {
  115. break;
  116. }
  117. Sleep(5000);
  118. }
  119. }
  120. else
  121. {
  122. nas = NetUseDel(NULL, RemoteResource, 0);
  123. }
  124. if( nas == NERR_Success )
  125. {
  126. return TRUE; // indicate success
  127. }
  128. SetLastError(nas);
  129. return FALSE;
  130. }
  131. BOOL
  132. EstablishShare(
  133. LPCWSTR Server, // in - server name
  134. LPWSTR Share, // in - share name
  135. LPWSTR Domain, // in - domain name for credentials to connect with
  136. LPWSTR UserName, // in - user name to connect as
  137. LPWSTR Password, // in - password for username
  138. BOOL bEstablish // in - TRUE=connect, FALSE=disconnect
  139. )
  140. {
  141. WCHAR RemoteResource[MAX_PATH];
  142. DWORD dwArraySizeOfRemoteResource = sizeof(RemoteResource)/sizeof(RemoteResource[0]);
  143. DWORD cchServer;
  144. NET_API_STATUS nas;
  145. //
  146. // do not allow NULL or empty server name
  147. //
  148. if(Server == NULL || *Server == L'\0')
  149. {
  150. SetLastError(ERROR_INVALID_COMPUTERNAME);
  151. return FALSE;
  152. }
  153. cchServer = lstrlenW( Server );
  154. if( Server[0] != L'\\' && Server[1] != L'\\')
  155. {
  156. //
  157. // prepend slashes and NULL terminate
  158. //
  159. RemoteResource[0] = L'\\';
  160. RemoteResource[1] = L'\\';
  161. RemoteResource[2] = L'\0';
  162. }
  163. else
  164. {
  165. cchServer -= 2; // drop slashes from count
  166. RemoteResource[0] = L'\0';
  167. }
  168. if(cchServer > CNLEN)
  169. {
  170. SetLastError(ERROR_INVALID_COMPUTERNAME);
  171. return FALSE;
  172. }
  173. if(lstrcatW(RemoteResource, Server) == NULL)
  174. {
  175. return FALSE;
  176. }
  177. // assume that Share has to be non-NULL
  178. if(Share == NULL
  179. || wcslen(RemoteResource) + wcslen(Share) >= dwArraySizeOfRemoteResource
  180. || lstrcatW(RemoteResource, Share) == NULL)
  181. {
  182. return FALSE;
  183. }
  184. //
  185. // disconnect or connect to the resource, based on bEstablish
  186. //
  187. if(bEstablish)
  188. {
  189. USE_INFO_2 ui2;
  190. DWORD errParm;
  191. ZeroMemory(&ui2, sizeof(ui2));
  192. ui2.ui2_local = NULL;
  193. ui2.ui2_remote = RemoteResource;
  194. ui2.ui2_asg_type = USE_DISKDEV;
  195. ui2.ui2_domainname = Domain;
  196. ui2.ui2_username = UserName;
  197. ui2.ui2_password = Password;
  198. // try establishing session for one minute
  199. // if computer is not accepting any more connections
  200. for (int i = 0; i < (60000 / 5000); i++)
  201. {
  202. nas = NetUseAdd(NULL, 2, (LPBYTE)&ui2, &errParm);
  203. if (nas != ERROR_REQ_NOT_ACCEP)
  204. {
  205. break;
  206. }
  207. Sleep(5000);
  208. }
  209. }
  210. else
  211. {
  212. nas = NetUseDel(NULL, RemoteResource, 0);
  213. }
  214. if( nas == NERR_Success )
  215. {
  216. return TRUE; // indicate success
  217. }
  218. SetLastError(nas);
  219. return FALSE;
  220. }
  221. void
  222. InitLsaString(
  223. PLSA_UNICODE_STRING LsaString, // i/o- pointer to LSA string to initialize
  224. LPWSTR String // in - value to initialize LSA string to
  225. )
  226. {
  227. DWORD StringLength;
  228. if( String == NULL )
  229. {
  230. LsaString->Buffer = NULL;
  231. LsaString->Length = 0;
  232. LsaString->MaximumLength = 0;
  233. }
  234. else
  235. {
  236. StringLength = lstrlenW(String);
  237. LsaString->Buffer = String;
  238. LsaString->Length = (USHORT) StringLength * sizeof(WCHAR);
  239. LsaString->MaximumLength = (USHORT) (StringLength + 1) * sizeof(WCHAR);
  240. }
  241. }
  242. BOOL
  243. GetDomainSid(
  244. LPWSTR PrimaryDC, // in - domain controller of domain to acquire Sid
  245. PSID * pDomainSid // out- points to allocated Sid on success
  246. )
  247. {
  248. NET_API_STATUS nas;
  249. PUSER_MODALS_INFO_2 umi2 = NULL;
  250. DWORD dwSidSize;
  251. BOOL bSuccess = FALSE; // assume this function will fail
  252. *pDomainSid = NULL; // invalidate pointer
  253. __try {
  254. //
  255. // obtain the domain Sid from the PDC
  256. //
  257. nas = NetUserModalsGet(PrimaryDC, 2, (LPBYTE *)&umi2);
  258. if(nas != NERR_Success) __leave;
  259. //
  260. // if the Sid is valid, obtain the size of the Sid
  261. //
  262. if(!IsValidSid(umi2->usrmod2_domain_id)) __leave;
  263. dwSidSize = GetLengthSid(umi2->usrmod2_domain_id);
  264. //
  265. // allocate storage and copy the Sid
  266. //
  267. *pDomainSid = LocalAlloc(LPTR, dwSidSize);
  268. if(*pDomainSid == NULL) __leave;
  269. if(!CopySid(dwSidSize, *pDomainSid, umi2->usrmod2_domain_id)) __leave;
  270. bSuccess = TRUE; // indicate success
  271. } // try
  272. __finally
  273. {
  274. if(umi2 != NULL)
  275. {
  276. NetApiBufferFree(umi2);
  277. }
  278. if(!bSuccess)
  279. {
  280. //
  281. // if the function failed, free memory and indicate result code
  282. //
  283. if(*pDomainSid != NULL)
  284. {
  285. FreeSid(*pDomainSid);
  286. *pDomainSid = NULL;
  287. }
  288. if( nas != NERR_Success )
  289. {
  290. SetLastError(nas);
  291. }
  292. }
  293. } // finally
  294. return bSuccess;
  295. }
  296. NTSTATUS
  297. OpenPolicy(
  298. LPWSTR ComputerName, // in - computer name
  299. DWORD DesiredAccess, // in - access rights needed for policy
  300. PLSA_HANDLE PolicyHandle // out- LSA handle
  301. )
  302. {
  303. LSA_OBJECT_ATTRIBUTES ObjectAttributes;
  304. LSA_UNICODE_STRING ComputerString;
  305. PLSA_UNICODE_STRING Computer = NULL;
  306. //
  307. // Always initialize the object attributes to all zeroes
  308. //
  309. ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));
  310. if(ComputerName != NULL)
  311. {
  312. //
  313. // Make a LSA_UNICODE_STRING out of the LPWSTR passed in
  314. //
  315. InitLsaString(&ComputerString, ComputerName);
  316. Computer = &ComputerString;
  317. }
  318. //
  319. // Attempt to open the policy
  320. //
  321. NTSTATUS status = LsaOpenPolicy(Computer,&ObjectAttributes,DesiredAccess,PolicyHandle);
  322. return status;
  323. }
  324. /*++
  325. This function sets the Primary Domain for the workstation.
  326. To join the workstation to a Workgroup, ppdi.Name should be the name of
  327. the Workgroup and ppdi.Sid should be NULL.
  328. --*/
  329. NTSTATUS
  330. SetPrimaryDomain(
  331. LSA_HANDLE PolicyHandle, // in -policy handle for computer
  332. PSID DomainSid, // in - sid for new domain
  333. LPWSTR TrustedDomainName // in - name of new domain
  334. )
  335. {
  336. POLICY_PRIMARY_DOMAIN_INFO ppdi;
  337. InitLsaString(&ppdi.Name, TrustedDomainName);
  338. ppdi.Sid = DomainSid;
  339. return LsaSetInformationPolicy(PolicyHandle,PolicyPrimaryDomainInformation,&ppdi);
  340. }
  341. // This function removes the information from the domain the computer used to
  342. // be a member of
  343. NTSTATUS
  344. QueryWorkstationTrustedDomainInfo(
  345. LSA_HANDLE PolicyHandle, // in - policy handle for computer
  346. PSID DomainSid, // in - SID for new domain the computer is member of
  347. BOOL bNoChange // in - flag indicating whether to write changes
  348. )
  349. {
  350. // This function is not currently used.
  351. NTSTATUS Status;
  352. LSA_ENUMERATION_HANDLE h = 0;
  353. LSA_TRUST_INFORMATION * ti = NULL;
  354. ULONG count;
  355. Status = LsaEnumerateTrustedDomains(PolicyHandle,&h,(void**)&ti,50000,&count);
  356. if ( Status == STATUS_SUCCESS )
  357. {
  358. for ( UINT i = 0 ; i < count ; i++ )
  359. {
  360. if ( !bNoChange && !EqualSid(DomainSid,ti[i].Sid) )
  361. {
  362. // Remove the old trust
  363. Status = LsaDeleteTrustedDomain(PolicyHandle,ti[i].Sid);
  364. if ( Status != STATUS_SUCCESS )
  365. {
  366. LsaFreeMemory(ti);
  367. return Status;
  368. }
  369. }
  370. }
  371. LsaFreeMemory(ti);
  372. }
  373. else
  374. {
  375. return Status;
  376. }
  377. return STATUS_SUCCESS;
  378. }
  379. /*++
  380. This function manipulates the trust associated with the supplied
  381. DomainSid.
  382. If the domain trust does not exist, it is created with the
  383. specified password. In this case, the supplied PolicyHandle must
  384. have been opened with POLICY_TRUST_ADMIN and POLICY_CREATE_SECRET
  385. access to the policy object.
  386. --*/
  387. NTSTATUS
  388. SetWorkstationTrustedDomainInfo(
  389. LSA_HANDLE PolicyHandle, // in - policy handle
  390. PSID DomainSid, // in - Sid of domain to manipulate
  391. LPWSTR TrustedDomainName, // in - trusted domain name to add/update
  392. LPWSTR Password, // in - new trust password for trusted domain
  393. LPWSTR errOut // out- error text if function fails
  394. )
  395. {
  396. LSA_UNICODE_STRING LsaPassword;
  397. LSA_UNICODE_STRING KeyName;
  398. LSA_UNICODE_STRING LsaDomainName;
  399. DWORD cchDomainName; // number of chars in TrustedDomainName
  400. NTSTATUS Status;
  401. InitLsaString(&LsaDomainName, TrustedDomainName);
  402. //
  403. // ...convert TrustedDomainName to uppercase...
  404. //
  405. cchDomainName = LsaDomainName.Length / sizeof(WCHAR);
  406. while(cchDomainName--)
  407. {
  408. LsaDomainName.Buffer[cchDomainName] = towupper(LsaDomainName.Buffer[cchDomainName]);
  409. }
  410. //
  411. // ...create the trusted domain object
  412. //
  413. Status = LsaSetTrustedDomainInformation(
  414. PolicyHandle,
  415. DomainSid,
  416. TrustedDomainNameInformation,
  417. &LsaDomainName
  418. );
  419. if(Status == STATUS_OBJECT_NAME_COLLISION)
  420. {
  421. //printf("LsaSetTrustedDomainInformation: Name Collision (ok)\n");
  422. }
  423. else if (Status != STATUS_SUCCESS)
  424. {
  425. err.SysMsgWrite(ErrE,LsaNtStatusToWinError(Status),DCT_MSG_LSA_OPERATION_FAILED_SD,L"LsaSetTrustedDomainInformation", Status);
  426. return RTN_ERROR;
  427. }
  428. InitLsaString(&KeyName, L"$MACHINE.ACC");
  429. InitLsaString(&LsaPassword, Password);
  430. //
  431. // Set the machine password
  432. //
  433. Status = LsaStorePrivateData(
  434. PolicyHandle,
  435. &KeyName,
  436. &LsaPassword
  437. );
  438. if(Status != STATUS_SUCCESS)
  439. {
  440. err.SysMsgWrite(ErrE,LsaNtStatusToWinError(Status),DCT_MSG_LSA_OPERATION_FAILED_SD,L"LsaStorePrivateData", Status);
  441. return RTN_ERROR;
  442. }
  443. return STATUS_SUCCESS;
  444. }
  445. //------------------------------------------------------------------------------
  446. // StorePassword Function
  447. //
  448. // Synopsis
  449. // Stores a password in LSA secret.
  450. //
  451. // Arguments
  452. // IN pszIdentifier - the key name to store the password under
  453. // IN pszPassword - the clear-text password to be stored
  454. //
  455. // Return
  456. // Returns Win32 error code.
  457. //------------------------------------------------------------------------------
  458. DWORD __stdcall StorePassword(PCWSTR pszIdentifier, PCWSTR pszPassword)
  459. {
  460. DWORD dwError = ERROR_SUCCESS;
  461. //
  462. // The identifier parameter must specify a pointer to a non-zero length string. Note
  463. // that a null password parameter is valid as this will delete the data and the key
  464. // named by the identifier parameter.
  465. //
  466. if (pszIdentifier && *pszIdentifier)
  467. {
  468. //
  469. // Open policy object with create secret access right.
  470. //
  471. LSA_HANDLE hPolicy = NULL;
  472. LSA_OBJECT_ATTRIBUTES oa = { sizeof(LSA_OBJECT_ATTRIBUTES), NULL, NULL, 0, NULL, NULL };
  473. NTSTATUS ntsStatus = LsaOpenPolicy(NULL, &oa, POLICY_CREATE_SECRET, &hPolicy);
  474. if (LSA_SUCCESS(ntsStatus))
  475. {
  476. //
  477. // Store specified password under key named by the identifier parameter.
  478. //
  479. PWSTR pszKey = const_cast<PWSTR>(pszIdentifier);
  480. USHORT cbKey = wcslen(pszIdentifier) * sizeof(WCHAR);
  481. LSA_UNICODE_STRING usKey = { cbKey, cbKey, pszKey };
  482. if (pszPassword)
  483. {
  484. PWSTR pszData = const_cast<PWSTR>(pszPassword);
  485. USHORT cbData = wcslen(pszPassword) * sizeof(WCHAR);
  486. LSA_UNICODE_STRING usData = { cbData, cbData, pszData };
  487. ntsStatus = LsaStorePrivateData(hPolicy, &usKey, &usData);
  488. }
  489. else
  490. {
  491. ntsStatus = LsaStorePrivateData(hPolicy, &usKey, NULL);
  492. }
  493. if (!LSA_SUCCESS(ntsStatus))
  494. {
  495. dwError = LsaNtStatusToWinError(ntsStatus);
  496. }
  497. //
  498. // Close policy object.
  499. //
  500. LsaClose(hPolicy);
  501. }
  502. else
  503. {
  504. dwError = LsaNtStatusToWinError(ntsStatus);
  505. }
  506. }
  507. else
  508. {
  509. dwError = ERROR_INVALID_PARAMETER;
  510. }
  511. return dwError;
  512. }
  513. //------------------------------------------------------------------------------
  514. // RetrievePassword Function
  515. //
  516. // Synopsis
  517. // Retrieves a password from LSA secret.
  518. //
  519. // Arguments
  520. // IN pszIdentifier - the key name to retrieve the password from
  521. // OUT pszPassword - the address of a buffer to return the clear-text password
  522. // IN cchPassword - the size of the buffer in characters
  523. //
  524. // Return
  525. // Returns Win32 error code.
  526. //------------------------------------------------------------------------------
  527. DWORD __stdcall RetrievePassword(PCWSTR pszIdentifier, PWSTR pszPassword, size_t cchPassword)
  528. {
  529. DWORD dwError = ERROR_SUCCESS;
  530. //
  531. // The identifier parameter must specify a pointer to a non-zero length string. The
  532. // password parameters must specify a pointer to a buffer with length greater than
  533. // zero.
  534. //
  535. if (pszIdentifier && *pszIdentifier && pszPassword && (cchPassword > 0))
  536. {
  537. memset(pszPassword, 0, cchPassword * sizeof(pszPassword[0]));
  538. //
  539. // Open policy object with get private information access right.
  540. //
  541. LSA_HANDLE hPolicy = NULL;
  542. LSA_OBJECT_ATTRIBUTES oa = { sizeof(LSA_OBJECT_ATTRIBUTES), NULL, NULL, 0, NULL, NULL };
  543. NTSTATUS ntsStatus = LsaOpenPolicy(NULL, &oa, POLICY_GET_PRIVATE_INFORMATION, &hPolicy);
  544. if (LSA_SUCCESS(ntsStatus))
  545. {
  546. //
  547. // Retrieve password from key named by specified identifier.
  548. //
  549. PWSTR pszKey = const_cast<PWSTR>(pszIdentifier);
  550. USHORT cbKey = wcslen(pszIdentifier) * sizeof(pszIdentifier[0]);
  551. LSA_UNICODE_STRING usKey = { cbKey, cbKey, pszKey };
  552. PLSA_UNICODE_STRING pusData;
  553. ntsStatus = LsaRetrievePrivateData(hPolicy, &usKey, &pusData);
  554. if (LSA_SUCCESS(ntsStatus))
  555. {
  556. size_t cch = pusData->Length / sizeof(WCHAR);
  557. if (cch < cchPassword)
  558. {
  559. wcsncpy(pszPassword, pusData->Buffer, cch);
  560. pszPassword[cch] = 0;
  561. }
  562. else
  563. {
  564. dwError = ERROR_INSUFFICIENT_BUFFER;
  565. }
  566. SecureZeroMemory(pusData->Buffer, pusData->Length);
  567. LsaFreeMemory(pusData);
  568. }
  569. else
  570. {
  571. dwError = LsaNtStatusToWinError(ntsStatus);
  572. }
  573. //
  574. // Close policy object.
  575. //
  576. LsaClose(hPolicy);
  577. }
  578. else
  579. {
  580. dwError = LsaNtStatusToWinError(ntsStatus);
  581. }
  582. }
  583. else
  584. {
  585. dwError = ERROR_INVALID_PARAMETER;
  586. }
  587. return dwError;
  588. }
  589. //------------------------------------------------------------------------------
  590. // GeneratePasswordIdentifier Function
  591. //
  592. // Synopsis
  593. // Generates a key name used to store a password under.
  594. //
  595. // Note that is important to delete the key after use as the system only allows
  596. // 2048 LSA secrets to be stored by all applications on a given machine.
  597. //
  598. // Arguments
  599. // IN pszIdentifier - the key name to retrieve the password from
  600. // OUT pszPassword - the address of a buffer to return the clear-text password
  601. // IN cchPassword - the size of the buffer in characters
  602. //
  603. // Return
  604. // Returns Win32 error code.
  605. //------------------------------------------------------------------------------
  606. DWORD __stdcall GeneratePasswordIdentifier(PWSTR pszIdentifier, size_t cchIdentifier)
  607. {
  608. DWORD dwError = ERROR_SUCCESS;
  609. //
  610. // The identifier parameter must specify a pointer to a buffer with a length
  611. // greater than or equal to the length of the password identifier.
  612. //
  613. if (pszIdentifier && (cchIdentifier > 0))
  614. {
  615. memset(pszIdentifier, 0, cchIdentifier * sizeof(pszIdentifier[0]));
  616. //
  617. // Generate unique identifier.
  618. //
  619. UUID uuid;
  620. UuidCreate(&uuid);
  621. PWSTR pszUuid;
  622. RPC_STATUS rsStatus = UuidToString(&uuid, &pszUuid);
  623. if (rsStatus == RPC_S_OK)
  624. {
  625. //
  626. // Concatenate prefix and unique identifier. This makes
  627. // it possible to identify keys generated by ADMT.
  628. //
  629. static const WCHAR IDENTIFIER_PREFIX[] = L"L$ADMT_PI_";
  630. if ((wcslen(IDENTIFIER_PREFIX) + wcslen(pszUuid)) < cchIdentifier)
  631. {
  632. wcscpy(pszIdentifier, IDENTIFIER_PREFIX);
  633. wcscat(pszIdentifier, pszUuid);
  634. }
  635. else
  636. {
  637. dwError = ERROR_INSUFFICIENT_BUFFER;
  638. }
  639. RpcStringFree(&pszUuid);
  640. }
  641. else
  642. {
  643. dwError = rsStatus;
  644. }
  645. }
  646. else
  647. {
  648. dwError = ERROR_INVALID_PARAMETER;
  649. }
  650. return dwError;
  651. }