xref: /reactos/base/services/telnetd/telnetd.c (revision cc439606)
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 kickoff_telnetd(void)
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   HANDLE     threadHandle;
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   threadHandle = CreateThread(NULL, 0, UserLoginThread, client, 0, NULL);
148   if (threadHandle == NULL)
149     free(client);
150   else
151     CloseHandle(threadHandle);
152 }
153 
154 /* Function: UserLoginThread */
155 static DWORD WINAPI UserLoginThread(LPVOID data)
156 {
157   client_t  *client = (client_t *) data;
158   char       welcome[256];
159   char       hostname[64] = "Unknown";
160   char      *pwdPrompt = "\r\npass:";
161   //char      *logonPrompt = "\r\nLogin OK, please wait...";
162   //char      *byebye = "\r\nWrong! bye bye...\r\n";
163   char       userID[USERID_SIZE];
164   char       password[USERID_SIZE];
165   int        received;
166   char      *terminator;
167 
168   if (DoTelnetHandshake(client->socket)) {
169     closesocket(client->socket);
170     free(client);
171     return 0;
172   }
173 
174   gethostname(hostname, sizeof(hostname));
175   sprintf(welcome, "\r\nWelcome to %s, please identify yourself\r\n\r\nuser:", hostname);
176 
177   if (send(client->socket, welcome, strlen(welcome), 0) < 0) {
178     closesocket(client->socket);
179     free(client);
180     return 0;
181   }
182   received = ReceiveLine(client->socket, userID, sizeof(userID), Echo );
183   if (received < 0) {
184     closesocket(client->socket);
185     free(client);
186     return 0;
187   } else if (received) {
188     if ((terminator = strchr(userID, CR)) != NULL) {
189       *terminator = '\0';
190     }
191   }
192 
193   if (send(client->socket, pwdPrompt, strlen(pwdPrompt), 0) < 0) {
194     closesocket(client->socket);
195     free(client);
196     return 0;
197   }
198   received = ReceiveLine(client->socket, password, sizeof(password), Password );
199 
200 #if 0
201   if (received < 0) {
202     closesocket(client->socket);
203     free(client);
204     return 0;
205   } else if (received) {
206     if ((terminator = strchr(password, CR)) != NULL) {
207       *terminator = '\0';
208     }
209   }
210 #endif
211 
212   /* TODO: do authentication here */
213 
214 
215   telnetd_printf("User '%p' logged on\n", userID);
216 #if 0
217   strcpy(client->userID, userID);
218   if (send(client->socket, logonPrompt, strlen(logonPrompt), 0) < 0) {
219     closesocket(client->socket);
220     free(client);
221     return 0;
222   }
223 #endif
224   RunShell(client);
225   return 0;
226 }
227 
228 /* Function: DoTelnetHandshake */
229 static int DoTelnetHandshake(int sock)
230 {
231   int retval;
232   int received;
233   fd_set set;
234   struct timeval timeout = { HANDSHAKE_TIMEOUT, 0 };
235 
236   char will_echo[]=
237       IAC DONT ECHO
238       IAC WILL ECHO
239       IAC WILL NAWS
240       IAC WILL SUPPRESS_GO_AHEAD
241       IAC DO SUPPRESS_GO_AHEAD
242       IAC DONT NEWENVIRON
243       IAC WONT NEWENVIRON
244       IAC WONT LINEMODE
245       IAC DO NAWS
246       IAC SB TERMINAL_TYPE "\x01" IAC SE
247       ;
248 
249   unsigned char client_reply[256];
250 
251   if (send(sock, will_echo, sizeof(will_echo), 0) < 0) {
252     return -1;
253   }
254 
255   /* Now wait for client response (and ignore it) */
256   FD_ZERO(&set);
257   FD_SET(sock, &set);
258 
259   do {
260     retval = select(0, &set, NULL, NULL, &timeout);
261     /* check for error */
262     if (retval < 0) {
263       return -1;
264       /* check for timeout */
265     } else if (retval == 0) {
266       return 0;
267     }
268     /* no error and no timeout, we have data in our sock */
269     received = recv(sock, (char *) client_reply, sizeof(client_reply), 0);
270     if (received <= 0) {
271      return -1;
272     }
273   } while (retval);
274 
275   return 0;
276 }
277 
278 /*
279 ** Function: ReceiveLine
280 **
281 ** Abstract: receive until timeout or CR
282 ** In      : sock, len
283 ** Out     : buffer
284 ** Result  : int
285 ** Pre     : 'sock' must be valid socket
286 ** Post    : (result = the number of bytes read into 'buffer')
287 **           OR (result = -1 and error)
288 */
289 static int ReceiveLine(int sock, char *buffer, int len, EchoMode echo)
290 {
291   int            i = 0;
292   int            retval;
293   fd_set         set;
294   struct timeval timeout = { 0, 100000 };
295   char           del[3] = { BS, ' ', BS };
296   char           asterisk[1] = { '*' };
297 
298   FD_ZERO(&set);
299   FD_SET(sock, &set);
300 
301   memset(buffer, '\0', len);
302 
303   do {
304     /* When we're in echo mode, we do not need a timeout */
305     retval = select(0, &set, NULL, NULL, (echo ? NULL : &timeout) );
306     /* check for error */
307     if (retval < 0) {
308       return -1;
309       /* check for timeout */
310     } else if (retval == 0) {
311       /* return number of characters received so far */
312       return i;
313     }
314     /* no error and no timeout, we have data in our sock */
315     if (recv(sock, &buffer[i], 1, 0) <= 0) {
316       return -1;
317     }
318     if ((buffer[i] == '\0') || (buffer[i] == LF)) {
319       /* ignore null characters and linefeeds from DOS telnet clients */
320       buffer[i] = '\0';
321     } else if ((buffer[i] == DEL) || (buffer[i] == BS)) {
322       /* handle delete and backspace */
323       buffer[i] = '\0';
324       if (echo) {
325 	      if (i > 0) {
326           i--;
327           buffer[i] = '\0';
328           if (send(sock, del, sizeof(del), 0) < 0) {
329             return -1;
330           }
331         }
332       } else {
333         buffer[i] = BS;  /* Let shell process handle it */
334 	      i++;
335       }
336     } else {
337       /* echo typed characters */
338       if (echo == Echo && send(sock, &buffer[i], 1, 0) < 0) {
339         return -1;
340       } else if (echo == Password && send(sock, asterisk, sizeof(asterisk), 0) < 0) {
341         return -1;
342       }
343       if (buffer[i] == CR) {
344         i++;
345         buffer[i] = LF; /* append LF for DOS command processor */
346         i++;
347         return i;
348       }
349 
350       i++;
351     }
352   } while (i < len);
353 
354   return i;
355 }
356 
357 /*
358 ** Function: RunShell
359 */
360 static void RunShell(client_t *client)
361 {
362    HANDLE                threadHandle;
363    HANDLE                hChildStdinRd;
364    HANDLE                hChildStdinWr;
365    HANDLE                hChildStdoutRd;
366    HANDLE                hChildStdoutWr;
367    STARTUPINFO           si;
368    PROCESS_INFORMATION   piProcInfo;
369    SECURITY_ATTRIBUTES   saAttr;
370    char cmd_path[MAX_PATH];
371 
372    if (!GetEnvironmentVariableA("COMSPEC", cmd_path, ARRAYSIZE(cmd_path)))
373    {
374       if (GetSystemDirectoryA(cmd_path, ARRAYSIZE(cmd_path)))
375       {
376          StringCchCatA(cmd_path, ARRAYSIZE(cmd_path), "\\cmd.exe");
377       }
378       else
379       {
380          StringCchCopyA(cmd_path, ARRAYSIZE(cmd_path), "C:\\ReactOS\\system32\\cmd.exe");
381       }
382    }
383 
384    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
385    saAttr.bInheritHandle = TRUE;
386    saAttr.lpSecurityDescriptor = NULL;
387 
388    // Create a pipe for the child process's STDOUT.
389    if (! CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
390       ErrorExit("Stdout pipe creation failed\n");
391 
392    if (! CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
393       ErrorExit("Stdin pipe creation failed\n");
394 
395 
396    client->bTerminate = FALSE;
397    client->bWriteToPipe = TRUE;
398    client->bReadFromPipe = TRUE;
399    client->hChildStdinWr = hChildStdinWr;
400    client->hChildStdoutRd = hChildStdoutRd;
401 
402 
403    // Create the child process (the shell)
404    telnetd_printf("Creating child process...\n");
405 
406    ZeroMemory( &si, sizeof(STARTUPINFO) );
407    si.cb = sizeof(STARTUPINFO);
408 
409    si.dwFlags = STARTF_USESTDHANDLES;
410    si.hStdInput = hChildStdinRd;
411    si.hStdOutput = hChildStdoutWr;
412    si.hStdError = hChildStdoutWr;
413 
414    //si.dwFlags |= STARTF_USESHOWWINDOW;
415    //si.wShowWindow = SW_SHOW;
416 
417    if (!CreateProcess(cmd_path,                  // executable module
418                       NULL,                      // command line
419                       NULL,                      // process security attributes
420                       NULL,                      // primary thread security attributes
421                       TRUE,                      // handles are inherited
422                       DETACHED_PROCESS +         // creation flags
423                       CREATE_NEW_PROCESS_GROUP,
424                       NULL,                      // use parent's environment
425                       NULL,                      // use parent's current directory
426                       &si,                       // startup info
427                       &piProcInfo)) {
428      ErrorExit("Create process failed");
429    }
430 
431    client->hProcess = piProcInfo.hProcess;
432    client->dwProcessId = piProcInfo.dwProcessId;
433 
434    telnetd_printf("New child created (groupid=%lu)\n", client->dwProcessId);
435 
436    // No longer need these in the parent...
437    if (!CloseHandle(hChildStdoutWr))
438      ErrorExit("Closing handle failed");
439 
440    if (!CloseHandle(hChildStdinRd))
441      ErrorExit("Closing handle failed");
442 
443    threadHandle = CreateThread(NULL, 0, WriteToPipeThread, client, 0, NULL);
444    if (threadHandle != NULL)
445      CloseHandle(threadHandle);
446 
447    threadHandle = CreateThread(NULL, 0, ReadFromPipeThread, client, 0, NULL);
448    if (threadHandle != NULL)
449      CloseHandle(threadHandle);
450 
451    threadHandle = CreateThread(NULL, 0, MonitorChildThread, client, 0, NULL);
452    if (threadHandle != NULL)
453      CloseHandle(threadHandle);
454 }
455 
456 /*
457  * Function: MonitorChildThread
458  *
459  * Abstract: Monitor the child (shell) process
460  */
461 static DWORD WINAPI MonitorChildThread(LPVOID data)
462 {
463   DWORD exitCode;
464   client_t *client = (client_t *) data;
465 
466   telnetd_printf("Monitor thread running...\n");
467 
468   WaitForSingleObject(client->hProcess, INFINITE);
469 
470   GetExitCodeProcess(client->hProcess, &exitCode);
471   telnetd_printf("Child process terminated with code %lx\n", exitCode);
472 
473   /* signal the other threads to give up */
474   client->bTerminate = TRUE;
475 
476   Sleep(500);
477 
478   CloseHandle(client->hChildStdoutRd);
479   CloseHandle(client->hChildStdinWr);
480   CloseHandle(client->hProcess);
481 
482   closesocket(client->socket);
483 
484   telnetd_printf("Waiting for all threads to give up..\n");
485 
486   while (client->bWriteToPipe || client->bReadFromPipe) {
487     telnetd_printf(".");
488     fflush(stdout);
489     Sleep(1000);
490   }
491 
492   telnetd_printf("Cleanup for user '%s'\n", client->userID);
493   free(client);
494   return 0;
495 }
496 
497 /*
498  * Function: WriteToPipeThread
499  *
500  * Abstract: read data from the telnet client socket
501  *           and pass it on to the shell process.
502  */
503 static DWORD WINAPI WriteToPipeThread(LPVOID data)
504 {
505   int       iRead;
506   DWORD     dwWritten;
507   CHAR      chBuf[BUFSIZE];
508   client_t *client = (client_t *) data;
509 
510   while (!client->bTerminate) {
511     iRead = ReceiveLine(client->socket, chBuf, BUFSIZE, FALSE);
512     if (iRead < 0) {
513       telnetd_printf("Client disconnect\n");
514       break;
515     } else if (iRead > 0) {
516       if (strchr(chBuf, CTRLC)) {
517         GenerateConsoleCtrlEvent(CTRL_C_EVENT, client->dwProcessId);
518       }
519       if (send(client->socket, chBuf, iRead, 0) < 0) {
520 		 telnetd_printf("error writing to socket\n");
521          break;
522 	  }
523       if (! WriteFile(client->hChildStdinWr, chBuf, (DWORD) iRead, &dwWritten, NULL)) {
524         telnetd_printf("Error writing to pipe\n");
525         break;
526       }
527     }
528   }
529 
530   if (!client->bTerminate)
531     TerminateShell(client);
532 
533   telnetd_printf("WriteToPipeThread terminated\n");
534 
535   client->bWriteToPipe = FALSE;
536   return 0;
537 }
538 
539 /*
540  * Function: ReadFromPipeThread
541  *
542  * Abstract: Read data from the shell's stdout handle and
543  *           pass it on to the telnet client socket.
544  */
545 static DWORD WINAPI ReadFromPipeThread(LPVOID data)
546 {
547   DWORD dwRead;
548   DWORD dwAvail;
549   CHAR chBuf[BUFSIZE];
550   CHAR txBuf[BUFSIZE*2];
551   DWORD from,to;
552   //char warning[] = "warning: rl_prep_terminal: cannot get terminal settings";
553 
554   client_t *client = (client_t *) data;
555 
556   while (!client->bTerminate && client->bWriteToPipe) {
557     // Since we do not want to block, first peek...
558     if (PeekNamedPipe(client->hChildStdoutRd, NULL, 0, NULL, &dwAvail, NULL) == 0) {
559       telnetd_printf("Failed to peek in pipe\n");
560       break;
561     }
562     if (dwAvail) {
563       if( ! ReadFile( client->hChildStdoutRd, chBuf, BUFSIZE, &dwRead, NULL) ||
564            dwRead == 0) {
565         telnetd_printf("Failed to read from pipe\n");
566         break;
567       }
568 	  for (from=0, to=0; from<dwRead; from++, to++) {
569         txBuf[to] = chBuf[from];
570 		if (txBuf[to] == '\n') {
571 			txBuf[to] = '\r';
572 			to++;
573 			txBuf[to] = '\n';
574 		}
575 	  }
576       if (send(client->socket, txBuf, to, 0) < 0) {
577 		 telnetd_printf("error writing to socket\n");
578          break;
579 	  }
580 	}
581     Sleep(100); /* Hmmm, oh well... what the heck! */
582   }
583 
584   if (!client->bTerminate)
585     TerminateShell(client);
586 
587   telnetd_printf("ReadFromPipeThread terminated\n");
588 
589   client->bReadFromPipe = FALSE;
590   return 0;
591 }
592 
593 /* TerminateShell */
594 static void TerminateShell(client_t *client)
595 {
596     DWORD exitCode;
597     DWORD dwWritten;
598     char stop[] = "\003\r\nexit\r\n"; /* Ctrl-C + exit */
599 
600     GetExitCodeProcess(client->hProcess, &exitCode);
601 
602     if (exitCode == STILL_ACTIVE)
603     {
604         HANDLE hEvent = NULL;
605         DWORD dwWaitResult;
606 
607         telnetd_printf("user shell still active, send Ctrl-Break to group-id %lu\n", client->dwProcessId );
608 
609         hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
610 
611         if (hEvent == NULL)
612             printf("CreateEvent error\n");
613 
614         if (!GenerateConsoleCtrlEvent( CTRL_BREAK_EVENT, client->dwProcessId ))
615             telnetd_printf("Failed to send Ctrl_break\n");
616 
617         if (!GenerateConsoleCtrlEvent( CTRL_C_EVENT, client->dwProcessId ))
618             telnetd_printf("Failed to send Ctrl_C\n");
619 
620         if (!WriteFile(client->hChildStdinWr, stop, sizeof(stop), &dwWritten, NULL))
621             telnetd_printf("Error writing to pipe\n");
622 
623         /* wait for our handler to be called */
624         dwWaitResult=WaitForSingleObject(hEvent, 500);
625 
626         if (WAIT_FAILED==dwWaitResult)
627             telnetd_printf("WaitForSingleObject failed\n");
628 
629         GetExitCodeProcess(client->hProcess, &exitCode);
630         if (exitCode == STILL_ACTIVE)
631         {
632             telnetd_printf("user shell still active, attempt to terminate it now...\n");
633 
634             if (hEvent != NULL)
635             {
636                 if (!CloseHandle(hEvent))
637                    telnetd_printf("CloseHandle");
638             }
639             TerminateProcess(client->hProcess, 0);
640         }
641         TerminateProcess(client->hProcess, 0);
642     }
643     TerminateProcess(client->hProcess, 0);
644 }
645 
646 /* ErrorExit */
647 static VOID ErrorExit (LPTSTR lpszMessage)
648 {
649    fprintf(stderr, "%s\n", lpszMessage);
650    if (bSocketInterfaceInitialised) {
651      telnetd_printf("WSAGetLastError=%d\n", WSAGetLastError());
652      WSACleanup();
653    }
654    ExitProcess(0);
655 }
656 
657