1/* Modified by Broadcom Corp. Portions Copyright (c) Broadcom Corp, 2007. */ 2/* utelnetd.c 3 * 4 * Simple telnet server 5 * 6 * Artur Bajor, Centrum Informatyki ROW 7 * <abajor@cirow.pl> 8 * 9 * Bjorn Wesen, Axis Communications AB 10 * <bjornw@axis.com> 11 * 12 * Joerg Schmitz-Linneweber, Aston GmbH 13 * <schmitz-linneweber@aston-technologie.de> 14 * 15 * Vladimir Oleynik 16 * <dzo@simtreas.ru> 17 * 18 * Robert Schwebel, Pengutronix 19 * <r.schwebel@pengutronix.de> 20 * 21 * 22 * This file is distributed under the GNU General Public License (GPL), 23 * please see the file LICENSE for further information. 24 * 25 * --------------------------------------------------------------------------- 26 * (C) 2000, 2001, 2002 by the authors mentioned above 27 * --------------------------------------------------------------------------- 28 * 29 * The telnetd manpage says it all: 30 * 31 * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for 32 * a client, then creating a login process which has the slave side of the 33 * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the 34 * master side of the pseudo-terminal, implementing the telnet protocol and 35 * passing characters between the remote client and the login process. 36 */ 37 38/* configuration - we'll separate this out later */ 39#define USE_SYSLOG 1 40#define USE_ISSUE 1 41 42 43#include <sys/time.h> 44#include <sys/socket.h> 45#include <sys/wait.h> 46#include <sys/stat.h> 47#include <sys/ioctl.h> 48#include <string.h> 49#include <unistd.h> 50 51/* we need getpty() */ 52#define __USE_GNU 1 53#define __USE_XOPEN 1 54#include <stdlib.h> 55#undef __USE_GNU 56#undef __USE_XOPEN 57 58#include <errno.h> 59#include <netinet/in.h> 60#include <fcntl.h> 61#include <stdio.h> 62#include <signal.h> 63 64#ifdef USE_SYSLOG 65#include <syslog.h> 66#endif 67 68#include <termios.h> 69 70#ifdef DEBUG 71#define TELCMDS 72#define TELOPTS 73#endif 74 75#include <arpa/telnet.h> 76#include <arpa/inet.h> 77#include <ctype.h> 78#include <net/if.h> 79 80#define BUFSIZE 4000 81#ifdef USE_ISSUE 82#define ISSUE_FILE "/etc/issue.net" 83#endif 84 85#define MIN(a,b) ((a) > (b) ? (b) : (a)) 86 87static char *loginpath = NULL; 88 89/* shell name and arguments */ 90 91static char *argv_init[] = {NULL, NULL}; 92 93/* structure that describes a session */ 94 95struct tsession { 96 struct tsession *next; 97 int sockfd, ptyfd; 98 int shell_pid; 99 /* two circular buffers */ 100 char *buf1, *buf2; 101 int rdidx1, wridx1, size1; 102 int rdidx2, wridx2, size2; 103}; 104 105#ifdef DEBUG 106#define DEBUG_OUT(...) fprintf(stderr, __VA_ARGS__) 107#else 108#define DEBUG_OUT(...) 109//static inline void DEBUG_OUT(const char *format, ...) {}; 110#endif 111 112/* 113 114 This is how the buffers are used. The arrows indicate the movement 115 of data. 116 117 +-------+ wridx1++ +------+ rdidx1++ +----------+ 118 | | <-------------- | buf1 | <-------------- | | 119 | | size1-- +------+ size1++ | | 120 | pty | | socket | 121 | | rdidx2++ +------+ wridx2++ | | 122 | | --------------> | buf2 | --------------> | | 123 +-------+ size2++ +------+ size2-- +----------+ 124 125 Each session has got two buffers. 126 127*/ 128 129static int maxfd; 130 131static struct tsession *sessions; 132 133/* 134 * This code was ported from a version which was made for busybox. 135 * So we have to define some helper functions which are originally 136 * available in busybox... 137 */ 138 139void show_usage(void) 140{ 141 printf("Usage: telnetd [-p port] [-i interface] [-l loginprogram] [-d]\n"); 142 printf("\n"); 143 printf(" -p port specify the tcp port to connect to\n"); 144 printf(" -i interface specify the network interface to listen on\n"); 145 printf(" (default: all interfaces)\n"); 146 printf(" -l loginprogram program started by the server\n"); 147 printf(" -d daemonize\n"); 148 printf("\n"); 149 exit(1); 150} 151 152void perror_msg_and_die(char *text) 153{ 154 fprintf(stderr,text); 155 exit(1); 156} 157 158void error_msg_and_die(char *text) 159{ 160 perror_msg_and_die(text); 161} 162 163 164/* 165 Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored 166 and must be removed so as to not be interpreted by the terminal). Make an 167 uninterrupted string of characters fit for the terminal. Do this by packing 168 all characters meant for the terminal sequentially towards the end of bf. 169 170 Return a pointer to the beginning of the characters meant for the terminal. 171 and make *processed equal to the number of characters that were actually 172 processed and *num_totty the number of characters that should be sent to 173 the terminal. 174 175 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends 176 past (bf + len) then that IAC will be left unprocessed and *processed will be 177 less than len. 178 179 FIXME - if we mean to send 0xFF to the terminal then it will be escaped, 180 what is the escape character? We aren't handling that situation here. 181 182 */ 183static char * 184remove_iacs(unsigned char *bf, int len, int *processed, int *num_totty) { 185 unsigned char *ptr = bf; 186 unsigned char *totty = bf; 187 unsigned char *end = bf + len; 188 189 while (ptr < end) { 190 if (*ptr != IAC) { 191 *totty++ = *ptr++; 192 } 193 else { 194 if ((ptr+2) < end) { 195 /* the entire IAC is contained in the buffer 196 we were asked to process. */ 197 DEBUG_OUT("Ignoring IAC 0x%02x, %s, %s\n", *ptr, TELCMD(*(ptr+1)), TELOPT(*(ptr+2))); 198 ptr += 3; 199 } else { 200 /* only the beginning of the IAC is in the 201 buffer we were asked to process, we can't 202 process this char. */ 203 break; 204 } 205 } 206 } 207 208 *processed = ptr - bf; 209 *num_totty = totty - bf; 210 /* move the chars meant for the terminal towards the end of the 211 buffer. */ 212 return memmove(ptr - *num_totty, bf, *num_totty); 213} 214 215 216static int getpty(char *line) 217{ 218 int p; 219 220 p = getpt(); 221 if (p < 0) { 222 DEBUG_OUT("getpty(): couldn't get pty\n"); 223 close(p); 224 return -1; 225 } 226 if (grantpt(p)<0 || unlockpt(p)<0) { 227 DEBUG_OUT("getpty(): couldn't grant and unlock pty\n"); 228 close(p); 229 return -1; 230 } 231 DEBUG_OUT("getpty(): got pty %s\n",ptsname(p)); 232 strcpy(line, (const char*)ptsname(p)); 233 234 return(p); 235} 236 237 238static void 239send_iac(struct tsession *ts, unsigned char command, int option) 240{ 241 /* We rely on that there is space in the buffer for now. */ 242 char *b = ts->buf2 + ts->rdidx2; 243 *b++ = IAC; 244 *b++ = command; 245 *b++ = option; 246 ts->rdidx2 += 3; 247 ts->size2 += 3; 248} 249 250 251static struct tsession * 252make_new_session(int sockfd) 253{ 254 struct termios termbuf; 255 int pty, pid; 256 static char tty_name[32]; 257 struct tsession *ts = (struct tsession *)malloc(sizeof(struct tsession)); 258 int t1, t2; 259#ifdef USE_ISSUE 260 FILE *fp; 261 int chr; 262#endif 263 ts->buf1 = (char *)malloc(BUFSIZE); 264 ts->buf2 = (char *)malloc(BUFSIZE); 265 266 ts->sockfd = sockfd; 267 268 ts->rdidx1 = ts->wridx1 = ts->size1 = 0; 269 ts->rdidx2 = ts->wridx2 = ts->size2 = 0; 270 271 /* Got a new connection, set up a tty and spawn a shell. */ 272 273 pty = getpty(tty_name); 274 275 if (pty < 0) { 276 fprintf(stderr, "All network ports in use!\n"); 277 return 0; 278 } 279 280 if (pty > maxfd) 281 maxfd = pty; 282 283 ts->ptyfd = pty; 284 285 /* Make the telnet client understand we will echo characters so it 286 * should not do it locally. We don't tell the client to run linemode, 287 * because we want to handle line editing and tab completion and other 288 * stuff that requires char-by-char support. 289 */ 290 291 send_iac(ts, DO, TELOPT_ECHO); 292 send_iac(ts, DO, TELOPT_LFLOW); 293 send_iac(ts, WILL, TELOPT_ECHO); 294 send_iac(ts, WILL, TELOPT_SGA); 295 296 297 if ((pid = fork()) < 0) { 298 perror("fork"); 299 } 300 if (pid == 0) { 301 /* In child, open the child's side of the tty. */ 302 int i, t; 303 304 for(i = 0; i <= maxfd; i++) 305 close(i); 306 307 /* make new process group */ 308 if (setsid() < 0) 309 perror_msg_and_die("setsid"); 310 t = open(tty_name, O_RDWR | O_NOCTTY); 311 //t = open(tty_name, O_RDWR); 312 if (t < 0) 313 perror_msg_and_die("Could not open tty"); 314 315 t1 = dup(0); 316 t2 = dup(1); 317 318 tcsetpgrp(0, getpid()); 319 if (ioctl(t, TIOCSCTTY, NULL)) { 320 perror_msg_and_die("could not set controlling tty"); 321 } 322 323 /* The pseudo-terminal allocated to the client is configured to operate in 324 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). 325 */ 326 327 tcgetattr(t, &termbuf); 328 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ 329 termbuf.c_oflag |= ONLCR|XTABS; 330 termbuf.c_iflag |= ICRNL; 331 termbuf.c_iflag &= ~IXOFF; 332 /* termbuf.c_lflag &= ~ICANON; */ 333 tcsetattr(t, TCSANOW, &termbuf); 334 335 DEBUG_OUT("stdin, stdout, stderr: %d %d %d\n", t, t1, t2); 336#ifdef USE_ISSUE 337 /* Display ISSUE_FILE */ 338 if ((fp = fopen(ISSUE_FILE, "r")) != NULL) { 339 DEBUG_OUT(" Open & start display %s\n", ISSUE_FILE); 340 while ((chr=fgetc(fp)) != EOF) { 341 if (chr == '\n') fputc('\r', stdout); 342 fputc(chr, stdout); 343 } 344 fclose(fp); 345 } 346#endif 347 /* exec shell, with correct argv and env */ 348 execv(loginpath, argv_init); 349 350 /* NOT REACHED */ 351 perror_msg_and_die("execv"); 352 } 353 354 ts->shell_pid = pid; 355 356 return ts; 357} 358 359static void 360free_session(struct tsession *ts) 361{ 362 struct tsession *t = sessions; 363 364 /* Unlink this telnet session from the session list. */ 365 if(t == ts) 366 sessions = ts->next; 367 else { 368 while(t->next != ts) 369 t = t->next; 370 t->next = ts->next; 371 } 372 373 free(ts->buf1); 374 free(ts->buf2); 375 376 kill(ts->shell_pid, SIGKILL); 377 378 wait4(ts->shell_pid, NULL, 0, NULL); 379 380 close(ts->ptyfd); 381 close(ts->sockfd); 382 383 if(ts->ptyfd == maxfd || ts->sockfd == maxfd) 384 maxfd--; 385 if(ts->ptyfd == maxfd || ts->sockfd == maxfd) 386 maxfd--; 387 388 free(ts); 389} 390 391int main(int argc, char **argv) 392{ 393 struct sockaddr_in sa; 394 int master_fd; 395 fd_set rdfdset, wrfdset; 396 int selret; 397 int on = 1; 398 int portnbr = 23; 399 int c, ii; 400 int daemonize = 0; 401 char *interface_name = NULL; 402 struct ifreq interface; 403 404#ifdef USE_SYSLOG 405 char *appname; 406 appname = strrchr (argv [0], '/'); 407 if (!appname) appname = argv [0]; 408 else appname++; 409#endif 410 411 /* check if user supplied a port number */ 412 413 for (;;) { 414 c = getopt( argc, argv, "i:p:l:hd"); 415 if (c == EOF) break; 416 switch (c) { 417 case 'p': 418 portnbr = atoi(optarg); 419 break; 420 case 'i': 421 interface_name = strdup(optarg); 422 break; 423 case 'l': 424 loginpath = strdup(optarg); 425 break; 426 case 'd': 427 daemonize = 1; 428 break; 429 case 'h': 430 default: 431 show_usage(); 432 exit(1); 433 } 434 } 435 436 if (!loginpath) { 437 loginpath = "/bin/login"; 438 if (access(loginpath, X_OK) < 0) 439 loginpath = "/bin/sh"; 440 } 441 442 if (access(loginpath, X_OK) < 0) { 443 /* workaround: error_msg_and_die has doesn't understand 444 variable argument lists yet */ 445 fprintf(stderr,"\"%s\"",loginpath); 446 perror_msg_and_die(" is no valid executable!\n"); 447 } 448 449 printf("telnetd: starting\n"); 450 printf(" port: %i; interface: %s; login program: %s\n", 451 portnbr, (interface_name)?interface_name:"any", loginpath); 452 453 argv_init[0] = loginpath; 454 sessions = 0; 455 456 /* Grab a TCP socket. */ 457 458 master_fd = socket(AF_INET, SOCK_STREAM, 0); 459 if (master_fd < 0) { 460 perror("socket"); 461 return 1; 462 } 463 (void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); 464 465 /* Set it to listen to specified port. */ 466 467 memset((void *)&sa, 0, sizeof(sa)); 468 sa.sin_family = AF_INET; 469 sa.sin_port = htons(portnbr); 470 471 /* Set it to listen on the specified interface */ 472 if (interface_name) { 473 strncpy(interface.ifr_ifrn.ifrn_name, interface_name, IFNAMSIZ); 474 (void)setsockopt(master_fd, SOL_SOCKET, 475 SO_BINDTODEVICE, &interface, sizeof(interface)); 476 } 477 478 if (bind(master_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { 479 perror("bind"); 480 return 1; 481 } 482 483 if (listen(master_fd, 1) < 0) { 484 perror("listen"); 485 return 1; 486 } 487 488 if (daemonize) 489 { 490 DEBUG_OUT(" daemonizing\n"); 491 if (daemon(0, 1) < 0) perror_msg_and_die("daemon"); 492 } 493 494#ifdef USE_SYSLOG 495 openlog(appname , LOG_NDELAY | LOG_PID, LOG_DAEMON); 496 syslog(LOG_INFO, "%s (port: %i, ifname: %s, login: %s) startup succeeded\n"\ 497 , appname, portnbr, (interface_name)?interface_name:"any", loginpath); 498 closelog(); 499#endif 500 501 maxfd = master_fd; 502 503 do { 504 struct tsession *ts; 505 506 FD_ZERO(&rdfdset); 507 FD_ZERO(&wrfdset); 508 509 /* select on the master socket, all telnet sockets and their 510 * ptys if there is room in their respective session buffers. 511 */ 512 513 FD_SET(master_fd, &rdfdset); 514 515 ts = sessions; 516 while (ts) { 517 /* buf1 is used from socket to pty 518 * buf2 is used from pty to socket 519 */ 520 if (ts->size1 > 0) { 521 FD_SET(ts->ptyfd, &wrfdset); /* can write to pty */ 522 } 523 if (ts->size1 < BUFSIZE) { 524 FD_SET(ts->sockfd, &rdfdset); /* can read from socket */ 525 } 526 if (ts->size2 > 0) { 527 FD_SET(ts->sockfd, &wrfdset); /* can write to socket */ 528 } 529 if (ts->size2 < BUFSIZE) { 530 FD_SET(ts->ptyfd, &rdfdset); /* can read from pty */ 531 } 532 ts = ts->next; 533 } 534 535 selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); 536 537 if (!selret) 538 break; 539 540 /* First check for and accept new sessions. */ 541 if (FD_ISSET(master_fd, &rdfdset)) { 542 int fd, salen; 543 544 salen = sizeof(sa); 545 if ((fd = accept(master_fd, (struct sockaddr *)&sa, 546 (socklen_t *)&salen)) < 0) { 547 continue; 548 } else { 549 /* Create a new session and link it into our active list. */ 550 struct tsession *new_ts; 551#ifdef USE_SYSLOG 552 openlog(appname , LOG_NDELAY, LOG_DAEMON); 553 syslog(LOG_INFO, "connection from: %s\n", inet_ntoa(sa.sin_addr)); 554 closelog(); 555#endif 556 new_ts = make_new_session(fd); 557 if (new_ts) { 558 new_ts->next = sessions; 559 sessions = new_ts; 560 if (fd > maxfd) 561 maxfd = fd; 562 } else { 563 close(fd); 564 } 565 } 566 } 567 568 /* Then check for data tunneling. */ 569 570 ts = sessions; 571 while (ts) { /* For all sessions... */ 572 int maxlen, w, r; 573 struct tsession *next = ts->next; /* in case we free ts. */ 574 575 if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { 576 int processed, num_totty; 577 char *ptr; 578 /* Write to pty from buffer 1. */ 579 580 maxlen = MIN(BUFSIZE - ts->wridx1, 581 ts->size1); 582 ptr = remove_iacs((unsigned char *)ts->buf1 + ts->wridx1, maxlen, 583 &processed, &num_totty); 584 585 /* the difference between processed and num_totty 586 is all the iacs we removed from the stream. 587 Adjust buf1 accordingly. */ 588 ts->wridx1 += processed - num_totty; 589 ts->size1 -= processed - num_totty; 590 591 w = write(ts->ptyfd, ptr, num_totty); 592 if (w < 0) { 593 perror("write"); 594 free_session(ts); 595 ts = next; 596 continue; 597 } 598 ts->wridx1 += w; 599 ts->size1 -= w; 600 if (ts->wridx1 == BUFSIZE) 601 ts->wridx1 = 0; 602 } 603 604 if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset)) { 605 /* Write to socket from buffer 2. */ 606 maxlen = MIN(BUFSIZE - ts->wridx2, 607 ts->size2); 608 w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen); 609 if (w < 0) { 610 perror("write"); 611 free_session(ts); 612 ts = next; 613 continue; 614 } 615 ts->wridx2 += w; 616 ts->size2 -= w; 617 if (ts->wridx2 == BUFSIZE) 618 ts->wridx2 = 0; 619 } 620 621 if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset)) { 622 /* Read from socket to buffer 1. */ 623 maxlen = MIN(BUFSIZE - ts->rdidx1, 624 BUFSIZE - ts->size1); 625 r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen); 626 if (!r || (r < 0 && errno != EINTR)) { 627 free_session(ts); 628 ts = next; 629 continue; 630 } 631 if(!*(ts->buf1 + ts->rdidx1 + r - 1)) { 632 r--; 633 if(!r) 634 continue; 635 } 636 ts->rdidx1 += r; 637 ts->size1 += r; 638 if (ts->rdidx1 == BUFSIZE) 639 ts->rdidx1 = 0; 640 } 641 642 if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { 643 /* Read from pty to buffer 2. */ 644 maxlen = MIN(BUFSIZE - ts->rdidx2, 645 BUFSIZE - ts->size2); 646 r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); 647 if (!r || (r < 0 && errno != EINTR)) { 648 free_session(ts); 649 ts = next; 650 continue; 651 } 652 for (ii=0; ii < r; ii++) 653 if (*(ts->buf2 + ts->rdidx2 + ii) == 3) 654 fprintf(stderr, "found <CTRL>-<C> in data!\n"); 655 ts->rdidx2 += r; 656 ts->size2 += r; 657 if (ts->rdidx2 == BUFSIZE) 658 ts->rdidx2 = 0; 659 } 660 661 if (ts->size1 == 0) { 662 ts->rdidx1 = 0; 663 ts->wridx1 = 0; 664 } 665 if (ts->size2 == 0) { 666 ts->rdidx2 = 0; 667 ts->wridx2 = 0; 668 } 669 ts = next; 670 } 671 672 } while (1); 673 674 return 0; 675} 676