freebsd-dev/libexec/rlogind/rlogind.c
Peter Wemm 5328c7eb7e As previously threatened, clean up the rshd -a option and make it default
on rshd and rlogind.  However, note that:
1: rshd used to drop a connection with -a if the hostname != ip address.
   This is unneeded, because iruserok() does it's own checking.
   It was also wrong if .rhosts had an explicit IP address in it,
   connections would be dropped from that host solely because the DNS was
   mismatched even though it was explicitly intended to work by IP address.
2: rlogind and rshd check the hostname mappings by default now because that
   is what goes into the utmp/wtmp and logs.  If the hostname != ip address,
   then it uses the IP address for logging/utmp/wtmp purposes.  There isn't
   much point logging ficticious hostnames.
3: rshd -a is now accepted (but ignored) for compatability.  If you really
   want to make life miserable for people with bad reverse DNS, use tcpd in
   paranoid mode (which is questionable anyway, given DNS ttl tweaking).
1998-12-16 07:20:45 +00:00

761 lines
17 KiB
C

/*-
* Copyright (c) 1983, 1988, 1989, 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.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1983, 1988, 1989, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
#if 0
static const char sccsid[] = "@(#)rlogind.c 8.1 (Berkeley) 6/4/93";
#endif
static const char rcsid[] =
"$Id: rlogind.c,v 1.19 1997/11/25 07:17:15 charnier Exp $";
#endif /* not lint */
/*
* remote login server:
* \0
* remuser\0
* locuser\0
* terminal_type/speed\0
* data
*/
#define FD_SETSIZE 16 /* don't need many bits for select */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <termios.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <libutil.h>
#include <pwd.h>
#include <syslog.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pathnames.h"
#ifndef TIOCPKT_WINDOW
#define TIOCPKT_WINDOW 0x80
#endif
#ifdef KERBEROS
#include <des.h>
#include <krb.h>
#define SECURE_MESSAGE "This rlogin session is using DES encryption for all transmissions.\r\n"
AUTH_DAT *kdata;
KTEXT ticket;
u_char auth_buf[sizeof(AUTH_DAT)];
u_char tick_buf[sizeof(KTEXT_ST)];
Key_schedule schedule;
int doencrypt, retval, use_kerberos, vacuous;
#define ARGSTR "Dalnkvx"
#else
#define ARGSTR "Daln"
#endif /* KERBEROS */
char *env[2];
#define NMAX 30
char lusername[NMAX+1], rusername[NMAX+1];
static char term[64] = "TERM=";
#define ENVSIZE (sizeof("TERM=")-1) /* skip null for concatenation */
int keepalive = 1;
int check_all = 0;
int no_delay;
struct passwd *pwd;
void doit __P((int, struct sockaddr_in *));
int control __P((int, char *, int));
void protocol __P((int, int));
void cleanup __P((int));
void fatal __P((int, char *, int));
int do_rlogin __P((struct sockaddr_in *));
void getstr __P((char *, int, char *));
void setup_term __P((int));
int do_krb_login __P((struct sockaddr_in *));
void usage __P((void));
int
main(argc, argv)
int argc;
char *argv[];
{
extern int __check_rhosts_file;
struct sockaddr_in from;
int ch, fromlen, on;
openlog("rlogind", LOG_PID | LOG_CONS, LOG_AUTH);
opterr = 0;
while ((ch = getopt(argc, argv, ARGSTR)) != -1)
switch (ch) {
case 'D':
no_delay = 1;
break;
case 'a':
check_all = 1;
break;
case 'l':
__check_rhosts_file = 0;
break;
case 'n':
keepalive = 0;
break;
#ifdef KERBEROS
case 'k':
use_kerberos = 1;
break;
case 'v':
vacuous = 1;
break;
#ifdef CRYPT
case 'x':
doencrypt = 1;
break;
#endif
#endif
case '?':
default:
usage();
break;
}
argc -= optind;
argv += optind;
#ifdef KERBEROS
if (use_kerberos && vacuous) {
usage();
fatal(STDERR_FILENO, "only one of -k and -v allowed", 0);
}
#endif
fromlen = sizeof (from);
if (getpeername(0, (struct sockaddr *)&from, &fromlen) < 0) {
syslog(LOG_ERR,"Can't get peer name of remote host: %m");
fatal(STDERR_FILENO, "Can't get peer name of remote host", 1);
}
on = 1;
if (keepalive &&
setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0)
syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
if (no_delay &&
setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) < 0)
syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m");
on = IPTOS_LOWDELAY;
if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *)&on, sizeof(int)) < 0)
syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
doit(0, &from);
return 0;
}
int child;
int netf;
char line[MAXPATHLEN];
int confirmed;
struct winsize win = { 0, 0, 0, 0 };
void
doit(f, fromp)
int f;
struct sockaddr_in *fromp;
{
int master, pid, on = 1;
int authenticated = 0;
register struct hostent *hp;
char hostname[2 * MAXHOSTNAMELEN + 1];
char c;
alarm(60);
read(f, &c, 1);
if (c != 0)
exit(1);
#ifdef KERBEROS
if (vacuous)
fatal(f, "Remote host requires Kerberos authentication", 0);
#endif
alarm(0);
fromp->sin_port = ntohs((u_short)fromp->sin_port);
hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof(struct in_addr),
fromp->sin_family);
if (hp) {
strncpy(hostname, hp->h_name, sizeof(hostname) - 1);
hostname[sizeof(hostname) - 1] = '\0';
hp = gethostbyname(hostname);
if (hp == NULL) {
strncpy(hostname, inet_ntoa(fromp->sin_addr),
sizeof(hostname) - 1);
} else for (; ; hp->h_addr_list++) {
if (hp->h_addr_list[0] == NULL) {
/* End of list - ditch it */
strncpy(hostname, inet_ntoa(fromp->sin_addr),
sizeof(hostname) - 1);
break;
}
if (!bcmp(hp->h_addr_list[0],
(caddr_t)&fromp->sin_addr,
sizeof(fromp->sin_addr)))
break; /* OK! */
}
} else {
strncpy(hostname, inet_ntoa(fromp->sin_addr),
sizeof(hostname) - 1);
}
hostname[sizeof(hostname) - 1] = '\0';
#ifdef KERBEROS
if (use_kerberos) {
retval = do_krb_login(fromp);
if (retval == 0)
authenticated++;
else if (retval > 0)
fatal(f, krb_err_txt[retval], 0);
write(f, &c, 1);
confirmed = 1; /* we sent the null! */
} else
#endif
{
if (fromp->sin_family != AF_INET ||
fromp->sin_port >= IPPORT_RESERVED ||
fromp->sin_port < IPPORT_RESERVED/2) {
syslog(LOG_NOTICE, "Connection from %s on illegal port",
inet_ntoa(fromp->sin_addr));
fatal(f, "Permission denied", 0);
}
#ifdef IP_OPTIONS
{
u_char optbuf[BUFSIZ/3];
int optsize = sizeof(optbuf), ipproto, i;
struct protoent *ip;
if ((ip = getprotobyname("ip")) != NULL)
ipproto = ip->p_proto;
else
ipproto = IPPROTO_IP;
if (getsockopt(0, ipproto, IP_OPTIONS, (char *)optbuf,
&optsize) == 0 && optsize != 0) {
for (i = 0; i < optsize; ) {
u_char c = optbuf[i];
if (c == IPOPT_LSRR || c == IPOPT_SSRR) {
syslog(LOG_NOTICE,
"Connection refused from %s with IP option %s",
inet_ntoa(fromp->sin_addr),
c == IPOPT_LSRR ? "LSRR" : "SSRR");
exit(1);
}
if (c == IPOPT_EOL)
break;
i += (c == IPOPT_NOP) ? 1 : optbuf[i+1];
}
}
}
#endif
if (do_rlogin(fromp) == 0)
authenticated++;
}
if (confirmed == 0) {
write(f, "", 1);
confirmed = 1; /* we sent the null! */
}
#ifdef KERBEROS
#ifdef CRYPT
if (doencrypt)
(void) des_enc_write(f,
SECURE_MESSAGE,
strlen(SECURE_MESSAGE),
schedule, &kdata->session);
#endif
#endif
netf = f;
pid = forkpty(&master, line, NULL, &win);
if (pid < 0) {
if (errno == ENOENT)
fatal(f, "Out of ptys", 0);
else
fatal(f, "Forkpty", 1);
}
if (pid == 0) {
if (f > 2) /* f should always be 0, but... */
(void) close(f);
setup_term(0);
if (*lusername=='-') {
syslog(LOG_ERR, "tried to pass user \"%s\" to login",
lusername);
fatal(STDERR_FILENO, "invalid user", 0);
}
if (authenticated) {
#ifdef KERBEROS
if (use_kerberos && (pwd->pw_uid == 0))
syslog(LOG_INFO|LOG_AUTH,
"ROOT Kerberos login from %s.%s@%s on %s\n",
kdata->pname, kdata->pinst, kdata->prealm,
hostname);
#endif
execl(_PATH_LOGIN, "login", "-p",
"-h", hostname, "-f", lusername, (char *)NULL);
} else
execl(_PATH_LOGIN, "login", "-p",
"-h", hostname, lusername, (char *)NULL);
fatal(STDERR_FILENO, _PATH_LOGIN, 1);
/*NOTREACHED*/
}
#ifdef CRYPT
#ifdef KERBEROS
/*
* If encrypted, don't turn on NBIO or the des read/write
* routines will croak.
*/
if (!doencrypt)
#endif
#endif
ioctl(f, FIONBIO, &on);
ioctl(master, FIONBIO, &on);
ioctl(master, TIOCPKT, &on);
signal(SIGCHLD, cleanup);
protocol(f, master);
signal(SIGCHLD, SIG_IGN);
cleanup(0);
}
char magic[2] = { 0377, 0377 };
char oobdata[] = {TIOCPKT_WINDOW};
/*
* Handle a "control" request (signaled by magic being present)
* in the data stream. For now, we are only willing to handle
* window size changes.
*/
int
control(pty, cp, n)
int pty;
char *cp;
int n;
{
struct winsize w;
if (n < 4+sizeof (w) || cp[2] != 's' || cp[3] != 's')
return (0);
oobdata[0] &= ~TIOCPKT_WINDOW; /* we know he heard */
bcopy(cp+4, (char *)&w, sizeof(w));
w.ws_row = ntohs(w.ws_row);
w.ws_col = ntohs(w.ws_col);
w.ws_xpixel = ntohs(w.ws_xpixel);
w.ws_ypixel = ntohs(w.ws_ypixel);
(void)ioctl(pty, TIOCSWINSZ, &w);
return (4+sizeof (w));
}
/*
* rlogin "protocol" machine.
*/
void
protocol(f, p)
register int f, p;
{
char pibuf[1024+1], fibuf[1024], *pbp, *fbp;
register pcc = 0, fcc = 0;
int cc, nfd, n;
char cntl;
/*
* Must ignore SIGTTOU, otherwise we'll stop
* when we try and set slave pty's window shape
* (our controlling tty is the master pty).
*/
(void) signal(SIGTTOU, SIG_IGN);
send(f, oobdata, 1, MSG_OOB); /* indicate new rlogin */
if (f > p)
nfd = f + 1;
else
nfd = p + 1;
if (nfd > FD_SETSIZE) {
syslog(LOG_ERR, "select mask too small, increase FD_SETSIZE");
fatal(f, "internal error (select mask too small)", 0);
}
for (;;) {
fd_set ibits, obits, ebits, *omask;
FD_ZERO(&ebits);
FD_ZERO(&ibits);
FD_ZERO(&obits);
omask = (fd_set *)NULL;
if (fcc) {
FD_SET(p, &obits);
omask = &obits;
} else
FD_SET(f, &ibits);
if (pcc >= 0)
if (pcc) {
FD_SET(f, &obits);
omask = &obits;
} else
FD_SET(p, &ibits);
FD_SET(p, &ebits);
if ((n = select(nfd, &ibits, omask, &ebits, 0)) < 0) {
if (errno == EINTR)
continue;
fatal(f, "select", 1);
}
if (n == 0) {
/* shouldn't happen... */
sleep(5);
continue;
}
#define pkcontrol(c) ((c)&(TIOCPKT_FLUSHWRITE|TIOCPKT_NOSTOP|TIOCPKT_DOSTOP))
if (FD_ISSET(p, &ebits)) {
cc = read(p, &cntl, 1);
if (cc == 1 && pkcontrol(cntl)) {
cntl |= oobdata[0];
send(f, &cntl, 1, MSG_OOB);
if (cntl & TIOCPKT_FLUSHWRITE) {
pcc = 0;
FD_CLR(p, &ibits);
}
}
}
if (FD_ISSET(f, &ibits)) {
#ifdef CRYPT
#ifdef KERBEROS
if (doencrypt)
fcc = des_enc_read(f, fibuf, sizeof(fibuf),
schedule, &kdata->session);
else
#endif
#endif
fcc = read(f, fibuf, sizeof(fibuf));
if (fcc < 0 && errno == EWOULDBLOCK)
fcc = 0;
else {
register char *cp;
int left, n;
if (fcc <= 0)
break;
fbp = fibuf;
top:
for (cp = fibuf; cp < fibuf+fcc-1; cp++)
if (cp[0] == magic[0] &&
cp[1] == magic[1]) {
left = fcc - (cp-fibuf);
n = control(p, cp, left);
if (n) {
left -= n;
if (left > 0)
bcopy(cp+n, cp, left);
fcc -= n;
goto top; /* n^2 */
}
}
FD_SET(p, &obits); /* try write */
}
}
if (FD_ISSET(p, &obits) && fcc > 0) {
cc = write(p, fbp, fcc);
if (cc > 0) {
fcc -= cc;
fbp += cc;
}
}
if (FD_ISSET(p, &ibits)) {
pcc = read(p, pibuf, sizeof (pibuf));
pbp = pibuf;
if (pcc < 0 && errno == EWOULDBLOCK)
pcc = 0;
else if (pcc <= 0)
break;
else if (pibuf[0] == 0) {
pbp++, pcc--;
#ifdef CRYPT
#ifdef KERBEROS
if (!doencrypt)
#endif
#endif
FD_SET(f, &obits); /* try write */
} else {
if (pkcontrol(pibuf[0])) {
pibuf[0] |= oobdata[0];
send(f, &pibuf[0], 1, MSG_OOB);
}
pcc = 0;
}
}
if ((FD_ISSET(f, &obits)) && pcc > 0) {
#ifdef CRYPT
#ifdef KERBEROS
if (doencrypt)
cc = des_enc_write(f, pbp, pcc,
schedule, &kdata->session);
else
#endif
#endif
cc = write(f, pbp, pcc);
if (cc < 0 && errno == EWOULDBLOCK) {
/*
* This happens when we try write after read
* from p, but some old kernels balk at large
* writes even when select returns true.
*/
if (!FD_ISSET(p, &ibits))
sleep(5);
continue;
}
if (cc > 0) {
pcc -= cc;
pbp += cc;
}
}
}
}
void
cleanup(signo)
int signo;
{
char *p;
p = line + sizeof(_PATH_DEV) - 1;
if (logout(p))
logwtmp(p, "", "");
(void)chmod(line, 0666);
(void)chown(line, 0, 0);
*p = 'p';
(void)chmod(line, 0666);
(void)chown(line, 0, 0);
shutdown(netf, 2);
exit(1);
}
void
fatal(f, msg, syserr)
int f;
char *msg;
int syserr;
{
int len;
char buf[BUFSIZ], *bp = buf;
/*
* Prepend binary one to message if we haven't sent
* the magic null as confirmation.
*/
if (!confirmed)
*bp++ = '\01'; /* error indicator */
if (syserr)
len = sprintf(bp, "rlogind: %s: %s.\r\n",
msg, strerror(errno));
else
len = sprintf(bp, "rlogind: %s.\r\n", msg);
(void) write(f, buf, bp + len - buf);
exit(1);
}
int
do_rlogin(dest)
struct sockaddr_in *dest;
{
getstr(rusername, sizeof(rusername), "remuser too long");
getstr(lusername, sizeof(lusername), "locuser too long");
getstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type too long");
pwd = getpwnam(lusername);
if (pwd == NULL)
return (-1);
/* XXX why don't we syslog() failure? */
return (iruserok(dest->sin_addr.s_addr, pwd->pw_uid == 0,
rusername, lusername));
}
void
getstr(buf, cnt, errmsg)
char *buf;
int cnt;
char *errmsg;
{
char c;
do {
if (read(0, &c, 1) != 1)
exit(1);
if (--cnt < 0)
fatal(STDOUT_FILENO, errmsg, 0);
*buf++ = c;
} while (c != 0);
}
extern char **environ;
void
setup_term(fd)
int fd;
{
register char *cp = index(term+ENVSIZE, '/');
char *speed;
struct termios tt;
#ifndef notyet
tcgetattr(fd, &tt);
if (cp) {
*cp++ = '\0';
speed = cp;
cp = index(speed, '/');
if (cp)
*cp++ = '\0';
cfsetspeed(&tt, atoi(speed));
}
tt.c_iflag = TTYDEF_IFLAG;
tt.c_oflag = TTYDEF_OFLAG;
tt.c_lflag = TTYDEF_LFLAG;
tcsetattr(fd, TCSAFLUSH, &tt);
#else
if (cp) {
*cp++ = '\0';
speed = cp;
cp = index(speed, '/');
if (cp)
*cp++ = '\0';
tcgetattr(fd, &tt);
cfsetspeed(&tt, atoi(speed));
tcsetattr(fd, TCSAFLUSH, &tt);
}
#endif
env[0] = term;
env[1] = 0;
environ = env;
}
#ifdef KERBEROS
#define VERSION_SIZE 9
/*
* Do the remote kerberos login to the named host with the
* given inet address
*
* Return 0 on valid authorization
* Return -1 on valid authentication, no authorization
* Return >0 for error conditions
*/
int
do_krb_login(dest)
struct sockaddr_in *dest;
{
int rc;
char instance[INST_SZ], version[VERSION_SIZE];
long authopts = 0L; /* !mutual */
struct sockaddr_in faddr;
kdata = (AUTH_DAT *) auth_buf;
ticket = (KTEXT) tick_buf;
instance[0] = '*';
instance[1] = '\0';
#ifdef CRYPT
if (doencrypt) {
rc = sizeof(faddr);
if (getsockname(0, (struct sockaddr *)&faddr, &rc))
return (-1);
authopts = KOPT_DO_MUTUAL;
rc = krb_recvauth(
authopts, 0,
ticket, "rcmd",
instance, dest, &faddr,
kdata, "", schedule, version);
des_set_key(&kdata->session, schedule);
} else
#endif
rc = krb_recvauth(
authopts, 0,
ticket, "rcmd",
instance, dest, (struct sockaddr_in *) 0,
kdata, "", NULL, version);
if (rc != KSUCCESS)
return (rc);
getstr(lusername, sizeof(lusername), "locuser");
/* get the "cmd" in the rcmd protocol */
getstr(term+ENVSIZE, sizeof(term)-ENVSIZE, "Terminal type");
pwd = getpwnam(lusername);
if (pwd == NULL)
return (-1);
/* returns nonzero for no access */
if (kuserok(kdata, lusername) != 0)
return (-1);
return (0);
}
#endif /* KERBEROS */
void
usage()
{
#ifdef KERBEROS
syslog(LOG_ERR, "usage: rlogind [-Daln] [-k | -v]");
#else
syslog(LOG_ERR, "usage: rlogind [-Daln]");
#endif
}