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 main(int argc
, char **argv
)
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 CreateThread(NULL
, 0, UserLoginThread
, client
, 0, &threadID
);
150 /* Function: UserLoginThread */
151 static DWORD WINAPI
UserLoginThread(LPVOID data
)
153 client_t
*client
= (client_t
*) data
;
155 char hostname
[64] = "Unknown";
156 char *pwdPrompt
= "\r\npass:";
157 //char *logonPrompt = "\r\nLogin OK, please wait...";
158 //char *byebye = "\r\nWrong! bye bye...\r\n";
159 char userID
[USERID_SIZE
];
160 char password
[USERID_SIZE
];
164 if (DoTelnetHandshake(client
->socket
)) {
165 closesocket(client
->socket
);
170 gethostname(hostname
, sizeof(hostname
));
171 sprintf(welcome
, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname
);
173 if (send(client
->socket
, welcome
, strlen(welcome
), 0) < 0) {
174 closesocket(client
->socket
);
178 received
= ReceiveLine(client
->socket
, userID
, sizeof(userID
), Echo
);
180 closesocket(client
->socket
);
183 } else if (received
) {
184 if ((terminator
= strchr(userID
, CR
)) != NULL
) {
189 if (send(client
->socket
, pwdPrompt
, strlen(pwdPrompt
), 0) < 0) {
190 closesocket(client
->socket
);
194 received
= ReceiveLine(client
->socket
, password
, sizeof(password
), Password
);
198 closesocket(client
->socket
);
201 } else if (received
) {
202 if ((terminator
= strchr(password
, CR
)) != NULL
) {
208 /* TODO: do authentication here */
211 telnetd_printf("User '%p' logged on\n", userID
);
213 strcpy(client
->userID
, userID
);
214 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
215 closesocket(client
->socket
);
224 /* Function: DoTelnetHandshake */
225 static int DoTelnetHandshake(int sock
)
230 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
236 IAC WILL SUPPRESS_GO_AHEAD
237 IAC DO SUPPRESS_GO_AHEAD
242 IAC SB TERMINAL_TYPE
"\x01" IAC SE
245 unsigned char client_reply
[256];
247 if (send(sock
, will_echo
, sizeof(will_echo
), 0) < 0) {
251 /* Now wait for client response (and ignore it) */
256 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
257 /* check for error */
260 /* check for timeout */
261 } else if (retval
== 0) {
264 /* no error and no timeout, we have data in our sock */
265 received
= recv(sock
, (char *) client_reply
, sizeof(client_reply
), 0);
275 ** Function: ReceiveLine
277 ** Abstract: receive until timeout or CR
281 ** Pre : 'sock' must be valid socket
282 ** Post : (result = the number of bytes read into 'buffer')
283 ** OR (result = -1 and error)
285 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
290 struct timeval timeout
= { 0, 100000 };
291 char del
[3] = { BS
, ' ', BS
};
292 char asterisk
[1] = { '*' };
297 memset(buffer
, '\0', len
);
300 /* When we're in echo mode, we do not need a timeout */
301 retval
= select(0, &set
, NULL
, NULL
, (echo
? NULL
: &timeout
) );
302 /* check for error */
305 /* check for timeout */
306 } else if (retval
== 0) {
307 /* return number of characters received so far */
310 /* no error and no timeout, we have data in our sock */
311 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
314 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
315 /* ignore null characters and linefeeds from DOS telnet clients */
317 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
318 /* handle delete and backspace */
324 if (send(sock
, del
, sizeof(del
), 0) < 0) {
329 buffer
[i
] = BS
; /* Let shell process handle it */
333 /* echo typed characters */
334 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
336 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
339 if (buffer
[i
] == CR
) {
341 buffer
[i
] = LF
; /* append LF for DOS command processor */
354 ** Function: RunShell
356 static void RunShell(client_t
*client
)
359 HANDLE hChildStdinRd
;
360 HANDLE hChildStdinWr
;
361 HANDLE hChildStdoutRd
;
362 HANDLE hChildStdoutWr
;
364 PROCESS_INFORMATION piProcInfo
;
365 SECURITY_ATTRIBUTES saAttr
;
367 const char *name
= "c:\\windows\\system32\\cmd.exe";
368 const char *cmd
= NULL
;
369 //const char *name = "d:\\cygwin\\bin\\bash.exe";
370 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
372 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
373 saAttr
.bInheritHandle
= TRUE
;
374 saAttr
.lpSecurityDescriptor
= NULL
;
376 // Create a pipe for the child process's STDOUT.
377 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
378 ErrorExit("Stdout pipe creation failed\n");
380 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
381 ErrorExit("Stdin pipe creation failed\n");
384 client
->bTerminate
= FALSE
;
385 client
->bWriteToPipe
= TRUE
;
386 client
->bReadFromPipe
= TRUE
;
387 client
->hChildStdinWr
= hChildStdinWr
;
388 client
->hChildStdoutRd
= hChildStdoutRd
;
391 // Create the child process (the shell)
392 telnetd_printf("Creating child process...\n");
394 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
395 si
.cb
= sizeof(STARTUPINFO
);
397 si
.dwFlags
= STARTF_USESTDHANDLES
;
398 si
.hStdInput
= hChildStdinRd
;
399 si
.hStdOutput
= hChildStdoutWr
;
400 si
.hStdError
= hChildStdoutWr
;
402 //si.dwFlags |= STARTF_USESHOWWINDOW;
403 //si.wShowWindow = SW_SHOW;
405 if (!CreateProcess((LPSTR
) name
, // executable module
406 (LPSTR
) cmd
, // command line
407 NULL
, // process security attributes
408 NULL
, // primary thread security attributes
409 TRUE
, // handles are inherited
410 DETACHED_PROCESS
+ // creation flags
411 CREATE_NEW_PROCESS_GROUP
,
412 NULL
, // use parent's environment
413 NULL
, // use parent's current directory
416 ErrorExit("Create process failed");
419 client
->hProcess
= piProcInfo
.hProcess
;
420 client
->dwProcessId
= piProcInfo
.dwProcessId
;
422 telnetd_printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
424 // No longer need these in the parent...
425 if (!CloseHandle(hChildStdoutWr
))
426 ErrorExit("Closing handle failed");
428 if (!CloseHandle(hChildStdinRd
))
429 ErrorExit("Closing handle failed");
431 CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, &threadID
);
432 CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, &threadID
);
433 CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, &threadID
);
437 * Function: MonitorChildThread
439 * Abstract: Monitor the child (shell) process
441 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
444 client_t
*client
= (client_t
*) data
;
446 telnetd_printf("Monitor thread running...\n");
448 WaitForSingleObject(client
->hProcess
, INFINITE
);
450 GetExitCodeProcess(client
->hProcess
, &exitCode
);
451 telnetd_printf("Child process terminated with code %lx\n", exitCode
);
453 /* signal the other threads to give up */
454 client
->bTerminate
= TRUE
;
458 CloseHandle(client
->hChildStdoutRd
);
459 CloseHandle(client
->hChildStdinWr
);
460 CloseHandle(client
->hProcess
);
462 closesocket(client
->socket
);
464 telnetd_printf("Waiting for all threads to give up..\n");
466 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
472 telnetd_printf("Cleanup for user '%s'\n", client
->userID
);
478 * Function: WriteToPipeThread
480 * Abstract: read data from the telnet client socket
481 * and pass it on to the shell process.
483 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
488 client_t
*client
= (client_t
*) data
;
490 while (!client
->bTerminate
) {
491 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
493 telnetd_printf("Client disconnect\n");
495 } else if (iRead
> 0) {
496 if (strchr(chBuf
, CTRLC
)) {
497 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
499 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
500 telnetd_printf("error writing to socket\n");
503 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
504 telnetd_printf("Error writing to pipe\n");
510 if (!client
->bTerminate
)
511 TerminateShell(client
);
513 telnetd_printf("WriteToPipeThread terminated\n");
515 client
->bWriteToPipe
= FALSE
;
520 * Function: ReadFromPipeThread
522 * Abstract: Read data from the shell's stdout handle and
523 * pass it on to the telnet client socket.
525 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
530 CHAR txBuf
[BUFSIZE
*2];
532 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
534 client_t
*client
= (client_t
*) data
;
536 while (!client
->bTerminate
&& client
->bWriteToPipe
) {
537 // Since we do not want to block, first peek...
538 if (PeekNamedPipe(client
->hChildStdoutRd
, NULL
, 0, NULL
, &dwAvail
, NULL
) == 0) {
539 telnetd_printf("Failed to peek in pipe\n");
543 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
545 telnetd_printf("Failed to read from pipe\n");
548 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
549 txBuf
[to
] = chBuf
[from
];
550 if (txBuf
[to
] == '\n') {
556 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
557 telnetd_printf("error writing to socket\n");
561 Sleep(100); /* Hmmm, oh well... what the heck! */
564 if (!client
->bTerminate
)
565 TerminateShell(client
);
567 telnetd_printf("ReadFromPipeThread terminated\n");
569 client
->bReadFromPipe
= FALSE
;
574 static void TerminateShell(client_t
*client
)
578 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
580 GetExitCodeProcess(client
->hProcess
, &exitCode
);
582 if (exitCode
== STILL_ACTIVE
)
584 HANDLE hEvent
= NULL
;
587 telnetd_printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
589 hEvent
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
592 printf("CreateEvent error\n");
594 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
))
595 telnetd_printf("Failed to send Ctrl_break\n");
597 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
))
598 telnetd_printf("Failed to send Ctrl_C\n");
600 if (!WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
))
601 telnetd_printf("Error writing to pipe\n");
603 /* wait for our handler to be called */
604 dwWaitResult
=WaitForSingleObject(hEvent
, 500);
606 if (WAIT_FAILED
==dwWaitResult
)
607 telnetd_printf("WaitForSingleObject failed\n");
609 GetExitCodeProcess(client
->hProcess
, &exitCode
);
610 if (exitCode
== STILL_ACTIVE
)
612 telnetd_printf("user shell still active, attempt to terminate it now...\n");
616 if (!CloseHandle(hEvent
))
617 telnetd_printf("CloseHandle");
619 TerminateProcess(client
->hProcess
, 0);
621 TerminateProcess(client
->hProcess
, 0);
623 TerminateProcess(client
->hProcess
, 0);
627 static VOID
ErrorExit (LPTSTR lpszMessage
)
629 fprintf(stderr
, "%s\n", lpszMessage
);
630 if (bSocketInterfaceInitialised
) {
631 telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());