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