1011 lines
33 KiB
C++
1011 lines
33 KiB
C++
// 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);
|
|
}
|