/*-
* Copyright (c) 1988, 1989 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.
*/
/*
* PAM modifications by Michael K. Johnson <johnsonm@redhat.com>
*/
char copyright[] =
"@(#) Copyright (c) 1988, 1989 The Regents of the University of California.\n"
"All rights reserved.\n";
/*
* From: @(#)rshd.c 5.38 (Berkeley) 3/2/91
*/
char rcsid[] =
"$Id: rshd.c,v 1.25 2000/07/23 04:16:24 dholland Exp $";
#include "../version.h"
/*
* remote shell server:
* [port]\0
* remuser\0
* locuser\0
* command\0
* data
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <resolv.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h> /* for vsnprintf */
#include <stdlib.h>
#include <string.h>
#include <paths.h>
#include <stdarg.h>
#include <ctype.h>
#include <assert.h>
#if defined(__GLIBC__) && (__GLIBC__ >= 2)
#define _check_rhosts_file __check_rhosts_file
#endif
#ifdef USE_PAM
#include <security/pam_appl.h>
#include <security/pam_misc.h>
static pam_handle_t *pamh;
#endif /* USE_PAM */
#define OPTIONS "ahlLn"
static int keepalive = 1;
static int check_all = 0;
static int paranoid = 0;
static int sent_null;
static int allow_root_rhosts=0;
char username[20] = "USER=";
char homedir[64] = "HOME=";
char shell[64] = "SHELL=";
char path[100] = "PATH=";
char *envinit[] =
{homedir, shell, path, username, 0};
extern char **environ;
static void error(const char *fmt, ...);
static void doit(struct sockaddr_in *fromp);
static void getstr(char *buf, int cnt, const char *err);
extern int _check_rhosts_file;
/*
* Report error to client.
* Note: can't be used until second socket has connected
* to client, or older clients will hang waiting
* for that connection first.
*/
static void
error(const char *fmt, ...) {
va_list ap;
char buf[BUFSIZ], *bp = buf;
if (sent_null == 0) *bp++ = 1;
va_start(ap, fmt);
vsnprintf(bp, sizeof(buf)-1, fmt, ap);
va_end(ap);
write(2, buf, strlen(buf));
}
static void fail(const char *errorstr,
const char *remuser, const char *hostname,
const char *locuser,
const char *cmdbuf)
{
/* log the (failed) rsh request */
syslog(LOG_INFO|LOG_AUTH, "rsh denied to %s@%s as %s: %s",
remuser, hostname, locuser, errorstr);
if (paranoid) {
syslog(LOG_INFO|LOG_AUTH, "rsh command was '%s'", cmdbuf);
}
error(errorstr, hostname);
exit(1);
}
static void getstr(char *buf, int cnt, const char *err) {
char c;
do {
if (read(0, &c, 1) != 1) exit(1);
*buf++ = c;
if (--cnt == 0) {
error("%s too long\n", err);
exit(1);
}
} while (c != 0);
}
static int getint(void) {
int port = 0;
char c;
do {
if (read(0, &c, 1) != 1) exit(1);
if (isascii(c) && isdigit(c)) port = port*10 + c-'0';
} while (c != 0);
return port;
}
static void stderr_parent(int sock, int pype, int pid) {
fd_set ready, readfrom;
char buf[BUFSIZ], sig;
int one = 1;
int nfd, cc, guys=2;
ioctl(pype, FIONBIO, (char *)&one);
/* should set s nbio! */
FD_ZERO(&readfrom);
FD_SET(sock, &readfrom);
FD_SET(pype, &readfrom);
if (pype > sock) nfd = pype+1;
else nfd = sock+1;
while (guys > 0) {
ready = readfrom;
if (select(nfd, &ready, NULL, NULL, NULL) < 0) {
if (errno != EINTR) {
break;
}
continue;
}
if (FD_ISSET(sock, &ready)) {
cc = read(sock, &sig, 1);
if (cc <= 0) {
FD_CLR(sock, &readfrom);
guys--;
}
else killpg(pid, sig);
}
if (FD_ISSET(pype, &ready)) {
cc = read(pype, buf, sizeof(buf));
if (cc <= 0) {
shutdown(sock, 2);
FD_CLR(pype, &readfrom);
guys--;
}
else write(sock, buf, cc);
}
}
#ifdef USE_PAM
/*
* This does not strike me as the right place for this; this is
* in a child process... what does this need to accomplish?
*
* No, it's not the child process, the code is just confusing.
*/
pam_close_session(pamh, 0);
pam_end(pamh, PAM_SUCCESS);
#endif
exit(0);
}
static struct passwd *doauth(const char *remuser,
const char *hostname,
const char *locuser)
{
#ifdef USE_PAM
static struct pam_conv conv = { misc_conv, NULL };
int retcode;
#endif
struct passwd *pwd = getpwnam(locuser);
if (pwd == NULL) return NULL;
if (pwd->pw_uid==0) paranoid = 1;
#ifdef USE_PAM
retcode = pam_start("rsh", locuser, &conv, &pamh);
if (retcode != PAM_SUCCESS) {
syslog(LOG_ERR, "pam_start: %s\n", pam_strerror(pamh, retcode));
exit (1);
}
pam_set_item (pamh, PAM_RUSER, remuser);
pam_set_item (pamh, PAM_RHOST, hostname);
pam_set_item (pamh, PAM_TTY, "tty");
retcode = pam_authenticate(pamh, 0);
if (retcode == PAM_SUCCESS) {
retcode = pam_acct_mgmt(pamh, 0);
}
if (retcode == PAM_SUCCESS) {
/*
* Why do we need to set groups here?
* Also, this stuff should be moved down near where the setuid() is.
*/
if (setgid(pwd->pw_gid) != 0) {
pam_end(pamh, PAM_SYSTEM_ERR);
return NULL;
}
if (initgroups(locuser, pwd->pw_gid) != 0) {
pam_end(pamh, PAM_SYSTEM_ERR);
return NULL;
}
retcode = pam_setcred(pamh, PAM_ESTABLISH_CRED);
}
if (retcode == PAM_SUCCESS) {
retcode = pam_open_session(pamh,0);
}
if (retcode != PAM_SUCCESS) {
pam_end(pamh, retcode);
return NULL;
}
return pwd;
#else
if (pwd->pw_uid==0 && !allow_root_rhosts) return NULL;
if (ruserok(hostname, pwd->pw_uid==0, remuser, locuser) < 0) {
return NULL;
}
return pwd;
#endif
}
static const char *findhostname(struct sockaddr_in *fromp,
const char *remuser, const char *locuser,
const char *cmdbuf)
{
struct hostent *hp;
const char *hostname;
hp = gethostbyaddr((char *)&fromp->sin_addr, sizeof (struct in_addr),
fromp->sin_family);
errno = ENOMEM; /* malloc (thus strdup) may not set it */
if (hp) hostname = strdup(hp->h_name);
else hostname = strdup(inet_ntoa(fromp->sin_addr));
if (hostname==NULL) {
/* out of memory? */
error("strdup: %s\n", strerror(errno));
exit(1);
}
/*
* Attempt to confirm the DNS.
*/
#ifdef RES_DNSRCH
_res.options &= ~RES_DNSRCH;
#endif
hp = gethostbyname(hostname);
if (hp == NULL) {
syslog(LOG_INFO, "Couldn't look up address for %s", hostname);
fail("Couldn't get address for your host (%s)\n",
remuser, inet_ntoa(fromp->sin_addr), locuser, cmdbuf);
}
while (hp->h_addr_list[0] != NULL) {
if (!memcmp(hp->h_addr_list[0], &fromp->sin_addr,
sizeof(fromp->sin_addr))) {
return hostname;
}
hp->h_addr_list++;
}
syslog(LOG_NOTICE, "Host addr %s not listed for host %s",
inet_ntoa(fromp->sin_addr), hp->h_name);
fail("Host address mismatch for %s\n",
remuser, inet_ntoa(fromp->sin_addr), locuser, cmdbuf);
return NULL; /* not reachable */
}
static void
doit(struct sockaddr_in *fromp)
{
char *cmdbuf;
long cmdbuflen;
const char *theshell, *shellname;
char locuser[16], remuser[16];
struct passwd *pwd;
int sock = -1;
const char *hostname;
u_short port;
int pv[2], pid, ifd;
cmdbuflen = sysconf (_SC_ARG_MAX);
if (!(cmdbuflen > 0)) {
syslog (LOG_ERR, "sysconf (_SC_ARG_MAX) failed");
exit (1);
}
cmdbuf = malloc (++cmdbuflen);
if (cmdbuf == NULL) {
syslog (LOG_ERR, "Could not allocate space for cmdbuf");
exit (1);
}
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
alarm(60);
port = getint();
alarm(0);
if (port != 0) {
int lport = IPPORT_RESERVED - 1;
sock = rresvport(&lport);
if (sock < 0) {
syslog(LOG_ERR, "can't get stderr port: %m");
exit(1);
}
if (port >= IPPORT_RESERVED) {
syslog(LOG_ERR, "2nd port not reserved\n");
exit(1);
}
fromp->sin_port = htons(port);
if (connect(sock, (struct sockaddr *)fromp,
sizeof(*fromp)) < 0) {
syslog(LOG_INFO, "connect second port: %m");
exit(1);
}
}
#if 0
/* We're running from inetd; socket is already on 0, 1, 2 */
dup2(f, 0);
dup2(f, 1);
dup2(f, 2);
#endif
getstr(remuser, sizeof(remuser), "remuser");
getstr(locuser, sizeof(locuser), "locuser");
getstr(cmdbuf, sizeof(cmdbuf), "command");
if (!strcmp(locuser, "root")) paranoid = 1;
hostname = findhostname(fromp, remuser, locuser, cmdbuf);
setpwent();
pwd = doauth(remuser, hostname, locuser);
if (pwd == NULL) {
fail("Permission denied.\n",
remuser, hostname, locuser, cmdbuf);
}
if (chdir(pwd->pw_dir) < 0) {
chdir("/");
/*
* error("No remote directory.\n");
* exit(1);
*/
}
if (pwd->pw_uid != 0 && !access(_PATH_NOLOGIN, F_OK)) {
error("Logins currently disabled.\n");
exit(1);
}
(void) write(2, "\0", 1);
sent_null = 1;
if (port) {
if (pipe(pv) < 0) {
error("Can't make pipe.\n");
exit(1);
}
pid = fork();
if (pid == -1) {
error("Can't fork; try again.\n");
exit(1);
}
if (pid) {
close(0);
close(1);
close(2);
close(pv[1]);
stderr_parent(sock, pv[0], pid);
/* NOTREACHED */
}
setpgrp();
close(sock);
close(pv[0]);
dup2(pv[1], 2);
close(pv[1]);
}
theshell = pwd->pw_shell;
if (!theshell || !*theshell) {
/* shouldn't we deny access? */
theshell = _PATH_BSHELL;
}
#if BSD > 43
if (setlogin(pwd->pw_name) < 0) {
syslog(LOG_ERR, "setlogin() failed: %m");
}
#endif
#ifndef USE_PAM
/* if PAM, already done */
if (setgid(pwd->pw_gid)) {
syslog(LOG_ERR, "setgid: %m");
exit(1);
}
if (initgroups(pwd->pw_name, pwd->pw_gid)) {
syslog(LOG_ERR, "initgroups: %m");
exit(1);
}
#endif
if (setuid(pwd->pw_uid)) {
syslog(LOG_ERR, "setuid: %m");
exit(1);
}
environ = envinit;
strncat(homedir, pwd->pw_dir, sizeof(homedir)-6);
homedir[sizeof(homedir)-1] = 0;
strcat(path, _PATH_DEFPATH);
strncat(shell, theshell, sizeof(shell)-7);
shell[sizeof(shell)-1] = 0;
strncat(username, pwd->pw_name, sizeof(username)-6);
username[sizeof(username)-1] = 0;
shellname = strrchr(theshell, '/');
if (shellname) shellname++;
else shellname = theshell;
endpwent();
if (paranoid) {
syslog(LOG_INFO|LOG_AUTH, "%s@%s as %s: cmd='%s'",
remuser, hostname, locuser, cmdbuf);
}
/*
* Close all fds, in case libc has left fun stuff like
* /etc/shadow open.
*/
for (ifd = getdtablesize()-1; ifd > 2; ifd--) close(ifd);
execl(theshell, shellname, "-c", cmdbuf, 0);
perror(theshell);
exit(1);
}
static void network_init(int fd, struct sockaddr_in *fromp)
{
struct linger linger;
socklen_t fromlen;
int on=1;
int port;
fromlen = sizeof(*fromp);
if (getpeername(fd, (struct sockaddr *) fromp, &fromlen) < 0) {
syslog(LOG_ERR, "getpeername: %m");
_exit(1);
}
if (keepalive &&
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&on,
sizeof(on)) < 0)
syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
linger.l_onoff = 1;
linger.l_linger = 60; /* XXX */
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *)&linger,
sizeof (linger)) < 0)
syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m");
if (fromp->sin_family != AF_INET) {
syslog(LOG_ERR, "malformed \"from\" address (af %d)\n",
fromp->sin_family);
exit(1);
}
#ifdef IP_OPTIONS
{
u_char optbuf[BUFSIZ/3], *cp;
char lbuf[BUFSIZ+1], *lp;
socklen_t optsize = sizeof(optbuf);
int ipproto;
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) &&
optsize != 0) {
lp = lbuf;
/*
* If these are true, this will not run off the end of lbuf[].
*/
assert(optsize <= BUFSIZ/3);
assert(3*optsize <= BUFSIZ);
for (cp = optbuf; optsize > 0; cp++, optsize--, lp += 3)
snprintf(lp, 4, " %2.2x", *cp);
syslog(LOG_NOTICE,
"Connection received from %s using IP options"
" (ignored): %s",
inet_ntoa(fromp->sin_addr), lbuf);
if (setsockopt(0, ipproto, IP_OPTIONS, NULL, optsize) != 0) {
syslog(LOG_ERR, "setsockopt IP_OPTIONS NULL: %m");
exit(1);
}
}
}
#endif
/*
* Check originating port for validity.
*/
port = ntohs(fromp->sin_port);
if (port >= IPPORT_RESERVED || port < IPPORT_RESERVED/2) {
syslog(LOG_NOTICE|LOG_AUTH, "Connection from %s on illegal port",
inet_ntoa(fromp->sin_addr));
exit(1);
}
}
int
main(int argc, char *argv[])
{
int ch;
struct sockaddr_in from;
_check_rhosts_file=1;
openlog("rshd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
opterr = 0;
while ((ch = getopt(argc, argv, OPTIONS)) != EOF) {
switch (ch) {
case 'a':
check_all = 1;
break;
case 'h':
allow_root_rhosts = 1;
break;
case 'l':
_check_rhosts_file = 0;
break;
case 'n':
keepalive = 0;
break;
case 'L':
paranoid = 1;
break;
case '?':
default:
syslog(LOG_ERR, "usage: rshd [-%s]", OPTIONS);
exit(2);
}
}
argc -= optind;
argv += optind;
#ifdef USE_PAM
if (_check_rhosts_file == 0 || allow_root_rhosts)
syslog(LOG_ERR, "-l and -h functionality has been moved to "
"pam_rhosts_auth in /etc/pam.conf");
#endif /* USE_PAM */
network_init(0, &from);
doit(&from);
return 0;
}