a5c365b3ad43252aedb0a7583fbbcb33b4e801ca
[reactos.git] / rosapps / applications / sysutils / telnetd / telnetd.c
1 /*
2 * File: TelnetD.c
3 *
4 * Abstract: a simple telnet 'daemon' for Windows hosts.
5 *
6 * Compiled & run successfully using MSVC 5.0 under Windows95 (requires
7 * Winsock2 update) and Windows98 and MSVC 6.0 under WindowsNT4
8 *
9 * Compiler options : no special options needed
10 * Linker options : add wsock32.lib or ws2_32.lib
11 *
12 * Written by fred.van.lieshout 'at' zonnet.nl
13 * Use freely, no copyrights.
14 * Use Linux.
15 *
16 * TODO:
17 * - access control
18 * - will/won't handshake
19 * - (run as) Windows NT service
20 */
21
22 #include <stdio.h>
23 #include <windows.h>
24
25 /*
26 ** macro definitions
27 */
28 #define TELNET_PORT (23)
29
30 #define BUFSIZE (4096)
31 #define USERID_SIZE (64)
32 #define CTRLC (3)
33 #define BS (8)
34 #define CR (13)
35 #define LF (10)
36 #define DEL (127)
37
38 #define IAC "\xff"
39 #define DONT "\xfe"
40 #define WONT "\xfc"
41 #define WILL "\xfb"
42 #define DO "\xfd"
43 #define SB "\xfa"
44 #define SE "\xf0"
45 #define ECHO "\x01"
46 #define SUPPRESS_GO_AHEAD "\x03"
47 #define TERMINAL_TYPE "\x18"
48 #define NAWS "\x1f"
49 #define LINEMODE "\x22"
50 #define NEWENVIRON "\x27"
51 #define MODE "\x01"
52
53
54 #define HANDSHAKE_TIMEOUT (3)
55
56 /*
57 ** types
58 */
59
60 typedef struct client_s
61 {
62 char userID[USERID_SIZE];
63 int socket;
64 BOOLEAN bTerminate;
65 BOOLEAN bReadFromPipe;
66 BOOLEAN bWriteToPipe;
67 HANDLE hProcess;
68 DWORD dwProcessId;
69 HANDLE hChildStdinWr;
70 HANDLE hChildStdoutRd;
71 } client_t;
72
73 typedef enum
74 {
75 NoEcho = 0,
76 Echo = 1,
77 Password = 2
78 } EchoMode;
79
80 /*
81 ** Local data
82 */
83
84 static BOOLEAN bShutdown = 0;
85 static BOOLEAN bSocketInterfaceInitialised = 0;
86
87 static int sock;
88
89 /*
90 ** Forward function declarations
91 */
92 static BOOL WINAPI Cleanup(DWORD dwControlType);
93 static void WaitForConnect(void);
94 static BOOLEAN StartSocketInterface(void);
95 static void CreateSocket(void);
96 static void UserLogin(int client_socket);
97 static DWORD WINAPI UserLoginThread(LPVOID);
98 static int DoTelnetHandshake(int sock);
99 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo);
100 static void RunShell(client_t *client);
101 //static BOOL CreateChildProcess(const char *);
102 static DWORD WINAPI MonitorChildThread(LPVOID);
103 static DWORD WINAPI WriteToPipeThread(LPVOID);
104 static DWORD WINAPI ReadFromPipeThread(LPVOID);
105 static void TerminateShell(client_t *client);
106 static VOID ErrorExit(LPTSTR);
107
108
109 /*
110 ** main
111 */
112 DWORD telnetd_main()
113 {
114 SetConsoleCtrlHandler(Cleanup, 1);
115
116 if (!StartSocketInterface()) {
117 ErrorExit("Unable to start socket interface\n");
118 }
119
120 CreateSocket();
121
122 while(!bShutdown) {
123 WaitForConnect();
124 }
125
126 WSACleanup();
127 return 0;
128 }
129
130 /*
131 ** Cleanup
132 */
133 static BOOL WINAPI Cleanup(DWORD dwControlType)
134 {
135 if (bSocketInterfaceInitialised) {
136 printf("Cleanup...\n");
137 WSACleanup();
138 }
139 return 0;
140 }
141
142 /*
143 ** StartSocketInterface
144 */
145 static BOOLEAN StartSocketInterface(void)
146 {
147 WORD wVersionRequested;
148 WSADATA wsaData;
149 int err;
150
151 wVersionRequested = MAKEWORD( 2, 0 );
152 err = WSAStartup(wVersionRequested, &wsaData);
153 if (err != 0) {
154 printf("requested winsock version not supported\n");
155 return 0;
156 }
157
158 bSocketInterfaceInitialised = 1; /* for ErrorExit function */
159
160 if ( wsaData.wVersion != wVersionRequested) {
161 printf("requested winsock version not supported\n");
162 return 0;
163 }
164 printf("TelnetD, using %s\n", wsaData.szDescription);
165 return 1;
166 }
167
168 /*
169 ** CreateSocket
170 */
171 static void CreateSocket(void)
172 {
173 struct sockaddr_in sa;
174
175 sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
176 if (sock < 0) {
177 ErrorExit("Cannot create socket");
178 }
179
180 memset(&sa, 0, sizeof(sa));
181 sa.sin_family = AF_INET;
182 sa.sin_addr.s_addr = INADDR_ANY;
183 sa.sin_port = htons(TELNET_PORT);
184 if (bind(sock, (struct sockaddr*) &sa, sizeof(sa)) != 0) {
185 ErrorExit("Cannot bind address to socket");
186 }
187 }
188
189 /*
190 ** WaitForConnect
191 */
192 static void WaitForConnect(void)
193 {
194 struct sockaddr_in sa;
195 int new_sock;
196
197 if (listen(sock, 1) < 0) {
198 ErrorExit("Cannot listen on socket");
199 }
200
201 if ((new_sock = accept(sock, (struct sockaddr*) &sa, NULL)) < 0) {
202 fprintf(stderr, "Failed to accept incoming call\n");
203 } else {
204 printf("user connected on socket %d, port %d, address %lx\n", new_sock,
205 htons(sa.sin_port), sa.sin_addr.s_addr);
206 UserLogin(new_sock);
207 }
208 }
209
210
211 /*
212 ** Function: UserLogin
213 */
214 static void UserLogin(int client_socket)
215 {
216 DWORD threadID;
217 client_t *client = malloc(sizeof(client_t));
218
219 if (client == NULL) {
220 ErrorExit("failed to allocate memory for client");
221 }
222
223 client->socket = client_socket;
224 CreateThread(NULL, 0, UserLoginThread, client, 0, &threadID);
225 }
226
227 /*
228 ** Function: UserLoginThread
229 */
230 static DWORD WINAPI UserLoginThread(LPVOID data)
231 {
232 client_t *client = (client_t *) data;
233 char welcome[256];
234 char hostname[64] = "Unknown";
235 char *pwdPrompt = "\r\npass:";
236 //char *logonPrompt = "\r\nLogin OK, please wait...";
237 //char *byebye = "\r\nWrong! bye bye...\r\n";
238 char userID[USERID_SIZE];
239 char password[USERID_SIZE];
240 int received;
241 char *terminator;
242
243 if (DoTelnetHandshake(client->socket)) {
244 closesocket(client->socket);
245 free(client);
246 return 0;
247 }
248
249 gethostname(hostname, sizeof(hostname));
250 sprintf(welcome, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname);
251
252 if (send(client->socket, welcome, strlen(welcome), 0) < 0) {
253 closesocket(client->socket);
254 free(client);
255 return 0;
256 }
257 received = ReceiveLine(client->socket, userID, sizeof(userID), Echo );
258 if (received < 0) {
259 closesocket(client->socket);
260 free(client);
261 return 0;
262 } else if (received) {
263 if ((terminator = strchr(userID, CR)) != NULL) {
264 *terminator = '\0';
265 }
266 }
267
268 if (send(client->socket, pwdPrompt, strlen(pwdPrompt), 0) < 0) {
269 closesocket(client->socket);
270 free(client);
271 return 0;
272 }
273 received = ReceiveLine(client->socket, password, sizeof(password), Password );
274
275 #if 0
276 if (received < 0) {
277 closesocket(client->socket);
278 free(client);
279 return 0;
280 } else if (received) {
281 if ((terminator = strchr(password, CR)) != NULL) {
282 *terminator = '\0';
283 }
284 }
285 #endif
286
287 /* TODO: do authentication here */
288
289
290 printf("User '%s' logged on\n", userID);
291 #if 0
292 strcpy(client->userID, userID);
293 if (send(client->socket, logonPrompt, strlen(logonPrompt), 0) < 0) {
294 closesocket(client->socket);
295 free(client);
296 return 0;
297 }
298 #endif
299 RunShell(client);
300 return 0;
301 }
302
303 /*
304 ** Function: DoTelnetHandshake
305 */
306 static int DoTelnetHandshake(int sock)
307 {
308 int retval;
309 int received;
310 fd_set set;
311 struct timeval timeout = { HANDSHAKE_TIMEOUT, 0 };
312
313 char will_echo[]=
314 IAC DONT ECHO
315 IAC WILL ECHO
316 IAC WILL NAWS
317 IAC WILL SUPPRESS_GO_AHEAD
318 IAC DO SUPPRESS_GO_AHEAD
319 IAC DONT NEWENVIRON
320 IAC WONT NEWENVIRON
321 IAC WONT LINEMODE
322 IAC DO NAWS
323 IAC SB TERMINAL_TYPE "\x01" IAC SE
324 ;
325
326 unsigned char client_reply[256];
327
328 if (send(sock, will_echo, sizeof(will_echo), 0) < 0) {
329 return -1;
330 }
331
332 /* Now wait for client response (and ignore it) */
333 FD_ZERO(&set);
334 FD_SET(sock, &set);
335
336 do {
337 retval = select(0, &set, NULL, NULL, &timeout);
338 /* check for error */
339 if (retval < 0) {
340 return -1;
341 /* check for timeout */
342 } else if (retval == 0) {
343 return 0;
344 }
345 /* no error and no timeout, we have data in our sock */
346 received = recv(sock, (char *) client_reply, sizeof(client_reply), 0);
347 if (received <= 0) {
348 return -1;
349 }
350 } while (retval);
351
352 return 0;
353 }
354
355 /*
356 ** Function: ReceiveLine
357 **
358 ** Abstract: receive until timeout or CR
359 ** In : sock, len
360 ** Out : buffer
361 ** Result : int
362 ** Pre : 'sock' must be valid socket
363 ** Post : (result = the number of bytes read into 'buffer')
364 ** OR (result = -1 and error)
365 */
366 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo)
367 {
368 int i = 0;
369 int retval;
370 fd_set set;
371 struct timeval timeout = { 0, 100000 };
372 char del[3] = { BS, ' ', BS };
373 char asterisk[1] = { '*' };
374
375 FD_ZERO(&set);
376 FD_SET(sock, &set);
377
378 memset(buffer, '\0', len);
379
380 do {
381 /* When we're in echo mode, we do not need a timeout */
382 retval = select(0, &set, NULL, NULL, (echo ? NULL : &timeout) );
383 /* check for error */
384 if (retval < 0) {
385 return -1;
386 /* check for timeout */
387 } else if (retval == 0) {
388 /* return number of characters received so far */
389 return i;
390 }
391 /* no error and no timeout, we have data in our sock */
392 if (recv(sock, &buffer[i], 1, 0) <= 0) {
393 return -1;
394 }
395 if ((buffer[i] == '\0') || (buffer[i] == LF)) {
396 /* ignore null characters and linefeeds from DOS telnet clients */
397 buffer[i] = '\0';
398 } else if ((buffer[i] == DEL) || (buffer[i] == BS)) {
399 /* handle delete and backspace */
400 buffer[i] = '\0';
401 if (echo) {
402 if (i > 0) {
403 i--;
404 buffer[i] = '\0';
405 if (send(sock, del, sizeof(del), 0) < 0) {
406 return -1;
407 }
408 }
409 } else {
410 buffer[i] = BS; /* Let shell process handle it */
411 i++;
412 }
413 } else {
414 /* echo typed characters */
415 if (echo == Echo && send(sock, &buffer[i], 1, 0) < 0) {
416 return -1;
417 } else if (echo == Password && send(sock, asterisk, sizeof(asterisk), 0) < 0) {
418 return -1;
419 }
420 if (buffer[i] == CR) {
421 i++;
422 buffer[i] = LF; /* append LF for DOS command processor */
423 i++;
424 return i;
425 }
426
427 i++;
428 }
429 } while (i < len);
430
431 return i;
432 }
433
434 /*
435 ** Function: RunShell
436 */
437 static void RunShell(client_t *client)
438 {
439 DWORD threadID;
440 HANDLE hChildStdinRd;
441 HANDLE hChildStdinWr;
442 HANDLE hChildStdoutRd;
443 HANDLE hChildStdoutWr;
444 STARTUPINFO si;
445 PROCESS_INFORMATION piProcInfo;
446 SECURITY_ATTRIBUTES saAttr;
447
448 const char *name = "c:\\windows\\system32\\cmd.exe";
449 const char *cmd = NULL;
450 //const char *name = "d:\\cygwin\\bin\\bash.exe";
451 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
452
453 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
454 saAttr.bInheritHandle = TRUE;
455 saAttr.lpSecurityDescriptor = NULL;
456
457 // Create a pipe for the child process's STDOUT.
458 if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
459 ErrorExit("Stdout pipe creation failed\n");
460
461 if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
462 ErrorExit("Stdin pipe creation failed\n");
463
464
465 client->bTerminate = FALSE;
466 client->bWriteToPipe = TRUE;
467 client->bReadFromPipe = TRUE;
468 client->hChildStdinWr = hChildStdinWr;
469 client->hChildStdoutRd = hChildStdoutRd;
470
471
472 // Create the child process (the shell)
473 printf("Creating child process...\n");
474
475 ZeroMemory( &si, sizeof(STARTUPINFO) );
476 si.cb = sizeof(STARTUPINFO);
477
478 si.dwFlags = STARTF_USESTDHANDLES;
479 si.hStdInput = hChildStdinRd;
480 si.hStdOutput = hChildStdoutWr;
481 si.hStdError = hChildStdoutWr;
482
483 //si.dwFlags |= STARTF_USESHOWWINDOW;
484 //si.wShowWindow = SW_SHOW;
485
486 if (!CreateProcess((LPSTR) name, // executable module
487 (LPSTR) cmd, // command line
488 NULL, // process security attributes
489 NULL, // primary thread security attributes
490 TRUE, // handles are inherited
491 DETACHED_PROCESS + // creation flags
492 CREATE_NEW_PROCESS_GROUP,
493 NULL, // use parent's environment
494 NULL, // use parent's current directory
495 &si, // startup info
496 &piProcInfo)) {
497 ErrorExit("Create process failed");
498 }
499
500 client->hProcess = piProcInfo.hProcess;
501 client->dwProcessId = piProcInfo.dwProcessId;
502
503 printf("New child created (groupid=%lu)\n", client->dwProcessId);
504
505 // No longer need these in the parent...
506 if (!CloseHandle(hChildStdoutWr))
507 ErrorExit("Closing handle failed");
508 if (!CloseHandle(hChildStdinRd))
509 ErrorExit("Closing handle failed");
510
511 CreateThread(NULL, 0, WriteToPipeThread, client, 0, &threadID);
512 CreateThread(NULL, 0, ReadFromPipeThread, client, 0, &threadID);
513 CreateThread(NULL, 0, MonitorChildThread, client, 0, &threadID);
514 }
515
516
517 /*
518 ** Function: MonitorChildThread
519 **
520 ** Abstract: Monitor the child (shell) process
521 */
522 static DWORD WINAPI MonitorChildThread(LPVOID data)
523 {
524 DWORD exitCode;
525 client_t *client = (client_t *) data;
526
527 printf("Monitor thread running...\n");
528
529 WaitForSingleObject(client->hProcess, INFINITE);
530
531 GetExitCodeProcess(client->hProcess, &exitCode);
532 printf("Child process terminated with code %lx\n", exitCode);
533
534 /* signal the other threads to give up */
535 client->bTerminate = TRUE;
536
537 Sleep(500);
538
539 CloseHandle(client->hChildStdoutRd);
540 CloseHandle(client->hChildStdinWr);
541 CloseHandle(client->hProcess);
542
543 closesocket(client->socket);
544
545 printf("Waiting for all threads to give up..\n");
546
547 while (client->bWriteToPipe || client->bReadFromPipe) {
548 printf(".");
549 fflush(stdout);
550 Sleep(1000);
551 }
552
553 printf("Cleanup for user '%s'\n", client->userID);
554 free(client);
555 return 0;
556 }
557
558 /*
559 ** Function: WriteToPipeThread
560 **
561 ** Abstract: read data from the telnet client socket
562 ** and pass it on to the shell process.
563 */
564 static DWORD WINAPI WriteToPipeThread(LPVOID data)
565 {
566 int iRead;
567 DWORD dwWritten;
568 CHAR chBuf[BUFSIZE];
569 client_t *client = (client_t *) data;
570
571 while (!client->bTerminate) {
572 iRead = ReceiveLine(client->socket, chBuf, BUFSIZE, FALSE);
573 if (iRead < 0) {
574 printf("Client disconnect\n");
575 break;
576 } else if (iRead > 0) {
577 if (strchr(chBuf, CTRLC)) {
578 GenerateConsoleCtrlEvent(CTRL_C_EVENT, client->dwProcessId);
579 }
580 if (send(client->socket, chBuf, iRead, 0) < 0) {
581 printf("error writing to socket\n");
582 break;
583 }
584 if (! WriteFile(client->hChildStdinWr, chBuf, (DWORD) iRead, &dwWritten, NULL)) {
585 printf("Error writing to pipe\n");
586 break;
587 }
588 }
589 }
590
591 if (!client->bTerminate) {
592 TerminateShell(client);
593 }
594
595 printf("WriteToPipeThread terminated\n");
596
597 client->bWriteToPipe = FALSE;
598 return 0;
599 }
600
601 /*
602 ** Function: ReadFromPipeThread
603 **
604 ** Abstract: Read data from the shell's stdout handle and
605 ** pass it on to the telnet client socket.
606 */
607 static DWORD WINAPI ReadFromPipeThread(LPVOID data)
608 {
609 DWORD dwRead;
610 DWORD dwAvail;
611 CHAR chBuf[BUFSIZE];
612 CHAR txBuf[BUFSIZE*2];
613 DWORD from,to;
614 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
615
616 client_t *client = (client_t *) data;
617
618 while (!client->bTerminate && client->bWriteToPipe) {
619 // Since we do not want to block, first peek...
620 if (PeekNamedPipe(client->hChildStdoutRd, NULL, 0, NULL, &dwAvail, NULL) == 0) {
621 printf("Failed to peek in pipe\n");
622 break;
623 }
624 if (dwAvail) {
625 if( ! ReadFile( client->hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
626 dwRead == 0) {
627 printf("Failed to read from pipe\n");
628 break;
629 }
630 for (from=0, to=0; from<dwRead; from++, to++) {
631 txBuf[to] = chBuf[from];
632 if (txBuf[to] == '\n') {
633 txBuf[to] = '\r';
634 to++;
635 txBuf[to] = '\n';
636 }
637 }
638 if (send(client->socket, txBuf, to, 0) < 0) {
639 printf("error writing to socket\n");
640 break;
641 }
642 }
643 Sleep(100); /* Hmmm, oh well... what the heck! */
644 }
645
646 if (!client->bTerminate) {
647 TerminateShell(client);
648 }
649
650 printf("ReadFromPipeThread terminated\n");
651
652 client->bReadFromPipe = FALSE;
653 return 0;
654 }
655
656 /*
657 ** TerminateShell
658 */
659 static void TerminateShell(client_t *client)
660 {
661 DWORD exitCode;
662 DWORD dwWritten;
663 char stop[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
664
665 GetExitCodeProcess(client->hProcess, &exitCode);
666 if (exitCode == STILL_ACTIVE) {
667 printf("user shell still active, send Ctrl-Break to group-id %lu\n", client->dwProcessId );
668
669 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT, client->dwProcessId )) {
670 printf("Failed to send Ctrl_break\n");
671 }
672
673 Sleep(500);
674
675 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT, client->dwProcessId )) {
676 printf("Failed to send Ctrl_C\n");
677 }
678
679 Sleep(500);
680
681 if (! WriteFile(client->hChildStdinWr, stop, sizeof(stop), &dwWritten, NULL)) {
682 printf("Error writing to pipe\n");
683 }
684
685 Sleep(500);
686
687 GetExitCodeProcess(client->hProcess, &exitCode);
688 if (exitCode == STILL_ACTIVE) {
689 printf("user shell still active, attempt to terminate it now...\n");
690 TerminateProcess(client->hProcess, 0);
691 }
692 }
693 }
694
695 /*
696 ** ErrorExit
697 */
698 static VOID ErrorExit (LPTSTR lpszMessage)
699 {
700 fprintf(stderr, "%s\n", lpszMessage);
701 if (bSocketInterfaceInitialised) {
702 printf("WSAGetLastError=%d\n", WSAGetLastError());
703 WSACleanup();
704 }
705 ExitProcess(0);
706 }
707