/* * Copyright (c) 1983, 1990, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 2002 Networks Associates Technology, Inc. * All rights reserved. * * Portions of this software were developed for the FreeBSD Project by * ThinkSec AS and NAI Labs, the Security Research Division of Network * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 * ("CBOSS"), as part of the DARPA CHATS research program. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1983, 1990, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #if 0 #ifndef lint static const char sccsid[] = "@(#)rlogin.c 8.1 (Berkeley) 6/6/93"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); /* * rlogin - remote login */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TIOCPKT_WINDOW #define TIOCPKT_WINDOW 0x80 #endif /* concession to Sun */ #ifndef SIGUSR1 #define SIGUSR1 30 #endif static int eight, rem; static struct termios deftty; static int family = PF_UNSPEC; static int noescape; static u_char escapechar = '~'; #define get_window_size(fd, wp) ioctl(fd, TIOCGWINSZ, wp) static struct winsize winsize; static void catch_child(int); static void copytochild(int); static _Noreturn void doit(long); static _Noreturn void done(int); static void echo(char); static u_int getescape(const char *); static void lostpeer(int); static void mode(int); static void msg(const char *); static void oob(int); static int reader(int); static void sendwindow(void); static void setsignal(int); static void sigwinch(int); static void stop(char); static _Noreturn void usage(void); static void writer(void); static void writeroob(int); int main(int argc, char *argv[]) { struct passwd *pw; struct servent *sp; struct termios tty; long omask; int argoff, ch, dflag, Dflag, one; uid_t uid; char *host, *localname, *p, *user, term[1024]; speed_t ospeed; struct sockaddr_storage ss; socklen_t sslen; size_t len, len2; int i; argoff = dflag = Dflag = 0; one = 1; host = localname = user = NULL; if ((p = strrchr(argv[0], '/'))) ++p; else p = argv[0]; if (strcmp(p, "rlogin")) host = p; /* handle "rlogin host flags" */ if (!host && argc > 2 && argv[1][0] != '-') { host = argv[1]; argoff = 1; } #define OPTIONS "468DEde:i:l:" while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != -1) switch(ch) { case '4': family = PF_INET; break; case '6': family = PF_INET6; break; case '8': eight = 1; break; case 'D': Dflag = 1; break; case 'E': noescape = 1; break; case 'd': dflag = 1; break; case 'e': noescape = 0; escapechar = getescape(optarg); break; case 'i': if (getuid() != 0) errx(1, "-i user: permission denied"); localname = optarg; break; case 'l': user = optarg; break; case '?': default: usage(); } optind += argoff; /* if haven't gotten a host yet, do so */ if (!host && !(host = argv[optind++])) usage(); if (argv[optind]) usage(); if (!(pw = getpwuid(uid = getuid()))) errx(1, "unknown user id"); if (!user) user = pw->pw_name; if (!localname) localname = pw->pw_name; sp = NULL; sp = getservbyname("login", "tcp"); if (sp == NULL) errx(1, "login/tcp: unknown service"); if ((p = getenv("TERM")) != NULL) (void)strlcpy(term, p, sizeof(term)); len = strlen(term); if (len < (sizeof(term) - 1) && tcgetattr(0, &tty) == 0) { /* start at 2 to include the / */ for (ospeed = i = cfgetospeed(&tty), len2 = 2; i > 9; len2++) i /= 10; if (len + len2 < sizeof(term)) (void)snprintf(term + len, len2 + 1, "/%d", ospeed); } (void)get_window_size(0, &winsize); (void)signal(SIGPIPE, lostpeer); /* will use SIGUSR1 for window size hack, so hold it off */ omask = sigblock(sigmask(SIGURG) | sigmask(SIGUSR1)); /* * We set SIGURG and SIGUSR1 below so that an * incoming signal will be held pending rather than being * discarded. Note that these routines will be ready to get * a signal by the time that they are unblocked below. */ (void)signal(SIGURG, copytochild); (void)signal(SIGUSR1, writeroob); rem = rcmd_af(&host, sp->s_port, localname, user, term, 0, family); if (rem < 0) exit(1); if (dflag && setsockopt(rem, SOL_SOCKET, SO_DEBUG, &one, sizeof(one)) < 0) warn("setsockopt"); if (Dflag && setsockopt(rem, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) < 0) warn("setsockopt NODELAY (ignored)"); sslen = sizeof(ss); one = IPTOS_LOWDELAY; if (getsockname(rem, (struct sockaddr *)&ss, &sslen) == 0 && ss.ss_family == AF_INET) { if (setsockopt(rem, IPPROTO_IP, IP_TOS, (char *)&one, sizeof(int)) < 0) warn("setsockopt TOS (ignored)"); } else if (ss.ss_family == AF_INET) warn("setsockopt getsockname failed"); (void)setuid(uid); doit(omask); /*NOTREACHED*/ } static int child; static void doit(long omask) { (void)signal(SIGINT, SIG_IGN); setsignal(SIGHUP); setsignal(SIGQUIT); mode(1); child = fork(); if (child == -1) { warn("fork"); done(1); } if (child == 0) { if (reader(omask) == 0) { msg("connection closed"); exit(0); } sleep(1); msg("\007connection closed"); exit(1); } /* * We may still own the socket, and may have a pending SIGURG (or might * receive one soon) that we really want to send to the reader. When * one of these comes in, the trap copytochild simply copies such * signals to the child. We can now unblock SIGURG and SIGUSR1 * that were set above. */ (void)sigsetmask(omask); (void)signal(SIGCHLD, catch_child); writer(); msg("closed connection"); done(0); } /* trap a signal, unless it is being ignored. */ static void setsignal(int sig) { int omask = sigblock(sigmask(sig)); if (signal(sig, exit) == SIG_IGN) (void)signal(sig, SIG_IGN); (void)sigsetmask(omask); } static void done(int status) { int w, wstatus; mode(0); if (child > 0) { /* make sure catch_child does not snap it up */ (void)signal(SIGCHLD, SIG_DFL); if (kill(child, SIGKILL) >= 0) while ((w = wait(&wstatus)) > 0 && w != child); } exit(status); } static int dosigwinch; /* * This is called when the reader process gets the out-of-band (urgent) * request to turn on the window-changing protocol. */ /* ARGSUSED */ static void writeroob(int signo __unused) { if (dosigwinch == 0) { sendwindow(); (void)signal(SIGWINCH, sigwinch); } dosigwinch = 1; } /* ARGSUSED */ static void catch_child(int signo __unused) { pid_t pid; int status; for (;;) { pid = wait3(&status, WNOHANG|WUNTRACED, NULL); if (pid == 0) return; /* if the child (reader) dies, just quit */ if (pid < 0 || (pid == child && !WIFSTOPPED(status))) done(WTERMSIG(status) | WEXITSTATUS(status)); } /* NOTREACHED */ } /* * writer: write to remote: 0 -> line. * ~. terminate * ~^Z suspend rlogin process. * ~ suspend rlogin process, but leave reader alone. */ static void writer(void) { int bol, local, n; char c; bol = 1; /* beginning of line */ local = 0; for (;;) { n = read(STDIN_FILENO, &c, 1); if (n <= 0) { if (n < 0 && errno == EINTR) continue; break; } /* * If we're at the beginning of the line and recognize a * command character, then we echo locally. Otherwise, * characters are echo'd remotely. If the command character * is doubled, this acts as a force and local echo is * suppressed. */ if (bol) { bol = 0; if (!noescape && c == escapechar) { local = 1; continue; } } else if (local) { local = 0; if (c == '.' || CCEQ(deftty.c_cc[VEOF], c)) { echo(c); break; } if (CCEQ(deftty.c_cc[VSUSP], c) || CCEQ(deftty.c_cc[VDSUSP], c)) { bol = 1; echo(c); stop(c); continue; } if (c != escapechar) (void)write(rem, &escapechar, 1); } if (write(rem, &c, 1) == 0) { msg("line gone"); break; } bol = CCEQ(deftty.c_cc[VKILL], c) || CCEQ(deftty.c_cc[VEOF], c) || CCEQ(deftty.c_cc[VINTR], c) || CCEQ(deftty.c_cc[VSUSP], c) || c == '\r' || c == '\n'; } } static void echo(char c) { char *p; char buf[8]; p = buf; c &= 0177; *p++ = escapechar; if (c < ' ') { *p++ = '^'; *p++ = c + '@'; } else if (c == 0177) { *p++ = '^'; *p++ = '?'; } else *p++ = c; *p++ = '\r'; *p++ = '\n'; (void)write(STDOUT_FILENO, buf, p - buf); } static void stop(char cmdc) { mode(0); (void)signal(SIGCHLD, SIG_IGN); (void)kill(CCEQ(deftty.c_cc[VSUSP], cmdc) ? 0 : getpid(), SIGTSTP); (void)signal(SIGCHLD, catch_child); mode(1); sigwinch(0); /* check for size changes */ } /* ARGSUSED */ static void sigwinch(int signo __unused) { struct winsize ws; if (dosigwinch && get_window_size(0, &ws) == 0 && bcmp(&ws, &winsize, sizeof(ws))) { winsize = ws; sendwindow(); } } /* * Send the window size to the server via the magic escape */ static void sendwindow(void) { struct winsize ws; char obuf[4 + sizeof (struct winsize)]; obuf[0] = 0377; obuf[1] = 0377; obuf[2] = 's'; obuf[3] = 's'; ws.ws_row = htons(winsize.ws_row); ws.ws_col = htons(winsize.ws_col); ws.ws_xpixel = htons(winsize.ws_xpixel); ws.ws_ypixel = htons(winsize.ws_ypixel); bcopy(&ws, obuf + 4, sizeof(ws)); (void)write(rem, obuf, sizeof(obuf)); } /* * reader: read from remote: line -> 1 */ #define READING 1 #define WRITING 2 static jmp_buf rcvtop; static int rcvcnt, rcvstate; static pid_t ppid; static char rcvbuf[8 * 1024]; /* ARGSUSED */ static void oob(int signo __unused) { struct termios tty; int atmark, n, rcvd; char waste[BUFSIZ], mark; rcvd = 0; while (recv(rem, &mark, 1, MSG_OOB) < 0) { switch (errno) { case EWOULDBLOCK: /* * Urgent data not here yet. It may not be possible * to send it yet if we are blocked for output and * our input buffer is full. */ if (rcvcnt < (int)sizeof(rcvbuf)) { n = read(rem, rcvbuf + rcvcnt, sizeof(rcvbuf) - rcvcnt); if (n <= 0) return; rcvd += n; } else { n = read(rem, waste, sizeof(waste)); if (n <= 0) return; } continue; default: return; } } if (mark & TIOCPKT_WINDOW) { /* Let server know about window size changes */ (void)kill(ppid, SIGUSR1); } if (!eight && (mark & TIOCPKT_NOSTOP)) { (void)tcgetattr(0, &tty); tty.c_iflag &= ~IXON; (void)tcsetattr(0, TCSANOW, &tty); } if (!eight && (mark & TIOCPKT_DOSTOP)) { (void)tcgetattr(0, &tty); tty.c_iflag |= (deftty.c_iflag & IXON); (void)tcsetattr(0, TCSANOW, &tty); } if (mark & TIOCPKT_FLUSHWRITE) { (void)tcflush(1, TCIOFLUSH); for (;;) { if (ioctl(rem, SIOCATMARK, &atmark) < 0) { warn("ioctl"); break; } if (atmark) break; n = read(rem, waste, sizeof (waste)); if (n <= 0) break; } /* * Don't want any pending data to be output, so clear the recv * buffer. If we were hanging on a write when interrupted, * don't want it to restart. If we were reading, restart * anyway. */ rcvcnt = 0; longjmp(rcvtop, 1); } /* oob does not do FLUSHREAD (alas!) */ /* * If we filled the receive buffer while a read was pending, longjmp * to the top to restart appropriately. Don't abort a pending write, * however, or we won't know how much was written. */ if (rcvd && rcvstate == READING) longjmp(rcvtop, 1); } /* reader: read from remote: line -> 1 */ static int reader(int omask) { int n, remaining; char *bufp; pid_t pid; pid = getpid(); (void)signal(SIGTTOU, SIG_IGN); (void)signal(SIGURG, oob); (void)signal(SIGUSR1, oob); /* When propogating SIGURG from parent */ ppid = getppid(); (void)fcntl(rem, F_SETOWN, pid); (void)setjmp(rcvtop); (void)sigsetmask(omask); bufp = rcvbuf; for (;;) { while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) { rcvstate = WRITING; n = write(STDOUT_FILENO, bufp, remaining); if (n < 0) { if (errno != EINTR) return (-1); continue; } bufp += n; } bufp = rcvbuf; rcvcnt = 0; rcvstate = READING; rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf)); if (rcvcnt == 0) return (0); if (rcvcnt < 0) { if (errno == EINTR) continue; warn("read"); return (-1); } } } static void mode(int f) { struct termios tty; switch (f) { case 0: (void)tcsetattr(0, TCSANOW, &deftty); break; case 1: (void)tcgetattr(0, &deftty); tty = deftty; /* This is loosely derived from sys/kern/tty_compat.c. */ tty.c_lflag &= ~(ECHO|ICANON|ISIG|IEXTEN); tty.c_iflag &= ~ICRNL; tty.c_oflag &= ~OPOST; tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 0; if (eight) { tty.c_iflag &= IXOFF; tty.c_cflag &= ~(CSIZE|PARENB); tty.c_cflag |= CS8; } (void)tcsetattr(0, TCSANOW, &tty); break; default: return; } } /* ARGSUSED */ static void lostpeer(int signo __unused) { (void)signal(SIGPIPE, SIG_IGN); msg("\007connection closed"); done(1); } /* copy SIGURGs to the child process via SIGUSR1. */ /* ARGSUSED */ static void copytochild(int signo __unused) { (void)kill(child, SIGUSR1); } static void msg(const char *str) { (void)fprintf(stderr, "rlogin: %s\r\n", str); } static void usage(void) { (void)fprintf(stderr, "usage: rlogin [-46%s]%s[-e char] [-i localname] [-l username] host\n", "8DEd", " "); exit(1); } static u_int getescape(const char *p) { long val; size_t len; if ((len = strlen(p)) == 1) /* use any single char, including '\' */ return ((u_int)*p); /* otherwise, \nnn */ if (*p == '\\' && len >= 2 && len <= 4) { val = strtol(++p, NULL, 8); for (;;) { if (!*++p) return ((u_int)val); if (*p < '0' || *p > '8') break; } } msg("illegal option value -- e"); usage(); /* NOTREACHED */ }