1 /* telnetd.c - Telnet Server 2 * 3 * Copyright 2013 Sandeep Sharma <sandeep.jack2756 (at) gmail.com> 4 * Copyright 2013 Kyungwan Han <asura321 (at) 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 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 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 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 if (bind(s, (struct sockaddr *)buf, ((af == AF_INET)? 147 (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6)))) == -1) { 148 close(s); 149 perror_exit("bind"); 150 } 151 152 if (listen(s, 1) < 0) perror_exit("listen"); 153 return s; 154 } 155 156 static void write_issue(char *tty) 157 { 158 int size; 159 char ch = 0; 160 struct utsname u; 161 int fd = open(TT.issue_path, O_RDONLY); 162 163 if (fd < 0) return ; 164 uname(&u); 165 while ((size = readall(fd, &ch, 1)) > 0) { 166 if (ch == '\\' || ch == '%') { 167 if (readall(fd, &ch, 1) <= 0) perror_exit("readall!"); 168 if (ch == 's') fputs(u.sysname, stdout); 169 if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout); 170 if (ch == 'r') fputs(u.release, stdout); 171 if (ch == 'm') fputs(u.machine, stdout); 172 if (ch == 'l') fputs(tty, stdout); 173 } 174 else if (ch == '\n') { 175 fputs("\n\r\0", stdout); 176 } else fputc(ch, stdout); 177 } 178 fflush(NULL); 179 close(fd); 180 } 181 182 static int new_session(int sockfd) 183 { 184 char *argv_login[2]; //arguments for execvp cmd, NULL 185 char tty_name[30]; //tty name length. 186 int fd, flags, i = 1; 187 char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS, 188 IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA }; 189 190 setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)); 191 flags = fcntl(sockfd, F_GETFL); 192 fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); 193 if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK); 194 195 writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs)); 196 if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) { 197 flags = fcntl(fd, F_GETFL); 198 fcntl(fd, F_SETFL, flags | O_NONBLOCK); 199 return fd; 200 } 201 if (TT.fork_pid < 0) perror_exit("fork"); 202 write_issue(tty_name); 203 argv_login[0] = strdup(TT.login_path); 204 argv_login[1] = NULL; 205 execvp(argv_login[0], argv_login); 206 exit(EXIT_FAILURE); 207 } 208 209 static int handle_iacs(struct term_session *tm, int c, int fd) 210 { 211 char *curr ,*start,*end; 212 int i = 0; 213 214 curr = start = tm->buff2+tm->buff2_avail; 215 end = tm->buff2 + c -1; 216 tm->rem = 0; 217 while (curr <= end) { 218 if (*curr != IAC){ 219 220 if (*curr != '\r') { 221 toybuf[i++] = *curr++; 222 continue; 223 } else { 224 toybuf[i++] = *curr++; 225 curr++; 226 if (curr < end && (*curr == '\n' || *curr == '\0')) 227 curr++; 228 continue; 229 } 230 } 231 232 if ((curr + 1) > end) { 233 tm->rem = 1; 234 break; 235 } 236 if (*(curr+1) == IAC) { //IAC as data --> IAC IAC 237 toybuf[i++] = *(curr+1); 238 curr += 2; //IAC IAC --> 2 bytes 239 continue; 240 } 241 if (*(curr + 1) == NOP || *(curr + 1) == SE) { 242 curr += 2; 243 continue; 244 } 245 246 if (*(curr + 1) == SB ) { 247 if (*(curr+2) == TELOPT_NAWS) { 248 struct winsize ws; 249 if ((curr+8) >= end) { //ensure we have data to process. 250 tm->rem = end - curr; 251 break; 252 } 253 ws.ws_col = (curr[3] << 8) | curr[4]; 254 ws.ws_row = (curr[5] << 8) | curr[6]; 255 ioctl(fd, TIOCSWINSZ, (char *)&ws); 256 curr += 9; 257 continue; 258 } else { //eat non-supported sub neg. options. 259 curr++, tm->rem++; 260 while (*curr != IAC && curr <= end) { 261 curr++; 262 tm->rem++; 263 } 264 if (*curr == IAC) { 265 tm->rem = 0; 266 continue; 267 } else break; 268 } 269 } 270 curr += 3; //skip non-supported 3 bytes. 271 } 272 memcpy(start, toybuf, i); 273 memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break; 274 return i; 275 } 276 277 static int dup_iacs(char *start, int fd, int len) 278 { 279 char arr[] = {IAC, IAC}; 280 char *needle = NULL; 281 int ret = 0, c, count = 0; 282 283 while (len) { 284 if (*start == IAC) { 285 count = writeall(fd, arr, sizeof(arr)); 286 if (count != 2) break; //short write 287 start++; 288 ret++; 289 len--; 290 continue; 291 } 292 needle = memchr(start, IAC, len); 293 if (needle) c = needle - start; 294 else c = len; 295 count = writeall(fd, start, c); 296 if (count < 0) break; 297 len -= count; 298 ret += count; 299 start += count; 300 } 301 return ret; 302 } 303 304 void telnetd_main(void) 305 { 306 errno = 0; 307 fd_set rd, wr; 308 struct term_session *tm = NULL; 309 struct timeval tv, *tv_ptr = NULL; 310 int pty_fd, new_fd, c = 0, w, master_fd = 0; 311 int inetd_m = toys.optflags & FLAG_i; 312 313 if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login"; 314 if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net"; 315 if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F; 316 if (!inetd_m) { 317 master_fd = listen_socket(); 318 fcntl(master_fd, F_SETFD, FD_CLOEXEC); 319 if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd; 320 if (!(toys.optflags & FLAG_F)) daemon(0, 0); 321 } else { 322 pty_fd = new_session(master_fd); //master_fd = 0 323 if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd; 324 tm = xzalloc(sizeof(struct term_session)); 325 tm->child_pid = TT.fork_pid; 326 tm->new_fd = 0; 327 tm->pty_fd = pty_fd; 328 if (session_list) { 329 tm->next = session_list; 330 session_list = tm; 331 } else session_list = tm; 332 } 333 334 if ((toys.optflags & FLAG_w) && !session_list) { 335 tv.tv_sec = TT.w_sec; 336 tv.tv_usec = 0; 337 tv_ptr = &tv; 338 } 339 signal(SIGCHLD, generic_signal); 340 341 for (;;) { 342 FD_ZERO(&rd); 343 FD_ZERO(&wr); 344 if (!inetd_m) FD_SET(master_fd, &rd); 345 346 tm = session_list; 347 while (tm) { 348 349 if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd); 350 if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd); 351 if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0) 352 FD_SET(tm->pty_fd, &wr); 353 if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0) 354 FD_SET(tm->new_fd, &wr); 355 tm = tm->next; 356 } 357 358 359 int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr); 360 if (!r) return; //timeout 361 if (r < -1) continue; 362 363 if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection 364 new_fd = accept(master_fd, NULL, NULL); 365 if (new_fd < 0) continue; 366 tv_ptr = NULL; 367 fcntl(new_fd, F_SETFD, FD_CLOEXEC); 368 if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd; 369 pty_fd = new_session(new_fd); 370 if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd; 371 372 tm = xzalloc(sizeof(struct term_session)); 373 tm->child_pid = TT.fork_pid; 374 tm->new_fd = new_fd; 375 tm->pty_fd = pty_fd; 376 if (session_list) { 377 tm->next = session_list; 378 session_list = tm; 379 } else session_list = tm; 380 } 381 382 tm = session_list; 383 for (;tm;tm=tm->next) { 384 if (FD_ISSET(tm->pty_fd, &rd)) { 385 if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail, 386 BUFSIZE-tm->buff1_avail)) <= 0) break; 387 tm->buff1_avail += c; 388 if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m, 389 tm->buff1_avail - tm->buff1_written)) < 0) break; 390 tm->buff1_written += w; 391 } 392 if (FD_ISSET(tm->new_fd, &rd)) { 393 if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail, 394 BUFSIZE-tm->buff2_avail)) <= 0) break; 395 c = handle_iacs(tm, c, tm->pty_fd); 396 tm->buff2_avail += c; 397 if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, 398 tm->buff2_avail - tm->buff2_written)) < 0) break; 399 tm->buff2_written += w; 400 } 401 if (FD_ISSET(tm->pty_fd, &wr)) { 402 if ((w = write(tm->pty_fd, tm->buff2 + tm->buff2_written, 403 tm->buff2_avail - tm->buff2_written)) < 0) break; 404 tm->buff2_written += w; 405 } 406 if (FD_ISSET(tm->new_fd, &wr)) { 407 if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m, 408 tm->buff1_avail - tm->buff1_written)) < 0) break; 409 tm->buff1_written += w; 410 } 411 if (tm->buff1_written == tm->buff1_avail) 412 tm->buff1_written = tm->buff1_avail = 0; 413 if (tm->buff2_written == tm->buff2_avail) 414 tm->buff2_written = tm->buff2_avail = 0; 415 fflush(NULL); 416 } 417 418 // Loop to handle (unknown number of) SIGCHLD notifications 419 while (toys.signal) { 420 int status; 421 struct term_session *prev = NULL; 422 pid_t pid; 423 424 // funny little dance to avoid race conditions. 425 toys.signal = 0; 426 pid = waitpid(-1, &status, WNOHANG); 427 if (pid < 0) break; 428 toys.signal++; 429 430 431 for (tm = session_list; tm; tm = tm->next) { 432 if (tm->child_pid == pid) break; 433 prev = tm; 434 } 435 if (!tm) return; // reparented child we don't care about 436 437 if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS); 438 if (!prev) session_list = session_list->next; 439 else prev->next = tm->next; 440 utmp_entry(); 441 xclose(tm->pty_fd); 442 xclose(tm->new_fd); 443 free(tm); 444 } 445 } 446 } 447