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.
16 * - will/won't handshake
17 * - Unify Debugging output and return StatusCodes
27 static BOOLEAN bShutdown
= 0;
28 static BOOLEAN bSocketInterfaceInitialised
= 0;
32 /* In the future, some options might be passed here to handle
33 * authentication options in the registry or command line
34 * options passed to the service
36 * Once you are ready to turn on the service
37 * rename this function
38 * int kickoff_telnetd(void)
40 int main(int argc
, char **argv
)
42 SetConsoleCtrlHandler(Cleanup
, 1);
44 if (!StartSocketInterface()) {
45 ErrorExit("Unable to start socket interface\n");
59 static BOOL WINAPI
Cleanup(DWORD dwControlType
)
61 if (bSocketInterfaceInitialised
) {
62 printf("Cleanup...\n");
68 /* StartSocketInterface */
69 static BOOLEAN
StartSocketInterface(void)
71 WORD wVersionRequested
;
75 wVersionRequested
= MAKEWORD( 2, 0 );
76 err
= WSAStartup(wVersionRequested
, &wsaData
);
78 printf("requested winsock version not supported\n");
82 bSocketInterfaceInitialised
= 1; /* for ErrorExit function */
84 if ( wsaData
.wVersion
!= wVersionRequested
) {
85 printf("requested winsock version not supported\n");
88 printf("TelnetD, using %s\n", wsaData
.szDescription
);
95 static void CreateSocket(void)
97 struct sockaddr_in sa
;
99 sock
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
101 ErrorExit("Cannot create socket");
104 memset(&sa
, 0, sizeof(sa
));
105 sa
.sin_family
= AF_INET
;
106 sa
.sin_addr
.s_addr
= INADDR_ANY
;
107 sa
.sin_port
= htons(TELNET_PORT
);
108 if (bind(sock
, (struct sockaddr
*) &sa
, sizeof(sa
)) != 0) {
109 ErrorExit("Cannot bind address to socket");
116 static void WaitForConnect(void)
118 struct sockaddr_in sa
;
121 if (listen(sock
, 1) < 0) {
122 ErrorExit("Cannot listen on socket");
125 if ((new_sock
= accept(sock
, (struct sockaddr
*) &sa
, NULL
)) < 0) {
126 fprintf(stderr
, "Failed to accept incoming call\n");
128 printf("user connected on socket %d, port %d, address %lx\n", new_sock
,
129 htons(sa
.sin_port
), sa
.sin_addr
.s_addr
);
136 ** Function: UserLogin
138 static void UserLogin(int client_socket
)
141 client_t
*client
= malloc(sizeof(client_t
));
143 if (client
== NULL
) {
144 ErrorExit("failed to allocate memory for client");
147 client
->socket
= client_socket
;
148 CreateThread(NULL
, 0, UserLoginThread
, client
, 0, &threadID
);
152 ** Function: UserLoginThread
154 static DWORD WINAPI
UserLoginThread(LPVOID data
)
156 client_t
*client
= (client_t
*) data
;
158 char hostname
[64] = "Unknown";
159 char *pwdPrompt
= "\r\npass:";
160 //char *logonPrompt = "\r\nLogin OK, please wait...";
161 //char *byebye = "\r\nWrong! bye bye...\r\n";
162 char userID
[USERID_SIZE
];
163 char password
[USERID_SIZE
];
167 if (DoTelnetHandshake(client
->socket
)) {
168 closesocket(client
->socket
);
173 gethostname(hostname
, sizeof(hostname
));
174 sprintf(welcome
, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname
);
176 if (send(client
->socket
, welcome
, strlen(welcome
), 0) < 0) {
177 closesocket(client
->socket
);
181 received
= ReceiveLine(client
->socket
, userID
, sizeof(userID
), Echo
);
183 closesocket(client
->socket
);
186 } else if (received
) {
187 if ((terminator
= strchr(userID
, CR
)) != NULL
) {
192 if (send(client
->socket
, pwdPrompt
, strlen(pwdPrompt
), 0) < 0) {
193 closesocket(client
->socket
);
197 received
= ReceiveLine(client
->socket
, password
, sizeof(password
), Password
);
201 closesocket(client
->socket
);
204 } else if (received
) {
205 if ((terminator
= strchr(password
, CR
)) != NULL
) {
211 /* TODO: do authentication here */
214 printf("User '%s' logged on\n", userID
);
216 strcpy(client
->userID
, userID
);
217 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
218 closesocket(client
->socket
);
228 ** Function: DoTelnetHandshake
230 static int DoTelnetHandshake(int sock
)
235 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
241 IAC WILL SUPPRESS_GO_AHEAD
242 IAC DO SUPPRESS_GO_AHEAD
247 IAC SB TERMINAL_TYPE
"\x01" IAC SE
250 unsigned char client_reply
[256];
252 if (send(sock
, will_echo
, sizeof(will_echo
), 0) < 0) {
256 /* Now wait for client response (and ignore it) */
261 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
262 /* check for error */
265 /* check for timeout */
266 } else if (retval
== 0) {
269 /* no error and no timeout, we have data in our sock */
270 received
= recv(sock
, (char *) client_reply
, sizeof(client_reply
), 0);
280 ** Function: ReceiveLine
282 ** Abstract: receive until timeout or CR
286 ** Pre : 'sock' must be valid socket
287 ** Post : (result = the number of bytes read into 'buffer')
288 ** OR (result = -1 and error)
290 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
295 struct timeval timeout
= { 0, 100000 };
296 char del
[3] = { BS
, ' ', BS
};
297 char asterisk
[1] = { '*' };
302 memset(buffer
, '\0', len
);
305 /* When we're in echo mode, we do not need a timeout */
306 retval
= select(0, &set
, NULL
, NULL
, (echo
? NULL
: &timeout
) );
307 /* check for error */
310 /* check for timeout */
311 } else if (retval
== 0) {
312 /* return number of characters received so far */
315 /* no error and no timeout, we have data in our sock */
316 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
319 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
320 /* ignore null characters and linefeeds from DOS telnet clients */
322 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
323 /* handle delete and backspace */
329 if (send(sock
, del
, sizeof(del
), 0) < 0) {
334 buffer
[i
] = BS
; /* Let shell process handle it */
338 /* echo typed characters */
339 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
341 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
344 if (buffer
[i
] == CR
) {
346 buffer
[i
] = LF
; /* append LF for DOS command processor */
359 ** Function: RunShell
361 static void RunShell(client_t
*client
)
364 HANDLE hChildStdinRd
;
365 HANDLE hChildStdinWr
;
366 HANDLE hChildStdoutRd
;
367 HANDLE hChildStdoutWr
;
369 PROCESS_INFORMATION piProcInfo
;
370 SECURITY_ATTRIBUTES saAttr
;
372 const char *name
= "c:\\windows\\system32\\cmd.exe";
373 const char *cmd
= NULL
;
374 //const char *name = "d:\\cygwin\\bin\\bash.exe";
375 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
377 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
378 saAttr
.bInheritHandle
= TRUE
;
379 saAttr
.lpSecurityDescriptor
= NULL
;
381 // Create a pipe for the child process's STDOUT.
382 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
383 ErrorExit("Stdout pipe creation failed\n");
385 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
386 ErrorExit("Stdin pipe creation failed\n");
389 client
->bTerminate
= FALSE
;
390 client
->bWriteToPipe
= TRUE
;
391 client
->bReadFromPipe
= TRUE
;
392 client
->hChildStdinWr
= hChildStdinWr
;
393 client
->hChildStdoutRd
= hChildStdoutRd
;
396 // Create the child process (the shell)
397 printf("Creating child process...\n");
399 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
400 si
.cb
= sizeof(STARTUPINFO
);
402 si
.dwFlags
= STARTF_USESTDHANDLES
;
403 si
.hStdInput
= hChildStdinRd
;
404 si
.hStdOutput
= hChildStdoutWr
;
405 si
.hStdError
= hChildStdoutWr
;
407 //si.dwFlags |= STARTF_USESHOWWINDOW;
408 //si.wShowWindow = SW_SHOW;
410 if (!CreateProcess((LPSTR
) name
, // executable module
411 (LPSTR
) cmd
, // command line
412 NULL
, // process security attributes
413 NULL
, // primary thread security attributes
414 TRUE
, // handles are inherited
415 DETACHED_PROCESS
+ // creation flags
416 CREATE_NEW_PROCESS_GROUP
,
417 NULL
, // use parent's environment
418 NULL
, // use parent's current directory
421 ErrorExit("Create process failed");
424 client
->hProcess
= piProcInfo
.hProcess
;
425 client
->dwProcessId
= piProcInfo
.dwProcessId
;
427 printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
429 // No longer need these in the parent...
430 if (!CloseHandle(hChildStdoutWr
))
431 ErrorExit("Closing handle failed");
432 if (!CloseHandle(hChildStdinRd
))
433 ErrorExit("Closing handle failed");
435 CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, &threadID
);
436 CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, &threadID
);
437 CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, &threadID
);
442 ** Function: MonitorChildThread
444 ** Abstract: Monitor the child (shell) process
446 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
449 client_t
*client
= (client_t
*) data
;
451 printf("Monitor thread running...\n");
453 WaitForSingleObject(client
->hProcess
, INFINITE
);
455 GetExitCodeProcess(client
->hProcess
, &exitCode
);
456 printf("Child process terminated with code %lx\n", exitCode
);
458 /* signal the other threads to give up */
459 client
->bTerminate
= TRUE
;
463 CloseHandle(client
->hChildStdoutRd
);
464 CloseHandle(client
->hChildStdinWr
);
465 CloseHandle(client
->hProcess
);
467 closesocket(client
->socket
);
469 printf("Waiting for all threads to give up..\n");
471 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
477 printf("Cleanup for user '%s'\n", client
->userID
);
483 ** Function: WriteToPipeThread
485 ** Abstract: read data from the telnet client socket
486 ** and pass it on to the shell process.
488 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
493 client_t
*client
= (client_t
*) data
;
495 while (!client
->bTerminate
) {
496 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
498 printf("Client disconnect\n");
500 } else if (iRead
> 0) {
501 if (strchr(chBuf
, CTRLC
)) {
502 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
504 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
505 printf("error writing to socket\n");
508 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
509 printf("Error writing to pipe\n");
515 if (!client
->bTerminate
) {
516 TerminateShell(client
);
519 printf("WriteToPipeThread terminated\n");
521 client
->bWriteToPipe
= FALSE
;
526 ** Function: ReadFromPipeThread
528 ** Abstract: Read data from the shell's stdout handle and
529 ** pass it on to the telnet client socket.
531 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
536 CHAR txBuf
[BUFSIZE
*2];
538 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
540 client_t
*client
= (client_t
*) data
;
542 while (!client
->bTerminate
&& client
->bWriteToPipe
) {
543 // Since we do not want to block, first peek...
544 if (PeekNamedPipe(client
->hChildStdoutRd
, NULL
, 0, NULL
, &dwAvail
, NULL
) == 0) {
545 printf("Failed to peek in pipe\n");
549 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
551 printf("Failed to read from pipe\n");
554 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
555 txBuf
[to
] = chBuf
[from
];
556 if (txBuf
[to
] == '\n') {
562 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
563 printf("error writing to socket\n");
567 Sleep(100); /* Hmmm, oh well... what the heck! */
570 if (!client
->bTerminate
) {
571 TerminateShell(client
);
574 printf("ReadFromPipeThread terminated\n");
576 client
->bReadFromPipe
= FALSE
;
583 static void TerminateShell(client_t
*client
)
587 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
589 GetExitCodeProcess(client
->hProcess
, &exitCode
);
590 if (exitCode
== STILL_ACTIVE
) {
591 printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
593 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
)) {
594 printf("Failed to send Ctrl_break\n");
599 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
)) {
600 printf("Failed to send Ctrl_C\n");
605 if (! WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
)) {
606 printf("Error writing to pipe\n");
611 GetExitCodeProcess(client
->hProcess
, &exitCode
);
612 if (exitCode
== STILL_ACTIVE
) {
613 printf("user shell still active, attempt to terminate it now...\n");
614 TerminateProcess(client
->hProcess
, 0);
622 static VOID
ErrorExit (LPTSTR lpszMessage
)
624 fprintf(stderr
, "%s\n", lpszMessage
);
625 if (bSocketInterfaceInitialised
) {
626 printf("WSAGetLastError=%d\n", WSAGetLastError());