- Some formating changes
[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 #define telnetd_printf printf
26
27 #if 0
28 extern void syslog (int priority, const char *fmt, ...);
29
30 int telnetd_printf(const char *format, ...)
31 {
32 syslog (6, format);
33 }
34 #endif
35
36 /* Local data */
37
38 static BOOLEAN bShutdown = 0;
39 static BOOLEAN bSocketInterfaceInitialised = 0;
40
41 static int sock;
42
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
46 *
47 * Once you are ready to turn on the service
48 * rename this function
49 * int kickoff_telnetd(void)
50 */
51 int main(int argc, char **argv)
52 {
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
147 client->socket = client_socket;
148 CreateThread(NULL, 0, UserLoginThread, client, 0, &threadID);
149 }
150
151 /* Function: UserLoginThread */
152 static DWORD WINAPI UserLoginThread(LPVOID data)
153 {
154 client_t *client = (client_t *) data;
155 char welcome[256];
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];
162 int received;
163 char *terminator;
164
165 if (DoTelnetHandshake(client->socket)) {
166 closesocket(client->socket);
167 free(client);
168 return 0;
169 }
170
171 gethostname(hostname, sizeof(hostname));
172 sprintf(welcome, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname);
173
174 if (send(client->socket, welcome, strlen(welcome), 0) < 0) {
175 closesocket(client->socket);
176 free(client);
177 return 0;
178 }
179 received = ReceiveLine(client->socket, userID, sizeof(userID), Echo );
180 if (received < 0) {
181 closesocket(client->socket);
182 free(client);
183 return 0;
184 } else if (received) {
185 if ((terminator = strchr(userID, CR)) != NULL) {
186 *terminator = '\0';
187 }
188 }
189
190 if (send(client->socket, pwdPrompt, strlen(pwdPrompt), 0) < 0) {
191 closesocket(client->socket);
192 free(client);
193 return 0;
194 }
195 received = ReceiveLine(client->socket, password, sizeof(password), Password );
196
197 #if 0
198 if (received < 0) {
199 closesocket(client->socket);
200 free(client);
201 return 0;
202 } else if (received) {
203 if ((terminator = strchr(password, CR)) != NULL) {
204 *terminator = '\0';
205 }
206 }
207 #endif
208
209 /* TODO: do authentication here */
210
211
212 telnetd_printf("User '%p' logged on\n", userID);
213 #if 0
214 strcpy(client->userID, userID);
215 if (send(client->socket, logonPrompt, strlen(logonPrompt), 0) < 0) {
216 closesocket(client->socket);
217 free(client);
218 return 0;
219 }
220 #endif
221 RunShell(client);
222 return 0;
223 }
224
225 /* Function: DoTelnetHandshake */
226 static int DoTelnetHandshake(int sock)
227 {
228 int retval;
229 int received;
230 fd_set set;
231 struct timeval timeout = { HANDSHAKE_TIMEOUT, 0 };
232
233 char will_echo[]=
234 IAC DONT ECHO
235 IAC WILL ECHO
236 IAC WILL NAWS
237 IAC WILL SUPPRESS_GO_AHEAD
238 IAC DO SUPPRESS_GO_AHEAD
239 IAC DONT NEWENVIRON
240 IAC WONT NEWENVIRON
241 IAC WONT LINEMODE
242 IAC DO NAWS
243 IAC SB TERMINAL_TYPE "\x01" IAC SE
244 ;
245
246 unsigned char client_reply[256];
247
248 if (send(sock, will_echo, sizeof(will_echo), 0) < 0) {
249 return -1;
250 }
251
252 /* Now wait for client response (and ignore it) */
253 FD_ZERO(&set);
254 FD_SET(sock, &set);
255
256 do {
257 retval = select(0, &set, NULL, NULL, &timeout);
258 /* check for error */
259 if (retval < 0) {
260 return -1;
261 /* check for timeout */
262 } else if (retval == 0) {
263 return 0;
264 }
265 /* no error and no timeout, we have data in our sock */
266 received = recv(sock, (char *) client_reply, sizeof(client_reply), 0);
267 if (received <= 0) {
268 return -1;
269 }
270 } while (retval);
271
272 return 0;
273 }
274
275 /*
276 ** Function: ReceiveLine
277 **
278 ** Abstract: receive until timeout or CR
279 ** In : sock, len
280 ** Out : buffer
281 ** Result : int
282 ** Pre : 'sock' must be valid socket
283 ** Post : (result = the number of bytes read into 'buffer')
284 ** OR (result = -1 and error)
285 */
286 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo)
287 {
288 int i = 0;
289 int retval;
290 fd_set set;
291 struct timeval timeout = { 0, 100000 };
292 char del[3] = { BS, ' ', BS };
293 char asterisk[1] = { '*' };
294
295 FD_ZERO(&set);
296 FD_SET(sock, &set);
297
298 memset(buffer, '\0', len);
299
300 do {
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 */
304 if (retval < 0) {
305 return -1;
306 /* check for timeout */
307 } else if (retval == 0) {
308 /* return number of characters received so far */
309 return i;
310 }
311 /* no error and no timeout, we have data in our sock */
312 if (recv(sock, &buffer[i], 1, 0) <= 0) {
313 return -1;
314 }
315 if ((buffer[i] == '\0') || (buffer[i] == LF)) {
316 /* ignore null characters and linefeeds from DOS telnet clients */
317 buffer[i] = '\0';
318 } else if ((buffer[i] == DEL) || (buffer[i] == BS)) {
319 /* handle delete and backspace */
320 buffer[i] = '\0';
321 if (echo) {
322 if (i > 0) {
323 i--;
324 buffer[i] = '\0';
325 if (send(sock, del, sizeof(del), 0) < 0) {
326 return -1;
327 }
328 }
329 } else {
330 buffer[i] = BS; /* Let shell process handle it */
331 i++;
332 }
333 } else {
334 /* echo typed characters */
335 if (echo == Echo && send(sock, &buffer[i], 1, 0) < 0) {
336 return -1;
337 } else if (echo == Password && send(sock, asterisk, sizeof(asterisk), 0) < 0) {
338 return -1;
339 }
340 if (buffer[i] == CR) {
341 i++;
342 buffer[i] = LF; /* append LF for DOS command processor */
343 i++;
344 return i;
345 }
346
347 i++;
348 }
349 } while (i < len);
350
351 return i;
352 }
353
354 /*
355 ** Function: RunShell
356 */
357 static void RunShell(client_t *client)
358 {
359 DWORD threadID;
360 HANDLE hChildStdinRd;
361 HANDLE hChildStdinWr;
362 HANDLE hChildStdoutRd;
363 HANDLE hChildStdoutWr;
364 STARTUPINFO si;
365 PROCESS_INFORMATION piProcInfo;
366 SECURITY_ATTRIBUTES saAttr;
367
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";
372
373 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
374 saAttr.bInheritHandle = TRUE;
375 saAttr.lpSecurityDescriptor = NULL;
376
377 // Create a pipe for the child process's STDOUT.
378 if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
379 ErrorExit("Stdout pipe creation failed\n");
380
381 if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
382 ErrorExit("Stdin pipe creation failed\n");
383
384
385 client->bTerminate = FALSE;
386 client->bWriteToPipe = TRUE;
387 client->bReadFromPipe = TRUE;
388 client->hChildStdinWr = hChildStdinWr;
389 client->hChildStdoutRd = hChildStdoutRd;
390
391
392 // Create the child process (the shell)
393 telnetd_printf("Creating child process...\n");
394
395 ZeroMemory( &si, sizeof(STARTUPINFO) );
396 si.cb = sizeof(STARTUPINFO);
397
398 si.dwFlags = STARTF_USESTDHANDLES;
399 si.hStdInput = hChildStdinRd;
400 si.hStdOutput = hChildStdoutWr;
401 si.hStdError = hChildStdoutWr;
402
403 //si.dwFlags |= STARTF_USESHOWWINDOW;
404 //si.wShowWindow = SW_SHOW;
405
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
415 &si, // startup info
416 &piProcInfo)) {
417 ErrorExit("Create process failed");
418 }
419
420 client->hProcess = piProcInfo.hProcess;
421 client->dwProcessId = piProcInfo.dwProcessId;
422
423 telnetd_printf("New child created (groupid=%lu)\n", client->dwProcessId);
424
425 // No longer need these in the parent...
426 if (!CloseHandle(hChildStdoutWr))
427 ErrorExit("Closing handle failed");
428
429 if (!CloseHandle(hChildStdinRd))
430 ErrorExit("Closing handle failed");
431
432 CreateThread(NULL, 0, WriteToPipeThread, client, 0, &threadID);
433 CreateThread(NULL, 0, ReadFromPipeThread, client, 0, &threadID);
434 CreateThread(NULL, 0, MonitorChildThread, client, 0, &threadID);
435 }
436
437 /*
438 * Function: MonitorChildThread
439 *
440 * Abstract: Monitor the child (shell) process
441 */
442 static DWORD WINAPI MonitorChildThread(LPVOID data)
443 {
444 DWORD exitCode;
445 client_t *client = (client_t *) data;
446
447 telnetd_printf("Monitor thread running...\n");
448
449 WaitForSingleObject(client->hProcess, INFINITE);
450
451 GetExitCodeProcess(client->hProcess, &exitCode);
452 telnetd_printf("Child process terminated with code %lx\n", exitCode);
453
454 /* signal the other threads to give up */
455 client->bTerminate = TRUE;
456
457 Sleep(500);
458
459 CloseHandle(client->hChildStdoutRd);
460 CloseHandle(client->hChildStdinWr);
461 CloseHandle(client->hProcess);
462
463 closesocket(client->socket);
464
465 telnetd_printf("Waiting for all threads to give up..\n");
466
467 while (client->bWriteToPipe || client->bReadFromPipe) {
468 telnetd_printf(".");
469 fflush(stdout);
470 Sleep(1000);
471 }
472
473 telnetd_printf("Cleanup for user '%s'\n", client->userID);
474 free(client);
475 return 0;
476 }
477
478 /*
479 * Function: WriteToPipeThread
480 *
481 * Abstract: read data from the telnet client socket
482 * and pass it on to the shell process.
483 */
484 static DWORD WINAPI WriteToPipeThread(LPVOID data)
485 {
486 int iRead;
487 DWORD dwWritten;
488 CHAR chBuf[BUFSIZE];
489 client_t *client = (client_t *) data;
490
491 while (!client->bTerminate) {
492 iRead = ReceiveLine(client->socket, chBuf, BUFSIZE, FALSE);
493 if (iRead < 0) {
494 telnetd_printf("Client disconnect\n");
495 break;
496 } else if (iRead > 0) {
497 if (strchr(chBuf, CTRLC)) {
498 GenerateConsoleCtrlEvent(CTRL_C_EVENT, client->dwProcessId);
499 }
500 if (send(client->socket, chBuf, iRead, 0) < 0) {
501 telnetd_printf("error writing to socket\n");
502 break;
503 }
504 if (! WriteFile(client->hChildStdinWr, chBuf, (DWORD) iRead, &dwWritten, NULL)) {
505 telnetd_printf("Error writing to pipe\n");
506 break;
507 }
508 }
509 }
510
511 if (!client->bTerminate)
512 TerminateShell(client);
513
514 telnetd_printf("WriteToPipeThread terminated\n");
515
516 client->bWriteToPipe = FALSE;
517 return 0;
518 }
519
520 /*
521 * Function: ReadFromPipeThread
522 *
523 * Abstract: Read data from the shell's stdout handle and
524 * pass it on to the telnet client socket.
525 */
526 static DWORD WINAPI ReadFromPipeThread(LPVOID data)
527 {
528 DWORD dwRead;
529 DWORD dwAvail;
530 CHAR chBuf[BUFSIZE];
531 CHAR txBuf[BUFSIZE*2];
532 DWORD from,to;
533 //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
534
535 client_t *client = (client_t *) data;
536
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");
541 break;
542 }
543 if (dwAvail) {
544 if( ! ReadFile( client->hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
545 dwRead == 0) {
546 telnetd_printf("Failed to read from pipe\n");
547 break;
548 }
549 for (from=0, to=0; from<dwRead; from++, to++) {
550 txBuf[to] = chBuf[from];
551 if (txBuf[to] == '\n') {
552 txBuf[to] = '\r';
553 to++;
554 txBuf[to] = '\n';
555 }
556 }
557 if (send(client->socket, txBuf, to, 0) < 0) {
558 telnetd_printf("error writing to socket\n");
559 break;
560 }
561 }
562 Sleep(100); /* Hmmm, oh well... what the heck! */
563 }
564
565 if (!client->bTerminate)
566 TerminateShell(client);
567
568 telnetd_printf("ReadFromPipeThread terminated\n");
569
570 client->bReadFromPipe = FALSE;
571 return 0;
572 }
573
574 /* TerminateShell */
575 static void TerminateShell(client_t *client)
576 {
577 DWORD exitCode;
578 DWORD dwWritten;
579 char stop[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
580
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 );
584
585 if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT, client->dwProcessId ))
586 telnetd_printf("Failed to send Ctrl_break\n");
587
588 Sleep(500);
589
590 if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT, client->dwProcessId ))
591 telnetd_printf("Failed to send Ctrl_C\n");
592
593 Sleep(500);
594
595 if (!WriteFile(client->hChildStdinWr, stop, sizeof(stop), &dwWritten, NULL))
596 telnetd_printf("Error writing to pipe\n");
597
598 Sleep(500);
599
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);
604 }
605 }
606 }
607
608 /* ErrorExit */
609 static VOID ErrorExit (LPTSTR lpszMessage)
610 {
611 fprintf(stderr, "%s\n", lpszMessage);
612 if (bSocketInterfaceInitialised) {
613 telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());
614 WSACleanup();
615 }
616 ExitProcess(0);
617 }
618