Always call TerminateProcess when exiting a session.
[reactos.git] / rosapps / applications / sysutils / telnetd / telnetd.c
1 /*
2 * Abstract: a simple telnet 'daemon' for Windows hosts.
3 *
4 * Compiled & run successfully using MSVC 5.0 under Windows95 (requires
5 * Winsock2 update) and Windows98 and MSVC 6.0 under WindowsNT4
6 *
7 * Compiler options : no special options needed
8 * Linker options : add wsock32.lib or ws2_32.lib
9 *
10 * Written by fred.van.lieshout 'at' zonnet.nl
11 * Use freely, no copyrights.
12 * Use Linux.
13 *
14 * Parts Copyright Steven Edwards
15 * Public Domain
16 *
17 * TODO:
18 * - access control
19 * - will/won't handshake
20 * - Unify Debugging output and return StatusCodes
21 */
22
23 #include "telnetd.h"
24
25 #define telnetd_printf printf
26 #if 0
27 static inline int telnetd_printf(const char *format, ...);
28 {
29 printf(format,...);
30 syslog (6, format);
31 }
32 #endif
33
34 /* Local data */
35
36 static BOOLEAN bShutdown = 0;
37 static BOOLEAN bSocketInterfaceInitialised = 0;
38 static int sock;
39
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
43 *
44 * Once you are ready to turn on the service
45 * rename this function
46 * int kickoff_telnetd(void)
47 */
48 int main(int argc, char **argv)
49 {
50 printf("Attempting to start Simple TelnetD\n");
51
52 // DetectPlatform();
53 SetConsoleCtrlHandler(Cleanup, 1);
54
55 if (!StartSocketInterface())
56 ErrorExit("Unable to start socket interface\n");
57
58 CreateSocket();
59
60 while(!bShutdown) {
61 WaitForConnect();
62 }
63
64 WSACleanup();
65 return 0;
66 }
67
68 /* Cleanup */
69 static BOOL WINAPI Cleanup(DWORD dwControlType)
70 {
71 if (bSocketInterfaceInitialised) {
72 telnetd_printf("Cleanup...\n");
73 WSACleanup();
74 }
75 return 0;
76 }
77
78 /* StartSocketInterface */
79 static BOOLEAN StartSocketInterface(void)
80 {
81 WORD wVersionRequested;
82 WSADATA wsaData;
83 int err;
84
85 wVersionRequested = MAKEWORD( 2, 0 );
86 err = WSAStartup(wVersionRequested, &wsaData);
87 if (err != 0) {
88 telnetd_printf("requested winsock version not supported\n");
89 return 0;
90 }
91
92 bSocketInterfaceInitialised = 1; /* for ErrorExit function */
93
94 if ( wsaData.wVersion != wVersionRequested)
95 ErrorExit("requested winsock version not supported\n");
96
97 telnetd_printf("TelnetD, using %s\n", wsaData.szDescription);
98 return 1;
99 }
100
101 /* CreateSocket */
102 static void CreateSocket(void)
103 {
104 struct sockaddr_in sa;
105
106 sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
107 if (sock < 0)
108 ErrorExit("Cannot create socket");
109
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);
114
115 if (bind(sock, (struct sockaddr*) &sa, sizeof(sa)) != 0)
116 ErrorExit("Cannot bind address to socket");
117 }
118
119 /* WaitForConnect */
120 static void WaitForConnect(void)
121 {
122 struct sockaddr_in sa;
123 int new_sock;
124
125 if (listen(sock, 1) < 0)
126 ErrorExit("Cannot listen on socket");
127
128 if ((new_sock = accept(sock, (struct sockaddr*) &sa, NULL)) < 0) {
129 fprintf(stderr, "Failed to accept incoming call\n");
130 } else {
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);
133 UserLogin(new_sock);
134 }
135 }
136
137 /* Function: UserLogin */
138 static void UserLogin(int client_socket)
139 {
140 DWORD threadID;
141 client_t *client = malloc(sizeof(client_t));
142
143 if (client == NULL)
144 ErrorExit("failed to allocate memory for client");
145
146 client->socket = client_socket;
147 CreateThread(NULL, 0, UserLoginThread, client, 0, &threadID);
148 }
149
150 /* Function: UserLoginThread */
151 static DWORD WINAPI UserLoginThread(LPVOID data)
152 {
153 client_t *client = (client_t *) data;
154 char welcome[256];
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];
161 int received;
162 char *terminator;
163
164 if (DoTelnetHandshake(client->socket)) {
165 closesocket(client->socket);
166 free(client);
167 return 0;
168 }
169
170 gethostname(hostname, sizeof(hostname));
171 sprintf(welcome, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname);
172
173 if (send(client->socket, welcome, strlen(welcome), 0) < 0) {
174 closesocket(client->socket);
175 free(client);
176 return 0;
177 }
178 received = ReceiveLine(client->socket, userID, sizeof(userID), Echo );
179 if (received < 0) {
180 closesocket(client->socket);
181 free(client);
182 return 0;
183 } else if (received) {
184 if ((terminator = strchr(userID, CR)) != NULL) {
185 *terminator = '\0';
186 }
187 }
188
189 if (send(client->socket, pwdPrompt, strlen(pwdPrompt), 0) < 0) {
190 closesocket(client->socket);
191 free(client);
192 return 0;
193 }
194 received = ReceiveLine(client->socket, password, sizeof(password), Password );
195
196 #if 0
197 if (received < 0) {
198 closesocket(client->socket);
199 free(client);
200 return 0;
201 } else if (received) {
202 if ((terminator = strchr(password, CR)) != NULL) {
203 *terminator = '\0';
204 }
205 }
206 #endif
207
208 /* TODO: do authentication here */
209
210
211 telnetd_printf("User '%p' logged on\n", userID);
212 #if 0
213 strcpy(client->userID, userID);
214 if (send(client->socket, logonPrompt, strlen(logonPrompt), 0) < 0) {
215 closesocket(client->socket);
216 free(client);
217 return 0;
218 }
219 #endif
220 RunShell(client);
221 return 0;
222 }
223
224 /* Function: DoTelnetHandshake */
225 static int DoTelnetHandshake(int sock)
226 {
227 int retval;
228 int received;
229 fd_set set;
230 struct timeval timeout = { HANDSHAKE_TIMEOUT, 0 };
231
232 char will_echo[]=
233 IAC DONT ECHO
234 IAC WILL ECHO
235 IAC WILL NAWS
236 IAC WILL SUPPRESS_GO_AHEAD
237 IAC DO SUPPRESS_GO_AHEAD
238 IAC DONT NEWENVIRON
239 IAC WONT NEWENVIRON
240 IAC WONT LINEMODE
241 IAC DO NAWS
242 IAC SB TERMINAL_TYPE "\x01" IAC SE
243 ;
244
245 unsigned char client_reply[256];
246
247 if (send(sock, will_echo, sizeof(will_echo), 0) < 0) {
248 return -1;
249 }
250
251 /* Now wait for client response (and ignore it) */
252 FD_ZERO(&set);
253 FD_SET(sock, &set);
254
255 do {
256 retval = select(0, &set, NULL, NULL, &timeout);
257 /* check for error */
258 if (retval < 0) {
259 return -1;
260 /* check for timeout */
261 } else if (retval == 0) {
262 return 0;
263 }
264 /* no error and no timeout, we have data in our sock */
265 received = recv(sock, (char *) client_reply, sizeof(client_reply), 0);
266 if (received <= 0) {
267 return -1;
268 }
269 } while (retval);
270
271 return 0;
272 }
273
274 /*
275 ** Function: ReceiveLine
276 **
277 ** Abstract: receive until timeout or CR
278 ** In : sock, len
279 ** Out : buffer
280 ** Result : int
281 ** Pre : 'sock' must be valid socket
282 ** Post : (result = the number of bytes read into 'buffer')
283 ** OR (result = -1 and error)
284 */
285 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo)
286 {
287 int i = 0;
288 int retval;
289 fd_set set;
290 struct timeval timeout = { 0, 100000 };
291 char del[3] = { BS, ' ', BS };
292 char asterisk[1] = { '*' };
293
294 FD_ZERO(&set);
295 FD_SET(sock, &set);
296
297 memset(buffer, '\0', len);
298
299 do {
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 */
303 if (retval < 0) {
304 return -1;
305 /* check for timeout */
306 } else if (retval == 0) {
307 /* return number of characters received so far */
308 return i;
309 }
310 /* no error and no timeout, we have data in our sock */
311 if (recv(sock, &buffer[i], 1, 0) <= 0) {
312 return -1;
313 }
314 if ((buffer[i] == '\0') || (buffer[i] == LF)) {
315 /* ignore null characters and linefeeds from DOS telnet clients */
316 buffer[i] = '\0';
317 } else if ((buffer[i] == DEL) || (buffer[i] == BS)) {
318 /* handle delete and backspace */
319 buffer[i] = '\0';
320 if (echo) {
321 if (i > 0) {
322 i--;
323 buffer[i] = '\0';
324 if (send(sock, del, sizeof(del), 0) < 0) {
325 return -1;
326 }
327 }
328 } else {
329 buffer[i] = BS; /* Let shell process handle it */
330 i++;
331 }
332 } else {
333 /* echo typed characters */
334 if (echo == Echo && send(sock, &buffer[i], 1, 0) < 0) {
335 return -1;
336 } else if (echo == Password && send(sock, asterisk, sizeof(asterisk), 0) < 0) {
337 return -1;
338 }
339 if (buffer[i] == CR) {
340 i++;
341 buffer[i] = LF; /* append LF for DOS command processor */
342 i++;
343 return i;
344 }
345
346 i++;
347 }
348 } while (i < len);
349
350 return i;
351 }
352
353 /*
354 ** Function: RunShell
355 */
356 static void RunShell(client_t *client)
357 {
358 DWORD threadID;
359 HANDLE hChildStdinRd;
360 HANDLE hChildStdinWr;
361 HANDLE hChildStdoutRd;
362 HANDLE hChildStdoutWr;
363 STARTUPINFO si;
364 PROCESS_INFORMATION piProcInfo;
365 SECURITY_ATTRIBUTES saAttr;
366
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";
371
372 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
373 saAttr.bInheritHandle = TRUE;
374 saAttr.lpSecurityDescriptor = NULL;
375
376 // Create a pipe for the child process's STDOUT.
377 if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
378 ErrorExit("Stdout pipe creation failed\n");
379
380 if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
381 ErrorExit("Stdin pipe creation failed\n");
382
383
384 client->bTerminate = FALSE;
385 client->bWriteToPipe = TRUE;
386 client->bReadFromPipe = TRUE;
387 client->hChildStdinWr = hChildStdinWr;
388 client->hChildStdoutRd = hChildStdoutRd;
389
390
391 // Create the child process (the shell)
392 telnetd_printf("Creating child process...\n");
393
394 ZeroMemory( &si, sizeof(STARTUPINFO) );
395 si.cb = sizeof(STARTUPINFO);
396
397 si.dwFlags = STARTF_USESTDHANDLES;
398 si.hStdInput = hChildStdinRd;
399 si.hStdOutput = hChildStdoutWr;
400 si.hStdError = hChildStdoutWr;
401
402 //si.dwFlags |= STARTF_USESHOWWINDOW;
403 //si.wShowWindow = SW_SHOW;
404
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
414 &si, // startup info
415 &piProcInfo)) {
416 ErrorExit("Create process failed");
417 }
418
419 client->hProcess = piProcInfo.hProcess;
420 client->dwProcessId = piProcInfo.dwProcessId;
421
422 telnetd_printf("New child created (groupid=%lu)\n", client->dwProcessId);
423
424 // No longer need these in the parent...
425 if (!CloseHandle(hChildStdoutWr))
426 ErrorExit("Closing handle failed");
427
428 if (!CloseHandle(hChildStdinRd))
429 ErrorExit("Closing handle failed");
430
431 CreateThread(NULL, 0, WriteToPipeThread, client, 0, &threadID);
432 CreateThread(NULL, 0, ReadFromPipeThread, client, 0, &threadID);
433 CreateThread(NULL, 0, MonitorChildThread, client, 0, &threadID);
434 }
435
436 /*
437 * Function: MonitorChildThread
438 *
439 * Abstract: Monitor the child (shell) process
440 */
441 static DWORD WINAPI MonitorChildThread(LPVOID data)
442 {
443 DWORD exitCode;
444 client_t *client = (client_t *) data;
445
446 telnetd_printf("Monitor thread running...\n");
447
448 WaitForSingleObject(client->hProcess, INFINITE);
449
450 GetExitCodeProcess(client->hProcess, &exitCode);
451 telnetd_printf("Child process terminated with code %lx\n", exitCode);
452
453 /* signal the other threads to give up */
454 client->bTerminate = TRUE;
455
456 Sleep(500);
457
458 CloseHandle(client->hChildStdoutRd);
459 CloseHandle(client->hChildStdinWr);
460 CloseHandle(client->hProcess);
461
462 closesocket(client->socket);
463
464 telnetd_printf("Waiting for all threads to give up..\n");
465
466 while (client->bWriteToPipe || client->bReadFromPipe) {
467 telnetd_printf(".");
468 fflush(stdout);
469 Sleep(1000);
470 }
471
472 telnetd_printf("Cleanup for user '%s'\n", client->userID);
473 free(client);
474 return 0;
475 }
476
477 /*
478 * Function: WriteToPipeThread
479 *
480 * Abstract: read data from the telnet client socket
481 * and pass it on to the shell process.
482 */
483 static DWORD WINAPI WriteToPipeThread(LPVOID data)
484 {
485 int iRead;
486 DWORD dwWritten;
487 CHAR chBuf[BUFSIZE];
488 client_t *client = (client_t *) data;
489
490 while (!client->bTerminate) {
491 iRead = ReceiveLine(client->socket, chBuf, BUFSIZE, FALSE);
492 if (iRead < 0) {
493 telnetd_printf("Client disconnect\n");
494 break;
495 } else if (iRead > 0) {
496 if (strchr(chBuf, CTRLC)) {
497 GenerateConsoleCtrlEvent(CTRL_C_EVENT, client->dwProcessId);
498 }
499 if (send(client->socket, chBuf, iRead, 0) < 0) {
500 telnetd_printf("error writing to socket\n");
501 break;
502 }
503 if (! WriteFile(client->hChildStdinWr, chBuf, (DWORD) iRead, &dwWritten, NULL)) {
504 telnetd_printf("Error writing to pipe\n");
505 break;
506 }
507 }
508 }
509
510 if (!client->bTerminate)
511 TerminateShell(client);
512
513 telnetd_printf("WriteToPipeThread terminated\n");
514
515 client->bWriteToPipe = FALSE;
516 return 0;
517 }
518
519 /*
520 * Function: ReadFromPipeThread
521 *
522 * Abstract: Read data from the shell's stdout handle and
523 * pass it on to the telnet client socket.
524 */
525 static DWORD WINAPI ReadFromPipeThread(LPVOID data)
526 {
527 DWORD dwRead;
528 DWORD dwAvail;
529 CHAR chBuf[BUFSIZE];
530 CHAR txBuf[BUFSIZE*2];
531 DWORD from,to;
532 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
533
534 client_t *client = (client_t *) data;
535
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");
540 break;
541 }
542 if (dwAvail) {
543 if( ! ReadFile( client->hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
544 dwRead == 0) {
545 telnetd_printf("Failed to read from pipe\n");
546 break;
547 }
548 for (from=0, to=0; from<dwRead; from++, to++) {
549 txBuf[to] = chBuf[from];
550 if (txBuf[to] == '\n') {
551 txBuf[to] = '\r';
552 to++;
553 txBuf[to] = '\n';
554 }
555 }
556 if (send(client->socket, txBuf, to, 0) < 0) {
557 telnetd_printf("error writing to socket\n");
558 break;
559 }
560 }
561 Sleep(100); /* Hmmm, oh well... what the heck! */
562 }
563
564 if (!client->bTerminate)
565 TerminateShell(client);
566
567 telnetd_printf("ReadFromPipeThread terminated\n");
568
569 client->bReadFromPipe = FALSE;
570 return 0;
571 }
572
573 /* TerminateShell */
574 static void TerminateShell(client_t *client)
575 {
576 DWORD exitCode;
577 DWORD dwWritten;
578 char stop[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
579
580 GetExitCodeProcess(client->hProcess, &exitCode);
581
582 if (exitCode == STILL_ACTIVE)
583 {
584 HANDLE hEvent = NULL;
585 DWORD dwWaitResult;
586
587 telnetd_printf("user shell still active, send Ctrl-Break to group-id %lu\n", client->dwProcessId );
588
589 hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
590
591 if (hEvent == NULL)
592 printf("CreateEvent error\n");
593
594 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT, client->dwProcessId ))
595 telnetd_printf("Failed to send Ctrl_break\n");
596
597 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT, client->dwProcessId ))
598 telnetd_printf("Failed to send Ctrl_C\n");
599
600 if (!WriteFile(client->hChildStdinWr, stop, sizeof(stop), &dwWritten, NULL))
601 telnetd_printf("Error writing to pipe\n");
602
603 /* wait for our handler to be called */
604 dwWaitResult=WaitForSingleObject(hEvent, 500);
605
606 if (WAIT_FAILED==dwWaitResult)
607 telnetd_printf("WaitForSingleObject failed\n");
608
609 GetExitCodeProcess(client->hProcess, &exitCode);
610 if (exitCode == STILL_ACTIVE)
611 {
612 telnetd_printf("user shell still active, attempt to terminate it now...\n");
613
614 if (hEvent != NULL)
615 {
616 if (!CloseHandle(hEvent))
617 telnetd_printf("CloseHandle");
618 }
619 TerminateProcess(client->hProcess, 0);
620 }
621 TerminateProcess(client->hProcess, 0);
622 }
623 TerminateProcess(client->hProcess, 0);
624 }
625
626 /* ErrorExit */
627 static VOID ErrorExit (LPTSTR lpszMessage)
628 {
629 fprintf(stderr, "%s\n", lpszMessage);
630 if (bSocketInterfaceInitialised) {
631 telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());
632 WSACleanup();
633 }
634 ExitProcess(0);
635 }
636