• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <utmp.h>
28 GLOBALS(
29     char *login_path;
30     char *issue_path;
31     int port;
32     char *host_addr;
33     long w_sec;
34 
35     int gmax_fd;
36     pid_t fork_pid;
37 )
38 
39 
40 # define IAC         255  /* interpret as command: */
41 # define DONT        254  /* you are not to use option */
42 # define DO          253  /* please, you use option */
43 # define WONT        252  /* I won't use option */
44 # define WILL        251  /* I will use option */
45 # define SB          250  /* interpret as subnegotiation */
46 # define SE          240  /* end sub negotiation */
47 # define NOP         241  /* No Operation */
48 # define TELOPT_ECHO   1  /* echo */
49 # define TELOPT_SGA    3  /* suppress go ahead */
50 # define TELOPT_TTYPE 24  /* terminal type */
51 # define TELOPT_NAWS  31  /* window size */
52 
53 #define BUFSIZE 4*1024
54 struct term_session {
55   int new_fd, pty_fd;
56   pid_t child_pid;
57   int buff1_avail, buff2_avail;
58   int buff1_written, buff2_written;
59   int rem;  //unprocessed data from socket
60   char buff1[BUFSIZE], buff2[BUFSIZE];
61   struct term_session *next;
62 };
63 
64 struct term_session *session_list = NULL;
65 
get_sockaddr(char * host,void * buf)66 static void get_sockaddr(char *host, void *buf)
67 {
68   in_port_t port_num = htons(TT.port);
69   struct addrinfo hints, *result;
70   int status, af = AF_UNSPEC;
71   char *s;
72 
73   // [ipv6]:port or exactly one :
74   if (*host == '[') {
75     host++;
76     s = strchr(host, ']');
77     if (s) *s++ = 0;
78     else error_exit("bad address '%s'", host-1);
79     af = AF_INET6;
80   } else {
81     s = strrchr(host, ':');
82     if (s && strchr(host, ':') == s) {
83       *s = 0;
84       af = AF_INET;
85     } else if (s && strchr(host, ':') != s) {
86       af = AF_INET6;
87       s = 0;
88     }
89   }
90 
91   if (s++) {
92     char *ss;
93     unsigned long p = strtoul(s, &ss, 0);
94     if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s);
95     port_num = htons(p);
96   }
97 
98   memset(&hints, 0 , sizeof(struct addrinfo));
99   hints.ai_family = af;
100   hints.ai_socktype = SOCK_STREAM;
101 
102   status = getaddrinfo(host, NULL, &hints, &result);
103   if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status));
104 
105   memcpy(buf, result->ai_addr, result->ai_addrlen);
106   freeaddrinfo(result);
107 
108   if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num;
109   else ((struct sockaddr_in6*)buf)->sin6_port = port_num;
110 }
111 
utmp_entry(void)112 static void utmp_entry(void)
113 {
114   struct utmp entry;
115   struct utmp *utp_ptr;
116   pid_t pid = getpid();
117 
118   utmpname(_PATH_UTMP);
119   setutent(); //start from start
120   while ((utp_ptr = getutent()) != NULL) {
121     if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break;
122   }
123   if (!utp_ptr) entry.ut_type = DEAD_PROCESS;
124   time(&entry.ut_time);
125   setutent();
126   pututline(&entry);
127 }
128 
listen_socket(void)129 static int listen_socket(void)
130 {
131   int s, af = AF_INET, yes = 1;
132   char buf[sizeof(struct sockaddr_storage)];
133 
134   memset(buf, 0, sizeof(buf));
135   if (toys.optflags & FLAG_b) {
136     get_sockaddr(TT.host_addr, buf);
137     af = ((struct sockaddr *)buf)->sa_family;
138   } else {
139     ((struct sockaddr_in*)buf)->sin_port = htons(TT.port);
140     ((struct sockaddr_in*)buf)->sin_family = af;
141   }
142   s = xsocket(af, SOCK_STREAM, 0);
143   if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1)
144     perror_exit("setsockopt");
145 
146   xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
147           (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
148 
149   if (listen(s, 1) < 0) perror_exit("listen");
150   return s;
151 }
152 
write_issue(char * tty)153 static void write_issue(char *tty)
154 {
155   int size;
156   char ch = 0;
157   struct utsname u;
158   int fd = open(TT.issue_path, O_RDONLY);
159 
160   if (fd < 0) return ;
161   uname(&u);
162   while ((size = readall(fd, &ch, 1)) > 0) {
163     if (ch == '\\' || ch == '%') {
164       if (readall(fd, &ch, 1) <= 0) perror_exit("readall!");
165       if (ch == 's') fputs(u.sysname, stdout);
166       if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout);
167       if (ch == 'r') fputs(u.release, stdout);
168       if (ch == 'm') fputs(u.machine, stdout);
169       if (ch == 'l') fputs(tty, stdout);
170     }
171     else if (ch == '\n') {
172       fputs("\n\r\0", stdout);
173     } else fputc(ch, stdout);
174   }
175   fflush(NULL);
176   close(fd);
177 }
178 
new_session(int sockfd)179 static int new_session(int sockfd)
180 {
181   char *argv_login[2]; //arguments for execvp cmd, NULL
182   char tty_name[30]; //tty name length.
183   int fd, flags, i = 1;
184   char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS,
185     IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA };
186 
187   setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
188   flags = fcntl(sockfd, F_GETFL);
189   fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
190   if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
191 
192   writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
193   if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
194     flags = fcntl(fd, F_GETFL);
195     fcntl(fd, F_SETFL, flags | O_NONBLOCK);
196     return fd;
197   }
198   if (TT.fork_pid < 0) perror_exit("fork");
199   write_issue(tty_name);
200   argv_login[0] = strdup(TT.login_path);
201   argv_login[1] = NULL;
202   execvp(argv_login[0], argv_login);
203   exit(EXIT_FAILURE);
204 }
205 
handle_iacs(struct term_session * tm,int c,int fd)206 static int handle_iacs(struct term_session *tm, int c, int fd)
207 {
208   char *curr ,*start,*end;
209   int i = 0;
210 
211   curr = start = tm->buff2+tm->buff2_avail;
212   end = tm->buff2 + c -1;
213   tm->rem = 0;
214   while (curr <= end) {
215     if (*curr != IAC){
216 
217       if (*curr != '\r') {
218         toybuf[i++] = *curr++;
219         continue;
220       } else {
221         toybuf[i++] = *curr++;
222         curr++;
223         if (curr < end && (*curr == '\n' || *curr == '\0'))
224           curr++;
225         continue;
226       }
227     }
228 
229     if ((curr + 1) > end) {
230       tm->rem = 1;
231       break;
232     }
233     if (*(curr+1) == IAC) { //IAC as data --> IAC IAC
234       toybuf[i++] = *(curr+1);
235       curr += 2; //IAC IAC --> 2 bytes
236       continue;
237     }
238     if (*(curr + 1) == NOP || *(curr + 1) == SE) {
239       curr += 2;
240       continue;
241     }
242 
243     if (*(curr + 1) == SB ) {
244       if (*(curr+2) == TELOPT_NAWS) {
245         struct winsize ws;
246         if ((curr+8) >= end) {  //ensure we have data to process.
247           tm->rem = end - curr;
248           break;
249         }
250         ws.ws_col = (curr[3] << 8) | curr[4];
251         ws.ws_row = (curr[5] << 8) | curr[6];
252         ioctl(fd, TIOCSWINSZ, (char *)&ws);
253         curr += 9;
254         continue;
255       } else { //eat non-supported sub neg. options.
256         curr++, tm->rem++;
257         while (*curr != IAC && curr <= end) {
258           curr++;
259           tm->rem++;
260         }
261         if (*curr == IAC) {
262           tm->rem = 0;
263           continue;
264         } else break;
265       }
266     }
267     curr += 3; //skip non-supported 3 bytes.
268   }
269   memcpy(start, toybuf, i);
270   memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break;
271   return i;
272 }
273 
dup_iacs(char * start,int fd,int len)274 static int dup_iacs(char *start, int fd, int len)
275 {
276   char arr[] = {IAC, IAC};
277   char *needle = NULL;
278   int ret = 0, c, count = 0;
279 
280   while (len) {
281     if (*start == IAC) {
282       count = writeall(fd, arr, sizeof(arr));
283       if (count != 2) break; //short write
284       start++;
285       ret++;
286       len--;
287       continue;
288     }
289     needle = memchr(start, IAC, len);
290     if (needle) c = needle - start;
291     else c = len;
292     count = writeall(fd, start, c);
293     if (count < 0) break;
294     len -= count;
295     ret += count;
296     start += count;
297   }
298   return ret;
299 }
300 
telnetd_main(void)301 void telnetd_main(void)
302 {
303   errno = 0;
304   fd_set rd, wr;
305   struct term_session *tm = NULL;
306   struct timeval tv, *tv_ptr = NULL;
307   int pty_fd, new_fd, c = 0, w, master_fd = 0;
308   int inetd_m = toys.optflags & FLAG_i;
309 
310   if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
311   if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
312   if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
313   if (!inetd_m) {
314     master_fd = listen_socket();
315     fcntl(master_fd, F_SETFD, FD_CLOEXEC);
316     if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
317     if (!(toys.optflags & FLAG_F)) daemon(0, 0);
318   } else {
319     pty_fd = new_session(master_fd); //master_fd = 0
320     if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
321     tm = xzalloc(sizeof(struct term_session));
322     tm->child_pid = TT.fork_pid;
323     tm->new_fd = 0;
324     tm->pty_fd = pty_fd;
325     if (session_list) {
326       tm->next = session_list;
327       session_list = tm;
328     } else session_list = tm;
329   }
330 
331   if ((toys.optflags & FLAG_w) && !session_list) {
332     tv.tv_sec = TT.w_sec;
333     tv.tv_usec = 0;
334     tv_ptr = &tv;
335   }
336   signal(SIGCHLD, generic_signal);
337 
338   for (;;) {
339     FD_ZERO(&rd);
340     FD_ZERO(&wr);
341     if (!inetd_m) FD_SET(master_fd, &rd);
342 
343     tm = session_list;
344     while (tm) {
345 
346       if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd);
347       if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd);
348       if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0)
349         FD_SET(tm->pty_fd, &wr);
350       if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0)
351         FD_SET(tm->new_fd, &wr);
352       tm = tm->next;
353     }
354 
355 
356     int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
357     if (!r) return; //timeout
358     if (r < -1) continue;
359 
360     if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
361       new_fd = accept(master_fd, NULL, NULL);
362       if (new_fd < 0) continue;
363       tv_ptr = NULL;
364       fcntl(new_fd, F_SETFD, FD_CLOEXEC);
365       if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd;
366       pty_fd = new_session(new_fd);
367       if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
368 
369       tm = xzalloc(sizeof(struct term_session));
370       tm->child_pid = TT.fork_pid;
371       tm->new_fd = new_fd;
372       tm->pty_fd = pty_fd;
373       if (session_list) {
374         tm->next = session_list;
375         session_list = tm;
376       } else session_list = tm;
377     }
378 
379     tm = session_list;
380     for (;tm;tm=tm->next) {
381       if (FD_ISSET(tm->pty_fd, &rd)) {
382         if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
383                 BUFSIZE-tm->buff1_avail)) <= 0) break;
384         tm->buff1_avail += c;
385         if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
386                 tm->buff1_avail - tm->buff1_written)) < 0) break;
387         tm->buff1_written += w;
388       }
389       if (FD_ISSET(tm->new_fd, &rd)) {
390         if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail,
391                 BUFSIZE-tm->buff2_avail)) <= 0) break;
392         c = handle_iacs(tm, c, tm->pty_fd);
393         tm->buff2_avail += c;
394         if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written,
395                 tm->buff2_avail - tm->buff2_written)) < 0) break;
396         tm->buff2_written += w;
397       }
398       if (FD_ISSET(tm->pty_fd, &wr)) {
399         if ((w = write(tm->pty_fd,  tm->buff2 + tm->buff2_written,
400                 tm->buff2_avail - tm->buff2_written)) < 0) break;
401         tm->buff2_written += w;
402       }
403       if (FD_ISSET(tm->new_fd, &wr)) {
404         if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
405                 tm->buff1_avail - tm->buff1_written)) < 0) break;
406         tm->buff1_written += w;
407       }
408       if (tm->buff1_written == tm->buff1_avail)
409         tm->buff1_written = tm->buff1_avail = 0;
410       if (tm->buff2_written == tm->buff2_avail)
411         tm->buff2_written = tm->buff2_avail = 0;
412       fflush(NULL);
413     }
414 
415     // Loop to handle (unknown number of) SIGCHLD notifications
416     while (toys.signal) {
417       int status;
418       struct term_session *prev = NULL;
419       pid_t pid;
420 
421       // funny little dance to avoid race conditions.
422       toys.signal = 0;
423       pid = waitpid(-1, &status, WNOHANG);
424       if (pid < 0) break;
425       toys.signal++;
426 
427 
428       for (tm = session_list; tm; tm = tm->next) {
429         if (tm->child_pid == pid) break;
430         prev = tm;
431       }
432       if (!tm) return; // reparented child we don't care about
433 
434       if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
435       if (!prev) session_list = session_list->next;
436       else prev->next = tm->next;
437       utmp_entry();
438       xclose(tm->pty_fd);
439       xclose(tm->new_fd);
440       free(tm);
441     }
442   }
443 }
444