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