Hi, In an attempt to solve the SSH problems we have with Windows, I wrote a little remote shell utility to replace the OpenSSH server we're currently using with Win(2000|XP|2003) and the builtin Telnet server we're using with Win2008. It also works with Vista, for which we currently have no other solution. Features: - Listens on a requested port (22 by default). - Provides clients with a cmd.exe shell. - Multiple clients can connect simultaneously. - Uses no authentication whatsoever. - Uses standard API calls that should work on all modern Windows variants. - Displays an informative message box if any API call fails. - Automatically closes all processes started by a client when it disconnects. - Allows clients to run GUI programs (see comment below). - Starts minimized by default so it doesn't interfere with step file tests. - Reports all activity in a text box. - The code is short (450 lines including comments). Tested and verified to work on WinXP 32, 2003 32, Vista 32 and 64, 2008 32. I haven't tested it on other Windows versions but it should work (should be interesting to test it on Windows 7). The source code is attached. I used MinGW (with Code::Blocks) to compile it under WinXP. Link it with ws2_32.lib. If anyone wants the binary please let me know and I'll send it somehow (I don't know if I'm supposed to send binaries to the list). Problems: - cmd.exe echoes back the command line sent to it. This means commands are echoed twice: first by the local terminal and then by the remote cmd.exe. So if you send "ver\r\n" to the server you get: "ver\r\nver\r\nMicrosoft Windows [Version ...]\r\nC:\\>" In order for it to work with Autotest we'll have to make some modifications to kvm_utils.kvm_spawn (which should be replaced by kvm_subprocess anyway) -- specifically disable the local terminal echo, and not assume that the command line is echoed exactly once (it may not be echoed at all by Linux guests). - The server does not send or receive files. For now we can transfer files into Windows guests using ISOs (-cdrom). If it turns out to be necessary, file send/receive support can be implemented into the shell server, or we can use an open source Windows FTP server or find some other solution. - Running GUI apps: Vista and 2008 seem to run GUI apps just fine from a remote shell, but in older Windows versions you must use cmd /c (e.g. "cmd /c notepad"). To be compatible with all Windows versions, Windows GUI tests should probably always use cmd /c to run GUI apps. There's no need to use it for console commands (e.g. dir). Note that when using cmd /c the command returns only when the GUI app exits. Without cmd /c the command returns immediately. - Some interactive console programs don't behave nicely when their output is redirected (to a remote client in this case). One example is the builtin ftp.exe. If you want to use it, you should do so without waiting for interactive output from the program, which means you should just send the FTP commands one by one and hope everything works, and finally send a 'quit' command to get back to cmd.exe. Installation on guests: The following should be done as a superuser: - Copy the program to the guest (C:\ should be fine). - Disable the firewall. - Enable the Administrator account. - Make Windows logon automatically using the Administrator account. - Make the server program run automatically on startup. I'm attaching a setup.bat file that does the above. setup.bat and rss.exe should be packaged together in an ISO and sent to the guest with -cdrom. Note that setup.bat assumes that rss.exe is in D:\. This will not be true if we run the guest with multiple hard drives, so we'll have to do something about that. Please send me comments if you have any. If you think this isn't a proper solution, or if you can suggest a better one, please let me know. Lucas: if we commit this, where should it go? Under tests/kvm/src maybe? Thanks, Michael
// Simple remote shell server // Author: Michael Goldish <mgoldish@xxxxxxxxxx> // Much of the code here was adapted from Microsoft's example code. // Usage: rss.exe [port] // If no port is specified the default is 22. #define _WIN32_WINNT 0x0500 #include <windows.h> #include <winsock2.h> #include <stdio.h> #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); } // 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); }
<<attachment: setup.bat>>