freebsd-dev/libexec/rlogind/rlogind.c
Ed Schouten b3608ae18f Replace index() and rindex() calls with strchr() and strrchr().
The index() and rindex() functions were marked LEGACY in the 2001
revision of POSIX and were subsequently removed from the 2008 revision.
The strchr() and strrchr() functions are part of the C standard.

This makes the source code a lot more consistent, as most of these C
files also call into other str*() routines. In fact, about a dozen
already perform strchr() calls.
2012-01-03 18:51:58 +00:00

590 lines
13 KiB
C

/*-
* Copyright (c) 1983, 1988, 1989, 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.
*/
#if 0
#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
static const char sccsid[] = "@(#)rlogind.c 8.1 (Berkeley) 6/4/93";
#endif /* not lint */
#endif
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* 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 <paths.h>
#include <pwd.h>
#include <syslog.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef TIOCPKT_WINDOW
#define TIOCPKT_WINDOW 0x80
#endif
#define ARGSTR "Daln"
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;
union sockunion {
struct sockinet {
u_char si_len;
u_char si_family;
u_short si_port;
} su_si;
struct sockaddr_in su_sin;
struct sockaddr_in6 su_sin6;
};
#define su_len su_si.si_len
#define su_family su_si.si_family
#define su_port su_si.si_port
void doit(int, union sockunion *);
int control(int, char *, int);
void protocol(int, int);
void cleanup(int);
void fatal(int, char *, int);
int do_rlogin(union sockunion *);
void getstr(char *, int, char *);
void setup_term(int);
int do_krb_login(struct sockaddr_in *);
void usage(void);
int
main(int argc, char *argv[])
{
extern int __check_rhosts_file;
union sockunion from;
socklen_t fromlen;
int ch, 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;
case '?':
default:
usage();
break;
}
argc -= optind;
argv += optind;
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");
if (from.su_family == AF_INET)
{
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(int f, union sockunion *fromp)
{
int master, pid, on = 1;
int authenticated = 0;
char hostname[2 * MAXHOSTNAMELEN + 1];
char nameinfo[2 * INET6_ADDRSTRLEN + 1];
char c;
alarm(60);
read(f, &c, 1);
if (c != 0)
exit(1);
alarm(0);
realhostname_sa(hostname, sizeof(hostname) - 1,
(struct sockaddr *)fromp, fromp->su_len);
/* error check ? */
fromp->su_port = ntohs((u_short)fromp->su_port);
hostname[sizeof(hostname) - 1] = '\0';
{
if ((fromp->su_family != AF_INET
#ifdef INET6
&& fromp->su_family != AF_INET6
#endif
) ||
fromp->su_port >= IPPORT_RESERVED ||
fromp->su_port < IPPORT_RESERVED/2) {
getnameinfo((struct sockaddr *)fromp,
fromp->su_len,
nameinfo, sizeof(nameinfo), NULL, 0,
NI_NUMERICHOST);
/* error check ? */
syslog(LOG_NOTICE, "Connection from %s on illegal port",
nameinfo);
fatal(f, "Permission denied", 0);
}
#ifdef IP_OPTIONS
if (fromp->su_family == AF_INET)
{
u_char optbuf[BUFSIZ/3];
socklen_t optsize = sizeof(optbuf);
int 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->su_sin.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! */
}
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) {
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*/
}
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(int pty, char *cp, int n)
{
struct winsize w;
if (n < 4 + (int)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(int f, int p)
{
char pibuf[1024+1], fibuf[1024], *pbp = NULL, *fbp = NULL;
int 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)) {
fcc = read(f, fibuf, sizeof(fibuf));
if (fcc < 0 && errno == EWOULDBLOCK)
fcc = 0;
else {
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--;
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) {
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(int signo __unused)
{
shutdown(netf, SHUT_RDWR);
exit(1);
}
void
fatal(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 = snprintf(bp, sizeof(buf), "rlogind: %s: %s.\r\n",
msg, strerror(errno));
else
len = snprintf(bp, sizeof(buf), "rlogind: %s.\r\n", msg);
if (len < 0)
len = 0;
(void) write(f, buf, bp + len - buf);
exit(1);
}
int
do_rlogin(union sockunion *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_sa(dest, dest->su_len, pwd->pw_uid == 0, rusername,
lusername));
}
void
getstr(char *buf, int cnt, char *errmsg)
{
char c;
do {
if (read(STDIN_FILENO, &c, 1) != 1)
exit(1);
if (--cnt < 0)
fatal(STDOUT_FILENO, errmsg, 0);
*buf++ = c;
} while (c != 0);
}
extern char **environ;
void
setup_term(int fd)
{
char *cp;
char *speed;
struct termios tt, def;
cp = strchr(term + ENVSIZE, '/');
#ifndef notyet
tcgetattr(fd, &tt);
if (cp) {
*cp++ = '\0';
speed = cp;
cp = strchr(speed, '/');
if (cp)
*cp++ = '\0';
cfsetspeed(&tt, atoi(speed));
}
cfmakesane(&def);
tt.c_iflag = def.c_iflag;
tt.c_oflag = def.c_oflag;
tt.c_lflag = def.c_lflag;
tcsetattr(fd, TCSAFLUSH, &tt);
#else
if (cp) {
*cp++ = '\0';
speed = cp;
cp = strchr(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;
}
void
usage(void)
{
syslog(LOG_ERR, "usage: rlogind [-" ARGSTR "]");
}