1 // Simple remote shell server (and file transfer server)
2 // Author: Michael Goldish <mgoldish@redhat.com>
3 // Much of the code here was adapted from Microsoft code samples.
4
5 // Usage: rss.exe [shell port] [file transfer port]
6 // If no shell port is specified the default is 10022.
7 // If no file transfer port is specified the default is 10023.
8
9 // Definitions:
10 // A 'msg' is a 32 bit integer.
11 // A 'packet' is a 32 bit unsigned integer followed by a string of bytes.
12 // The 32 bit integer indicates the length of the string.
13
14 // Protocol for file transfers:
15 //
16 // When uploading files/directories to the server:
17 // 1. The client connects.
18 // 2. The server sends RSS_MAGIC.
19 // 3. The client sends the chunk size for file transfers (a 32 bit integer
20 // between 512 and 1048576 indicating the size in bytes).
21 // 4. The client sends RSS_SET_PATH, followed by a packet (as defined above)
22 // containing the path (in the server's filesystem) where files and/or
23 // directories are to be stored.
24 // Uploading a file (optional, can be repeated many times):
25 // 5. The client sends RSS_CREATE_FILE, followed by a packet containing the
26 // filename (filename only, without a path), followed by a series of
27 // packets (called chunks) containing the file's contents. The size of
28 // each chunk is the size set by the client in step 3, except for the
29 // last chunk, which must be smaller.
30 // Uploading a directory (optional, can be repeated many times):
31 // 6. The client sends RSS_CREATE_DIR, followed by a packet containing the
32 // name of the directory to be created (directory name only, without a
33 // path).
34 // 7. The client uploads files and directories to the new directory (using
35 // steps 5, 6, 8).
36 // 8. The client sends RSS_LEAVE_DIR.
37 // 9. The client sends RSS_DONE and waits for a response.
38 // 10. The server sends RSS_OK to indicate that it's still listening.
39 // 11. Steps 4-10 are repeated as many times as necessary.
40 // 12. The client disconnects.
41 // If a critical error occurs at any time, the server may send RSS_ERROR
42 // followed by a packet containing an error message, and the connection is
43 // closed.
44 //
45 // When downloading files from the server:
46 // 1. The client connects.
47 // 2. The server sends RSS_MAGIC.
48 // 3. The client sends the chunk size for file transfers (a 32 bit integer
49 // between 512 and 1048576 indicating the size in bytes).
50 // 4. The client sends RSS_SET_PATH, followed by a packet (as defined above)
51 // containing a path (in the server's filesystem) or a wildcard pattern
52 // indicating the files/directories the client wants to download.
53 // The server then searches the given path. For every file found:
54 // 5. The server sends RSS_CREATE_FILE, followed by a packet containing the
55 // filename (filename only, without a path), followed by a series of
56 // packets (called chunks) containing the file's contents. The size of
57 // each chunk is the size set by the client in step 3, except for the
58 // last chunk, which must be smaller.
59 // For every directory found:
60 // 6. The server sends RSS_CREATE_DIR, followed by a packet containing the
61 // name of the directory to be created (directory name only, without a
62 // path).
63 // 7. The server sends files and directories located inside the directory
64 // (using steps 5, 6, 8).
65 // 8. The server sends RSS_LEAVE_DIR.
66 // 9. The server sends RSS_DONE.
67 // 10. Steps 4-9 are repeated as many times as necessary.
68 // 11. The client disconnects.
69 // If a critical error occurs, the server may send RSS_ERROR followed by a
70 // packet containing an error message, and the connection is closed.
71 // RSS_ERROR may be sent only when the client expects a msg.
72
73 #define _WIN32_WINNT 0x0500
74
75 #include <winsock2.h>
76 #include <windows.h>
77 #include <stdio.h>
78 #include <stdarg.h>
79 #include <shlwapi.h>
80
81 #pragma comment(lib, "ws2_32.lib")
82 #pragma comment(lib, "shlwapi.lib")
83
84 #define TEXTBOX_LIMIT 262144
85
86 // Constants for file transfer server
87 #define RSS_MAGIC 0x525353
88 #define RSS_OK 1
89 #define RSS_ERROR 2
90 #define RSS_UPLOAD 3
91 #define RSS_DOWNLOAD 4
92 #define RSS_SET_PATH 5
93 #define RSS_CREATE_FILE 6
94 #define RSS_CREATE_DIR 7
95 #define RSS_LEAVE_DIR 8
96 #define RSS_DONE 9
97
98 // Globals
99 int shell_port = 10022;
100 int file_transfer_port = 10023;
101
102 HWND hMainWindow = NULL;
103 HWND hTextBox = NULL;
104
105 char text_buffer[8192] = {0};
106 int text_size = 0;
107
108 CRITICAL_SECTION critical_section;
109
110 FILE *log_file;
111
112 struct client_info {
113 SOCKET socket;
114 char addr_str[256];
115 int pid;
116 HWND hwnd;
117 HANDLE hJob;
118 HANDLE hChildOutputRead;
119 HANDLE hThreadChildToSocket;
120 char *chunk_buffer;
121 int chunk_size;
122 };
123
124 /*-----------------
125 * Shared functions
126 *-----------------*/
127
ExitOnError(const char * message,BOOL winsock=FALSE)128 void ExitOnError(const char *message, BOOL winsock = FALSE)
129 {
130 LPVOID system_message;
131 char buffer[512];
132 int error_code;
133
134 if (winsock)
135 error_code = WSAGetLastError();
136 else
137 error_code = GetLastError();
138 WSACleanup();
139
140 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
141 FORMAT_MESSAGE_FROM_SYSTEM,
142 NULL,
143 error_code,
144 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
145 (LPTSTR)&system_message,
146 0,
147 NULL);
148 sprintf(buffer,
149 "%s!\n"
150 "Error code = %d\n"
151 "Error message = %s",
152 message, error_code, (char *)system_message);
153 MessageBox(hMainWindow, buffer, "Error", MB_OK | MB_ICONERROR);
154
155 LocalFree(system_message);
156 ExitProcess(1);
157 }
158
FlushTextBuffer()159 void FlushTextBuffer()
160 {
161 if (!text_size) return;
162 // Clear the text box if it contains too much text
163 int len = GetWindowTextLength(hTextBox);
164 while (len > TEXTBOX_LIMIT - sizeof(text_buffer)) {
165 SendMessage(hTextBox, EM_SETSEL, 0, TEXTBOX_LIMIT * 1/4);
166 SendMessage(hTextBox, EM_REPLACESEL, FALSE, (LPARAM)"...");
167 len = GetWindowTextLength(hTextBox);
168 }
169 // Append the contents of text_buffer to the text box
170 SendMessage(hTextBox, EM_SETSEL, len, len);
171 SendMessage(hTextBox, EM_REPLACESEL, FALSE, (LPARAM)text_buffer);
172 // Clear text_buffer
173 text_buffer[0] = 0;
174 text_size = 0;
175 // Make sure the log file's buffer is flushed as well
176 if (log_file)
177 fflush(log_file);
178 }
179
AppendMessage(const char * message,...)180 void AppendMessage(const char *message, ...)
181 {
182 va_list args;
183 char str[512] = {0};
184
185 va_start(args, message);
186 vsnprintf(str, sizeof(str) - 3, message, args);
187 va_end(args);
188 strcat(str, "\r\n");
189 int len = strlen(str);
190
191 EnterCriticalSection(&critical_section);
192 // Write message to the log file
193 if (log_file)
194 fwrite(str, len, 1, log_file);
195 // Flush the text buffer if necessary
196 if (text_size + len + 1 > sizeof(text_buffer))
197 FlushTextBuffer();
198 // Append message to the text buffer
199 strcpy(text_buffer + text_size, str);
200 text_size += len;
201 LeaveCriticalSection(&critical_section);
202 }
203
204 // Flush the text buffer every 250 ms
UpdateTextBox(LPVOID client_info_ptr)205 DWORD WINAPI UpdateTextBox(LPVOID client_info_ptr)
206 {
207 while (1) {
208 Sleep(250);
209 EnterCriticalSection(&critical_section);
210 FlushTextBuffer();
211 LeaveCriticalSection(&critical_section);
212 }
213 return 0;
214 }
215
FormatStringForPrinting(char * dst,const char * src,int size)216 void FormatStringForPrinting(char *dst, const char *src, int size)
217 {
218 int j = 0;
219
220 for (int i = 0; i < size && src[i]; i++) {
221 if (src[i] == '\n') {
222 dst[j++] = '\\';
223 dst[j++] = 'n';
224 } else if (src[i] == '\r') {
225 dst[j++] = '\\';
226 dst[j++] = 'r';
227 } else if (src[i] == '\t') {
228 dst[j++] = '\\';
229 dst[j++] = 't';
230 } else if (src[i] == '\\') {
231 dst[j++] = '\\';
232 dst[j++] = '\\';
233 } else dst[j++] = src[i];
234 }
235 dst[j] = 0;
236 }
237
PrepareListenSocket(int port)238 SOCKET PrepareListenSocket(int port)
239 {
240 sockaddr_in addr;
241 linger l;
242 int result;
243
244 // Create socket
245 SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
246 if (ListenSocket == INVALID_SOCKET)
247 ExitOnError("Socket creation failed", TRUE);
248
249 // Enable lingering
250 l.l_linger = 10;
251 l.l_onoff = 1;
252 setsockopt(ListenSocket, SOL_SOCKET, SO_LINGER, (char *)&l, sizeof(l));
253
254 // Bind the socket
255 addr.sin_family = AF_INET;
256 addr.sin_addr.s_addr = htonl(INADDR_ANY);
257 addr.sin_port = htons(port);
258
259 result = bind(ListenSocket, (sockaddr *)&addr, sizeof(addr));
260 if (result == SOCKET_ERROR)
261 ExitOnError("bind failed", TRUE);
262
263 // Start listening for incoming connections
264 result = listen(ListenSocket, SOMAXCONN);
265 if (result == SOCKET_ERROR)
266 ExitOnError("listen failed", TRUE);
267
268 return ListenSocket;
269 }
270
Accept(SOCKET ListenSocket)271 client_info* Accept(SOCKET ListenSocket)
272 {
273 sockaddr_in addr;
274 int addrlen = sizeof(addr);
275
276 // Accept the connection
277 SOCKET socket = accept(ListenSocket, (sockaddr *)&addr, &addrlen);
278 if (socket == INVALID_SOCKET) {
279 if (WSAGetLastError() == WSAEINTR)
280 return NULL;
281 else
282 ExitOnError("accept failed", TRUE);
283 }
284
285 // Allocate a new client_info struct
286 client_info *ci = (client_info *)calloc(1, sizeof(client_info));
287 if (!ci)
288 ExitOnError("Could not allocate client_info struct");
289 // Populate the new struct
290 ci->socket = socket;
291 const char *address = inet_ntoa(addr.sin_addr);
292 if (!address) address = "unknown";
293 sprintf(ci->addr_str, "%s:%d", address, addr.sin_port);
294
295 return ci;
296 }
297
298 // Read a given number of bytes into a buffer
Receive(SOCKET socket,char * buffer,int len)299 BOOL Receive(SOCKET socket, char *buffer, int len)
300 {
301 while (len > 0) {
302 int bytes_received = recv(socket, buffer, len, 0);
303 if (bytes_received <= 0)
304 return FALSE;
305 buffer += bytes_received;
306 len -= bytes_received;
307 }
308 return TRUE;
309 }
310
311 // Send a given number of bytes from a buffer
Send(SOCKET socket,const char * buffer,int len)312 BOOL Send(SOCKET socket, const char *buffer, int len)
313 {
314 while (len > 0) {
315 int bytes_sent = send(socket, buffer, len, 0);
316 if (bytes_sent <= 0)
317 return FALSE;
318 buffer += bytes_sent;
319 len -= bytes_sent;
320 }
321 return TRUE;
322 }
323
324 /*-------------
325 * Shell server
326 *-------------*/
327
ChildToSocket(LPVOID client_info_ptr)328 DWORD WINAPI ChildToSocket(LPVOID client_info_ptr)
329 {
330 client_info *ci = (client_info *)client_info_ptr;
331 char buffer[1024];
332 DWORD bytes_read;
333
334 while (1) {
335 // Read data from the child's STDOUT/STDERR pipes
336 if (!ReadFile(ci->hChildOutputRead,
337 buffer, sizeof(buffer),
338 &bytes_read, NULL) || !bytes_read) {
339 if (GetLastError() == ERROR_BROKEN_PIPE)
340 break; // Pipe done -- normal exit path
341 else
342 ExitOnError("ReadFile failed"); // Something bad happened
343 }
344 // Send data to the client
345 Send(ci->socket, buffer, bytes_read);
346 }
347
348 AppendMessage("Child exited");
349 closesocket(ci->socket);
350 return 0;
351 }
352
SocketToChild(LPVOID client_info_ptr)353 DWORD WINAPI SocketToChild(LPVOID client_info_ptr)
354 {
355 client_info *ci = (client_info *)client_info_ptr;
356 char buffer[256], formatted_buffer[768];
357 int bytes_received;
358
359 AppendMessage("Shell server: new client connected (%s)", ci->addr_str);
360
361 while (1) {
362 // Receive data from the socket
363 ZeroMemory(buffer, sizeof(buffer));
364 bytes_received = recv(ci->socket, buffer, sizeof(buffer), 0);
365 if (bytes_received <= 0)
366 break;
367 // Report the data received
368 FormatStringForPrinting(formatted_buffer, buffer, sizeof(buffer));
369 AppendMessage("Client (%s) entered text: \"%s\"",
370 ci->addr_str, formatted_buffer);
371 // Send the data as a series of WM_CHAR messages to the console window
372 for (int i = 0; i < bytes_received; i++) {
373 SendMessage(ci->hwnd, WM_CHAR, buffer[i], 0);
374 SendMessage(ci->hwnd, WM_SETFOCUS, 0, 0);
375 }
376 }
377
378 AppendMessage("Shell server: client disconnected (%s)", ci->addr_str);
379
380 // Attempt to terminate the child's process tree:
381 // Using taskkill (where available)
382 sprintf(buffer, "taskkill /PID %d /T /F", ci->pid);
383 system(buffer);
384 // .. and using TerminateJobObject()
385 TerminateJobObject(ci->hJob, 0);
386 // Wait for the ChildToSocket thread to terminate
387 WaitForSingleObject(ci->hThreadChildToSocket, 10000);
388 // In case the thread refuses to exit, terminate it
389 TerminateThread(ci->hThreadChildToSocket, 0);
390 // Close the socket
391 closesocket(ci->socket);
392
393 // Free resources
394 CloseHandle(ci->hJob);
395 CloseHandle(ci->hThreadChildToSocket);
396 CloseHandle(ci->hChildOutputRead);
397 free(ci);
398
399 AppendMessage("SocketToChild thread exited");
400 return 0;
401 }
402
PrepAndLaunchRedirectedChild(client_info * ci,HANDLE hChildStdOut,HANDLE hChildStdErr)403 void PrepAndLaunchRedirectedChild(client_info *ci,
404 HANDLE hChildStdOut,
405 HANDLE hChildStdErr)
406 {
407 PROCESS_INFORMATION pi;
408 STARTUPINFO si;
409
410 // Allocate a new console for the child
411 HWND hwnd = GetForegroundWindow();
412 FreeConsole();
413 AllocConsole();
414 ShowWindow(GetConsoleWindow(), SW_HIDE);
415 if (hwnd)
416 SetForegroundWindow(hwnd);
417
418 // Set up the start up info struct.
419 ZeroMemory(&si, sizeof(STARTUPINFO));
420 si.cb = sizeof(STARTUPINFO);
421 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
422 si.hStdOutput = hChildStdOut;
423 si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
424 si.hStdError = hChildStdErr;
425 // Use this if you want to hide the child:
426 si.wShowWindow = SW_HIDE;
427 // Note that dwFlags must include STARTF_USESHOWWINDOW if you want to
428 // use the wShowWindow flags.
429
430 // Launch the process that you want to redirect.
431 if (!CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE,
432 0, NULL, "C:\\", &si, &pi))
433 ExitOnError("CreateProcess failed");
434
435 // Close any unnecessary handles.
436 if (!CloseHandle(pi.hThread))
437 ExitOnError("CloseHandle failed");
438
439 // Keep the process ID
440 ci->pid = pi.dwProcessId;
441 // Assign the process to a newly created JobObject
442 ci->hJob = CreateJobObject(NULL, NULL);
443 AssignProcessToJobObject(ci->hJob, pi.hProcess);
444 // Keep the console window's handle
445 ci->hwnd = GetConsoleWindow();
446
447 // Detach from the child's console
448 FreeConsole();
449 }
450
SpawnSession(client_info * ci)451 void SpawnSession(client_info *ci)
452 {
453 HANDLE hOutputReadTmp, hOutputRead, hOutputWrite;
454 HANDLE hErrorWrite;
455 SECURITY_ATTRIBUTES sa;
456
457 // Set up the security attributes struct.
458 sa.nLength = sizeof(SECURITY_ATTRIBUTES);
459 sa.lpSecurityDescriptor = NULL;
460 sa.bInheritHandle = TRUE;
461
462 // Create the child output pipe.
463 if (!CreatePipe(&hOutputReadTmp, &hOutputWrite, &sa, 0))
464 ExitOnError("CreatePipe failed");
465
466 // Create a duplicate of the output write handle for the std error
467 // write handle. This is necessary in case the child application
468 // closes one of its std output handles.
469 if (!DuplicateHandle(GetCurrentProcess(), hOutputWrite,
470 GetCurrentProcess(), &hErrorWrite, 0,
471 TRUE, DUPLICATE_SAME_ACCESS))
472 ExitOnError("DuplicateHandle failed");
473
474 // Create new output read handle and the input write handles. Set
475 // the Properties to FALSE. Otherwise, the child inherits the
476 // properties and, as a result, non-closeable handles to the pipes
477 // are created.
478 if (!DuplicateHandle(GetCurrentProcess(), hOutputReadTmp,
479 GetCurrentProcess(),
480 &hOutputRead, // Address of new handle.
481 0, FALSE, // Make it uninheritable.
482 DUPLICATE_SAME_ACCESS))
483 ExitOnError("DuplicateHandle failed");
484
485 // Close inheritable copies of the handles you do not want to be
486 // inherited.
487 if (!CloseHandle(hOutputReadTmp))
488 ExitOnError("CloseHandle failed");
489
490 PrepAndLaunchRedirectedChild(ci, hOutputWrite, hErrorWrite);
491
492 ci->hChildOutputRead = hOutputRead;
493
494 // Close pipe handles (do not continue to modify the parent).
495 // You need to make sure that no handles to the write end of the
496 // output pipe are maintained in this process or else the pipe will
497 // not close when the child process exits and the ReadFile will hang.
498 if (!CloseHandle(hOutputWrite)) ExitOnError("CloseHandle failed");
499 if (!CloseHandle(hErrorWrite)) ExitOnError("CloseHandle failed");
500 }
501
ShellListenThread(LPVOID param)502 DWORD WINAPI ShellListenThread(LPVOID param)
503 {
504 HANDLE hThread;
505
506 SOCKET ListenSocket = PrepareListenSocket(shell_port);
507
508 // Inform the user
509 AppendMessage("Shell server: waiting for clients to connect...");
510
511 while (1) {
512 client_info *ci = Accept(ListenSocket);
513 if (!ci) break;
514 // Under heavy load, spawning cmd.exe might take a while, so tell the
515 // client to be patient
516 const char *message = "Please wait...\r\n";
517 Send(ci->socket, message, strlen(message));
518 // Spawn a new redirected cmd.exe process
519 SpawnSession(ci);
520 // Start transferring data from the child process to the client
521 hThread = CreateThread(NULL, 0, ChildToSocket, (LPVOID)ci, 0, NULL);
522 if (!hThread)
523 ExitOnError("Could not create ChildToSocket thread");
524 ci->hThreadChildToSocket = hThread;
525 // ... and from the client to the child process
526 hThread = CreateThread(NULL, 0, SocketToChild, (LPVOID)ci, 0, NULL);
527 if (!hThread)
528 ExitOnError("Could not create SocketToChild thread");
529 }
530
531 return 0;
532 }
533
534 /*---------------------
535 * File transfer server
536 *---------------------*/
537
ReceivePacket(SOCKET socket,char * buffer,DWORD max_size)538 int ReceivePacket(SOCKET socket, char *buffer, DWORD max_size)
539 {
540 DWORD packet_size = 0;
541
542 if (!Receive(socket, (char *)&packet_size, 4))
543 return -1;
544 if (packet_size > max_size)
545 return -1;
546 if (!Receive(socket, buffer, packet_size))
547 return -1;
548
549 return packet_size;
550 }
551
ReceiveStrPacket(SOCKET socket,char * buffer,DWORD max_size)552 int ReceiveStrPacket(SOCKET socket, char *buffer, DWORD max_size)
553 {
554 memset(buffer, 0, max_size);
555 return ReceivePacket(socket, buffer, max_size - 1);
556 }
557
SendPacket(SOCKET socket,const char * buffer,DWORD len)558 BOOL SendPacket(SOCKET socket, const char *buffer, DWORD len)
559 {
560 if (!Send(socket, (char *)&len, 4))
561 return FALSE;
562 return Send(socket, buffer, len);
563 }
564
SendMsg(SOCKET socket,DWORD msg)565 BOOL SendMsg(SOCKET socket, DWORD msg)
566 {
567 return Send(socket, (char *)&msg, 4);
568 }
569
570 // Send data from a file
SendFileChunks(client_info * ci,const char * filename)571 BOOL SendFileChunks(client_info *ci, const char *filename)
572 {
573 FILE *fp = fopen(filename, "rb");
574 if (!fp) return FALSE;
575
576 while (1) {
577 int bytes_read = fread(ci->chunk_buffer, 1, ci->chunk_size, fp);
578 if (!SendPacket(ci->socket, ci->chunk_buffer, bytes_read))
579 break;
580 if (bytes_read < ci->chunk_size) {
581 if (ferror(fp))
582 break;
583 else {
584 fclose(fp);
585 return TRUE;
586 }
587 }
588 }
589
590 fclose(fp);
591 return FALSE;
592 }
593
594 // Receive data into a file
ReceiveFileChunks(client_info * ci,const char * filename)595 BOOL ReceiveFileChunks(client_info *ci, const char *filename)
596 {
597 FILE *fp = fopen(filename, "wb");
598 if (!fp) return FALSE;
599
600 while (1) {
601 int bytes_received = ReceivePacket(ci->socket, ci->chunk_buffer,
602 ci->chunk_size);
603 if (bytes_received < 0)
604 break;
605 if (bytes_received > 0)
606 if (fwrite(ci->chunk_buffer, bytes_received, 1, fp) < 1)
607 break;
608 if (bytes_received < ci->chunk_size) {
609 fclose(fp);
610 return TRUE;
611 }
612 }
613
614 fclose(fp);
615 return FALSE;
616 }
617
ExpandPath(char * path,int max_size)618 BOOL ExpandPath(char *path, int max_size)
619 {
620 char temp[512];
621 int result;
622
623 PathRemoveBackslash(path);
624 result = ExpandEnvironmentStrings(path, temp, sizeof(temp));
625 if (result == 0 || result > sizeof(temp))
626 return FALSE;
627 strncpy(path, temp, max_size - 1);
628 return TRUE;
629 }
630
TerminateTransfer(client_info * ci,const char * message)631 int TerminateTransfer(client_info *ci, const char *message)
632 {
633 AppendMessage(message);
634 AppendMessage("File transfer server: client disconnected (%s)",
635 ci->addr_str);
636 closesocket(ci->socket);
637 free(ci->chunk_buffer);
638 free(ci);
639 return 0;
640 }
641
TerminateWithError(client_info * ci,const char * message)642 int TerminateWithError(client_info *ci, const char *message)
643 {
644 SendMsg(ci->socket, RSS_ERROR);
645 SendPacket(ci->socket, message, strlen(message));
646 return TerminateTransfer(ci, message);
647 }
648
ReceiveThread(client_info * ci)649 int ReceiveThread(client_info *ci)
650 {
651 char path[512], filename[512];
652 DWORD msg;
653
654 AppendMessage("Client (%s) wants to upload files", ci->addr_str);
655
656 while (1) {
657 if (!Receive(ci->socket, (char *)&msg, 4))
658 return TerminateTransfer(ci, "Could not receive further msgs");
659
660 switch (msg) {
661 case RSS_SET_PATH:
662 if (ReceiveStrPacket(ci->socket, path, sizeof(path)) < 0)
663 return TerminateWithError(ci,
664 "RSS_SET_PATH: could not receive path, or path too long");
665 AppendMessage("Client (%s) set path to %s", ci->addr_str, path);
666 if (!ExpandPath(path, sizeof(path)))
667 return TerminateWithError(ci,
668 "RSS_SET_PATH: error expanding environment strings");
669 break;
670
671 case RSS_CREATE_FILE:
672 if (ReceiveStrPacket(ci->socket, filename, sizeof(filename)) < 0)
673 return TerminateWithError(ci,
674 "RSS_CREATE_FILE: could not receive filename");
675 if (PathIsDirectory(path))
676 PathAppend(path, filename);
677 AppendMessage("Client (%s) is uploading %s", ci->addr_str, path);
678 if (!ReceiveFileChunks(ci, path))
679 return TerminateWithError(ci,
680 "RSS_CREATE_FILE: error receiving or writing file "
681 "contents");
682 PathAppend(path, "..");
683 break;
684
685 case RSS_CREATE_DIR:
686 if (ReceiveStrPacket(ci->socket, filename, sizeof(filename)) < 0)
687 return TerminateWithError(ci,
688 "RSS_CREATE_DIR: could not receive dirname");
689 if (PathIsDirectory(path))
690 PathAppend(path, filename);
691 AppendMessage("Entering dir %s", path);
692 if (PathFileExists(path)) {
693 if (!PathIsDirectory(path))
694 return TerminateWithError(ci,
695 "RSS_CREATE_DIR: path exists and is not a directory");
696 } else {
697 if (!CreateDirectory(path, NULL))
698 return TerminateWithError(ci,
699 "RSS_CREATE_DIR: could not create directory");
700 }
701 break;
702
703 case RSS_LEAVE_DIR:
704 PathAppend(path, "..");
705 AppendMessage("Returning to dir %s", path);
706 break;
707
708 case RSS_DONE:
709 if (!SendMsg(ci->socket, RSS_OK))
710 return TerminateTransfer(ci,
711 "RSS_DONE: could not send OK msg");
712 break;
713
714 default:
715 return TerminateWithError(ci, "Received unexpected msg");
716 }
717 }
718 }
719
720 // Given a path or a pattern with wildcards, send files or directory trees to
721 // the client
SendFiles(client_info * ci,const char * pattern)722 int SendFiles(client_info *ci, const char *pattern)
723 {
724 char path[512];
725 WIN32_FIND_DATA ffd;
726
727 HANDLE hFind = FindFirstFile(pattern, &ffd);
728 if (hFind == INVALID_HANDLE_VALUE) {
729 // If a weird error occurred (like failure to list directory contents
730 // due to insufficient permissions) print a warning and continue.
731 if (GetLastError() != ERROR_FILE_NOT_FOUND)
732 AppendMessage("WARNING: FindFirstFile failed on pattern %s",
733 pattern);
734 return 1;
735 }
736
737 strncpy(path, pattern, sizeof(path) - 1);
738 PathAppend(path, "..");
739
740 do {
741 if (ffd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
742 continue;
743 if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
744 // Directory
745 if (!strcmp(ffd.cFileName, ".") || !strcmp(ffd.cFileName, ".."))
746 continue;
747 PathAppend(path, ffd.cFileName);
748 AppendMessage("Entering dir %s", path);
749 PathAppend(path, "*");
750 if (!SendMsg(ci->socket, RSS_CREATE_DIR)) {
751 FindClose(hFind);
752 return TerminateTransfer(ci,
753 "Could not send RSS_CREATE_DIR msg");
754 }
755 if (!SendPacket(ci->socket, ffd.cFileName,
756 strlen(ffd.cFileName))) {
757 FindClose(hFind);
758 return TerminateTransfer(ci, "Could not send dirname");
759 }
760 if (!SendFiles(ci, path)) {
761 FindClose(hFind);
762 return 0;
763 }
764 if (!SendMsg(ci->socket, RSS_LEAVE_DIR)) {
765 FindClose(hFind);
766 return TerminateTransfer(ci,
767 "Could not send RSS_LEAVE_DIR msg");
768 }
769 PathAppend(path, "..");
770 PathAppend(path, "..");
771 AppendMessage("Returning to dir %s", path);
772 } else {
773 // File
774 PathAppend(path, ffd.cFileName);
775 AppendMessage("Client (%s) is downloading %s", ci->addr_str, path);
776 // Make sure the file is readable
777 FILE *fp = fopen(path, "rb");
778 if (fp) fclose(fp);
779 else {
780 AppendMessage("WARNING: could not read file %s", path);
781 PathAppend(path, "..");
782 continue;
783 }
784 if (!SendMsg(ci->socket, RSS_CREATE_FILE)) {
785 FindClose(hFind);
786 return TerminateTransfer(ci,
787 "Could not send RSS_CREATE_FILE msg");
788 }
789 if (!SendPacket(ci->socket, ffd.cFileName,
790 strlen(ffd.cFileName))) {
791 FindClose(hFind);
792 return TerminateTransfer(ci, "Could not send filename");
793 }
794 if (!SendFileChunks(ci, path)) {
795 FindClose(hFind);
796 return TerminateTransfer(ci, "Could not send file contents");
797 }
798 PathAppend(path, "..");
799 }
800 } while (FindNextFile(hFind, &ffd));
801
802 if (GetLastError() == ERROR_NO_MORE_FILES) {
803 FindClose(hFind);
804 return 1;
805 } else {
806 FindClose(hFind);
807 return TerminateWithError(ci, "FindNextFile failed");
808 }
809 }
810
SendThread(client_info * ci)811 int SendThread(client_info *ci)
812 {
813 char pattern[512];
814 DWORD msg;
815
816 AppendMessage("Client (%s) wants to download files", ci->addr_str);
817
818 while (1) {
819 if (!Receive(ci->socket, (char *)&msg, 4))
820 return TerminateTransfer(ci, "Could not receive further msgs");
821
822 switch (msg) {
823 case RSS_SET_PATH:
824 if (ReceiveStrPacket(ci->socket, pattern, sizeof(pattern)) < 0)
825 return TerminateWithError(ci,
826 "RSS_SET_PATH: could not receive path, or path too long");
827 AppendMessage("Client (%s) asked for %s", ci->addr_str, pattern);
828 if (!ExpandPath(pattern, sizeof(pattern)))
829 return TerminateWithError(ci,
830 "RSS_SET_PATH: error expanding environment strings");
831 if (!SendFiles(ci, pattern))
832 return 0;
833 if (!SendMsg(ci->socket, RSS_DONE))
834 return TerminateTransfer(ci,
835 "RSS_SET_PATH: could not send RSS_DONE msg");
836 break;
837
838 default:
839 return TerminateWithError(ci, "Received unexpected msg");
840 }
841 }
842 }
843
TransferThreadEntry(LPVOID client_info_ptr)844 DWORD WINAPI TransferThreadEntry(LPVOID client_info_ptr)
845 {
846 client_info *ci = (client_info *)client_info_ptr;
847 DWORD msg;
848
849 AppendMessage("File transfer server: new client connected (%s)",
850 ci->addr_str);
851
852 if (!SendMsg(ci->socket, RSS_MAGIC))
853 return TerminateTransfer(ci, "Could not send greeting message");
854 if (!Receive(ci->socket, (char *)&ci->chunk_size, 4))
855 return TerminateTransfer(ci, "Error receiving chunk size");
856 AppendMessage("Client (%s) set chunk size to %d", ci->addr_str,
857 ci->chunk_size);
858 if (ci->chunk_size > 1048576 || ci->chunk_size < 512)
859 return TerminateWithError(ci, "Client set invalid chunk size");
860 if (!(ci->chunk_buffer = (char *)malloc(ci->chunk_size)))
861 return TerminateWithError(ci, "Memory allocation error");
862 if (!Receive(ci->socket, (char *)&msg, 4))
863 return TerminateTransfer(ci, "Error receiving msg");
864
865 if (msg == RSS_UPLOAD)
866 return ReceiveThread(ci);
867 else if (msg == RSS_DOWNLOAD)
868 return SendThread(ci);
869 return TerminateWithError(ci, "Received unexpected msg");
870 }
871
FileTransferListenThread(LPVOID param)872 DWORD WINAPI FileTransferListenThread(LPVOID param)
873 {
874 SOCKET ListenSocket = PrepareListenSocket(file_transfer_port);
875
876 // Inform the user
877 AppendMessage("File transfer server: waiting for clients to connect...");
878
879 while (1) {
880 client_info *ci = Accept(ListenSocket);
881 if (!ci) break;
882 if (!CreateThread(NULL, 0, TransferThreadEntry, (LPVOID)ci, 0, NULL))
883 ExitOnError("Could not create file transfer thread");
884 }
885
886 return 0;
887 }
888
889 /*--------------------
890 * WndProc and WinMain
891 *--------------------*/
892
WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)893 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
894 {
895 RECT rect;
896 WSADATA wsaData;
897 SYSTEMTIME lt;
898 char log_filename[256];
899
900 switch (msg) {
901 case WM_CREATE:
902 // Create text box
903 GetClientRect(hwnd, &rect);
904 hTextBox = CreateWindowEx(WS_EX_CLIENTEDGE,
905 "EDIT", "",
906 WS_CHILD | WS_VISIBLE | WS_VSCROLL |
907 ES_MULTILINE | ES_AUTOVSCROLL,
908 20, 20,
909 rect.right - 40,
910 rect.bottom - 40,
911 hwnd,
912 NULL,
913 GetModuleHandle(NULL),
914 NULL);
915 if (!hTextBox)
916 ExitOnError("Could not create text box");
917 // Set font
918 SendMessage(hTextBox, WM_SETFONT,
919 (WPARAM)GetStockObject(DEFAULT_GUI_FONT),
920 MAKELPARAM(FALSE, 0));
921 // Set size limit
922 SendMessage(hTextBox, EM_LIMITTEXT, TEXTBOX_LIMIT, 0);
923 // Initialize critical section object for text buffer access
924 InitializeCriticalSection(&critical_section);
925 // Open log file
926 GetLocalTime(<);
927 sprintf(log_filename, "rss_%02d-%02d-%02d_%02d-%02d-%02d.log",
928 lt.wYear, lt.wMonth, lt.wDay,
929 lt.wHour, lt.wMinute, lt.wSecond);
930 log_file = fopen(log_filename, "wb");
931 // Create text box update thread
932 if (!CreateThread(NULL, 0, UpdateTextBox, NULL, 0, NULL))
933 ExitOnError("Could not create text box update thread");
934 // Initialize Winsock
935 if (WSAStartup(MAKEWORD(2, 2), &wsaData))
936 ExitOnError("Winsock initialization failed");
937 // Start the listening threads
938 if (!CreateThread(NULL, 0, ShellListenThread, NULL, 0, NULL))
939 ExitOnError("Could not create shell server listen thread");
940 if (!CreateThread(NULL, 0, FileTransferListenThread, NULL, 0, NULL))
941 ExitOnError("Could not create file transfer server listen thread");
942 break;
943
944 case WM_SIZE:
945 MoveWindow(hTextBox, 20, 20,
946 LOWORD(lParam) - 40, HIWORD(lParam) - 40, TRUE);
947 break;
948
949 case WM_DESTROY:
950 if (WSACleanup())
951 ExitOnError("WSACleanup failed");
952 PostQuitMessage(0);
953 break;
954
955 default:
956 return DefWindowProc(hwnd, msg, wParam, lParam);
957 }
958
959 return 0;
960 }
961
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)962 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
963 LPSTR lpCmdLine, int nShowCmd)
964 {
965 WNDCLASSEX wc;
966 MSG msg;
967 char title[256];
968
969 if (strlen(lpCmdLine))
970 sscanf(lpCmdLine, "%d %d", &shell_port, &file_transfer_port);
971
972 sprintf(title, "Remote Shell Server (listening on ports %d, %d)",
973 shell_port, file_transfer_port);
974
975 // Create the window class
976 wc.cbSize = sizeof(WNDCLASSEX);
977 wc.style = CS_HREDRAW | CS_VREDRAW;
978 wc.lpfnWndProc = WndProc;
979 wc.cbClsExtra = 0;
980 wc.cbWndExtra = 0;
981 wc.hInstance = hInstance;
982 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
983 wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
984 wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
985 wc.lpszMenuName = NULL;
986 wc.lpszClassName = "RemoteShellServerWindowClass";
987 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
988
989 if (!RegisterClassEx(&wc))
990 ExitOnError("Could not register window class");
991
992 // Create the main window
993 hMainWindow =
994 CreateWindow("RemoteShellServerWindowClass", title,
995 WS_OVERLAPPEDWINDOW,
996 20, 20, 600, 400,
997 NULL, NULL, hInstance, NULL);
998 if (!hMainWindow)
999 ExitOnError("Could not create window");
1000
1001 ShowWindow(hMainWindow, SW_SHOWMINNOACTIVE);
1002 UpdateWindow(hMainWindow);
1003
1004 // Main message loop
1005 while (GetMessage(&msg, NULL, 0, 0)) {
1006 TranslateMessage(&msg);
1007 DispatchMessage(&msg);
1008 }
1009
1010 ExitProcess(0);
1011 }
1012