1 /* telnetd.c - Telnet Server
2  *
3  * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com>
4  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5  *
6 USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN))
7 
8 config TELNETD
9   bool "telnetd"
10   default n
11   help
12     Handle incoming telnet connections
13 
14     -l LOGIN  Exec LOGIN on connect
15     -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue
16     -K Close connection as soon as login exits
17     -p PORT   Port to listen on
18     -b ADDR[:PORT]  Address to bind to
19     -F Run in foreground
20     -i Inetd mode
21     -w SEC    Inetd 'wait' mode, linger time SEC
22     -S Log to syslog (implied by -i or without -F and -w)
23 */
24 
25 #define FOR_telnetd
26 #include "toys.h"
27 #include <arpa/telnet.h>
28 
29 GLOBALS(
30     char *login_path;
31     char *issue_path;
32     int port;
33     char *host_addr;
34     long w_sec;
35 
36     int gmax_fd;
37     pid_t fork_pid;
38 )
39 
40 #define BUFSIZE 4*1024
41 struct term_session {
42   int new_fd, pty_fd;
43   pid_t child_pid;
44   int buff1_avail, buff2_avail;
45   int buff1_written, buff2_written;
46   int rem;  //unprocessed data from socket
47   char buff1[BUFSIZE], buff2[BUFSIZE];
48   struct term_session *next;
49 };
50 
51 struct term_session *session_list = NULL;
52 
get_sockaddr(char * host,void * buf)53 static void get_sockaddr(char *host, void *buf)
54 {
55   in_port_t port_num = htons(TT.port);
56   struct addrinfo hints, *result;
57   int status, af = AF_UNSPEC;
58   char *s;
59 
60   // [ipv6]:port or exactly one :
61   if (*host == '[') {
62     host++;
63     s = strchr(host, ']');
64     if (s) *s++ = 0;
65     else error_exit("bad address '%s'", host-1);
66     af = AF_INET6;
67   } else {
68     s = strrchr(host, ':');
69     if (s && strchr(host, ':') == s) {
70       *s = 0;
71       af = AF_INET;
72     } else if (s && strchr(host, ':') != s) {
73       af = AF_INET6;
74       s = 0;
75     }
76   }
77 
78   if (s++) {
79     char *ss;
80     unsigned long p = strtoul(s, &ss, 0);
81     if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s);
82     port_num = htons(p);
83   }
84 
85   memset(&hints, 0 , sizeof(struct addrinfo));
86   hints.ai_family = af;
87   hints.ai_socktype = SOCK_STREAM;
88 
89   status = getaddrinfo(host, NULL, &hints, &result);
90   if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
91 
92   memcpy(buf, result->ai_addr, result->ai_addrlen);
93   freeaddrinfo(result);
94 
95   if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num;
96   else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
97 }
98 
listen_socket(void)99 static int listen_socket(void)
100 {
101   int s, af = AF_INET, yes = 1;
102   char buf[sizeof(struct sockaddr_storage)];
103 
104   memset(buf, 0, sizeof(buf));
105   if (FLAG(b)) {
106     get_sockaddr(TT.host_addr, buf);
107     af = ((struct sockaddr *)buf)->sa_family;
108   } else {
109     ((struct sockaddr_in*)buf)->sin_port = htons(TT.port);
110     ((struct sockaddr_in*)buf)->sin_family = af;
111   }
112   s = xsocket(af, SOCK_STREAM, 0);
113   xsetsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
114 
115   xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
116           (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
117 
118   if (listen(s, 1) < 0) perror_exit("listen");
119   return s;
120 }
121 
write_issue(char * tty)122 static void write_issue(char *tty)
123 {
124   int size;
125   char ch = 0;
126   struct utsname u;
127   int fd = open(TT.issue_path, O_RDONLY);
128 
129   if (fd < 0) return ;
130   uname(&u);
131   while ((size = readall(fd, &ch, 1)) > 0) {
132     if (ch == '\\' || ch == '%') {
133       if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
134       if (ch == 's') fputs(u.sysname, stdout);
135       if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
136       if (ch == 'r') fputs(u.release, stdout);
137       if (ch == 'm') fputs(u.machine, stdout);
138       if (ch == 'l') fputs(tty, stdout);
139     }
140     else if (ch == '\n') {
141       fputs("\n\r\0", stdout);
142     } else fputc(ch, stdout);
143   }
144   fflush(NULL);
145   close(fd);
146 }
147 
new_session(int sockfd)148 static int new_session(int sockfd)
149 {
150   char *argv_login[] = {NULL, "-h", NULL, NULL};
151   char tty_name[30]; //tty name length.
152   int fd, i = 1;
153   char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
154     IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
155   struct sockaddr_storage sa;
156   socklen_t sl = sizeof(sa);
157 
158   setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
159 
160   writeall(FLAG(i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
161   if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) return fd;
162   if (TT.fork_pid < 0) perror_exit("fork");
163 
164   if (getpeername(sockfd, (void *)&sa, &sl)) perror_exit("getpeername");
165   if (getnameinfo((void *)&sa, sl, toybuf, sizeof(toybuf), NULL, 0, NI_NUMERICHOST))
166     perror_exit("getnameinfo");
167 
168   write_issue(tty_name);
169   argv_login[0] = TT.login_path;
170   argv_login[2] = toybuf;
171   execvp(argv_login[0], argv_login);
172   exit(EXIT_FAILURE);
173 }
174 
handle_iacs(struct term_session * tm,int c,int fd)175 static int handle_iacs(struct term_session *tm, int c, int fd)
176 {
177   char *curr ,*start,*end;
178   int i = 0;
179 
180   curr = start = tm->buff2+tm->buff2_avail;
181   end = tm->buff2 + c -1;
182   tm->rem = 0;
183   while (curr <= end) {
184     if (*curr != IAC){
185 
186       if (*curr != '\r') {
187         toybuf[i++] = *curr++;
188         continue;
189       } else {
190         toybuf[i++] = *curr++;
191         curr++;
192         if (curr < end && (*curr == '\n' || *curr == '\0'))
193           curr++;
194         continue;
195       }
196     }
197 
198     if ((curr + 1) > end) {
199       tm->rem = 1;
200       break;
201     }
202     if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
203       toybuf[i++] = *(curr+1);
204       curr += 2; //IAC IAC --> 2 bytes
205       continue;
206     }
207     if (*(curr + 1) == NOP || *(curr + 1) == SE) {
208       curr += 2;
209       continue;
210     }
211 
212     if (*(curr + 1) == SB ) {
213       if (*(curr+2) == TELOPT_NAWS) {
214         struct winsize ws;
215         if ((curr+8) >= end) {  //ensure we have data to process.
216           tm->rem = end - curr;
217           break;
218         }
219         ws.ws_col = (curr[3] << 8) | curr[4];
220         ws.ws_row = (curr[5] << 8) | curr[6];
221         ioctl(fd, TIOCSWINSZ, (char *)&ws);
222         curr += 9;
223         continue;
224       } else { //eat non-supported sub neg. options.
225         curr++, tm->rem++;
226         while (*curr != IAC && curr <= end) {
227           curr++;
228           tm->rem++;
229         }
230         if (*curr == IAC) {
231           tm->rem = 0;
232           continue;
233         } else break;
234       }
235     }
236     curr += 3; //skip non-supported 3 bytes.
237   }
238   memcpy(start, toybuf, i);
239   memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
240   return i;
241 }
242 
dup_iacs(char * start,int fd,int len)243 static int dup_iacs(char *start, int fd, int len)
244 {
245   char arr[] = {IAC, IAC};
246   char *needle = NULL;
247   int ret = 0, c, count = 0;
248 
249   while (len) {
250     if (*start == IAC) {
251       count = writeall(fd, arr, sizeof(arr));
252       if (count != 2) break; //short write
253       start++;
254       ret++;
255       len--;
256       continue;
257     }
258     needle = memchr(start, IAC, len);
259     if (needle) c = needle - start;
260     else c = len;
261     count = writeall(fd, start, c);
262     if (count < 0) break;
263     len -= count;
264     ret += count;
265     start += count;
266   }
267   return ret;
268 }
269 
telnetd_main(void)270 void telnetd_main(void)
271 {
272   fd_set rd, wr;
273   struct term_session *tm = NULL;
274   struct timeval tv, *tv_ptr = NULL;
275   int pty_fd, new_fd, c = 0, w, master_fd = 0;
276 
277   if (!FLAG(l)) TT.login_path = "/bin/login";
278   if (!FLAG(f)) TT.issue_path = "/etc/issue.net";
279   if (FLAG(w)) toys.optflags |= FLAG_F;
280   if (!FLAG(i)) {
281     master_fd = listen_socket();
282     fcntl(master_fd, F_SETFD, FD_CLOEXEC);
283     if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
284     if (!FLAG(F)) daemon(0, 0);
285   } else {
286     pty_fd = new_session(master_fd); //master_fd = 0
287     if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
288     tm = xzalloc(sizeof(struct term_session));
289     tm->child_pid = TT.fork_pid;
290     tm->new_fd = 0;
291     tm->pty_fd = pty_fd;
292     if (session_list) {
293       tm->next = session_list;
294       session_list = tm;
295     } else session_list = tm;
296   }
297 
298   if (FLAG(w) && !session_list) {
299     tv.tv_sec = TT.w_sec;
300     tv.tv_usec = 0;
301     tv_ptr = &tv;
302   }
303   signal(SIGCHLD, generic_signal);
304 
305   for (;;) {
306     FD_ZERO(&rd);
307     FD_ZERO(&wr);
308     if (!FLAG(i)) FD_SET(master_fd, &rd);
309 
310     tm = session_list;
311     while (tm) {
312 
313       if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
314       if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
315       if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)
316         FD_SET(tm->pty_fd, &wr);
317       if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)
318         FD_SET(tm->new_fd, &wr);
319       tm = tm->next;
320     }
321 
322 
323     int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
324     if (!r) error_exit("select timed out");
325     if (r < -1) continue;
326 
327     if (!FLAG(i) && FD_ISSET(master_fd, &rd)) { //accept new connection
328       new_fd = accept(master_fd, NULL, NULL);
329       if (new_fd < 0) continue;
330       tv_ptr = NULL;
331       fcntl(new_fd, F_SETFD, FD_CLOEXEC);
332       if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
333       pty_fd = new_session(new_fd);
334       if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
335 
336       tm = xzalloc(sizeof(struct term_session));
337       tm->child_pid = TT.fork_pid;
338       tm->new_fd = new_fd;
339       tm->pty_fd = pty_fd;
340       if (session_list) {
341         tm->next = session_list;
342         session_list = tm;
343       } else session_list = tm;
344     }
345 
346     tm = session_list;
347     for (;tm;tm=tm->next) {
348       if (FD_ISSET(tm->pty_fd, &rd)) {
349         if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
350                 BUFSIZE-tm->buff1_avail)) <= 0) break;
351         tm->buff1_avail += c;
352         if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
353                 tm->buff1_avail - tm->buff1_written)) < 0) break;
354         tm->buff1_written += w;
355       }
356       if (FD_ISSET(tm->new_fd, &rd)) {
357         if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
358                 BUFSIZE-tm->buff2_avail)) <= 0) {
359           // The other side went away without a proper shutdown. Happens if
360           // you exit telnet via ^]^D, leaving the socket in TIME_WAIT.
361           xclose(tm->new_fd);
362           tm->new_fd = -1;
363           xclose(tm->pty_fd);
364           tm->pty_fd = -1;
365           break;
366         }
367         c = handle_iacs(tm, c, tm->pty_fd);
368         tm->buff2_avail += c;
369         if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written,
370                 tm->buff2_avail - tm->buff2_written)) < 0) break;
371         tm->buff2_written += w;
372       }
373       if (FD_ISSET(tm->pty_fd, &wr)) {
374         if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written,
375                 tm->buff2_avail - tm->buff2_written)) < 0) break;
376         tm->buff2_written += w;
377       }
378       if (FD_ISSET(tm->new_fd, &wr)) {
379         if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
380                 tm->buff1_avail - tm->buff1_written)) < 0) break;
381         tm->buff1_written += w;
382       }
383       if (tm->buff1_written == tm->buff1_avail)
384         tm->buff1_written = tm->buff1_avail = 0;
385       if (tm->buff2_written == tm->buff2_avail)
386         tm->buff2_written = tm->buff2_avail = 0;
387       fflush(NULL);
388     }
389 
390     // Loop to handle (unknown number of) SIGCHLD notifications
391     while (toys.signal) {
392       int status;
393       struct term_session *prev = NULL;
394       pid_t pid;
395 
396       // funny little dance to avoid race conditions.
397       toys.signal = 0;
398       pid = waitpid(-1, &status, WNOHANG);
399       if (pid <= 0) break;
400       toys.signal++;
401 
402       for (tm = session_list; tm; tm = tm->next) {
403         if (tm->child_pid == pid) break;
404         prev = tm;
405       }
406       if (!tm) error_exit("unexpected reparenting of %d", pid);
407 
408       if (FLAG(i)) exit(EXIT_SUCCESS);
409 
410       if (!prev) session_list = session_list->next;
411       else prev->next = tm->next;
412       xclose(tm->pty_fd);
413       xclose(tm->new_fd);
414       free(tm);
415     }
416   }
417 }
418