4 * Abstract: a simple telnet 'daemon' for Windows hosts.
6 * Compiled & run successfully using MSVC 5.0 under Windows95 (requires
7 * Winsock2 update) and Windows98 and MSVC 6.0 under WindowsNT4
9 * Compiler options : no special options needed
10 * Linker options : add wsock32.lib or ws2_32.lib
12 * Written by fred.van.lieshout 'at' zonnet.nl
13 * Use freely, no copyrights.
18 * - will/won't handshake
19 * - (run as) Windows NT service
28 #define TELNET_PORT (23)
30 #define BUFSIZE (4096)
31 #define USERID_SIZE (64)
45 #define HANDSHAKE_TIMEOUT (3)
51 typedef struct client_s
53 char userID
[USERID_SIZE
];
56 BOOLEAN bReadFromPipe
;
61 HANDLE hChildStdoutRd
;
75 static BOOLEAN bShutdown
= 0;
76 static BOOLEAN bSocketInterfaceInitialised
= 0;
81 ** Forward function declarations
83 static BOOL WINAPI
Cleanup(DWORD dwControlType
);
84 static void WaitForConnect(void);
85 static BOOLEAN
StartSocketInterface(void);
86 static void CreateSocket(void);
87 static void UserLogin(int client_socket
);
88 static DWORD WINAPI
UserLoginThread(LPVOID
);
89 static int DoTelnetHandshake(int sock
);
90 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
);
91 static void RunShell(client_t
*client
);
92 static BOOL
CreateChildProcess(const char *);
93 static DWORD WINAPI
MonitorChildThread(LPVOID
);
94 static DWORD WINAPI
WriteToPipeThread(LPVOID
);
95 static DWORD WINAPI
ReadFromPipeThread(LPVOID
);
96 static void TerminateShell(client_t
*client
);
97 static VOID
ErrorExit(LPTSTR
);
105 SetConsoleCtrlHandler(Cleanup
, 1);
107 if (!StartSocketInterface()) {
108 ErrorExit("Unable to start socket interface\n");
124 static BOOL WINAPI
Cleanup(DWORD dwControlType
)
126 if (bSocketInterfaceInitialised
) {
127 printf("Cleanup...\n");
134 ** StartSocketInterface
136 static BOOLEAN
StartSocketInterface(void)
138 WORD wVersionRequested
;
142 wVersionRequested
= MAKEWORD( 2, 0 );
143 err
= WSAStartup(wVersionRequested
, &wsaData
);
145 printf("requested winsock version not supported\n");
149 bSocketInterfaceInitialised
= 1; /* for ErrorExit function */
151 if ( wsaData
.wVersion
!= wVersionRequested
) {
152 printf("requested winsock version not supported\n");
155 printf("TelnetD, using %s\n", wsaData
.szDescription
);
162 static void CreateSocket(void)
164 struct sockaddr_in sa
;
166 sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
168 ErrorExit("Cannot create socket");
171 memset(&sa
, 0, sizeof(sa
));
172 sa
.sin_family
= AF_INET
;
173 sa
.sin_addr
.s_addr
= INADDR_ANY
;
174 sa
.sin_port
= htons(TELNET_PORT
);
175 if (bind(sock
, (struct sockaddr
*) &sa
, sizeof(sa
)) != 0) {
176 ErrorExit("Cannot bind address to socket");
183 static void WaitForConnect(void)
185 struct sockaddr_in sa
;
188 if (listen(sock
, 1) < 0) {
189 ErrorExit("Cannot listen on socket");
192 if ((new_sock
= accept(sock
, (struct sockaddr
*) &sa
, NULL
)) < 0) {
193 fprintf(stderr
, "Failed to accept incoming call\n");
195 printf("user connected on socket %d, port %d, address %lx\n", new_sock
,
196 htons(sa
.sin_port
), sa
.sin_addr
.s_addr
);
203 ** Function: UserLogin
205 static void UserLogin(int client_socket
)
208 client_t
*client
= malloc(sizeof(client_t
));
210 if (client
== NULL
) {
211 ErrorExit("failed to allocate memory for client");
214 client
->socket
= client_socket
;
215 CreateThread(NULL
, 0, UserLoginThread
, client
, 0, &threadID
);
219 ** Function: UserLoginThread
221 static DWORD WINAPI
UserLoginThread(LPVOID data
)
223 client_t
*client
= (client_t
*) data
;
225 char hostname
[64] = "Unknown";
226 char *pwdPrompt
= "\r\npass:";
227 char *logonPrompt
= "\r\nLogin OK, please wait...";
228 char *byebye
= "\r\nWrong! bye bye...\r\n";
229 char userID
[USERID_SIZE
];
230 char password
[USERID_SIZE
];
234 if (DoTelnetHandshake(client
->socket
)) {
235 closesocket(client
->socket
);
240 gethostname(hostname
, sizeof(hostname
));
241 sprintf(welcome
, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname
);
243 if (send(client
->socket
, welcome
, strlen(welcome
), 0) < 0) {
244 closesocket(client
->socket
);
248 received
= ReceiveLine(client
->socket
, userID
, sizeof(userID
), Echo
);
250 closesocket(client
->socket
);
253 } else if (received
) {
254 if ((terminator
= strchr(userID
, CR
)) != NULL
) {
259 if (send(client
->socket
, pwdPrompt
, strlen(pwdPrompt
), 0) < 0) {
260 closesocket(client
->socket
);
264 received
= ReceiveLine(client
->socket
, password
, sizeof(password
), Password
);
268 closesocket(client
->socket
);
271 } else if (received
) {
272 if ((terminator
= strchr(password
, CR
)) != NULL
) {
278 /* TODO: do authentication here */
281 printf("User '%s' logged on\n", userID
);
283 strcpy(client
->userID
, userID
);
284 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
285 closesocket(client
->socket
);
295 ** Function: DoTelnetHandshake
297 static int DoTelnetHandshake(int sock
)
302 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
303 unsigned char will_echo
[3] = { IAC
, WILL
, ECHO
};
304 unsigned char client_reply
[256];
306 if (send(sock
, (const char *) will_echo
, sizeof(will_echo
), 0) < 0) {
310 /* Now wait for client response (and ignore it) */
315 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
316 /* check for error */
319 /* check for timeout */
320 } else if (retval
== 0) {
323 /* no error and no timeout, we have data in our sock */
324 received
= recv(sock
, client_reply
, sizeof(client_reply
), 0);
334 ** Function: ReceiveLine
336 ** Abstract: receive until timeout or CR
340 ** Pre : 'sock' must be valid socket
341 ** Post : (result = the number of bytes read into 'buffer')
342 ** OR (result = -1 and error)
344 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
349 struct timeval timeout
= { 0, 100000 };
350 char del
[3] = { BS
, ' ', BS
};
351 char asterisk
[1] = { '*' };
356 memset(buffer
, '\0', len
);
359 /* When we're in echo mode, we do not need a timeout */
360 retval
= select(0, &set
, NULL
, NULL
, (echo
? NULL
: &timeout
) );
361 /* check for error */
364 /* check for timeout */
365 } else if (retval
== 0) {
366 /* return number of characters received so far */
369 /* no error and no timeout, we have data in our sock */
370 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
373 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
374 /* ignore null characters and linefeeds from DOS telnet clients */
376 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
377 /* handle delete and backspace */
383 if (send(sock
, del
, sizeof(del
), 0) < 0) {
388 buffer
[i
] = BS
; /* Let shell process handle it */
392 /* echo typed characters */
393 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
395 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
398 if (buffer
[i
] == CR
) {
400 buffer
[i
] = LF
; /* append LF for DOS command processor */
413 ** Function: RunShell
415 static void RunShell(client_t
*client
)
418 HANDLE hChildStdinRd
;
419 HANDLE hChildStdinWr
;
420 HANDLE hChildStdoutRd
;
421 HANDLE hChildStdoutWr
;
423 PROCESS_INFORMATION piProcInfo
;
424 SECURITY_ATTRIBUTES saAttr
;
426 const char *name
= "c:\\windows\\system32\\cmd.exe";
427 const char *cmd
= NULL
;
428 //const char *name = "d:\\cygwin\\bin\\bash.exe";
429 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
431 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
432 saAttr
.bInheritHandle
= TRUE
;
433 saAttr
.lpSecurityDescriptor
= NULL
;
435 // Create a pipe for the child process's STDOUT.
436 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
437 ErrorExit("Stdout pipe creation failed\n");
439 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
440 ErrorExit("Stdin pipe creation failed\n");
443 client
->bTerminate
= FALSE
;
444 client
->bWriteToPipe
= TRUE
;
445 client
->bReadFromPipe
= TRUE
;
446 client
->hChildStdinWr
= hChildStdinWr
;
447 client
->hChildStdoutRd
= hChildStdoutRd
;
450 // Create the child process (the shell)
451 printf("Creating child process...\n");
453 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
454 si
.cb
= sizeof(STARTUPINFO
);
456 si
.dwFlags
= STARTF_USESTDHANDLES
;
457 si
.hStdInput
= hChildStdinRd
;
458 si
.hStdOutput
= hChildStdoutWr
;
459 si
.hStdError
= hChildStdoutWr
;
461 //si.dwFlags |= STARTF_USESHOWWINDOW;
462 //si.wShowWindow = SW_SHOW;
464 if (!CreateProcess((LPSTR
) name
, // executable module
465 (LPSTR
) cmd
, // command line
466 NULL
, // process security attributes
467 NULL
, // primary thread security attributes
468 TRUE
, // handles are inherited
469 //DETACHED_PROCESS + // creation flags
470 CREATE_NEW_PROCESS_GROUP
,
471 NULL
, // use parent's environment
472 NULL
, // use parent's current directory
475 ErrorExit("Create process failed");
478 client
->hProcess
= piProcInfo
.hProcess
;
479 client
->dwProcessId
= piProcInfo
.dwProcessId
;
481 printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
483 // No longer need these in the parent...
484 if (!CloseHandle(hChildStdoutWr
))
485 ErrorExit("Closing handle failed");
486 if (!CloseHandle(hChildStdinRd
))
487 ErrorExit("Closing handle failed");
489 CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, &threadID
);
490 CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, &threadID
);
491 CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, &threadID
);
496 ** Function: MonitorChildThread
498 ** Abstract: Monitor the child (shell) process
500 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
503 client_t
*client
= (client_t
*) data
;
505 printf("Monitor thread running...\n");
507 WaitForSingleObject(client
->hProcess
, INFINITE
);
509 GetExitCodeProcess(client
->hProcess
, &exitCode
);
510 printf("Child process terminated with code %d\n", exitCode
);
512 /* signal the other threads to give up */
513 client
->bTerminate
= TRUE
;
517 CloseHandle(client
->hChildStdoutRd
);
518 CloseHandle(client
->hChildStdinWr
);
519 CloseHandle(client
->hProcess
);
521 closesocket(client
->socket
);
523 printf("Waiting for all threads to give up..\n");
525 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
531 printf("Cleanup for user '%s'\n", client
->userID
);
537 ** Function: WriteToPipeThread
539 ** Abstract: read data from the telnet client socket
540 ** and pass it on to the shell process.
542 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
547 client_t
*client
= (client_t
*) data
;
549 while (!client
->bTerminate
) {
550 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
552 printf("Client disconnect\n");
554 } else if (iRead
> 0) {
555 if (strchr(chBuf
, CTRLC
)) {
556 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
558 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
559 printf("error writing to socket\n");
562 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
563 printf("Error writing to pipe\n");
569 if (!client
->bTerminate
) {
570 TerminateShell(client
);
573 printf("WriteToPipeThread terminated\n");
575 client
->bWriteToPipe
= FALSE
;
580 ** Function: ReadFromPipeThread
582 ** Abstract: Read data from the shell's stdout handle and
583 ** pass it on to the telnet client socket.
585 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
590 CHAR txBuf
[BUFSIZE
*2];
592 char warning
[] = "warning: rl_prep_terminal: cannot get terminal settings";
594 client_t
*client
= (client_t
*) data
;
596 while (!client
->bTerminate
&& client
->bWriteToPipe
) {
597 // Since we do not want to block, first peek...
598 if (PeekNamedPipe(client
->hChildStdoutRd
, NULL
, 0, NULL
, &dwAvail
, NULL
) == 0) {
599 printf("Failed to peek in pipe\n");
603 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
605 printf("Failed to read from pipe\n");
608 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
609 txBuf
[to
] = chBuf
[from
];
610 if (txBuf
[to
] == '\n') {
616 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
617 printf("error writing to socket\n");
621 Sleep(100); /* Hmmm, oh well... what the heck! */
624 if (!client
->bTerminate
) {
625 TerminateShell(client
);
628 printf("ReadFromPipeThread terminated\n");
630 client
->bReadFromPipe
= FALSE
;
637 static void TerminateShell(client_t
*client
)
641 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
643 GetExitCodeProcess(client
->hProcess
, &exitCode
);
644 if (exitCode
== STILL_ACTIVE
) {
645 printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
647 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
)) {
648 printf("Failed to send Ctrl_break\n");
653 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
)) {
654 printf("Failed to send Ctrl_C\n");
659 if (! WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
)) {
660 printf("Error writing to pipe\n");
665 GetExitCodeProcess(client
->hProcess
, &exitCode
);
666 if (exitCode
== STILL_ACTIVE
) {
667 printf("user shell still active, attempt to terminate it now...\n");
668 TerminateProcess(client
->hProcess
, 0);
676 static VOID
ErrorExit (LPTSTR lpszMessage
)
678 fprintf(stderr
, "%s\n", lpszMessage
);
679 if (bSocketInterfaceInitialised
) {
680 printf("WSAGetLastError=%d\n", WSAGetLastError());