| // Simple remote shell server (and file transfer server) |
| // Author: Michael Goldish <mgoldish@redhat.com> |
| // Much of the code here was adapted from Microsoft code samples. |
| |
| // Usage: rss.exe [shell port] [file transfer port] |
| // If no shell port is specified the default is 10022. |
| // If no file transfer port is specified the default is 10023. |
| |
| // Definitions: |
| // A 'msg' is a 32 bit integer. |
| // A 'packet' is a 32 bit unsigned integer followed by a string of bytes. |
| // The 32 bit integer indicates the length of the string. |
| |
| // Protocol for file transfers: |
| // |
| // When uploading files/directories to the server: |
| // 1. The client connects. |
| // 2. The server sends RSS_MAGIC. |
| // 3. The client sends the chunk size for file transfers (a 32 bit integer |
| // between 512 and 1048576 indicating the size in bytes). |
| // 4. The client sends RSS_SET_PATH, followed by a packet (as defined above) |
| // containing the path (in the server's filesystem) where files and/or |
| // directories are to be stored. |
| // Uploading a file (optional, can be repeated many times): |
| // 5. The client sends RSS_CREATE_FILE, followed by a packet containing the |
| // filename (filename only, without a path), followed by a series of |
| // packets (called chunks) containing the file's contents. The size of |
| // each chunk is the size set by the client in step 3, except for the |
| // last chunk, which must be smaller. |
| // Uploading a directory (optional, can be repeated many times): |
| // 6. The client sends RSS_CREATE_DIR, followed by a packet containing the |
| // name of the directory to be created (directory name only, without a |
| // path). |
| // 7. The client uploads files and directories to the new directory (using |
| // steps 5, 6, 8). |
| // 8. The client sends RSS_LEAVE_DIR. |
| // 9. The client sends RSS_DONE and waits for a response. |
| // 10. The server sends RSS_OK to indicate that it's still listening. |
| // 11. Steps 4-10 are repeated as many times as necessary. |
| // 12. The client disconnects. |
| // If a critical error occurs at any time, the server may send RSS_ERROR |
| // followed by a packet containing an error message, and the connection is |
| // closed. |
| // |
| // When downloading files from the server: |
| // 1. The client connects. |
| // 2. The server sends RSS_MAGIC. |
| // 3. The client sends the chunk size for file transfers (a 32 bit integer |
| // between 512 and 1048576 indicating the size in bytes). |
| // 4. The client sends RSS_SET_PATH, followed by a packet (as defined above) |
| // containing a path (in the server's filesystem) or a wildcard pattern |
| // indicating the files/directories the client wants to download. |
| // The server then searches the given path. For every file found: |
| // 5. The server sends RSS_CREATE_FILE, followed by a packet containing the |
| // filename (filename only, without a path), followed by a series of |
| // packets (called chunks) containing the file's contents. The size of |
| // each chunk is the size set by the client in step 3, except for the |
| // last chunk, which must be smaller. |
| // For every directory found: |
| // 6. The server sends RSS_CREATE_DIR, followed by a packet containing the |
| // name of the directory to be created (directory name only, without a |
| // path). |
| // 7. The server sends files and directories located inside the directory |
| // (using steps 5, 6, 8). |
| // 8. The server sends RSS_LEAVE_DIR. |
| // 9. The server sends RSS_DONE. |
| // 10. Steps 4-9 are repeated as many times as necessary. |
| // 11. The client disconnects. |
| // If a critical error occurs, the server may send RSS_ERROR followed by a |
| // packet containing an error message, and the connection is closed. |
| // RSS_ERROR may be sent only when the client expects a msg. |
| |
| #define _WIN32_WINNT 0x0500 |
| |
| #include <winsock2.h> |
| #include <windows.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <shlwapi.h> |
| |
| #pragma comment(lib, "ws2_32.lib") |
| #pragma comment(lib, "shlwapi.lib") |
| |
| #define TEXTBOX_LIMIT 262144 |
| |
| // Constants for file transfer server |
| #define RSS_MAGIC 0x525353 |
| #define RSS_OK 1 |
| #define RSS_ERROR 2 |
| #define RSS_UPLOAD 3 |
| #define RSS_DOWNLOAD 4 |
| #define RSS_SET_PATH 5 |
| #define RSS_CREATE_FILE 6 |
| #define RSS_CREATE_DIR 7 |
| #define RSS_LEAVE_DIR 8 |
| #define RSS_DONE 9 |
| |
| // Globals |
| int shell_port = 10022; |
| int file_transfer_port = 10023; |
| |
| HWND hMainWindow = NULL; |
| HWND hTextBox = NULL; |
| |
| char text_buffer[8192] = {0}; |
| int text_size = 0; |
| |
| CRITICAL_SECTION critical_section; |
| |
| FILE *log_file; |
| |
| struct client_info { |
| SOCKET socket; |
| char addr_str[256]; |
| int pid; |
| HWND hwnd; |
| HANDLE hJob; |
| HANDLE hChildOutputRead; |
| HANDLE hThreadChildToSocket; |
| char *chunk_buffer; |
| int chunk_size; |
| }; |
| |
| /*----------------- |
| * Shared functions |
| *-----------------*/ |
| |
| void ExitOnError(const char *message, BOOL winsock = FALSE) |
| { |
| 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 FlushTextBuffer() |
| { |
| if (!text_size) return; |
| // Clear the text box if it contains too much text |
| int len = GetWindowTextLength(hTextBox); |
| while (len > TEXTBOX_LIMIT - sizeof(text_buffer)) { |
| SendMessage(hTextBox, EM_SETSEL, 0, TEXTBOX_LIMIT * 1/4); |
| SendMessage(hTextBox, EM_REPLACESEL, FALSE, (LPARAM)"..."); |
| len = GetWindowTextLength(hTextBox); |
| } |
| // Append the contents of text_buffer to the text box |
| SendMessage(hTextBox, EM_SETSEL, len, len); |
| SendMessage(hTextBox, EM_REPLACESEL, FALSE, (LPARAM)text_buffer); |
| // Clear text_buffer |
| text_buffer[0] = 0; |
| text_size = 0; |
| // Make sure the log file's buffer is flushed as well |
| if (log_file) |
| fflush(log_file); |
| } |
| |
| void AppendMessage(const char *message, ...) |
| { |
| va_list args; |
| char str[512] = {0}; |
| |
| va_start(args, message); |
| vsnprintf(str, sizeof(str) - 3, message, args); |
| va_end(args); |
| strcat(str, "\r\n"); |
| int len = strlen(str); |
| |
| EnterCriticalSection(&critical_section); |
| // Write message to the log file |
| if (log_file) |
| fwrite(str, len, 1, log_file); |
| // Flush the text buffer if necessary |
| if (text_size + len + 1 > sizeof(text_buffer)) |
| FlushTextBuffer(); |
| // Append message to the text buffer |
| strcpy(text_buffer + text_size, str); |
| text_size += len; |
| LeaveCriticalSection(&critical_section); |
| } |
| |
| // Flush the text buffer every 250 ms |
| DWORD WINAPI UpdateTextBox(LPVOID client_info_ptr) |
| { |
| while (1) { |
| Sleep(250); |
| EnterCriticalSection(&critical_section); |
| FlushTextBuffer(); |
| LeaveCriticalSection(&critical_section); |
| } |
| return 0; |
| } |
| |
| void FormatStringForPrinting(char *dst, const 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; |
| } |
| |
| SOCKET PrepareListenSocket(int port) |
| { |
| sockaddr_in addr; |
| linger l; |
| int result; |
| |
| // Create socket |
| SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
| if (ListenSocket == INVALID_SOCKET) |
| ExitOnError("Socket creation failed", TRUE); |
| |
| // Enable lingering |
| l.l_linger = 10; |
| l.l_onoff = 1; |
| setsockopt(ListenSocket, SOL_SOCKET, SO_LINGER, (char *)&l, sizeof(l)); |
| |
| // 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", TRUE); |
| |
| // Start listening for incoming connections |
| result = listen(ListenSocket, SOMAXCONN); |
| if (result == SOCKET_ERROR) |
| ExitOnError("listen failed", TRUE); |
| |
| return ListenSocket; |
| } |
| |
| client_info* Accept(SOCKET ListenSocket) |
| { |
| sockaddr_in addr; |
| int addrlen = sizeof(addr); |
| |
| // Accept the connection |
| SOCKET socket = accept(ListenSocket, (sockaddr *)&addr, &addrlen); |
| if (socket == INVALID_SOCKET) { |
| if (WSAGetLastError() == WSAEINTR) |
| return NULL; |
| else |
| ExitOnError("accept failed", TRUE); |
| } |
| |
| // Allocate a new client_info struct |
| client_info *ci = (client_info *)calloc(1, sizeof(client_info)); |
| if (!ci) |
| ExitOnError("Could not allocate client_info struct"); |
| // Populate the new struct |
| ci->socket = socket; |
| const char *address = inet_ntoa(addr.sin_addr); |
| if (!address) address = "unknown"; |
| sprintf(ci->addr_str, "%s:%d", address, addr.sin_port); |
| |
| return ci; |
| } |
| |
| // Read a given number of bytes into a buffer |
| BOOL Receive(SOCKET socket, char *buffer, int len) |
| { |
| while (len > 0) { |
| int bytes_received = recv(socket, buffer, len, 0); |
| if (bytes_received <= 0) |
| return FALSE; |
| buffer += bytes_received; |
| len -= bytes_received; |
| } |
| return TRUE; |
| } |
| |
| // Send a given number of bytes from a buffer |
| BOOL Send(SOCKET socket, const char *buffer, int len) |
| { |
| while (len > 0) { |
| int bytes_sent = send(socket, buffer, len, 0); |
| if (bytes_sent <= 0) |
| return FALSE; |
| buffer += bytes_sent; |
| len -= bytes_sent; |
| } |
| return TRUE; |
| } |
| |
| /*------------- |
| * Shell server |
| *-------------*/ |
| |
| DWORD WINAPI ChildToSocket(LPVOID client_info_ptr) |
| { |
| client_info *ci = (client_info *)client_info_ptr; |
| char buffer[1024]; |
| DWORD bytes_read; |
| |
| 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 |
| Send(ci->socket, buffer, bytes_read); |
| } |
| |
| AppendMessage("Child exited"); |
| closesocket(ci->socket); |
| return 0; |
| } |
| |
| DWORD WINAPI SocketToChild(LPVOID client_info_ptr) |
| { |
| client_info *ci = (client_info *)client_info_ptr; |
| char buffer[256], formatted_buffer[768]; |
| int bytes_received; |
| |
| AppendMessage("Shell server: new client connected (%s)", ci->addr_str); |
| |
| 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)); |
| AppendMessage("Client (%s) entered text: \"%s\"", |
| ci->addr_str, formatted_buffer); |
| // Send the data as a series of WM_CHAR messages to the console window |
| for (int i = 0; i < bytes_received; i++) { |
| SendMessage(ci->hwnd, WM_CHAR, buffer[i], 0); |
| SendMessage(ci->hwnd, WM_SETFOCUS, 0, 0); |
| } |
| } |
| |
| AppendMessage("Shell server: client disconnected (%s)", ci->addr_str); |
| |
| // 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 |
| closesocket(ci->socket); |
| |
| // Free resources |
| CloseHandle(ci->hJob); |
| CloseHandle(ci->hThreadChildToSocket); |
| CloseHandle(ci->hChildOutputRead); |
| free(ci); |
| |
| AppendMessage("SocketToChild thread exited"); |
| return 0; |
| } |
| |
| void PrepAndLaunchRedirectedChild(client_info *ci, |
| HANDLE hChildStdOut, |
| HANDLE hChildStdErr) |
| { |
| PROCESS_INFORMATION pi; |
| STARTUPINFO si; |
| |
| // Allocate a new console for the child |
| HWND hwnd = GetForegroundWindow(); |
| FreeConsole(); |
| AllocConsole(); |
| ShowWindow(GetConsoleWindow(), SW_HIDE); |
| if (hwnd) |
| SetForegroundWindow(hwnd); |
| |
| // 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 = GetStdHandle(STD_INPUT_HANDLE); |
| 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, |
| 0, 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); |
| // Keep the console window's handle |
| ci->hwnd = GetConsoleWindow(); |
| |
| // Detach from the child's console |
| FreeConsole(); |
| } |
| |
| void SpawnSession(client_info *ci) |
| { |
| HANDLE hOutputReadTmp, hOutputRead, hOutputWrite; |
| 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 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"); |
| |
| // Close inheritable copies of the handles you do not want to be |
| // inherited. |
| if (!CloseHandle(hOutputReadTmp)) |
| ExitOnError("CloseHandle failed"); |
| |
| PrepAndLaunchRedirectedChild(ci, hOutputWrite, hErrorWrite); |
| |
| ci->hChildOutputRead = hOutputRead; |
| |
| // 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(hErrorWrite)) ExitOnError("CloseHandle failed"); |
| } |
| |
| DWORD WINAPI ShellListenThread(LPVOID param) |
| { |
| HANDLE hThread; |
| |
| SOCKET ListenSocket = PrepareListenSocket(shell_port); |
| |
| // Inform the user |
| AppendMessage("Shell server: waiting for clients to connect..."); |
| |
| while (1) { |
| client_info *ci = Accept(ListenSocket); |
| if (!ci) break; |
| // Under heavy load, spawning cmd.exe might take a while, so tell the |
| // client to be patient |
| const char *message = "Please wait...\r\n"; |
| Send(ci->socket, message, strlen(message)); |
| // 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; |
| } |
| |
| /*--------------------- |
| * File transfer server |
| *---------------------*/ |
| |
| int ReceivePacket(SOCKET socket, char *buffer, DWORD max_size) |
| { |
| DWORD packet_size = 0; |
| |
| if (!Receive(socket, (char *)&packet_size, 4)) |
| return -1; |
| if (packet_size > max_size) |
| return -1; |
| if (!Receive(socket, buffer, packet_size)) |
| return -1; |
| |
| return packet_size; |
| } |
| |
| int ReceiveStrPacket(SOCKET socket, char *buffer, DWORD max_size) |
| { |
| memset(buffer, 0, max_size); |
| return ReceivePacket(socket, buffer, max_size - 1); |
| } |
| |
| BOOL SendPacket(SOCKET socket, const char *buffer, DWORD len) |
| { |
| if (!Send(socket, (char *)&len, 4)) |
| return FALSE; |
| return Send(socket, buffer, len); |
| } |
| |
| BOOL SendMsg(SOCKET socket, DWORD msg) |
| { |
| return Send(socket, (char *)&msg, 4); |
| } |
| |
| // Send data from a file |
| BOOL SendFileChunks(client_info *ci, const char *filename) |
| { |
| FILE *fp = fopen(filename, "rb"); |
| if (!fp) return FALSE; |
| |
| while (1) { |
| int bytes_read = fread(ci->chunk_buffer, 1, ci->chunk_size, fp); |
| if (!SendPacket(ci->socket, ci->chunk_buffer, bytes_read)) |
| break; |
| if (bytes_read < ci->chunk_size) { |
| if (ferror(fp)) |
| break; |
| else { |
| fclose(fp); |
| return TRUE; |
| } |
| } |
| } |
| |
| fclose(fp); |
| return FALSE; |
| } |
| |
| // Receive data into a file |
| BOOL ReceiveFileChunks(client_info *ci, const char *filename) |
| { |
| FILE *fp = fopen(filename, "wb"); |
| if (!fp) return FALSE; |
| |
| while (1) { |
| int bytes_received = ReceivePacket(ci->socket, ci->chunk_buffer, |
| ci->chunk_size); |
| if (bytes_received < 0) |
| break; |
| if (bytes_received > 0) |
| if (fwrite(ci->chunk_buffer, bytes_received, 1, fp) < 1) |
| break; |
| if (bytes_received < ci->chunk_size) { |
| fclose(fp); |
| return TRUE; |
| } |
| } |
| |
| fclose(fp); |
| return FALSE; |
| } |
| |
| BOOL ExpandPath(char *path, int max_size) |
| { |
| char temp[512]; |
| int result; |
| |
| PathRemoveBackslash(path); |
| result = ExpandEnvironmentStrings(path, temp, sizeof(temp)); |
| if (result == 0 || result > sizeof(temp)) |
| return FALSE; |
| strncpy(path, temp, max_size - 1); |
| return TRUE; |
| } |
| |
| int TerminateTransfer(client_info *ci, const char *message) |
| { |
| AppendMessage(message); |
| AppendMessage("File transfer server: client disconnected (%s)", |
| ci->addr_str); |
| closesocket(ci->socket); |
| free(ci->chunk_buffer); |
| free(ci); |
| return 0; |
| } |
| |
| int TerminateWithError(client_info *ci, const char *message) |
| { |
| SendMsg(ci->socket, RSS_ERROR); |
| SendPacket(ci->socket, message, strlen(message)); |
| return TerminateTransfer(ci, message); |
| } |
| |
| int ReceiveThread(client_info *ci) |
| { |
| char path[512], filename[512]; |
| DWORD msg; |
| |
| AppendMessage("Client (%s) wants to upload files", ci->addr_str); |
| |
| while (1) { |
| if (!Receive(ci->socket, (char *)&msg, 4)) |
| return TerminateTransfer(ci, "Could not receive further msgs"); |
| |
| switch (msg) { |
| case RSS_SET_PATH: |
| if (ReceiveStrPacket(ci->socket, path, sizeof(path)) < 0) |
| return TerminateWithError(ci, |
| "RSS_SET_PATH: could not receive path, or path too long"); |
| AppendMessage("Client (%s) set path to %s", ci->addr_str, path); |
| if (!ExpandPath(path, sizeof(path))) |
| return TerminateWithError(ci, |
| "RSS_SET_PATH: error expanding environment strings"); |
| break; |
| |
| case RSS_CREATE_FILE: |
| if (ReceiveStrPacket(ci->socket, filename, sizeof(filename)) < 0) |
| return TerminateWithError(ci, |
| "RSS_CREATE_FILE: could not receive filename"); |
| if (PathIsDirectory(path)) |
| PathAppend(path, filename); |
| AppendMessage("Client (%s) is uploading %s", ci->addr_str, path); |
| if (!ReceiveFileChunks(ci, path)) |
| return TerminateWithError(ci, |
| "RSS_CREATE_FILE: error receiving or writing file " |
| "contents"); |
| PathAppend(path, ".."); |
| break; |
| |
| case RSS_CREATE_DIR: |
| if (ReceiveStrPacket(ci->socket, filename, sizeof(filename)) < 0) |
| return TerminateWithError(ci, |
| "RSS_CREATE_DIR: could not receive dirname"); |
| if (PathIsDirectory(path)) |
| PathAppend(path, filename); |
| AppendMessage("Entering dir %s", path); |
| if (PathFileExists(path)) { |
| if (!PathIsDirectory(path)) |
| return TerminateWithError(ci, |
| "RSS_CREATE_DIR: path exists and is not a directory"); |
| } else { |
| if (!CreateDirectory(path, NULL)) |
| return TerminateWithError(ci, |
| "RSS_CREATE_DIR: could not create directory"); |
| } |
| break; |
| |
| case RSS_LEAVE_DIR: |
| PathAppend(path, ".."); |
| AppendMessage("Returning to dir %s", path); |
| break; |
| |
| case RSS_DONE: |
| if (!SendMsg(ci->socket, RSS_OK)) |
| return TerminateTransfer(ci, |
| "RSS_DONE: could not send OK msg"); |
| break; |
| |
| default: |
| return TerminateWithError(ci, "Received unexpected msg"); |
| } |
| } |
| } |
| |
| // Given a path or a pattern with wildcards, send files or directory trees to |
| // the client |
| int SendFiles(client_info *ci, const char *pattern) |
| { |
| char path[512]; |
| WIN32_FIND_DATA ffd; |
| |
| HANDLE hFind = FindFirstFile(pattern, &ffd); |
| if (hFind == INVALID_HANDLE_VALUE) { |
| // If a weird error occurred (like failure to list directory contents |
| // due to insufficient permissions) print a warning and continue. |
| if (GetLastError() != ERROR_FILE_NOT_FOUND) |
| AppendMessage("WARNING: FindFirstFile failed on pattern %s", |
| pattern); |
| return 1; |
| } |
| |
| strncpy(path, pattern, sizeof(path) - 1); |
| PathAppend(path, ".."); |
| |
| do { |
| if (ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) |
| continue; |
| if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
| // Directory |
| if (!strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, "..")) |
| continue; |
| PathAppend(path, ffd.cFileName); |
| AppendMessage("Entering dir %s", path); |
| PathAppend(path, "*"); |
| if (!SendMsg(ci->socket, RSS_CREATE_DIR)) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, |
| "Could not send RSS_CREATE_DIR msg"); |
| } |
| if (!SendPacket(ci->socket, ffd.cFileName, |
| strlen(ffd.cFileName))) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, "Could not send dirname"); |
| } |
| if (!SendFiles(ci, path)) { |
| FindClose(hFind); |
| return 0; |
| } |
| if (!SendMsg(ci->socket, RSS_LEAVE_DIR)) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, |
| "Could not send RSS_LEAVE_DIR msg"); |
| } |
| PathAppend(path, ".."); |
| PathAppend(path, ".."); |
| AppendMessage("Returning to dir %s", path); |
| } else { |
| // File |
| PathAppend(path, ffd.cFileName); |
| AppendMessage("Client (%s) is downloading %s", ci->addr_str, path); |
| // Make sure the file is readable |
| FILE *fp = fopen(path, "rb"); |
| if (fp) fclose(fp); |
| else { |
| AppendMessage("WARNING: could not read file %s", path); |
| PathAppend(path, ".."); |
| continue; |
| } |
| if (!SendMsg(ci->socket, RSS_CREATE_FILE)) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, |
| "Could not send RSS_CREATE_FILE msg"); |
| } |
| if (!SendPacket(ci->socket, ffd.cFileName, |
| strlen(ffd.cFileName))) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, "Could not send filename"); |
| } |
| if (!SendFileChunks(ci, path)) { |
| FindClose(hFind); |
| return TerminateTransfer(ci, "Could not send file contents"); |
| } |
| PathAppend(path, ".."); |
| } |
| } while (FindNextFile(hFind, &ffd)); |
| |
| if (GetLastError() == ERROR_NO_MORE_FILES) { |
| FindClose(hFind); |
| return 1; |
| } else { |
| FindClose(hFind); |
| return TerminateWithError(ci, "FindNextFile failed"); |
| } |
| } |
| |
| int SendThread(client_info *ci) |
| { |
| char pattern[512]; |
| DWORD msg; |
| |
| AppendMessage("Client (%s) wants to download files", ci->addr_str); |
| |
| while (1) { |
| if (!Receive(ci->socket, (char *)&msg, 4)) |
| return TerminateTransfer(ci, "Could not receive further msgs"); |
| |
| switch (msg) { |
| case RSS_SET_PATH: |
| if (ReceiveStrPacket(ci->socket, pattern, sizeof(pattern)) < 0) |
| return TerminateWithError(ci, |
| "RSS_SET_PATH: could not receive path, or path too long"); |
| AppendMessage("Client (%s) asked for %s", ci->addr_str, pattern); |
| if (!ExpandPath(pattern, sizeof(pattern))) |
| return TerminateWithError(ci, |
| "RSS_SET_PATH: error expanding environment strings"); |
| if (!SendFiles(ci, pattern)) |
| return 0; |
| if (!SendMsg(ci->socket, RSS_DONE)) |
| return TerminateTransfer(ci, |
| "RSS_SET_PATH: could not send RSS_DONE msg"); |
| break; |
| |
| default: |
| return TerminateWithError(ci, "Received unexpected msg"); |
| } |
| } |
| } |
| |
| DWORD WINAPI TransferThreadEntry(LPVOID client_info_ptr) |
| { |
| client_info *ci = (client_info *)client_info_ptr; |
| DWORD msg; |
| |
| AppendMessage("File transfer server: new client connected (%s)", |
| ci->addr_str); |
| |
| if (!SendMsg(ci->socket, RSS_MAGIC)) |
| return TerminateTransfer(ci, "Could not send greeting message"); |
| if (!Receive(ci->socket, (char *)&ci->chunk_size, 4)) |
| return TerminateTransfer(ci, "Error receiving chunk size"); |
| AppendMessage("Client (%s) set chunk size to %d", ci->addr_str, |
| ci->chunk_size); |
| if (ci->chunk_size > 1048576 || ci->chunk_size < 512) |
| return TerminateWithError(ci, "Client set invalid chunk size"); |
| if (!(ci->chunk_buffer = (char *)malloc(ci->chunk_size))) |
| return TerminateWithError(ci, "Memory allocation error"); |
| if (!Receive(ci->socket, (char *)&msg, 4)) |
| return TerminateTransfer(ci, "Error receiving msg"); |
| |
| if (msg == RSS_UPLOAD) |
| return ReceiveThread(ci); |
| else if (msg == RSS_DOWNLOAD) |
| return SendThread(ci); |
| return TerminateWithError(ci, "Received unexpected msg"); |
| } |
| |
| DWORD WINAPI FileTransferListenThread(LPVOID param) |
| { |
| SOCKET ListenSocket = PrepareListenSocket(file_transfer_port); |
| |
| // Inform the user |
| AppendMessage("File transfer server: waiting for clients to connect..."); |
| |
| while (1) { |
| client_info *ci = Accept(ListenSocket); |
| if (!ci) break; |
| if (!CreateThread(NULL, 0, TransferThreadEntry, (LPVOID)ci, 0, NULL)) |
| ExitOnError("Could not create file transfer thread"); |
| } |
| |
| return 0; |
| } |
| |
| /*-------------------- |
| * WndProc and WinMain |
| *--------------------*/ |
| |
| LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| RECT rect; |
| WSADATA wsaData; |
| SYSTEMTIME lt; |
| char log_filename[256]; |
| |
| 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 font |
| SendMessage(hTextBox, WM_SETFONT, |
| (WPARAM)GetStockObject(DEFAULT_GUI_FONT), |
| MAKELPARAM(FALSE, 0)); |
| // Set size limit |
| SendMessage(hTextBox, EM_LIMITTEXT, TEXTBOX_LIMIT, 0); |
| // Initialize critical section object for text buffer access |
| InitializeCriticalSection(&critical_section); |
| // Open log file |
| GetLocalTime(<); |
| sprintf(log_filename, "rss_%02d-%02d-%02d_%02d-%02d-%02d.log", |
| lt.wYear, lt.wMonth, lt.wDay, |
| lt.wHour, lt.wMinute, lt.wSecond); |
| log_file = fopen(log_filename, "wb"); |
| // Create text box update thread |
| if (!CreateThread(NULL, 0, UpdateTextBox, NULL, 0, NULL)) |
| ExitOnError("Could not create text box update thread"); |
| // Initialize Winsock |
| if (WSAStartup(MAKEWORD(2, 2), &wsaData)) |
| ExitOnError("Winsock initialization failed"); |
| // Start the listening threads |
| if (!CreateThread(NULL, 0, ShellListenThread, NULL, 0, NULL)) |
| ExitOnError("Could not create shell server listen thread"); |
| if (!CreateThread(NULL, 0, FileTransferListenThread, NULL, 0, NULL)) |
| ExitOnError("Could not create file transfer server listen thread"); |
| break; |
| |
| case WM_SIZE: |
| MoveWindow(hTextBox, 20, 20, |
| LOWORD(lParam) - 40, HIWORD(lParam) - 40, TRUE); |
| break; |
| |
| case WM_DESTROY: |
| if (WSACleanup()) |
| ExitOnError("WSACleanup failed"); |
| 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; |
| char title[256]; |
| |
| if (strlen(lpCmdLine)) |
| sscanf(lpCmdLine, "%d %d", &shell_port, &file_transfer_port); |
| |
| sprintf(title, "Remote Shell Server (listening on ports %d, %d)", |
| shell_port, file_transfer_port); |
| |
| // 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", title, |
| WS_OVERLAPPEDWINDOW, |
| 20, 20, 600, 400, |
| 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); |
| } |