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