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
25 #define telnetd_printf printf
28 extern void syslog (int priority
, const char *fmt
, ...);
30 int telnetd_printf(const char *format
, ...)
38 static BOOLEAN bShutdown
= 0;
39 static BOOLEAN bSocketInterfaceInitialised
= 0;
43 /* In the future, some options might be passed here to handle
44 * authentication options in the registry or command line
45 * options passed to the service
47 * Once you are ready to turn on the service
48 * rename this function
49 * int kickoff_telnetd(void)
51 int main(int argc
, char **argv
)
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");
147 client
->socket
= client_socket
;
148 CreateThread(NULL
, 0, UserLoginThread
, client
, 0, &threadID
);
151 /* Function: UserLoginThread */
152 static DWORD WINAPI
UserLoginThread(LPVOID data
)
154 client_t
*client
= (client_t
*) data
;
156 char hostname
[64] = "Unknown";
157 char *pwdPrompt
= "\r\npass:";
158 //char *logonPrompt = "\r\nLogin OK, please wait...";
159 //char *byebye = "\r\nWrong! bye bye...\r\n";
160 char userID
[USERID_SIZE
];
161 char password
[USERID_SIZE
];
165 if (DoTelnetHandshake(client
->socket
)) {
166 closesocket(client
->socket
);
171 gethostname(hostname
, sizeof(hostname
));
172 sprintf(welcome
, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname
);
174 if (send(client
->socket
, welcome
, strlen(welcome
), 0) < 0) {
175 closesocket(client
->socket
);
179 received
= ReceiveLine(client
->socket
, userID
, sizeof(userID
), Echo
);
181 closesocket(client
->socket
);
184 } else if (received
) {
185 if ((terminator
= strchr(userID
, CR
)) != NULL
) {
190 if (send(client
->socket
, pwdPrompt
, strlen(pwdPrompt
), 0) < 0) {
191 closesocket(client
->socket
);
195 received
= ReceiveLine(client
->socket
, password
, sizeof(password
), Password
);
199 closesocket(client
->socket
);
202 } else if (received
) {
203 if ((terminator
= strchr(password
, CR
)) != NULL
) {
209 /* TODO: do authentication here */
212 telnetd_printf("User '%p' logged on\n", userID
);
214 strcpy(client
->userID
, userID
);
215 if (send(client
->socket
, logonPrompt
, strlen(logonPrompt
), 0) < 0) {
216 closesocket(client
->socket
);
225 /* Function: DoTelnetHandshake */
226 static int DoTelnetHandshake(int sock
)
231 struct timeval timeout
= { HANDSHAKE_TIMEOUT
, 0 };
237 IAC WILL SUPPRESS_GO_AHEAD
238 IAC DO SUPPRESS_GO_AHEAD
243 IAC SB TERMINAL_TYPE
"\x01" IAC SE
246 unsigned char client_reply
[256];
248 if (send(sock
, will_echo
, sizeof(will_echo
), 0) < 0) {
252 /* Now wait for client response (and ignore it) */
257 retval
= select(0, &set
, NULL
, NULL
, &timeout
);
258 /* check for error */
261 /* check for timeout */
262 } else if (retval
== 0) {
265 /* no error and no timeout, we have data in our sock */
266 received
= recv(sock
, (char *) client_reply
, sizeof(client_reply
), 0);
276 ** Function: ReceiveLine
278 ** Abstract: receive until timeout or CR
282 ** Pre : 'sock' must be valid socket
283 ** Post : (result = the number of bytes read into 'buffer')
284 ** OR (result = -1 and error)
286 static int ReceiveLine(int sock
, char *buffer
, int len
, EchoMode echo
)
291 struct timeval timeout
= { 0, 100000 };
292 char del
[3] = { BS
, ' ', BS
};
293 char asterisk
[1] = { '*' };
298 memset(buffer
, '\0', len
);
301 /* When we're in echo mode, we do not need a timeout */
302 retval
= select(0, &set
, NULL
, NULL
, (echo
? NULL
: &timeout
) );
303 /* check for error */
306 /* check for timeout */
307 } else if (retval
== 0) {
308 /* return number of characters received so far */
311 /* no error and no timeout, we have data in our sock */
312 if (recv(sock
, &buffer
[i
], 1, 0) <= 0) {
315 if ((buffer
[i
] == '\0') || (buffer
[i
] == LF
)) {
316 /* ignore null characters and linefeeds from DOS telnet clients */
318 } else if ((buffer
[i
] == DEL
) || (buffer
[i
] == BS
)) {
319 /* handle delete and backspace */
325 if (send(sock
, del
, sizeof(del
), 0) < 0) {
330 buffer
[i
] = BS
; /* Let shell process handle it */
334 /* echo typed characters */
335 if (echo
== Echo
&& send(sock
, &buffer
[i
], 1, 0) < 0) {
337 } else if (echo
== Password
&& send(sock
, asterisk
, sizeof(asterisk
), 0) < 0) {
340 if (buffer
[i
] == CR
) {
342 buffer
[i
] = LF
; /* append LF for DOS command processor */
355 ** Function: RunShell
357 static void RunShell(client_t
*client
)
360 HANDLE hChildStdinRd
;
361 HANDLE hChildStdinWr
;
362 HANDLE hChildStdoutRd
;
363 HANDLE hChildStdoutWr
;
365 PROCESS_INFORMATION piProcInfo
;
366 SECURITY_ATTRIBUTES saAttr
;
368 const char *name
= "c:\\windows\\system32\\cmd.exe";
369 const char *cmd
= NULL
;
370 //const char *name = "d:\\cygwin\\bin\\bash.exe";
371 //const char *cmd = "d:\\cygwin\\bin\\bash.exe --login -i";
373 saAttr
.nLength
= sizeof(SECURITY_ATTRIBUTES
);
374 saAttr
.bInheritHandle
= TRUE
;
375 saAttr
.lpSecurityDescriptor
= NULL
;
377 // Create a pipe for the child process's STDOUT.
378 if (! CreatePipe(&hChildStdoutRd
, &hChildStdoutWr
, &saAttr
, 0))
379 ErrorExit("Stdout pipe creation failed\n");
381 if (! CreatePipe(&hChildStdinRd
, &hChildStdinWr
, &saAttr
, 0))
382 ErrorExit("Stdin pipe creation failed\n");
385 client
->bTerminate
= FALSE
;
386 client
->bWriteToPipe
= TRUE
;
387 client
->bReadFromPipe
= TRUE
;
388 client
->hChildStdinWr
= hChildStdinWr
;
389 client
->hChildStdoutRd
= hChildStdoutRd
;
392 // Create the child process (the shell)
393 telnetd_printf("Creating child process...\n");
395 ZeroMemory( &si
, sizeof(STARTUPINFO
) );
396 si
.cb
= sizeof(STARTUPINFO
);
398 si
.dwFlags
= STARTF_USESTDHANDLES
;
399 si
.hStdInput
= hChildStdinRd
;
400 si
.hStdOutput
= hChildStdoutWr
;
401 si
.hStdError
= hChildStdoutWr
;
403 //si.dwFlags |= STARTF_USESHOWWINDOW;
404 //si.wShowWindow = SW_SHOW;
406 if (!CreateProcess((LPSTR
) name
, // executable module
407 (LPSTR
) cmd
, // command line
408 NULL
, // process security attributes
409 NULL
, // primary thread security attributes
410 TRUE
, // handles are inherited
411 DETACHED_PROCESS
+ // creation flags
412 CREATE_NEW_PROCESS_GROUP
,
413 NULL
, // use parent's environment
414 NULL
, // use parent's current directory
417 ErrorExit("Create process failed");
420 client
->hProcess
= piProcInfo
.hProcess
;
421 client
->dwProcessId
= piProcInfo
.dwProcessId
;
423 telnetd_printf("New child created (groupid=%lu)\n", client
->dwProcessId
);
425 // No longer need these in the parent...
426 if (!CloseHandle(hChildStdoutWr
))
427 ErrorExit("Closing handle failed");
429 if (!CloseHandle(hChildStdinRd
))
430 ErrorExit("Closing handle failed");
432 CreateThread(NULL
, 0, WriteToPipeThread
, client
, 0, &threadID
);
433 CreateThread(NULL
, 0, ReadFromPipeThread
, client
, 0, &threadID
);
434 CreateThread(NULL
, 0, MonitorChildThread
, client
, 0, &threadID
);
438 * Function: MonitorChildThread
440 * Abstract: Monitor the child (shell) process
442 static DWORD WINAPI
MonitorChildThread(LPVOID data
)
445 client_t
*client
= (client_t
*) data
;
447 telnetd_printf("Monitor thread running...\n");
449 WaitForSingleObject(client
->hProcess
, INFINITE
);
451 GetExitCodeProcess(client
->hProcess
, &exitCode
);
452 telnetd_printf("Child process terminated with code %lx\n", exitCode
);
454 /* signal the other threads to give up */
455 client
->bTerminate
= TRUE
;
459 CloseHandle(client
->hChildStdoutRd
);
460 CloseHandle(client
->hChildStdinWr
);
461 CloseHandle(client
->hProcess
);
463 closesocket(client
->socket
);
465 telnetd_printf("Waiting for all threads to give up..\n");
467 while (client
->bWriteToPipe
|| client
->bReadFromPipe
) {
473 telnetd_printf("Cleanup for user '%s'\n", client
->userID
);
479 * Function: WriteToPipeThread
481 * Abstract: read data from the telnet client socket
482 * and pass it on to the shell process.
484 static DWORD WINAPI
WriteToPipeThread(LPVOID data
)
489 client_t
*client
= (client_t
*) data
;
491 while (!client
->bTerminate
) {
492 iRead
= ReceiveLine(client
->socket
, chBuf
, BUFSIZE
, FALSE
);
494 telnetd_printf("Client disconnect\n");
496 } else if (iRead
> 0) {
497 if (strchr(chBuf
, CTRLC
)) {
498 GenerateConsoleCtrlEvent(CTRL_C_EVENT
, client
->dwProcessId
);
500 if (send(client
->socket
, chBuf
, iRead
, 0) < 0) {
501 telnetd_printf("error writing to socket\n");
504 if (! WriteFile(client
->hChildStdinWr
, chBuf
, (DWORD
) iRead
, &dwWritten
, NULL
)) {
505 telnetd_printf("Error writing to pipe\n");
511 if (!client
->bTerminate
)
512 TerminateShell(client
);
514 telnetd_printf("WriteToPipeThread terminated\n");
516 client
->bWriteToPipe
= FALSE
;
521 * Function: ReadFromPipeThread
523 * Abstract: Read data from the shell's stdout handle and
524 * pass it on to the telnet client socket.
526 static DWORD WINAPI
ReadFromPipeThread(LPVOID data
)
531 CHAR txBuf
[BUFSIZE
*2];
533 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
535 client_t
*client
= (client_t
*) data
;
537 while (!client
->bTerminate
&& client
->bWriteToPipe
) {
538 // Since we do not want to block, first peek...
539 if (PeekNamedPipe(client
->hChildStdoutRd
, NULL
, 0, NULL
, &dwAvail
, NULL
) == 0) {
540 telnetd_printf("Failed to peek in pipe\n");
544 if( ! ReadFile( client
->hChildStdoutRd
, chBuf
, BUFSIZE
, &dwRead
, NULL
) ||
546 telnetd_printf("Failed to read from pipe\n");
549 for (from
=0, to
=0; from
<dwRead
; from
++, to
++) {
550 txBuf
[to
] = chBuf
[from
];
551 if (txBuf
[to
] == '\n') {
557 if (send(client
->socket
, txBuf
, to
, 0) < 0) {
558 telnetd_printf("error writing to socket\n");
562 Sleep(100); /* Hmmm, oh well... what the heck! */
565 if (!client
->bTerminate
)
566 TerminateShell(client
);
568 telnetd_printf("ReadFromPipeThread terminated\n");
570 client
->bReadFromPipe
= FALSE
;
575 static void TerminateShell(client_t
*client
)
579 char stop
[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
581 GetExitCodeProcess(client
->hProcess
, &exitCode
);
582 if (exitCode
== STILL_ACTIVE
) {
583 telnetd_printf("user shell still active, send Ctrl-Break to group-id %lu\n", client
->dwProcessId
);
585 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT
, client
->dwProcessId
))
586 telnetd_printf("Failed to send Ctrl_break\n");
590 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT
, client
->dwProcessId
))
591 telnetd_printf("Failed to send Ctrl_C\n");
595 if (!WriteFile(client
->hChildStdinWr
, stop
, sizeof(stop
), &dwWritten
, NULL
))
596 telnetd_printf("Error writing to pipe\n");
600 GetExitCodeProcess(client
->hProcess
, &exitCode
);
601 if (exitCode
== STILL_ACTIVE
) {
602 telnetd_printf("user shell still active, attempt to terminate it now...\n");
603 TerminateProcess(client
->hProcess
, 0);
609 static VOID
ErrorExit (LPTSTR lpszMessage
)
611 fprintf(stderr
, "%s\n", lpszMessage
);
612 if (bSocketInterfaceInitialised
) {
613 telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());