Mercurial > hg > toybox
view toys/pending/telnetd.c @ 1572:da1bf31ed322 draft
Tweak the "ignoring return value" fortify workaround for readlinkat.
We zero the buffer and if the link read fails that's left alone, so
it's ok for the symlink not to be there. Unfortunately, typecasting the
return value to (void) doesn't shut up gcc, and having an if(); with the
semicolon on the same line doesn't shut up llvm. (The semicolon on a new
line would, but C does not have significant whitespace and I'm not going
to humor llvm if it plans to start.)
So far, empty curly brackets consistently get the warning to shut up.
author | Rob Landley <rob@landley.net> |
---|---|
date | Mon, 24 Nov 2014 17:23:23 -0600 |
parents | 685a0da6ca59 |
children | c1715c752e89 |
line wrap: on
line source
/* telnetd.c - Telnet Server * * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com> * Copyright 2013 Kyungwan Han <asura321@gmail.com> * USE_TELNETD(NEWTOY(telnetd, "w#<0b:p#<0>65535=23f:l:FSKi[!wi]", TOYFLAG_USR|TOYFLAG_BIN)) config TELNETD bool "telnetd" default n depends on TOYBOX_PTY help Handle incoming telnet connections -l LOGIN Exec LOGIN on connect -f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue -K Close connection as soon as login exits -p PORT Port to listen on -b ADDR[:PORT] Address to bind to -F Run in foreground -i Inetd mode -w SEC Inetd 'wait' mode, linger time SEC -S Log to syslog (implied by -i or without -F and -w) */ #define FOR_telnetd #include "toys.h" #include <utmp.h> GLOBALS( char *login_path; char *issue_path; int port; char *host_addr; long w_sec; int gmax_fd; pid_t fork_pid; ) # define IAC 255 /* interpret as command: */ # define DONT 254 /* you are not to use option */ # define DO 253 /* please, you use option */ # define WONT 252 /* I won't use option */ # define WILL 251 /* I will use option */ # define SB 250 /* interpret as subnegotiation */ # define SE 240 /* end sub negotiation */ # define NOP 241 /* No Operation */ # define TELOPT_ECHO 1 /* echo */ # define TELOPT_SGA 3 /* suppress go ahead */ # define TELOPT_TTYPE 24 /* terminal type */ # define TELOPT_NAWS 31 /* window size */ #define BUFSIZE 4*1024 struct term_session { int new_fd, pty_fd; pid_t child_pid; int buff1_avail, buff2_avail; int buff1_written, buff2_written; int rem; //unprocessed data from socket char buff1[BUFSIZE], buff2[BUFSIZE]; struct term_session *next; }; struct term_session *session_list = NULL; static void get_sockaddr(char *host, void *buf) { in_port_t port_num = htons(TT.port); struct addrinfo hints, *result; int status, af = AF_UNSPEC; char *s; // [ipv6]:port or exactly one : if (*host == '[') { host++; s = strchr(host, ']'); if (s) *s++ = 0; else error_exit("bad address '%s'", host-1); af = AF_INET6; } else { s = strrchr(host, ':'); if (s && strchr(host, ':') == s) { *s = 0; af = AF_INET; } else if (s && strchr(host, ':') != s) { af = AF_INET6; s = 0; } } if (s++) { char *ss; unsigned long p = strtoul(s, &ss, 0); if (!*s || *ss || p > 65535) error_exit("bad port '%s'", s); port_num = htons(p); } memset(&hints, 0 , sizeof(struct addrinfo)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; status = getaddrinfo(host, NULL, &hints, &result); if (status) error_exit("bad address '%s' : %s", host, gai_strerror(status)); memcpy(buf, result->ai_addr, result->ai_addrlen); freeaddrinfo(result); if (af == AF_INET) ((struct sockaddr_in*)buf)->sin_port = port_num; else ((struct sockaddr_in6*)buf)->sin6_port = port_num; } static void utmp_entry(void) { struct utmp entry; struct utmp *utp_ptr; pid_t pid = getpid(); utmpname(_PATH_UTMP); setutent(); //start from start while ((utp_ptr = getutent()) != NULL) { if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break; } if (!utp_ptr) entry.ut_type = DEAD_PROCESS; time(&entry.ut_time); setutent(); pututline(&entry); } static int listen_socket(void) { int s, af = AF_INET, yes = 1; char buf[sizeof(struct sockaddr_storage)]; memset(buf, 0, sizeof(buf)); if (toys.optflags & FLAG_b) { get_sockaddr(TT.host_addr, buf); af = ((struct sockaddr *)buf)->sa_family; } else { ((struct sockaddr_in*)buf)->sin_port = htons(TT.port); ((struct sockaddr_in*)buf)->sin_family = af; } s = xsocket(af, SOCK_STREAM, 0); if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1) perror_exit("setsockopt"); if (bind(s, (struct sockaddr *)buf, ((af == AF_INET)? (sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6)))) == -1) { close(s); perror_exit("bind"); } if (listen(s, 1) < 0) perror_exit("listen"); return s; } static void write_issue(char *tty) { int size; char ch = 0; struct utsname u; int fd = open(TT.issue_path, O_RDONLY); if (fd < 0) return ; uname(&u); while ((size = readall(fd, &ch, 1)) > 0) { if (ch == '\\' || ch == '%') { if (readall(fd, &ch, 1) <= 0) perror_exit("readall!"); if (ch == 's') fputs(u.sysname, stdout); if (ch == 'n'|| ch == 'h') fputs(u.nodename, stdout); if (ch == 'r') fputs(u.release, stdout); if (ch == 'm') fputs(u.machine, stdout); if (ch == 'l') fputs(tty, stdout); } else if (ch == '\n') { fputs("\n\r\0", stdout); } else fputc(ch, stdout); } fflush(NULL); close(fd); } static int new_session(int sockfd) { char *argv_login[2]; //arguments for execvp cmd, NULL char tty_name[30]; //tty name length. int fd, flags, i = 1; char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS, IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA }; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)); flags = fcntl(sockfd, F_GETFL); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK); writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs)); if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) { flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | O_NONBLOCK); return fd; } if (TT.fork_pid < 0) perror_exit("fork"); write_issue(tty_name); argv_login[0] = strdup(TT.login_path); argv_login[1] = NULL; execvp(argv_login[0], argv_login); exit(EXIT_FAILURE); } static int handle_iacs(struct term_session *tm, int c, int fd) { char *curr ,*start,*end; int i = 0; curr = start = tm->buff2+tm->buff2_avail; end = tm->buff2 + c -1; tm->rem = 0; while (curr <= end) { if (*curr != IAC){ if (*curr != '\r') { toybuf[i++] = *curr++; continue; } else { toybuf[i++] = *curr++; curr++; if (curr < end && (*curr == '\n' || *curr == '\0')) curr++; continue; } } if ((curr + 1) > end) { tm->rem = 1; break; } if (*(curr+1) == IAC) { //IAC as data --> IAC IAC toybuf[i++] = *(curr+1); curr += 2; //IAC IAC --> 2 bytes continue; } if (*(curr + 1) == NOP || *(curr + 1) == SE) { curr += 2; continue; } if (*(curr + 1) == SB ) { if (*(curr+2) == TELOPT_NAWS) { struct winsize ws; if ((curr+8) >= end) { //ensure we have data to process. tm->rem = end - curr; break; } ws.ws_col = (curr[3] << 8) | curr[4]; ws.ws_row = (curr[5] << 8) | curr[6]; ioctl(fd, TIOCSWINSZ, (char *)&ws); curr += 9; continue; } else { //eat non-supported sub neg. options. curr++, tm->rem++; while (*curr != IAC && curr <= end) { curr++; tm->rem++; } if (*curr == IAC) { tm->rem = 0; continue; } else break; } } curr += 3; //skip non-supported 3 bytes. } memcpy(start, toybuf, i); memcpy(start + i, end - tm->rem, tm->rem); //put remaining if we break; return i; } static int dup_iacs(char *start, int fd, int len) { char arr[] = {IAC, IAC}; char *needle = NULL; int ret = 0, c, count = 0; while (len) { if (*start == IAC) { count = writeall(fd, arr, sizeof(arr)); if (count != 2) break; //short write start++; ret++; len--; continue; } needle = memchr(start, IAC, len); if (needle) c = needle - start; else c = len; count = writeall(fd, start, c); if (count < 0) break; len -= count; ret += count; start += count; } return ret; } void telnetd_main(void) { errno = 0; fd_set rd, wr; struct term_session *tm = NULL; struct timeval tv, *tv_ptr = NULL; int pty_fd, new_fd, c = 0, w, master_fd = 0; int inetd_m = toys.optflags & FLAG_i; if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login"; if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net"; if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F; if (!inetd_m) { master_fd = listen_socket(); fcntl(master_fd, F_SETFD, FD_CLOEXEC); if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd; if (!(toys.optflags & FLAG_F)) daemon(0, 0); } else { pty_fd = new_session(master_fd); //master_fd = 0 if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd; tm = xzalloc(sizeof(struct term_session)); tm->child_pid = TT.fork_pid; tm->new_fd = 0; tm->pty_fd = pty_fd; if (session_list) { tm->next = session_list; session_list = tm; } else session_list = tm; } if ((toys.optflags & FLAG_w) && !session_list) { tv.tv_sec = TT.w_sec; tv.tv_usec = 0; tv_ptr = &tv; } signal(SIGCHLD, generic_signal); for (;;) { FD_ZERO(&rd); FD_ZERO(&wr); if (!inetd_m) FD_SET(master_fd, &rd); tm = session_list; while (tm) { if (tm->pty_fd > 0 && tm->buff1_avail < BUFSIZE) FD_SET(tm->pty_fd, &rd); if (tm->new_fd >= 0 && tm->buff2_avail < BUFSIZE) FD_SET(tm->new_fd, &rd); if (tm->pty_fd > 0 && (tm->buff2_avail - tm->buff2_written) > 0) FD_SET(tm->pty_fd, &wr); if (tm->new_fd >= 0 && (tm->buff1_avail - tm->buff1_written) > 0) FD_SET(tm->new_fd, &wr); tm = tm->next; } int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr); if (!r) return; //timeout if (r < -1) continue; if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection new_fd = accept(master_fd, NULL, NULL); if (new_fd < 0) continue; tv_ptr = NULL; fcntl(new_fd, F_SETFD, FD_CLOEXEC); if (new_fd > TT.gmax_fd) TT.gmax_fd = new_fd; pty_fd = new_session(new_fd); if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd; tm = xzalloc(sizeof(struct term_session)); tm->child_pid = TT.fork_pid; tm->new_fd = new_fd; tm->pty_fd = pty_fd; if (session_list) { tm->next = session_list; session_list = tm; } else session_list = tm; } tm = session_list; for (;tm;tm=tm->next) { if (FD_ISSET(tm->pty_fd, &rd)) { if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail, BUFSIZE-tm->buff1_avail)) <= 0) break; tm->buff1_avail += c; if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m, tm->buff1_avail - tm->buff1_written)) < 0) break; tm->buff1_written += w; } if (FD_ISSET(tm->new_fd, &rd)) { if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail, BUFSIZE-tm->buff2_avail)) <= 0) break; c = handle_iacs(tm, c, tm->pty_fd); tm->buff2_avail += c; if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, tm->buff2_avail - tm->buff2_written)) < 0) break; tm->buff2_written += w; } if (FD_ISSET(tm->pty_fd, &wr)) { if ((w = write(tm->pty_fd, tm->buff2 + tm->buff2_written, tm->buff2_avail - tm->buff2_written)) < 0) break; tm->buff2_written += w; } if (FD_ISSET(tm->new_fd, &wr)) { if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m, tm->buff1_avail - tm->buff1_written)) < 0) break; tm->buff1_written += w; } if (tm->buff1_written == tm->buff1_avail) tm->buff1_written = tm->buff1_avail = 0; if (tm->buff2_written == tm->buff2_avail) tm->buff2_written = tm->buff2_avail = 0; fflush(NULL); } // Loop to handle (unknown number of) SIGCHLD notifications while (toys.signal) { int status; struct term_session *prev = NULL; pid_t pid; // funny little dance to avoid race conditions. toys.signal = 0; pid = waitpid(-1, &status, WNOHANG); if (pid < 0) break; toys.signal++; for (tm = session_list; tm; tm = tm->next) { if (tm->child_pid == pid) break; prev = tm; } if (!tm) return; // reparented child we don't care about if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS); if (!prev) session_list = session_list->next; else prev->next = tm->next; utmp_entry(); xclose(tm->pty_fd); xclose(tm->new_fd); free(tm); } } }