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
);
266 closesocket(client
->socket
);
269 } else if (received
) {
270 if ((terminator
= strchr(password
, CR
)) != NULL
) {
276 /* TODO: do authentication here */
279 printf("User '%s' logged on\n", userID
);
280 strcpy(client
->userID
, userID
);
281 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
282 closesocket(client
->socket
);
291 ** Function: DoTelnetHandshake
293 static int DoTelnetHandshake(int sock
)
298 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
299 unsigned char will_echo
[3] = { IAC
, WILL
, ECHO
};
300 unsigned char client_reply
[256];
302 if (send(sock
, (const char *) will_echo
, sizeof(will_echo
), 0) < 0) {
306 /* Now wait for client response (and ignore it) */
311 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
312 /* check for error */
315 /* check for timeout */
316 } else if (retval
== 0) {
319 /* no error and no timeout, we have data in our sock */
320 received
= recv(sock
, client_reply
, sizeof(client_reply
), 0);
330 ** Function: ReceiveLine
332 ** Abstract: receive until timeout or CR
336 ** Pre : 'sock' must be valid socket
337 ** Post : (result = the number of bytes read into 'buffer')
338 ** OR (result = -1 and error)
340 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
345 struct timeval timeout
= { 0, 100000 };
346 char del
[3] = { BS
, ' ', BS
};
347 char asterisk
[1] = { '*' };
352 memset(buffer
, '\0', len
);
355 /* When we're in echo mode, we do not need a timeout */
356 retval
= select(0, &set
, NULL
, NULL
, (echo
? NULL
: &timeout
) );
357 /* check for error */
360 /* check for timeout */
361 } else if (retval
== 0) {
362 /* return number of characters received so far */
365 /* no error and no timeout, we have data in our sock */
366 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
369 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
370 /* ignore null characters and linefeeds from DOS telnet clients */
372 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
373 /* handle delete and backspace */
379 if (send(sock
, del
, sizeof(del
), 0) < 0) {
384 buffer
[i
] = BS
; /* Let shell process handle it */
388 /* echo typed characters */
389 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
391 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
394 if (buffer
[i
] == CR
) {
396 buffer
[i
] = LF
; /* append LF for DOS command processor */
409 ** Function: RunShell
411 static void RunShell(client_t
*client
)
414 HANDLE hChildStdinRd
;
415 HANDLE hChildStdinWr
;
416 HANDLE hChildStdoutRd
;
417 HANDLE hChildStdoutWr
;
419 PROCESS_INFORMATION piProcInfo
;
420 SECURITY_ATTRIBUTES saAttr
;
422 const char *name
= "c:\\windows\\system32\\cmd.exe";
423 const char *cmd
= NULL
;
424 //const char *name = "d:\\cygwin\\bin\\bash.exe";
425 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
427 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
428 saAttr
.bInheritHandle
= TRUE
;
429 saAttr
.lpSecurityDescriptor
= NULL
;
431 // Create a pipe for the child process's STDOUT.
432 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
433 ErrorExit("Stdout pipe creation failed\n");
435 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
436 ErrorExit("Stdin pipe creation failed\n");
439 client
->bTerminate
= FALSE
;
440 client
->bWriteToPipe
= TRUE
;
441 client
->bReadFromPipe
= TRUE
;
442 client
->hChildStdinWr
= hChildStdinWr
;
443 client
->hChildStdoutRd
= hChildStdoutRd
;
446 // Create the child process (the shell)
447 printf("Creating child process...\n");
449 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
450 si
.cb
= sizeof(STARTUPINFO
);
452 si
.dwFlags
= STARTF_USESTDHANDLES
;
453 si
.hStdInput
= hChildStdinRd
;
454 si
.hStdOutput
= hChildStdoutWr
;
455 si
.hStdError
= hChildStdoutWr
;
457 //si.dwFlags |= STARTF_USESHOWWINDOW;
458 //si.wShowWindow = SW_SHOW;
460 if (!CreateProcess((LPSTR
) name
, // executable module
461 (LPSTR
) cmd
, // command line
462 NULL
, // process security attributes
463 NULL
, // primary thread security attributes
464 TRUE
, // handles are inherited
465 //DETACHED_PROCESS + // creation flags
466 CREATE_NEW_PROCESS_GROUP
,
467 NULL
, // use parent's environment
468 NULL
, // use parent's current directory
471 ErrorExit("Create process failed");
474 client
->hProcess
= piProcInfo
.hProcess
;
475 client
->dwProcessId
= piProcInfo
.dwProcessId
;
477 printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
479 // No longer need these in the parent...
480 if (!CloseHandle(hChildStdoutWr
))
481 ErrorExit("Closing handle failed");
482 if (!CloseHandle(hChildStdinRd
))
483 ErrorExit("Closing handle failed");
485 CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, &threadID
);
486 CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, &threadID
);
487 CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, &threadID
);
492 ** Function: MonitorChildThread
494 ** Abstract: Monitor the child (shell) process
496 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
499 client_t
*client
= (client_t
*) data
;
501 printf("Monitor thread running...\n");
503 WaitForSingleObject(client
->hProcess
, INFINITE
);
505 GetExitCodeProcess(client
->hProcess
, &exitCode
);
506 printf("Child process terminated with code %d\n", exitCode
);
508 /* signal the other threads to give up */
509 client
->bTerminate
= TRUE
;
513 CloseHandle(client
->hChildStdoutRd
);
514 CloseHandle(client
->hChildStdinWr
);
515 CloseHandle(client
->hProcess
);
517 closesocket(client
->socket
);
519 printf("Waiting for all threads to give up..\n");
521 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
527 printf("Cleanup for user '%s'\n", client
->userID
);
533 ** Function: WriteToPipeThread
535 ** Abstract: read data from the telnet client socket
536 ** and pass it on to the shell process.
538 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
543 client_t
*client
= (client_t
*) data
;
545 while (!client
->bTerminate
) {
546 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
548 printf("Client disconnect\n");
550 } else if (iRead
> 0) {
551 if (strchr(chBuf
, CTRLC
)) {
552 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
554 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
555 printf("error writing to socket\n");
558 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
559 printf("Error writing to pipe\n");
565 if (!client
->bTerminate
) {
566 TerminateShell(client
);
569 printf("WriteToPipeThread terminated\n");
571 client
->bWriteToPipe
= FALSE
;
576 ** Function: ReadFromPipeThread
578 ** Abstract: Read data from the shell's stdout handle and
579 ** pass it on to the telnet client socket.
581 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
586 CHAR txBuf
[BUFSIZE
*2];
588 char warning
[] = "warning: rl_prep_terminal: cannot get terminal settings";
590 client_t
*client
= (client_t
*) data
;
592 while (!client
->bTerminate
&& client
->bWriteToPipe
) {
593 // Since we do not want to block, first peek...
594 if (PeekNamedPipe(client
->hChildStdoutRd
, NULL
, 0, NULL
, &dwAvail
, NULL
) == 0) {
595 printf("Failed to peek in pipe\n");
599 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
601 printf("Failed to read from pipe\n");
604 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
605 txBuf
[to
] = chBuf
[from
];
606 if (txBuf
[to
] == '\n') {
612 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
613 printf("error writing to socket\n");
617 Sleep(100); /* Hmmm, oh well... what the heck! */
620 if (!client
->bTerminate
) {
621 TerminateShell(client
);
624 printf("ReadFromPipeThread terminated\n");
626 client
->bReadFromPipe
= FALSE
;
633 static void TerminateShell(client_t
*client
)
637 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
639 GetExitCodeProcess(client
->hProcess
, &exitCode
);
640 if (exitCode
== STILL_ACTIVE
) {
641 printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
643 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
)) {
644 printf("Failed to send Ctrl_break\n");
649 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
)) {
650 printf("Failed to send Ctrl_C\n");
655 if (! WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
)) {
656 printf("Error writing to pipe\n");
661 GetExitCodeProcess(client
->hProcess
, &exitCode
);
662 if (exitCode
== STILL_ACTIVE
) {
663 printf("user shell still active, attempt to terminate it now...\n");
664 TerminateProcess(client
->hProcess
, 0);
672 static VOID
ErrorExit (LPTSTR lpszMessage
)
674 fprintf(stderr
, "%s\n", lpszMessage
);
675 if (bSocketInterfaceInitialised
) {
676 printf("WSAGetLastError=%d\n", WSAGetLastError());