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