2 * Abstract: a simple telnet 'daemon' for Windows hosts.
4 * Compiled & run successfully using MSVC 5.0 under Windows95 (requires
5 * Winsock2 update) and Windows98 and MSVC 6.0 under WindowsNT4
7 * Compiler options : no special options needed
8 * Linker options : add wsock32.lib or ws2_32.lib
10 * Written by fred.van.lieshout 'at' zonnet.nl
11 * Use freely, no copyrights.
14 * Parts Copyright Steven Edwards
19 * - will/won't handshake
20 * - Unify Debugging output and return StatusCodes
25 #define telnetd_printf printf
27 static inline int telnetd_printf(const char *format
, ...);
36 static BOOLEAN bShutdown
= 0;
37 static BOOLEAN bSocketInterfaceInitialised
= 0;
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
44 * Once you are ready to turn on the service
45 * rename this function
46 * int kickoff_telnetd(void)
48 int kickoff_telnetd(void)
50 printf("Attempting to start Simple TelnetD\n");
53 SetConsoleCtrlHandler(Cleanup
, 1);
55 if (!StartSocketInterface())
56 ErrorExit("Unable to start socket interface\n");
69 static BOOL WINAPI
Cleanup(DWORD dwControlType
)
71 if (bSocketInterfaceInitialised
) {
72 telnetd_printf("Cleanup...\n");
78 /* StartSocketInterface */
79 static BOOLEAN
StartSocketInterface(void)
81 WORD wVersionRequested
;
85 wVersionRequested
= MAKEWORD( 2, 0 );
86 err
= WSAStartup(wVersionRequested
, &wsaData
);
88 telnetd_printf("requested winsock version not supported\n");
92 bSocketInterfaceInitialised
= 1; /* for ErrorExit function */
94 if ( wsaData
.wVersion
!= wVersionRequested
)
95 ErrorExit("requested winsock version not supported\n");
97 telnetd_printf("TelnetD, using %s\n", wsaData
.szDescription
);
102 static void CreateSocket(void)
104 struct sockaddr_in sa
;
106 sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
108 ErrorExit("Cannot create socket");
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
);
115 if (bind(sock
, (struct sockaddr
*) &sa
, sizeof(sa
)) != 0)
116 ErrorExit("Cannot bind address to socket");
120 static void WaitForConnect(void)
122 struct sockaddr_in sa
;
125 if (listen(sock
, 1) < 0)
126 ErrorExit("Cannot listen on socket");
128 if ((new_sock
= accept(sock
, (struct sockaddr
*) &sa
, NULL
)) < 0) {
129 fprintf(stderr
, "Failed to accept incoming call\n");
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
);
137 /* Function: UserLogin */
138 static void UserLogin(int client_socket
)
141 client_t
*client
= malloc(sizeof(client_t
));
144 ErrorExit("failed to allocate memory for client");
146 client
->socket
= client_socket
;
147 threadHandle
= CreateThread(NULL
, 0, UserLoginThread
, client
, 0, NULL
);
148 if (threadHandle
== NULL
)
151 CloseHandle(threadHandle
);
154 /* Function: UserLoginThread */
155 static DWORD WINAPI
UserLoginThread(LPVOID data
)
157 client_t
*client
= (client_t
*) data
;
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
];
168 if (DoTelnetHandshake(client
->socket
)) {
169 closesocket(client
->socket
);
174 gethostname(hostname
, sizeof(hostname
));
175 sprintf(welcome
, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname
);
177 if (send(client
->socket
, welcome
, strlen(welcome
), 0) < 0) {
178 closesocket(client
->socket
);
182 received
= ReceiveLine(client
->socket
, userID
, sizeof(userID
), Echo
);
184 closesocket(client
->socket
);
187 } else if (received
) {
188 if ((terminator
= strchr(userID
, CR
)) != NULL
) {
193 if (send(client
->socket
, pwdPrompt
, strlen(pwdPrompt
), 0) < 0) {
194 closesocket(client
->socket
);
198 received
= ReceiveLine(client
->socket
, password
, sizeof(password
), Password
);
202 closesocket(client
->socket
);
205 } else if (received
) {
206 if ((terminator
= strchr(password
, CR
)) != NULL
) {
212 /* TODO: do authentication here */
215 telnetd_printf("User '%p' logged on\n", userID
);
217 strcpy(client
->userID
, userID
);
218 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
219 closesocket(client
->socket
);
228 /* Function: DoTelnetHandshake */
229 static int DoTelnetHandshake(int sock
)
234 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
240 IAC WILL SUPPRESS_GO_AHEAD
241 IAC DO SUPPRESS_GO_AHEAD
246 IAC SB TERMINAL_TYPE
"\x01" IAC SE
249 unsigned char client_reply
[256];
251 if (send(sock
, will_echo
, sizeof(will_echo
), 0) < 0) {
255 /* Now wait for client response (and ignore it) */
260 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
261 /* check for error */
264 /* check for timeout */
265 } else if (retval
== 0) {
268 /* no error and no timeout, we have data in our sock */
269 received
= recv(sock
, (char *) client_reply
, sizeof(client_reply
), 0);
279 ** Function: ReceiveLine
281 ** Abstract: receive until timeout or CR
285 ** Pre : 'sock' must be valid socket
286 ** Post : (result = the number of bytes read into 'buffer')
287 ** OR (result = -1 and error)
289 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
294 struct timeval timeout
= { 0, 100000 };
295 char del
[3] = { BS
, ' ', BS
};
296 char asterisk
[1] = { '*' };
301 memset(buffer
, '\0', len
);
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 */
309 /* check for timeout */
310 } else if (retval
== 0) {
311 /* return number of characters received so far */
314 /* no error and no timeout, we have data in our sock */
315 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
318 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
319 /* ignore null characters and linefeeds from DOS telnet clients */
321 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
322 /* handle delete and backspace */
328 if (send(sock
, del
, sizeof(del
), 0) < 0) {
333 buffer
[i
] = BS
; /* Let shell process handle it */
337 /* echo typed characters */
338 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
340 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
343 if (buffer
[i
] == CR
) {
345 buffer
[i
] = LF
; /* append LF for DOS command processor */
358 ** Function: RunShell
360 static void RunShell(client_t
*client
)
363 HANDLE hChildStdinRd
;
364 HANDLE hChildStdinWr
;
365 HANDLE hChildStdoutRd
;
366 HANDLE hChildStdoutWr
;
368 PROCESS_INFORMATION piProcInfo
;
369 SECURITY_ATTRIBUTES saAttr
;
370 char cmd_path
[MAX_PATH
];
372 if (!GetEnvironmentVariableA("COMSPEC", cmd_path
, ARRAYSIZE(cmd_path
)))
374 if (GetSystemDirectoryA(cmd_path
, ARRAYSIZE(cmd_path
)))
376 StringCchCatA(cmd_path
, ARRAYSIZE(cmd_path
), "\\cmd.exe");
380 StringCchCopyA(cmd_path
, ARRAYSIZE(cmd_path
), "C:\\ReactOS\\system32\\cmd.exe");
384 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
385 saAttr
.bInheritHandle
= TRUE
;
386 saAttr
.lpSecurityDescriptor
= NULL
;
388 // Create a pipe for the child process's STDOUT.
389 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
390 ErrorExit("Stdout pipe creation failed\n");
392 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
393 ErrorExit("Stdin pipe creation failed\n");
396 client
->bTerminate
= FALSE
;
397 client
->bWriteToPipe
= TRUE
;
398 client
->bReadFromPipe
= TRUE
;
399 client
->hChildStdinWr
= hChildStdinWr
;
400 client
->hChildStdoutRd
= hChildStdoutRd
;
403 // Create the child process (the shell)
404 telnetd_printf("Creating child process...\n");
406 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
407 si
.cb
= sizeof(STARTUPINFO
);
409 si
.dwFlags
= STARTF_USESTDHANDLES
;
410 si
.hStdInput
= hChildStdinRd
;
411 si
.hStdOutput
= hChildStdoutWr
;
412 si
.hStdError
= hChildStdoutWr
;
414 //si.dwFlags |= STARTF_USESHOWWINDOW;
415 //si.wShowWindow = SW_SHOW;
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
428 ErrorExit("Create process failed");
431 client
->hProcess
= piProcInfo
.hProcess
;
432 client
->dwProcessId
= piProcInfo
.dwProcessId
;
434 telnetd_printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
436 // No longer need these in the parent...
437 if (!CloseHandle(hChildStdoutWr
))
438 ErrorExit("Closing handle failed");
440 if (!CloseHandle(hChildStdinRd
))
441 ErrorExit("Closing handle failed");
443 threadHandle
= CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, NULL
);
444 if (threadHandle
!= NULL
)
445 CloseHandle(threadHandle
);
447 threadHandle
= CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, NULL
);
448 if (threadHandle
!= NULL
)
449 CloseHandle(threadHandle
);
451 threadHandle
= CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, NULL
);
452 if (threadHandle
!= NULL
)
453 CloseHandle(threadHandle
);
457 * Function: MonitorChildThread
459 * Abstract: Monitor the child (shell) process
461 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
464 client_t
*client
= (client_t
*) data
;
466 telnetd_printf("Monitor thread running...\n");
468 WaitForSingleObject(client
->hProcess
, INFINITE
);
470 GetExitCodeProcess(client
->hProcess
, &exitCode
);
471 telnetd_printf("Child process terminated with code %lx\n", exitCode
);
473 /* signal the other threads to give up */
474 client
->bTerminate
= TRUE
;
478 CloseHandle(client
->hChildStdoutRd
);
479 CloseHandle(client
->hChildStdinWr
);
480 CloseHandle(client
->hProcess
);
482 closesocket(client
->socket
);
484 telnetd_printf("Waiting for all threads to give up..\n");
486 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
492 telnetd_printf("Cleanup for user '%s'\n", client
->userID
);
498 * Function: WriteToPipeThread
500 * Abstract: read data from the telnet client socket
501 * and pass it on to the shell process.
503 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
508 client_t
*client
= (client_t
*) data
;
510 while (!client
->bTerminate
) {
511 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
513 telnetd_printf("Client disconnect\n");
515 } else if (iRead
> 0) {
516 if (strchr(chBuf
, CTRLC
)) {
517 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
519 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
520 telnetd_printf("error writing to socket\n");
523 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
524 telnetd_printf("Error writing to pipe\n");
530 if (!client
->bTerminate
)
531 TerminateShell(client
);
533 telnetd_printf("WriteToPipeThread terminated\n");
535 client
->bWriteToPipe
= FALSE
;
540 * Function: ReadFromPipeThread
542 * Abstract: Read data from the shell's stdout handle and
543 * pass it on to the telnet client socket.
545 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
550 CHAR txBuf
[BUFSIZE
*2];
552 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
554 client_t
*client
= (client_t
*) data
;
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");
563 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
565 telnetd_printf("Failed to read from pipe\n");
568 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
569 txBuf
[to
] = chBuf
[from
];
570 if (txBuf
[to
] == '\n') {
576 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
577 telnetd_printf("error writing to socket\n");
581 Sleep(100); /* Hmmm, oh well... what the heck! */
584 if (!client
->bTerminate
)
585 TerminateShell(client
);
587 telnetd_printf("ReadFromPipeThread terminated\n");
589 client
->bReadFromPipe
= FALSE
;
594 static void TerminateShell(client_t
*client
)
598 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
600 GetExitCodeProcess(client
->hProcess
, &exitCode
);
602 if (exitCode
== STILL_ACTIVE
)
604 HANDLE hEvent
= NULL
;
607 telnetd_printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
609 hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
612 printf("CreateEvent error\n");
614 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
))
615 telnetd_printf("Failed to send Ctrl_break\n");
617 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
))
618 telnetd_printf("Failed to send Ctrl_C\n");
620 if (!WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
))
621 telnetd_printf("Error writing to pipe\n");
623 /* wait for our handler to be called */
624 dwWaitResult
=WaitForSingleObject(hEvent
, 500);
626 if (WAIT_FAILED
==dwWaitResult
)
627 telnetd_printf("WaitForSingleObject failed\n");
629 GetExitCodeProcess(client
->hProcess
, &exitCode
);
630 if (exitCode
== STILL_ACTIVE
)
632 telnetd_printf("user shell still active, attempt to terminate it now...\n");
636 if (!CloseHandle(hEvent
))
637 telnetd_printf("CloseHandle");
639 TerminateProcess(client
->hProcess
, 0);
641 TerminateProcess(client
->hProcess
, 0);
643 TerminateProcess(client
->hProcess
, 0);
647 static VOID
ErrorExit (LPTSTR lpszMessage
)
649 fprintf(stderr
, "%s\n", lpszMessage
);
650 if (bSocketInterfaceInitialised
) {
651 telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());