/* * Copyright (c) 1983, 1990, 1993 * The Regents of the University of California. All rights reserved. * * 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. */ /* * rlogin - remote login */ #include "bsd_locl.h" RCSID("$Id: rlogin.c,v 1.67.2.2 2000/10/10 12:54:26 assar Exp $"); CREDENTIALS cred; Key_schedule schedule; int use_kerberos = 1, doencrypt; char dst_realm_buf[REALM_SZ], *dest_realm = NULL; #ifndef CCEQ #define c2uc(x) ((unsigned char) x) #define CCEQ__(val, c) (c == val ? val != c2uc(_POSIX_VDISABLE) : 0) #define CCEQ(val, c) CCEQ__(c2uc(val), c2uc(c)) #endif int eight, rem; struct termios deftty; int noescape; char escapechar = '~'; struct winsize winsize; int parent, rcvcnt; char rcvbuf[8 * 1024]; int child; 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'; write(STDOUT_FILENO, buf, p - buf); } static void mode(int f) { struct termios tty; switch (f) { case 0: tcsetattr(0, TCSANOW, &deftty); break; case 1: tcgetattr(0, &deftty); tty = deftty; /* This is loosely derived from sys/compat/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; } tcsetattr(0, TCSANOW, &tty); break; default: return; } } static void done(int status) { int w, wstatus; mode(0); if (child > 0) { /* make sure catch_child does not snap it up */ signal(SIGCHLD, SIG_DFL); if (kill(child, SIGKILL) >= 0) while ((w = wait(&wstatus)) > 0 && w != child); } exit(status); } static RETSIGTYPE catch_child(int foo) { int status; int pid; for (;;) { pid = waitpid(-1, &status, WNOHANG|WUNTRACED); if (pid == 0) return; /* if the child (reader) dies, just quit */ if (pid < 0 || (pid == child && !WIFSTOPPED(status))) done(WTERMSIG(status) | WEXITSTATUS(status)); } /* NOTREACHED */ } /* * There is a race in the SunOS5 rlogind. If the slave end has not yet * been opened by the child when setting tty size the size is reset to * zero when the child opens it. Therefore we send the window update * twice. */ static int tty_kludge = 1; /* Return the number of OOB bytes processed. */ static int oob_real(void) { struct termios tty; int atmark, n, out, rcvd; char waste[BUFSIZ], mark; out = O_RDWR; rcvd = 0; if (recv(rem, &mark, 1, MSG_OOB) < 0) { return -1; } if (mark & TIOCPKT_WINDOW) { /* Let server know about window size changes */ kill(parent, SIGUSR1); } else if (tty_kludge) { /* Let server know about window size changes */ kill(parent, SIGUSR1); tty_kludge = 0; } if (!eight && (mark & TIOCPKT_NOSTOP)) { tcgetattr(0, &tty); tty.c_iflag &= ~IXON; tcsetattr(0, TCSANOW, &tty); } if (!eight && (mark & TIOCPKT_DOSTOP)) { tcgetattr(0, &tty); tty.c_iflag |= (deftty.c_iflag & IXON); tcsetattr(0, TCSANOW, &tty); } if (mark & TIOCPKT_FLUSHWRITE) { #ifdef TCOFLUSH tcflush(1, TCOFLUSH); #else ioctl(1, TIOCFLUSH, (char *)&out); #endif 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; } /* oob does not do FLUSHREAD (alas!) */ return 1; } /* reader: read from remote: line -> 1 */ static int reader(void) { int n, remaining; char *bufp; int kludgep = 1; bufp = rcvbuf; for (;;) { fd_set readfds, exceptfds; while ((remaining = rcvcnt - (bufp - rcvbuf)) > 0) { n = write(STDOUT_FILENO, bufp, remaining); if (n < 0) { if (errno != EINTR) return (-1); continue; } bufp += n; } bufp = rcvbuf; rcvcnt = 0; FD_ZERO (&readfds); if (rem >= FD_SETSIZE) errx (1, "fd too large"); FD_SET (rem, &readfds); FD_ZERO (&exceptfds); if (kludgep) FD_SET (rem, &exceptfds); if (select(rem+1, &readfds, 0, &exceptfds, 0) == -1) { if (errno == EINTR) continue; /* Got signal */ else errx(1, "select failed mysteriously"); } if (!FD_ISSET(rem, &exceptfds) && !FD_ISSET(rem, &readfds)) { warnx("select: nothing to read?"); continue; } if (FD_ISSET(rem, &exceptfds)) { int foo = oob_real (); if (foo >= 1) continue; /* First check if there is more OOB data. */ else if (foo < 0) kludgep = 0; } if (!FD_ISSET(rem, &readfds)) continue; /* Nothing to read. */ kludgep = 1; #ifndef NOENCRYPTION if (doencrypt) rcvcnt = des_enc_read(rem, rcvbuf, sizeof(rcvbuf), schedule, &cred.session); else #endif rcvcnt = read(rem, rcvbuf, sizeof (rcvbuf)); if (rcvcnt == 0) return (0); if (rcvcnt < 0) { if (errno == EINTR) continue; warn("read"); return (-1); } } } /* * Send the window size to the server via the magic escape */ static void sendwindow(void) { char obuf[4 + 4 * sizeof (u_int16_t)]; unsigned short *p; p = (u_int16_t *)(obuf + 4); obuf[0] = 0377; obuf[1] = 0377; obuf[2] = 's'; obuf[3] = 's'; *p++ = htons(winsize.ws_row); *p++ = htons(winsize.ws_col); #ifdef HAVE_WS_XPIXEL *p++ = htons(winsize.ws_xpixel); #else *p++ = htons(0); #endif #ifdef HAVE_WS_YPIXEL *p++ = htons(winsize.ws_ypixel); #else *p++ = htons(0); #endif #ifndef NOENCRYPTION if(doencrypt) des_enc_write(rem, obuf, sizeof(obuf), schedule, &cred.session); else #endif write(rem, obuf, sizeof(obuf)); } static RETSIGTYPE sigwinch(int foo) { struct winsize ws; if (get_window_size(0, &ws) == 0 && memcmp(&ws, &winsize, sizeof(ws))) { winsize = ws; sendwindow(); } } static void stop(int all) { mode(0); signal(SIGCHLD, SIG_IGN); kill(all ? 0 : getpid(), SIGTSTP); signal(SIGCHLD, catch_child); mode(1); #ifdef SIGWINCH kill(SIGWINCH, getpid()); /* check for size changes, if caught */ #endif } /* * 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)) { bol = 1; echo(c); stop(1); continue; } #ifdef VDSUSP /* Is VDSUSP called something else on Linux? * Perhaps VDELAY is a better thing? */ if (CCEQ(deftty.c_cc[VDSUSP], c)) { bol = 1; echo(c); stop(0); continue; } #endif /* VDSUSP */ if (c != escapechar) { #ifndef NOENCRYPTION if (doencrypt) des_enc_write(rem, &escapechar,1, schedule, &cred.session); else #endif write(rem, &escapechar, 1); } } if (doencrypt) { #ifdef NOENCRYPTION if (write(rem, &c, 1) == 0) { #else if (des_enc_write(rem, &c, 1, schedule, &cred.session) == 0) { #endif warnx("line gone"); break; } } else if (write(rem, &c, 1) == 0) { warnx("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 RETSIGTYPE lostpeer(int foo) { signal(SIGPIPE, SIG_IGN); warnx("\aconnection closed.\r"); done(1); } /* * This is called in the parent when the reader process gets the * out-of-band (urgent) request to turn on the window-changing * protocol. It is signalled from the child(reader). */ static RETSIGTYPE sigusr1(int foo) { /* * Now we now daemon supports winsize hack, */ sendwindow(); #ifdef SIGWINCH signal(SIGWINCH, sigwinch); /* so we start to support it */ #endif SIGRETURN(0); } static void doit(void) { signal(SIGINT, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGQUIT, SIG_IGN); signal(SIGCHLD, catch_child); /* * Child sends parent this signal for window size hack. */ signal(SIGUSR1, sigusr1); signal(SIGPIPE, lostpeer); mode(1); parent = getpid(); child = fork(); if (child == -1) { warn("fork"); done(1); } if (child == 0) { signal(SIGCHLD, SIG_IGN); signal(SIGTTOU, SIG_IGN); if (reader() == 0) errx(1, "connection closed.\r"); sleep(1); errx(1, "\aconnection closed.\r"); } writer(); warnx("closed connection.\r"); done(0); } static void usage(void) { fprintf(stderr, "usage: rlogin [ -%s]%s[-e char] [ -l username ] host\n", "8DEKLdx", " [-k realm] "); exit(1); } static u_int getescape(char *p) { long val; int 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; } } warnx("illegal option value -- e"); usage(); return 0; } int main(int argc, char **argv) { struct passwd *pw; int sv_port, user_port = 0; int argoff, ch, dflag, Dflag, one, uid; char *host, *user, term[1024]; argoff = dflag = Dflag = 0; one = 1; host = user = NULL; set_progname(argv[0]); /* handle "rlogin host flags" */ if (argc > 2 && argv[1][0] != '-') { host = argv[1]; argoff = 1; } #define OPTIONS "8DEKLde:k:l:xp:" while ((ch = getopt(argc - argoff, argv + argoff, OPTIONS)) != -1) switch(ch) { case '8': eight = 1; break; case 'D': Dflag = 1; break; case 'E': noescape = 1; break; case 'K': use_kerberos = 0; break; case 'd': dflag = 1; break; case 'e': noescape = 0; escapechar = getescape(optarg); break; case 'k': dest_realm = dst_realm_buf; strlcpy(dest_realm, optarg, REALM_SZ); break; case 'l': user = optarg; break; case 'x': doencrypt = 1; break; case 'p': { char *endptr; user_port = strtol (optarg, &endptr, 0); if (user_port == 0 && optarg == endptr) errx (1, "Bad port `%s'", optarg); user_port = htons(user_port); 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 = k_getpwuid(uid = getuid()))) errx(1, "unknown user id."); if (!user) user = pw->pw_name; if (user_port) sv_port = user_port; else sv_port = get_login_port(use_kerberos, doencrypt); { char *p = getenv("TERM"); struct termios tty; int i; if (p == NULL) p = "network"; if (tcgetattr(0, &tty) == 0 && (i = speed_t2int (cfgetospeed(&tty))) > 0) snprintf (term, sizeof(term), "%s/%d", p, i); else snprintf (term, sizeof(term), "%s", p); } get_window_size(0, &winsize); if (use_kerberos) { paranoid_setuid(getuid()); rem = KSUCCESS; errno = 0; if (dest_realm == NULL) dest_realm = krb_realmofhost(host); if (doencrypt) rem = krcmd_mutual(&host, sv_port, user, term, 0, dest_realm, &cred, schedule); else rem = krcmd(&host, sv_port, user, term, 0, dest_realm); if (rem < 0) { int i; char **newargv; if (errno == ECONNREFUSED) warning("remote host doesn't support Kerberos"); if (errno == ENOENT) warning("can't provide Kerberos auth data"); newargv = malloc((argc + 2) * sizeof(*newargv)); if (newargv == NULL) err(1, "malloc"); newargv[0] = argv[0]; newargv[1] = "-K"; for(i = 1; i < argc; ++i) newargv[i + 1] = argv[i]; newargv[argc + 1] = NULL; execv(_PATH_RLOGIN, newargv); } } else { if (doencrypt) errx(1, "the -x flag requires Kerberos authentication."); if (geteuid() != 0) errx(1, "not installed setuid root, " "only root may use non kerberized rlogin"); rem = rcmd(&host, sv_port, pw->pw_name, user, term, 0); } if (rem < 0) exit(1); #ifdef HAVE_SETSOCKOPT #ifdef SO_DEBUG if (dflag && setsockopt(rem, SOL_SOCKET, SO_DEBUG, (void *)&one, sizeof(one)) < 0) warn("setsockopt"); #endif #ifdef TCP_NODELAY if (Dflag && setsockopt(rem, IPPROTO_TCP, TCP_NODELAY, (void *)&one, sizeof(one)) < 0) warn("setsockopt(TCP_NODELAY)"); #endif #ifdef IP_TOS one = IPTOS_LOWDELAY; if (setsockopt(rem, IPPROTO_IP, IP_TOS, (void *)&one, sizeof(int)) < 0) warn("setsockopt(IP_TOS)"); #endif /* IP_TOS */ #endif /* HAVE_SETSOCKOPT */ paranoid_setuid(uid); doit(); return 0; }