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