blob: ee09671ddb96391149774a96f098eb345cc1319b [file] [log] [blame]
// 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(&lt);
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);
}