- Implement SERVICE_CONFIG_DESCRIPTION and SERVICE_CONFIG_FAILURE_ACTIONS for RChange...
[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
266 #if 0
267 if (received < 0) {
268 closesocket(client->socket);
269 free(client);
270 return 0;
271 } else if (received) {
272 if ((terminator = strchr(password, CR)) != NULL) {
273 *terminator = '\0';
274 }
275 }
276 #endif
277
278 /* TODO: do authentication here */
279
280
281 printf("User '%s' logged on\n", userID);
282 #if 0
283 strcpy(client->userID, userID);
284 if (send(client->socket, logonPrompt, strlen(logonPrompt), 0) < 0) {
285 closesocket(client->socket);
286 free(client);
287 return 0;
288 }
289 #endif
290 RunShell(client);
291 return 0;
292 }
293
294 /*
295 ** Function: DoTelnetHandshake
296 */
297 static int DoTelnetHandshake(int sock)
298 {
299 int retval;
300 int received;
301 fd_set set;
302 struct timeval timeout = { HANDSHAKE_TIMEOUT, 0 };
303 unsigned char will_echo[3] = { IAC, WILL, ECHO };
304 unsigned char client_reply[256];
305
306 if (send(sock, (const char *) will_echo, sizeof(will_echo), 0) < 0) {
307 return -1;
308 }
309
310 /* Now wait for client response (and ignore it) */
311 FD_ZERO(&set);
312 FD_SET(sock, &set);
313
314 do {
315 retval = select(0, &set, NULL, NULL, &timeout);
316 /* check for error */
317 if (retval < 0) {
318 return -1;
319 /* check for timeout */
320 } else if (retval == 0) {
321 return 0;
322 }
323 /* no error and no timeout, we have data in our sock */
324 received = recv(sock, client_reply, sizeof(client_reply), 0);
325 if (received <= 0) {
326 return -1;
327 }
328 } while (retval);
329
330 return 0;
331 }
332
333 /*
334 ** Function: ReceiveLine
335 **
336 ** Abstract: receive until timeout or CR
337 ** In : sock, len
338 ** Out : buffer
339 ** Result : int
340 ** Pre : 'sock' must be valid socket
341 ** Post : (result = the number of bytes read into 'buffer')
342 ** OR (result = -1 and error)
343 */
344 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo)
345 {
346 int i = 0;
347 int retval;
348 fd_set set;
349 struct timeval timeout = { 0, 100000 };
350 char del[3] = { BS, ' ', BS };
351 char asterisk[1] = { '*' };
352
353 FD_ZERO(&set);
354 FD_SET(sock, &set);
355
356 memset(buffer, '\0', len);
357
358 do {
359 /* When we're in echo mode, we do not need a timeout */
360 retval = select(0, &set, NULL, NULL, (echo ? NULL : &timeout) );
361 /* check for error */
362 if (retval < 0) {
363 return -1;
364 /* check for timeout */
365 } else if (retval == 0) {
366 /* return number of characters received so far */
367 return i;
368 }
369 /* no error and no timeout, we have data in our sock */
370 if (recv(sock, &buffer[i], 1, 0) <= 0) {
371 return -1;
372 }
373 if ((buffer[i] == '\0') || (buffer[i] == LF)) {
374 /* ignore null characters and linefeeds from DOS telnet clients */
375 buffer[i] = '\0';
376 } else if ((buffer[i] == DEL) || (buffer[i] == BS)) {
377 /* handle delete and backspace */
378 buffer[i] = '\0';
379 if (echo) {
380 if (i > 0) {
381 i--;
382 buffer[i] = '\0';
383 if (send(sock, del, sizeof(del), 0) < 0) {
384 return -1;
385 }
386 }
387 } else {
388 buffer[i] = BS; /* Let shell process handle it */
389 i++;
390 }
391 } else {
392 /* echo typed characters */
393 if (echo == Echo && send(sock, &buffer[i], 1, 0) < 0) {
394 return -1;
395 } else if (echo == Password && send(sock, asterisk, sizeof(asterisk), 0) < 0) {
396 return -1;
397 }
398 if (buffer[i] == CR) {
399 i++;
400 buffer[i] = LF; /* append LF for DOS command processor */
401 i++;
402 return i;
403 }
404
405 i++;
406 }
407 } while (i < len);
408
409 return i;
410 }
411
412 /*
413 ** Function: RunShell
414 */
415 static void RunShell(client_t *client)
416 {
417 DWORD threadID;
418 HANDLE hChildStdinRd;
419 HANDLE hChildStdinWr;
420 HANDLE hChildStdoutRd;
421 HANDLE hChildStdoutWr;
422 STARTUPINFO si;
423 PROCESS_INFORMATION piProcInfo;
424 SECURITY_ATTRIBUTES saAttr;
425
426 const char *name = "c:\\windows\\system32\\cmd.exe";
427 const char *cmd = NULL;
428 //const char *name = "d:\\cygwin\\bin\\bash.exe";
429 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
430
431 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
432 saAttr.bInheritHandle = TRUE;
433 saAttr.lpSecurityDescriptor = NULL;
434
435 // Create a pipe for the child process's STDOUT.
436 if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
437 ErrorExit("Stdout pipe creation failed\n");
438
439 if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
440 ErrorExit("Stdin pipe creation failed\n");
441
442
443 client->bTerminate = FALSE;
444 client->bWriteToPipe = TRUE;
445 client->bReadFromPipe = TRUE;
446 client->hChildStdinWr = hChildStdinWr;
447 client->hChildStdoutRd = hChildStdoutRd;
448
449
450 // Create the child process (the shell)
451 printf("Creating child process...\n");
452
453 ZeroMemory( &si, sizeof(STARTUPINFO) );
454 si.cb = sizeof(STARTUPINFO);
455
456 si.dwFlags = STARTF_USESTDHANDLES;
457 si.hStdInput = hChildStdinRd;
458 si.hStdOutput = hChildStdoutWr;
459 si.hStdError = hChildStdoutWr;
460
461 //si.dwFlags |= STARTF_USESHOWWINDOW;
462 //si.wShowWindow = SW_SHOW;
463
464 if (!CreateProcess((LPSTR) name, // executable module
465 (LPSTR) cmd, // command line
466 NULL, // process security attributes
467 NULL, // primary thread security attributes
468 TRUE, // handles are inherited
469 //DETACHED_PROCESS + // creation flags
470 CREATE_NEW_PROCESS_GROUP,
471 NULL, // use parent's environment
472 NULL, // use parent's current directory
473 &si, // startup info
474 &piProcInfo)) {
475 ErrorExit("Create process failed");
476 }
477
478 client->hProcess = piProcInfo.hProcess;
479 client->dwProcessId = piProcInfo.dwProcessId;
480
481 printf("New child created (groupid=%lu)\n", client->dwProcessId);
482
483 // No longer need these in the parent...
484 if (!CloseHandle(hChildStdoutWr))
485 ErrorExit("Closing handle failed");
486 if (!CloseHandle(hChildStdinRd))
487 ErrorExit("Closing handle failed");
488
489 CreateThread(NULL, 0, WriteToPipeThread, client, 0, &threadID);
490 CreateThread(NULL, 0, ReadFromPipeThread, client, 0, &threadID);
491 CreateThread(NULL, 0, MonitorChildThread, client, 0, &threadID);
492 }
493
494
495 /*
496 ** Function: MonitorChildThread
497 **
498 ** Abstract: Monitor the child (shell) process
499 */
500 static DWORD WINAPI MonitorChildThread(LPVOID data)
501 {
502 DWORD exitCode;
503 client_t *client = (client_t *) data;
504
505 printf("Monitor thread running...\n");
506
507 WaitForSingleObject(client->hProcess, INFINITE);
508
509 GetExitCodeProcess(client->hProcess, &exitCode);
510 printf("Child process terminated with code %d\n", exitCode);
511
512 /* signal the other threads to give up */
513 client->bTerminate = TRUE;
514
515 Sleep(500);
516
517 CloseHandle(client->hChildStdoutRd);
518 CloseHandle(client->hChildStdinWr);
519 CloseHandle(client->hProcess);
520
521 closesocket(client->socket);
522
523 printf("Waiting for all threads to give up..\n");
524
525 while (client->bWriteToPipe || client->bReadFromPipe) {
526 printf(".");
527 fflush(stdout);
528 Sleep(1000);
529 }
530
531 printf("Cleanup for user '%s'\n", client->userID);
532 free(client);
533 return 0;
534 }
535
536 /*
537 ** Function: WriteToPipeThread
538 **
539 ** Abstract: read data from the telnet client socket
540 ** and pass it on to the shell process.
541 */
542 static DWORD WINAPI WriteToPipeThread(LPVOID data)
543 {
544 int iRead;
545 DWORD dwWritten;
546 CHAR chBuf[BUFSIZE];
547 client_t *client = (client_t *) data;
548
549 while (!client->bTerminate) {
550 iRead = ReceiveLine(client->socket, chBuf, BUFSIZE, FALSE);
551 if (iRead < 0) {
552 printf("Client disconnect\n");
553 break;
554 } else if (iRead > 0) {
555 if (strchr(chBuf, CTRLC)) {
556 GenerateConsoleCtrlEvent(CTRL_C_EVENT, client->dwProcessId);
557 }
558 if (send(client->socket, chBuf, iRead, 0) < 0) {
559 printf("error writing to socket\n");
560 break;
561 }
562 if (! WriteFile(client->hChildStdinWr, chBuf, (DWORD) iRead, &dwWritten, NULL)) {
563 printf("Error writing to pipe\n");
564 break;
565 }
566 }
567 }
568
569 if (!client->bTerminate) {
570 TerminateShell(client);
571 }
572
573 printf("WriteToPipeThread terminated\n");
574
575 client->bWriteToPipe = FALSE;
576 return 0;
577 }
578
579 /*
580 ** Function: ReadFromPipeThread
581 **
582 ** Abstract: Read data from the shell's stdout handle and
583 ** pass it on to the telnet client socket.
584 */
585 static DWORD WINAPI ReadFromPipeThread(LPVOID data)
586 {
587 DWORD dwRead;
588 DWORD dwAvail;
589 CHAR chBuf[BUFSIZE];
590 CHAR txBuf[BUFSIZE*2];
591 DWORD from,to;
592 char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
593
594 client_t *client = (client_t *) data;
595
596 while (!client->bTerminate && client->bWriteToPipe) {
597 // Since we do not want to block, first peek...
598 if (PeekNamedPipe(client->hChildStdoutRd, NULL, 0, NULL, &dwAvail, NULL) == 0) {
599 printf("Failed to peek in pipe\n");
600 break;
601 }
602 if (dwAvail) {
603 if( ! ReadFile( client->hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
604 dwRead == 0) {
605 printf("Failed to read from pipe\n");
606 break;
607 }
608 for (from=0, to=0; from<dwRead; from++, to++) {
609 txBuf[to] = chBuf[from];
610 if (txBuf[to] == '\n') {
611 txBuf[to] = '\r';
612 to++;
613 txBuf[to] = '\n';
614 }
615 }
616 if (send(client->socket, txBuf, to, 0) < 0) {
617 printf("error writing to socket\n");
618 break;
619 }
620 }
621 Sleep(100); /* Hmmm, oh well... what the heck! */
622 }
623
624 if (!client->bTerminate) {
625 TerminateShell(client);
626 }
627
628 printf("ReadFromPipeThread terminated\n");
629
630 client->bReadFromPipe = FALSE;
631 return 0;
632 }
633
634 /*
635 ** TerminateShell
636 */
637 static void TerminateShell(client_t *client)
638 {
639 DWORD exitCode;
640 DWORD dwWritten;
641 char stop[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
642
643 GetExitCodeProcess(client->hProcess, &exitCode);
644 if (exitCode == STILL_ACTIVE) {
645 printf("user shell still active, send Ctrl-Break to group-id %lu\n", client->dwProcessId );
646
647 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT, client->dwProcessId )) {
648 printf("Failed to send Ctrl_break\n");
649 }
650
651 Sleep(500);
652
653 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT, client->dwProcessId )) {
654 printf("Failed to send Ctrl_C\n");
655 }
656
657 Sleep(500);
658
659 if (! WriteFile(client->hChildStdinWr, stop, sizeof(stop), &dwWritten, NULL)) {
660 printf("Error writing to pipe\n");
661 }
662
663 Sleep(500);
664
665 GetExitCodeProcess(client->hProcess, &exitCode);
666 if (exitCode == STILL_ACTIVE) {
667 printf("user shell still active, attempt to terminate it now...\n");
668 TerminateProcess(client->hProcess, 0);
669 }
670 }
671 }
672
673 /*
674 ** ErrorExit
675 */
676 static VOID ErrorExit (LPTSTR lpszMessage)
677 {
678 fprintf(stderr, "%s\n", lpszMessage);
679 if (bSocketInterfaceInitialised) {
680 printf("WSAGetLastError=%d\n", WSAGetLastError());
681 WSACleanup();
682 }
683 ExitProcess(0);
684 }