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 657