From patchwork Sun Aug 9 16:51:26 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael Goldish X-Patchwork-Id: 40274 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n79Gn022002758 for ; Sun, 9 Aug 2009 16:49:01 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751945AbZHIQs4 (ORCPT ); Sun, 9 Aug 2009 12:48:56 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751944AbZHIQsz (ORCPT ); Sun, 9 Aug 2009 12:48:55 -0400 Received: from mx2.redhat.com ([66.187.237.31]:45711 "EHLO mx2.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751891AbZHIQsy (ORCPT ); Sun, 9 Aug 2009 12:48:54 -0400 Received: from int-mx2.corp.redhat.com (int-mx2.corp.redhat.com [172.16.27.26]) by mx2.redhat.com (8.13.8/8.13.8) with ESMTP id n79GmsnS025806; Sun, 9 Aug 2009 12:48:54 -0400 Received: from ns3.rdu.redhat.com (ns3.rdu.redhat.com [10.11.255.199]) by int-mx2.corp.redhat.com (8.13.1/8.13.1) with ESMTP id n79Gmrru000499; Sun, 9 Aug 2009 12:48:53 -0400 Received: from localhost.localdomain (dhcp-1-31.tlv.redhat.com [10.35.1.31]) by ns3.rdu.redhat.com (8.13.8/8.13.8) with ESMTP id n79GmoCJ022741; Sun, 9 Aug 2009 12:48:52 -0400 From: Michael Goldish To: autotest@test.kernel.org, kvm@vger.kernel.org Cc: Michael Goldish Subject: [KVM-AUTOTEST PATCH 1/7] KVM test: Simple remote shell server for use with Windows guests Date: Sun, 9 Aug 2009 19:51:26 +0300 Message-Id: In-Reply-To: <1249836692-18292-1-git-send-email-mgoldish@redhat.com> References: <1249836692-18292-1-git-send-email-mgoldish@redhat.com> X-Scanned-By: MIMEDefang 2.58 on 172.16.27.26 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org This server provides clients with an interactive cmd.exe shell. It uses no encryption or authentication. Usage: rss.exe [port] The default port is 22. Signed-off-by: Michael Goldish --- client/tests/kvm/deps/rss.cpp | 467 +++++++++++++++++++++++++++++++++++++++++ 1 files changed, 467 insertions(+), 0 deletions(-) create mode 100644 client/tests/kvm/deps/rss.cpp diff --git a/client/tests/kvm/deps/rss.cpp b/client/tests/kvm/deps/rss.cpp new file mode 100644 index 0000000..29e4957 --- /dev/null +++ b/client/tests/kvm/deps/rss.cpp @@ -0,0 +1,467 @@ +// Simple remote shell server +// Author: Michael Goldish +// Much of the code here was adapted from Microsoft code samples. + +// Usage: rss.exe [port] +// If no port is specified the default is 22. + +#define _WIN32_WINNT 0x0500 + +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") + +int port = 22; + +HWND hMainWindow = NULL; +HWND hTextBox = NULL; + +struct client_info { + SOCKET socket; + sockaddr_in addr; + int pid; + HANDLE hJob; + HANDLE hChildOutputRead; + HANDLE hChildInputWrite; + HANDLE hThreadChildToSocket; +}; + +void ExitOnError(char *message, BOOL winsock = 0) +{ + LPVOID system_message; + char buffer[512]; + + int error_code; + if (winsock) + error_code = WSAGetLastError(); + else + error_code = GetLastError(); + + WSACleanup(); + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&system_message, 0, NULL); + + sprintf(buffer, + "%s!\n" + "Error code = %d\n" + "Error message = %s", + message, error_code, (char *)system_message); + + MessageBox(hMainWindow, buffer, "Error", MB_OK | MB_ICONERROR); + + LocalFree(system_message); + ExitProcess(1); +} + +void AppendMessage(char *message) +{ + int length = GetWindowTextLength(hTextBox); + SendMessage(hTextBox, EM_SETSEL, (WPARAM)length, (LPARAM)length); + SendMessage(hTextBox, EM_REPLACESEL, (WPARAM)FALSE, (LPARAM)message); +} + +void FormatStringForPrinting(char *dst, char *src, int size) +{ + int j = 0; + + for (int i = 0; i < size && src[i]; i++) { + if (src[i] == '\n') { + dst[j++] = '\\'; + dst[j++] = 'n'; + } else if (src[i] == '\r') { + dst[j++] = '\\'; + dst[j++] = 'r'; + } else if (src[i] == '\t') { + dst[j++] = '\\'; + dst[j++] = 't'; + } else if (src[i] == '\\') { + dst[j++] = '\\'; + dst[j++] = '\\'; + } else dst[j++] = src[i]; + } + dst[j] = 0; +} + +char* GetClientIPAddress(client_info *ci) +{ + char *address = inet_ntoa(ci->addr.sin_addr); + if (address) + return address; + else + return "unknown"; +} + +DWORD WINAPI ChildToSocket(LPVOID client_info_ptr) +{ + char buffer[1024], message[1024]; + client_info ci; + DWORD bytes_read; + int bytes_sent; + + memcpy(&ci, client_info_ptr, sizeof(ci)); + + while (1) { + // Read data from the child's STDOUT/STDERR pipes + if (!ReadFile(ci.hChildOutputRead, + buffer, sizeof(buffer), + &bytes_read, NULL) || !bytes_read) { + if (GetLastError() == ERROR_BROKEN_PIPE) + break; // Pipe done -- normal exit path + else + ExitOnError("ReadFile failed"); // Something bad happened + } + // Send data to the client + bytes_sent = send(ci.socket, buffer, bytes_read, 0); + /* + // Make sure all the data was sent + if (bytes_sent != bytes_read) { + sprintf(message, + "ChildToSocket: bytes read (%d) != bytes sent (%d)", + bytes_read, bytes_sent); + ExitOnError(message, 1); + } + */ + } + + AppendMessage("Child exited\r\n"); + shutdown(ci.socket, SD_BOTH); + + return 0; +} + +DWORD WINAPI SocketToChild(LPVOID client_info_ptr) +{ + char buffer[256], formatted_buffer[768]; + char message[1024], client_info_str[256]; + client_info ci; + DWORD bytes_written; + int bytes_received; + + memcpy(&ci, client_info_ptr, sizeof(ci)); + + sprintf(client_info_str, "address %s, port %d", + GetClientIPAddress(&ci), ci.addr.sin_port); + + sprintf(message, "New client connected (%s)\r\n", client_info_str); + AppendMessage(message); + + while (1) { + // Receive data from the socket + ZeroMemory(buffer, sizeof(buffer)); + bytes_received = recv(ci.socket, buffer, sizeof(buffer), 0); + if (bytes_received <= 0) + break; + // Report the data received + FormatStringForPrinting(formatted_buffer, buffer, sizeof(buffer)); + sprintf(message, "Client (%s) entered text: \"%s\"\r\n", + client_info_str, formatted_buffer); + AppendMessage(message); + // Write the data to the child's STDIN + WriteFile(ci.hChildInputWrite, buffer, bytes_received, + &bytes_written, NULL); + // Make sure all the data was written + if (bytes_written != bytes_received) { + sprintf(message, + "SocketToChild: bytes received (%d) != bytes written (%d)", + bytes_received, bytes_written); + ExitOnError(message, 1); + } + } + + sprintf(message, "Client disconnected (%s)\r\n", client_info_str); + AppendMessage(message); + + // Attempt to terminate the child's process tree: + // Using taskkill (where available) + sprintf(buffer, "taskkill /PID %d /T /F", ci.pid); + system(buffer); + // .. and using TerminateJobObject() + TerminateJobObject(ci.hJob, 0); + // Wait for the ChildToSocket thread to terminate + WaitForSingleObject(ci.hThreadChildToSocket, 10000); + // In case the thread refuses to exit -- terminate it + TerminateThread(ci.hThreadChildToSocket, 0); + // Close the socket + shutdown(ci.socket, SD_BOTH); + closesocket(ci.socket); + + // Close unnecessary handles + CloseHandle(ci.hJob); + CloseHandle(ci.hThreadChildToSocket); + CloseHandle(ci.hChildOutputRead); + CloseHandle(ci.hChildInputWrite); + + AppendMessage("SocketToChild thread exited\r\n"); + + return 0; +} + +void PrepAndLaunchRedirectedChild(client_info *ci, + HANDLE hChildStdOut, + HANDLE hChildStdIn, + HANDLE hChildStdErr) +{ + PROCESS_INFORMATION pi; + STARTUPINFO si; + + // Set up the start up info struct. + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.hStdOutput = hChildStdOut; + si.hStdInput = hChildStdIn; + si.hStdError = hChildStdErr; + // Use this if you want to hide the child: + si.wShowWindow = SW_HIDE; + // Note that dwFlags must include STARTF_USESHOWWINDOW if you want to + // use the wShowWindow flags. + + // Launch the process that you want to redirect. + if (!CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, + CREATE_NEW_CONSOLE, NULL, "C:\\", &si, &pi)) + ExitOnError("CreateProcess failed"); + + // Close any unnecessary handles. + if (!CloseHandle(pi.hThread)) + ExitOnError("CloseHandle failed"); + + // Keep the process ID + ci->pid = pi.dwProcessId; + // Assign the process to a newly created JobObject + ci->hJob = CreateJobObject(NULL, NULL); + AssignProcessToJobObject(ci->hJob, pi.hProcess); +} + +void SpawnSession(client_info *ci) +{ + HANDLE hOutputReadTmp, hOutputRead, hOutputWrite; + HANDLE hInputWriteTmp, hInputRead, hInputWrite; + HANDLE hErrorWrite; + SECURITY_ATTRIBUTES sa; + + // Set up the security attributes struct. + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + // Create the child output pipe. + if (!CreatePipe(&hOutputReadTmp, &hOutputWrite, &sa, 0)) + ExitOnError("CreatePipe failed"); + + // Create a duplicate of the output write handle for the std error + // write handle. This is necessary in case the child application + // closes one of its std output handles. + if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite, + GetCurrentProcess(), &hErrorWrite, 0, + TRUE, DUPLICATE_SAME_ACCESS)) + ExitOnError("DuplicateHandle failed"); + + // Create the child input pipe. + if (!CreatePipe(&hInputRead, &hInputWriteTmp, &sa, 0)) + ExitOnError("CreatePipe failed"); + + // Create new output read handle and the input write handles. Set + // the Properties to FALSE. Otherwise, the child inherits the + // properties and, as a result, non-closeable handles to the pipes + // are created. + if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp, + GetCurrentProcess(), + &hOutputRead, // Address of new handle. + 0, FALSE, // Make it uninheritable. + DUPLICATE_SAME_ACCESS)) + ExitOnError("DuplicateHandle failed"); + + if (!DuplicateHandle(GetCurrentProcess(), hInputWriteTmp, + GetCurrentProcess(), + &hInputWrite, // Address of new handle. + 0, FALSE, // Make it uninheritable. + DUPLICATE_SAME_ACCESS)) + ExitOnError("DuplicateHandle failed"); + + // Close inheritable copies of the handles you do not want to be + // inherited. + if (!CloseHandle(hOutputReadTmp)) ExitOnError("CloseHandle failed"); + if (!CloseHandle(hInputWriteTmp)) ExitOnError("CloseHandle failed"); + + PrepAndLaunchRedirectedChild(ci, hOutputWrite, hInputRead, hErrorWrite); + + ci->hChildOutputRead = hOutputRead; + ci->hChildInputWrite = hInputWrite; + + // Close pipe handles (do not continue to modify the parent). + // You need to make sure that no handles to the write end of the + // output pipe are maintained in this process or else the pipe will + // not close when the child process exits and the ReadFile will hang. + if (!CloseHandle(hOutputWrite)) ExitOnError("CloseHandle failed"); + if (!CloseHandle(hInputRead )) ExitOnError("CloseHandle failed"); + if (!CloseHandle(hErrorWrite)) ExitOnError("CloseHandle failed"); +} + +DWORD WINAPI ListenThread(LPVOID param) +{ + WSADATA wsaData; + SOCKET ListenSocket = INVALID_SOCKET; + sockaddr_in addr; + int result, addrlen; + client_info ci; + HANDLE hThread; + + // Initialize Winsock + result = WSAStartup(MAKEWORD(2,2), &wsaData); + if (result) + ExitOnError("Winsock initialization failed"); + + // Create socket + ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (ListenSocket == INVALID_SOCKET) + ExitOnError("Socket creation failed", 1); + + // Bind the socket + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + result = bind(ListenSocket, (sockaddr *)&addr, sizeof(addr)); + if (result == SOCKET_ERROR) + ExitOnError("bind failed", 1); + + // Start listening for incoming connections + result = listen(ListenSocket, SOMAXCONN); + if (result == SOCKET_ERROR) + ExitOnError("listen failed", 1); + + // Inform the user + AppendMessage("Waiting for clients to connect...\r\n"); + + while (1) { + addrlen = sizeof(ci.addr); + ci.socket = accept(ListenSocket, (sockaddr *)&ci.addr, &addrlen); + if (ci.socket == INVALID_SOCKET) { + if (WSAGetLastError() == WSAEINTR) + break; + else + ExitOnError("accept failed", 1); + } + + // Under heavy load, spawning cmd.exe might take a while, so tell the + // client to be patient + char *message = "Please wait...\r\n"; + send(ci.socket, message, strlen(message), 0); + // Spawn a new redirected cmd.exe process + SpawnSession(&ci); + // Start transferring data from the child process to the client + hThread = CreateThread(NULL, 0, ChildToSocket, (LPVOID)&ci, 0, NULL); + if (!hThread) + ExitOnError("Could not create ChildToSocket thread"); + ci.hThreadChildToSocket = hThread; + // ... and from the client to the child process + hThread = CreateThread(NULL, 0, SocketToChild, (LPVOID)&ci, 0, NULL); + if (!hThread) + ExitOnError("Could not create SocketToChild thread"); + } + + return 0; +} + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + RECT rect; + HANDLE hListenThread; + + switch (msg) { + case WM_CREATE: + // Create text box + GetClientRect(hwnd, &rect); + hTextBox = CreateWindowEx(WS_EX_CLIENTEDGE, + "EDIT", "", + WS_CHILD|WS_VISIBLE|WS_VSCROLL| + ES_MULTILINE|ES_AUTOVSCROLL, + 20, 20, + rect.right - 40, + rect.bottom - 40, + hwnd, + NULL, + GetModuleHandle(NULL), + NULL); + if (!hTextBox) + ExitOnError("Could not create text box"); + + // Set the font + SendMessage(hTextBox, WM_SETFONT, + (WPARAM)GetStockObject(DEFAULT_GUI_FONT), + MAKELPARAM(FALSE, 0)); + + // Start the listening thread + hListenThread = + CreateThread(NULL, 0, ListenThread, NULL, 0, NULL); + if (!hListenThread) + ExitOnError("Could not create server thread"); + break; + + case WM_DESTROY: + WSACleanup(); + PostQuitMessage(0); + break; + + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + + return 0; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nShowCmd) +{ + WNDCLASSEX wc; + MSG msg; + + if (strlen(lpCmdLine)) + sscanf(lpCmdLine, "%d", &port); + + // Make sure the firewall is disabled + system("netsh firewall set opmode disable"); + + // Create the window class + wc.cbSize = sizeof(WNDCLASSEX); + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = "RemoteShellServerWindowClass"; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + + if (!RegisterClassEx(&wc)) + ExitOnError("Could not register window class"); + + // Create the main window + hMainWindow = + CreateWindow("RemoteShellServerWindowClass", + "Remote Shell Server", + WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX, + 20, 20, 500, 300, + NULL, NULL, hInstance, NULL); + if (!hMainWindow) + ExitOnError("Could not create window"); + + ShowWindow(hMainWindow, SW_SHOWMINNOACTIVE); + UpdateWindow(hMainWindow); + + // Main message loop + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + ExitProcess(0); +}