1/* telnetd.c - Telnet Server 2 * 3 * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com> 4 * Copyright 2013 Kyungwan Han <asura321@gmail.com> 5 * 6USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN)) 7 8config 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 29GLOBALS( 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 41struct 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 51struct term_session *session_list = NULL; 52 53static 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 99static 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 122static 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 148static 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 175static 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 243static 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 270void 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