Source code of Windows XP (NT5)
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.

941 lines
19 KiB

  1. /****************************************************************************
  2. Copyright (c) Microsoft Corporation 1997
  3. All rights reserved
  4. ***************************************************************************/
  5. #include <nt.h>
  6. #include <ntrtl.h>
  7. #include <nturtl.h>
  8. #include <windows.h>
  9. #include <assert.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <ctype.h>
  14. #include <time.h>
  15. #include <winsock2.h>
  16. #include <rccxport.h>
  17. //
  18. // Main entry routine
  19. //
  20. DWORD
  21. RCCClntInitDll(
  22. HINSTANCE hInst,
  23. DWORD Reason,
  24. PVOID Context
  25. )
  26. /*++
  27. Routine Description:
  28. This routine simply returns TRUE. Normally it would initialize all global information, but we have none.
  29. Arguments:
  30. hInst - Instance that is calling us.
  31. Reason - Why we are being invoked.
  32. Context - Context passed for this init.
  33. Return Value:
  34. TRUE if successful, else FALSE
  35. --*/
  36. {
  37. return(TRUE);
  38. }
  39. DWORD
  40. RCCClntCreateInstance(
  41. OUT HANDLE *phHandle
  42. )
  43. /*++
  44. Routine Description:
  45. This routine creates a RCC_CLIENT instance, and returns a handle (pointer) to it.
  46. Arguments:
  47. phHandle - A pointer to storage for storing a pointer to internal representation of a remote
  48. command port client.
  49. Return Value:
  50. ERROR_SUCCESS if successful, else an appropriate error code.
  51. --*/
  52. {
  53. WORD Version;
  54. DWORD Error;
  55. WSADATA WSAData;
  56. PRCC_CLIENT Client;
  57. //
  58. // See if we can start up WinSock
  59. //
  60. Version = WINSOCK_VERSION;
  61. Error = WSAStartup(Version, &WSAData);
  62. if (Error != 0) {
  63. return WSAGetLastError();
  64. }
  65. if ((HIBYTE(WSAData.wVersion) != 2) ||
  66. (LOBYTE(WSAData.wVersion) != 2)) {
  67. WSACleanup();
  68. return ERROR_RMODE_APP;
  69. }
  70. //
  71. // Now get space for our internal data
  72. //
  73. Client = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_CLIENT));
  74. if (Client == NULL) {
  75. WSACleanup();
  76. return ERROR_OUTOFMEMORY;
  77. }
  78. //
  79. // Init the data structure
  80. //
  81. Client->CommandSequenceNumber = 0;
  82. Client->MySocket = INVALID_SOCKET;
  83. Client->RemoteSocket = INVALID_SOCKET;
  84. *phHandle = (HANDLE)Client;
  85. return ERROR_SUCCESS;
  86. }
  87. VOID
  88. RCCClntDestroyInstance(
  89. OUT HANDLE *phHandle
  90. )
  91. /*++
  92. Routine Description:
  93. This routine destroys a RCC_CLIENT instance, and NULLs the handle.
  94. Arguments:
  95. phHandle - A pointer to previously created remote command port instance.
  96. Return Value:
  97. None.
  98. --*/
  99. {
  100. PRCC_CLIENT Client = *((PRCC_CLIENT *)phHandle);
  101. if (Client == NULL) {
  102. return;
  103. }
  104. if (Client->MySocket != INVALID_SOCKET) {
  105. shutdown(Client->MySocket, SD_BOTH);
  106. closesocket(Client->MySocket);
  107. }
  108. if (Client->RemoteSocket != INVALID_SOCKET) {
  109. shutdown(Client->RemoteSocket, SD_BOTH);
  110. closesocket(Client->RemoteSocket);
  111. }
  112. LocalFree(Client);
  113. *phHandle = NULL;
  114. }
  115. DWORD
  116. RCCClntConnectToRCCPort(
  117. IN HANDLE hHandle,
  118. IN PSTR MachineName
  119. )
  120. /*++
  121. Routine Description:
  122. This routine takes a previously created instance, and attempts to make a connection from it to
  123. a remote command port on the given machine name.
  124. Arguments:
  125. hHandle - A handle to a previously created instance.
  126. MachineName - An ASCII string of the machine to attempt to connect to.
  127. Return Value:
  128. ERROR_SUCCESS if successful, else an appropriate error code.
  129. --*/
  130. {
  131. char InAddr[4];
  132. DWORD Error;
  133. struct sockaddr_in SockAddrIn;
  134. struct hostent *HostAddr;
  135. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  136. //
  137. // Check handle
  138. //
  139. if (Client == NULL) {
  140. return ERROR_INVALID_HANDLE;
  141. }
  142. HostAddr = gethostbyname(MachineName);
  143. if (HostAddr == NULL) {
  144. return WSAGetLastError();
  145. }
  146. if (HostAddr->h_addrtype != AF_INET) {
  147. return WSAGetLastError();
  148. }
  149. if (Client->RemoteSocket != INVALID_SOCKET) {
  150. shutdown(Client->RemoteSocket, SD_BOTH);
  151. closesocket(Client->RemoteSocket);
  152. Client->RemoteSocket = INVALID_SOCKET;
  153. }
  154. //
  155. // Make a socket descriptor
  156. //
  157. Client->MySocket = socket(PF_INET, SOCK_STREAM, 0);
  158. if (Client->MySocket == INVALID_SOCKET) {
  159. return WSAGetLastError();
  160. }
  161. //
  162. // Build the sockaddr_in structure
  163. //
  164. memset(&(SockAddrIn), 0, sizeof(SockAddrIn));
  165. SockAddrIn.sin_family = AF_INET;
  166. //
  167. // Fill in the socket structure
  168. //
  169. Error = bind(Client->MySocket, (struct sockaddr *)(&SockAddrIn), sizeof(struct sockaddr_in));
  170. if (Error != 0) {
  171. closesocket(Client->MySocket);
  172. return WSAGetLastError();
  173. }
  174. //
  175. // Make a socket descriptor for connecting to client
  176. //
  177. Client->RemoteSocket = socket(PF_INET, SOCK_STREAM, 0);
  178. if (Client->RemoteSocket == INVALID_SOCKET) {
  179. closesocket(Client->MySocket);
  180. return WSAGetLastError();
  181. }
  182. //
  183. // Build the sockaddr_in structure for the remote site.
  184. //
  185. memset(&(SockAddrIn), 0, sizeof(SockAddrIn));
  186. memcpy(&(SockAddrIn.sin_addr), HostAddr->h_addr_list[0], HostAddr->h_length);
  187. SockAddrIn.sin_family = AF_INET;
  188. SockAddrIn.sin_port = htons(385);
  189. //
  190. // Connect to the remote site
  191. //
  192. Error = connect(Client->RemoteSocket, (struct sockaddr *)(&SockAddrIn), sizeof(struct sockaddr_in));
  193. if (Error != 0) {
  194. closesocket(Client->MySocket);
  195. closesocket(Client->RemoteSocket);
  196. Client->RemoteSocket = INVALID_SOCKET;
  197. return WSAGetLastError();
  198. }
  199. return ERROR_SUCCESS;
  200. }
  201. DWORD
  202. GetResponse(
  203. IN PRCC_CLIENT Client,
  204. IN ULONG CommandCode,
  205. IN PUCHAR Buffer,
  206. IN ULONG BufferSize
  207. )
  208. /*++
  209. Routine Description:
  210. This routine reads from a socket until either (a) a full response is received for the command given,
  211. (b) a timeout occurs, or (c) too much data arrives (it is tossed out).
  212. Arguments:
  213. Client - A remote command port client instance.
  214. CommandCode - The command code response to wait for.
  215. Buffer - Storage for the response.
  216. BufferSize - Size of Buffer in bytes.
  217. Return Value:
  218. ERROR_SUCCESS if successful, else an appropriate error code.
  219. --*/
  220. {
  221. fd_set FdSet;
  222. struct timeval Timeout;
  223. int Count;
  224. DWORD Bytes;
  225. DWORD Error;
  226. ULONG TotalBytes;
  227. PRCC_RESPONSE Response;
  228. BOOLEAN BufferTooSmall = FALSE;
  229. char *DataBuffer;
  230. FD_ZERO(&FdSet);
  231. FD_SET(Client->RemoteSocket, &FdSet);
  232. while (1) {
  233. Timeout.tv_usec = 0;
  234. Timeout.tv_sec = 30;
  235. Count = select(0, &FdSet, NULL, NULL, &Timeout);
  236. if (Count == 0) {
  237. return WAIT_TIMEOUT;
  238. }
  239. Bytes = recv(Client->RemoteSocket, (char *)Buffer, BufferSize, 0);
  240. if (Bytes == SOCKET_ERROR) {
  241. return GetLastError();
  242. }
  243. Response = (PRCC_RESPONSE)Buffer;
  244. TotalBytes = Bytes;
  245. if ((Response->DataLength + sizeof(RCC_RESPONSE) - 1) > BufferSize) {
  246. BufferTooSmall = TRUE;
  247. }
  248. //
  249. // Receive the entire response.
  250. //
  251. while (TotalBytes < (Response->DataLength + sizeof(RCC_RESPONSE) - 1)) {
  252. if (BufferTooSmall) {
  253. DataBuffer = (char *)(Buffer + sizeof(RCC_RESPONSE)); // preserve the response data structure for getting the needed size later.
  254. } else {
  255. DataBuffer = (char *)(Buffer + TotalBytes);
  256. }
  257. Count = select(0, &FdSet, NULL, NULL, &Timeout);
  258. if (Count == 0) {
  259. return WAIT_TIMEOUT;
  260. }
  261. Bytes = recv(Client->RemoteSocket,
  262. DataBuffer,
  263. (BufferTooSmall ? (BufferSize - sizeof(RCC_RESPONSE)) : (BufferSize - TotalBytes)),
  264. 0
  265. );
  266. if (Bytes == SOCKET_ERROR) {
  267. return GetLastError();
  268. }
  269. TotalBytes += Bytes;
  270. }
  271. if (TotalBytes > (Response->DataLength + sizeof(RCC_RESPONSE) - 1)) {
  272. //
  273. // Something is weird here - we got more data than we expected, skip this message.
  274. //
  275. ASSERT(0);
  276. continue;
  277. }
  278. //
  279. // Check that this is the response we were looking for
  280. //
  281. if ((Response->CommandSequenceNumber == Client->CommandSequenceNumber) &&
  282. (Response->CommandCode == CommandCode)) {
  283. break;
  284. }
  285. }
  286. if (BufferTooSmall) {
  287. return SEC_E_BUFFER_TOO_SMALL;
  288. }
  289. return ERROR_SUCCESS;
  290. }
  291. DWORD
  292. RCCClntGetTList(
  293. IN HANDLE hHandle,
  294. IN ULONG BufferSize,
  295. OUT PRCC_RSP_TLIST Buffer,
  296. OUT PULONG ResponseSize
  297. )
  298. /*++
  299. Routine Description:
  300. This routine submits a "TLIST" command to a remote machine via the remote command port, and then waits
  301. for a response.
  302. Arguments:
  303. hHandle - A previously created instance.
  304. BufferSize - Size of Buffer in bytes.
  305. Buffer - The buffer for holding the response.
  306. ResponseSize - The number of bytes in buffer that were filled. If BufferSize is too small, then this
  307. will contain the number of bytes needed.
  308. Return Value:
  309. ERROR_SUCCESS if successful, else an appropriate error code.
  310. --*/
  311. {
  312. DWORD Error;
  313. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  314. PRCC_REQUEST Request;
  315. PRCC_RESPONSE Response;
  316. //
  317. // Check handle
  318. //
  319. if (Client == NULL) {
  320. return ERROR_INVALID_HANDLE;
  321. }
  322. //
  323. // Build the request
  324. //
  325. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST));
  326. if (Request == NULL) {
  327. return ERROR_OUTOFMEMORY;
  328. }
  329. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  330. Request->CommandCode = RCC_CMD_TLIST;
  331. Request->OptionLength = 0;
  332. Request->Options[0] = '\0';
  333. //
  334. // Send the request.
  335. //
  336. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  337. if (Error == SOCKET_ERROR) {
  338. LocalFree(Request);
  339. return GetLastError();
  340. }
  341. //
  342. // Get the response.
  343. //
  344. Error = GetResponse(Client, RCC_CMD_TLIST, (PUCHAR)Buffer, BufferSize);
  345. Response = (PRCC_RESPONSE)Buffer;
  346. if (Error != ERROR_SUCCESS) {
  347. if (ERROR == SEC_E_BUFFER_TOO_SMALL) {
  348. *ResponseSize = Response->DataLength;
  349. }
  350. LocalFree(Request);
  351. return Error;
  352. }
  353. //
  354. // Extract the data
  355. //
  356. Error = Response->Error;
  357. *ResponseSize = Response->DataLength;
  358. RtlCopyMemory(Buffer, &(Response->Data[0]), Response->DataLength);
  359. LocalFree(Request);
  360. //
  361. // Return the status.
  362. //
  363. return Error;
  364. }
  365. DWORD
  366. RCCClntKillProcess(
  367. IN HANDLE hHandle,
  368. IN DWORD ProcessId
  369. )
  370. /*++
  371. Routine Description:
  372. This routine submits a "KILL" command to a remote machine via the remote command port, and then waits
  373. for a response.
  374. Arguments:
  375. hHandle - A previously created instance.
  376. ProcessId - The process id on the remote machine to kill.
  377. Return Value:
  378. ERROR_SUCCESS if successful, else an appropriate error code.
  379. --*/
  380. {
  381. DWORD Error;
  382. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  383. PRCC_REQUEST Request;
  384. RCC_RESPONSE Response;
  385. //
  386. // Check handle
  387. //
  388. if (Client == NULL) {
  389. return ERROR_INVALID_HANDLE;
  390. }
  391. //
  392. // Build the request
  393. //
  394. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST));
  395. if (Request == NULL) {
  396. return ERROR_OUTOFMEMORY;
  397. }
  398. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  399. Request->CommandCode = RCC_CMD_KILL;
  400. Request->OptionLength = sizeof(DWORD);
  401. RtlCopyMemory(&(Request->Options[0]), &ProcessId, sizeof(DWORD));
  402. //
  403. // Send the request.
  404. //
  405. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  406. if (Error == SOCKET_ERROR) {
  407. LocalFree(Request);
  408. return GetLastError();
  409. }
  410. //
  411. // Get the response.
  412. //
  413. Error = GetResponse(Client, RCC_CMD_KILL, (PUCHAR)&Response, sizeof(Response));
  414. if (Error != ERROR_SUCCESS) {
  415. LocalFree(Request);
  416. return Error;
  417. }
  418. //
  419. // Extract the result
  420. //
  421. Error = Response.Error;
  422. LocalFree(Request);
  423. //
  424. // Return the status.
  425. //
  426. return Error;
  427. }
  428. DWORD
  429. RCCClntReboot(
  430. IN HANDLE hHandle
  431. )
  432. /*++
  433. Routine Description:
  434. This routine submits a "REBOOT" command to a remote machine via the remote command port, and then waits
  435. for a response.
  436. Arguments:
  437. hHandle - A previously created instance.
  438. ProcessId - The process id on the remote machine to kill.
  439. Return Value:
  440. ERROR_SUCCESS if successful, else an appropriate error code.
  441. --*/
  442. {
  443. DWORD Error;
  444. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  445. PRCC_REQUEST Request;
  446. RCC_RESPONSE Response;
  447. //
  448. // Check handle
  449. //
  450. if (Client == NULL) {
  451. return ERROR_INVALID_HANDLE;
  452. }
  453. //
  454. // Build the request
  455. //
  456. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST));
  457. if (Request == NULL) {
  458. return ERROR_OUTOFMEMORY;
  459. }
  460. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  461. Request->CommandCode = RCC_CMD_REBOOT;
  462. Request->OptionLength = 0;
  463. //
  464. // Send the request.
  465. //
  466. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  467. if (Error == SOCKET_ERROR) {
  468. LocalFree(Request);
  469. return GetLastError();
  470. }
  471. //
  472. // Get the response.
  473. //
  474. Error = GetResponse(Client, RCC_CMD_REBOOT, (PUCHAR)&Response, sizeof(Response));
  475. if (Error != ERROR_SUCCESS) {
  476. LocalFree(Request);
  477. return Error;
  478. }
  479. //
  480. // Extract the result
  481. //
  482. Error = Response.Error;
  483. LocalFree(Request);
  484. //
  485. // Close the connection if there was no error
  486. //
  487. if (Error == ERROR_SUCCESS) {
  488. shutdown(Client->RemoteSocket, SD_BOTH);
  489. closesocket(Client->RemoteSocket);
  490. Client->RemoteSocket = INVALID_SOCKET;
  491. }
  492. //
  493. // Return the status.
  494. //
  495. return Error;
  496. }
  497. DWORD
  498. RCCClntLowerProcessPriority(
  499. IN HANDLE hHandle,
  500. IN DWORD ProcessId
  501. )
  502. /*++
  503. Routine Description:
  504. This routine submits a "LOWER" command to a remote machine via the remote command port, and then waits
  505. for a response.
  506. Arguments:
  507. hHandle - A previously created instance.
  508. ProcessId - The process id on the remote machine to reduce the priority of.
  509. Return Value:
  510. ERROR_SUCCESS if successful, else an appropriate error code.
  511. --*/
  512. {
  513. DWORD Error;
  514. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  515. PRCC_REQUEST Request;
  516. RCC_RESPONSE Response;
  517. //
  518. // Check handle
  519. //
  520. if (Client == NULL) {
  521. return ERROR_INVALID_HANDLE;
  522. }
  523. //
  524. // Build the request
  525. //
  526. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST));
  527. if (Request == NULL) {
  528. return ERROR_OUTOFMEMORY;
  529. }
  530. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  531. Request->CommandCode = RCC_CMD_LOWER;
  532. Request->OptionLength = sizeof(DWORD);
  533. RtlCopyMemory(&(Request->Options[0]), &ProcessId, sizeof(DWORD));
  534. //
  535. // Send the request.
  536. //
  537. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  538. if (Error == SOCKET_ERROR) {
  539. LocalFree(Request);
  540. return GetLastError();
  541. }
  542. //
  543. // Get the response.
  544. //
  545. Error = GetResponse(Client, RCC_CMD_LOWER, (PUCHAR)&Response, sizeof(Response));
  546. if (Error != ERROR_SUCCESS) {
  547. LocalFree(Request);
  548. return Error;
  549. }
  550. //
  551. // Extract the result
  552. //
  553. Error = Response.Error;
  554. LocalFree(Request);
  555. //
  556. // Return the status.
  557. //
  558. return Error;
  559. }
  560. DWORD
  561. RCCClntLimitProcessMemory(
  562. IN HANDLE hHandle,
  563. IN DWORD ProcessId,
  564. IN DWORD MemoryLimit
  565. )
  566. /*++
  567. Routine Description:
  568. This routine submits a "LIMIT" command to a remote machine via the remote command port, and then waits
  569. for a response.
  570. Arguments:
  571. hHandle - A previously created instance.
  572. ProcessId - The process id on the remote machine to limit the memory of.
  573. MemoryLimit - Number of MB to set the limit to.
  574. Return Value:
  575. ERROR_SUCCESS if successful, else an appropriate error code.
  576. --*/
  577. {
  578. DWORD Error;
  579. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  580. PRCC_REQUEST Request;
  581. RCC_RESPONSE Response;
  582. //
  583. // Check handle
  584. //
  585. if (Client == NULL) {
  586. return ERROR_INVALID_HANDLE;
  587. }
  588. //
  589. // Build the request
  590. //
  591. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST) + sizeof(DWORD));
  592. if (Request == NULL) {
  593. return ERROR_OUTOFMEMORY;
  594. }
  595. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  596. Request->CommandCode = RCC_CMD_LIMIT;
  597. Request->OptionLength = 2 * sizeof(DWORD);
  598. RtlCopyMemory(&(Request->Options[0]), &ProcessId, sizeof(DWORD));
  599. RtlCopyMemory(&(Request->Options[sizeof(DWORD)]), &MemoryLimit, sizeof(DWORD));
  600. //
  601. // Send the request.
  602. //
  603. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  604. if (Error == SOCKET_ERROR) {
  605. LocalFree(Request);
  606. return GetLastError();
  607. }
  608. //
  609. // Get the response.
  610. //
  611. Error = GetResponse(Client, RCC_CMD_LIMIT, (PUCHAR)&Response, sizeof(Response));
  612. if (Error != ERROR_SUCCESS) {
  613. LocalFree(Request);
  614. return Error;
  615. }
  616. //
  617. // Extract the result
  618. //
  619. Error = Response.Error;
  620. LocalFree(Request);
  621. //
  622. // Return the status.
  623. //
  624. return Error;
  625. }
  626. DWORD
  627. RCCClntCrashDump(
  628. IN HANDLE hHandle
  629. )
  630. /*++
  631. Routine Description:
  632. This routine submits a "CRASHDUMP" command to a remote machine via the remote command port,
  633. and then waits for a response.
  634. Arguments:
  635. hHandle - A previously created instance.
  636. Return Value:
  637. ERROR_SUCCESS if successful, else an appropriate error code.
  638. --*/
  639. {
  640. DWORD Error;
  641. PRCC_CLIENT Client = (PRCC_CLIENT)hHandle;
  642. PRCC_REQUEST Request;
  643. RCC_RESPONSE Response;
  644. //
  645. // Check handle
  646. //
  647. if (Client == NULL) {
  648. return ERROR_INVALID_HANDLE;
  649. }
  650. //
  651. // Build the request
  652. //
  653. Request = LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(RCC_REQUEST) + sizeof(DWORD));
  654. if (Request == NULL) {
  655. return ERROR_OUTOFMEMORY;
  656. }
  657. Request->CommandSequenceNumber = ++(Client->CommandSequenceNumber);
  658. Request->CommandCode = RCC_CMD_CRASHDUMP;
  659. Request->OptionLength = 0;
  660. //
  661. // Send the request.
  662. //
  663. Error = send(Client->RemoteSocket, (char *)Request, sizeof(RCC_REQUEST) - 1 + Request->OptionLength, 0);
  664. if (Error == SOCKET_ERROR) {
  665. LocalFree(Request);
  666. return GetLastError();
  667. }
  668. //
  669. // Get the response.
  670. //
  671. Error = GetResponse(Client, RCC_CMD_LIMIT, (PUCHAR)&Response, sizeof(Response));
  672. if (Error != ERROR_SUCCESS) {
  673. LocalFree(Request);
  674. return Error;
  675. }
  676. //
  677. // Extract the result
  678. //
  679. Error = Response.Error;
  680. LocalFree(Request);
  681. //
  682. // Return the status.
  683. //
  684. return Error;
  685. }