• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&lt);
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