/*-
 * 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 <sys/wait.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>
#include <sys/wait.h>

#if (defined(__GLIBC__) && (__GLIBC__ >= 2)) || defined(__CYGWIN__)
#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 */

#ifdef USE_AUDIT
#include <libaudit.h>
#endif /* USE_AUDIT */

#ifndef _PATH_NOLOGIN
#define _PATH_NOLOGIN   "/etc/nologin"
#endif

#define	OPTIONS	"aDhlLn"

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	remoteuser[20] = "REMOTEUSER=";
char	remotehost[50] = "REMOTEHOST=";
char	*envinit[] =
	    {homedir, shell, path, username, remoteuser, remotehost, 0};
extern	char	**environ;
#ifdef USE_PAM
static char** env;
#endif

static void error(const char *fmt, ...);
static void doit(struct sockaddr_storage *fromp, socklen_t fromlen);
static void getstr(char *buf, int cnt, const char *err);
#ifdef __CYGWIN__
int unwrap_ipv4_ipv6(struct sockaddr *p, socklen_t plen);
#endif

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((int)((unsigned char)(c))) && isdigit((int)((unsigned char)(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);
}

#define PAM_SET_ITEM(item,val) \
    do { \
	retcode = pam_set_item(pamh, (item), (val)); \
	if (retcode != PAM_SUCCESS) { \
	    syslog(LOG_ERR, "pam_set_item: %s\n", pam_strerror(pamh, retcode)); \
	    exit (1); \
	} \
    } while (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(PAM_RUSER, remuser);
    PAM_SET_ITEM(PAM_RHOST, hostname);
    PAM_SET_ITEM(PAM_TTY, "rsh");  /* we don't use a tty, so punt */
    
    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 *fromp,
				socklen_t fromlen,
				const char *remuser, const char *locuser,
				const char *cmdbuf) 
{
	const char *hostname;
	char remote_address[INET6_ADDRSTRLEN];
	char remote_hostname[NI_MAXHOST];
	struct addrinfo hints;
	struct addrinfo *res0, *res;
	int err;
	struct sockaddr_storage copy_from_storage;
	struct sockaddr * copy_fromp = (struct sockaddr *)&copy_from_storage;
	memset (&copy_from_storage, 0x00, sizeof (copy_from_storage));
	memcpy (copy_fromp, fromp, fromlen);

#ifdef __CYGWIN__
	unwrap_ipv4_ipv6 (copy_fromp, fromlen);
#endif
	if (! inet_ntop(copy_fromp->sa_family,
		(( copy_fromp->sa_family == AF_INET6 )
		? (void *)( &((struct sockaddr_in6 *)copy_fromp)->sin6_addr )
		: (void *)( &((struct sockaddr_in *)copy_fromp)->sin_addr )),
		remote_address, sizeof(remote_address))) {
	    syslog(LOG_NOTICE|LOG_AUTH,
	    	"Failed to retrieve the socket remote address");
	    exit(1);
	}

	err = getnameinfo(copy_fromp, fromlen, remote_hostname, NI_MAXHOST,
		NULL, 0, 0);
	if (err && (err == EAI_AGAIN) && !check_all)
		err = getnameinfo (copy_fromp, fromlen, remote_hostname, NI_MAXHOST,
				   NULL, 0, NI_NUMERICHOST);
	if (err) {
		syslog(LOG_NOTICE|LOG_AUTH,
		       "Failed to retrieve the hostname information for %s",
		       remote_address);
		exit(1);
	}

	errno = ENOMEM; /* malloc (thus strdup) may not set it */
	hostname = strdup(remote_hostname);

	if (hostname==NULL) {
	    /* out of memory? */
	    error("strdup: %s\n", strerror(errno));
	    exit(1);
	}

	/*
	 * Attempt to confirm the DNS. 
	 */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	if (getaddrinfo(hostname, NULL, &hints, &res)) {
	    syslog(LOG_INFO, "Couldn't look up address for %s/%s",
		   hostname, remote_address);
	    fail("Couldn't get address for your host\n", 
		 remuser, hostname, locuser, cmdbuf);
	}

	res0 = res;
	while (res) {
	    struct sockaddr *sa;
	    char res_address[INET6_ADDRSTRLEN];
	    sa = res->ai_addr;

	    if (inet_ntop(sa->sa_family,
		(( sa->sa_family == AF_INET6 )
		? (void *)( &((struct sockaddr_in6 *)sa)->sin6_addr )
		: (void *)( &((struct sockaddr_in *)sa)->sin_addr )),
		res_address, sizeof(res_address))
		&& strcmp(remote_address, res_address) == 0) {
		    freeaddrinfo(res0);
		    return hostname;
	    }
	    res = res->ai_next;
	}
	freeaddrinfo(res0);

        /* Special case for INADDR_LOOPBACK, since getnameinfo
         * returns the canonical machine name when it ought to
         * return "localhost". Fix it.
         */
        {
            int is_localhost = 0;
            switch (copy_fromp->sa_family)
            {
            case AF_INET:
              {
                if (IN_LOOPBACK(ntohl(((struct sockaddr_in *)copy_fromp)->sin_addr.s_addr)))
                    is_localhost = 1;
                break;
              }

            case AF_INET6:
              {
                /* Don't need to worry about ::ffff:127.0.0.1 because
                 * we've already assured that copy_fromp has been
                 * transformed into an actual IPv4 addr in that case.
                 */
                struct sockaddr_in6 * fromp6 = (struct sockaddr_in6 *)copy_fromp;
                if (IN6_IS_ADDR_LOOPBACK(&fromp6->sin6_addr)) {
                    is_localhost = 1;
                }
                break;
              }

            default:
                break;
            }

            if (is_localhost) {
                free ((void *)(intptr_t)hostname);

                errno = ENOMEM; /* malloc (thus strdup) may not set it */
                hostname = strdup("localhost");

                if (hostname==NULL) {
                    /* out of memory? */
                    error("strdup: %s\n", strerror(errno));
                    exit(1);
                }

                return hostname;
            }
        }

	syslog(LOG_NOTICE, "Host addr %s not listed for host %s",
	       remote_address, hostname);
	fail("Host address mismatch\n", 
	     remuser, hostname, locuser, cmdbuf);
	return NULL; /* not reachable */
}

static int log_audit(const char *username, int uid, const char *hostname,
			int success)
{
#ifdef USE_AUDIT
	int err;
	int audit_fd = audit_open();
	if (audit_fd < 0) {
		if (errno != EINVAL && errno != EPROTONOSUPPORT &&
					errno != EAFNOSUPPORT) 
			return 1;
	} else {
		int rc = audit_log_acct_message(audit_fd, AUDIT_USER_LOGIN,
				NULL, "login", username, uid, hostname, NULL,
				"rsh", success);
		err = errno;
		close(audit_fd);
		/*
		 * ECONNREFUSED is returned when kernel is compiled without
		 * audit support
		 */
		if (rc <= 0 && err != ECONNREFUSED) 
			return 1;
	}
#else
	(void)username;
	(void)uid;
	(void)hostname;
	(void)success;
#endif
	return 0;
}

#ifdef __CYGWIN__
int unwrap_ipv4_ipv6(struct sockaddr *p, socklen_t plen)
{
  if (p->sa_family == AF_INET)
    return 1;
  else if (p->sa_family == AF_INET6)
    {
      if (IN6_IS_ADDR_V4MAPPED(&(((struct sockaddr_in6 *)p)->sin6_addr)))
        {
          struct sockaddr_storage tmp_storage;
          struct sockaddr_in * tmp_ptr = (struct sockaddr_in *)&tmp_storage;
          memset (&tmp_storage, 0x00, sizeof (tmp_storage));

          tmp_ptr->sin_family = AF_INET;
          tmp_ptr->sin_port = ((struct sockaddr_in6 *)p)->sin6_port;
          tmp_ptr->sin_addr.s_addr = ((const uint32_t *)&(((struct sockaddr_in6 *)p)->sin6_addr.s6_addr32))[3];
          memcpy (p, tmp_ptr, plen);
          return 2;
	}
    }
  return 0;
}
#endif /* __CYGWIN__ */

static void
doit(struct sockaddr_storage *fromp, socklen_t fromlen)
{
	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;
#ifdef USE_PAM
        int i;
#endif

	cmdbuflen = sysconf (_SC_ARG_MAX);
	if (!(cmdbuflen > 0)) {
		syslog (LOG_ERR, "sysconf (_SC_ARG_MAX) failed");
		exit (1);
	}

	cmdbuflen++;
	/* Decrease cmdbuflen to reasonable number if it's too high */
	if ((size_t) cmdbuflen > 131072) {
		cmdbuflen = 131072;
		syslog (LOG_INFO, "Decreasing cmdbuflen because it was too high (>131072 bytes)");		
	}

	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) {
		struct sockaddr_storage second_connect;
		int lport = IPPORT_RESERVED - 1;

		memcpy((void *)&second_connect, (void *)fromp, fromlen);
#ifdef __CYGWIN__
		unwrap_ipv4_ipv6((struct sockaddr *)&second_connect, fromlen);
#endif
		sock = rresvport_af(&lport,
			((struct sockaddr *)&second_connect)->sa_family);
		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);
		}
		if (((struct sockaddr *)&second_connect)->sa_family == AF_INET6)
		    ((struct sockaddr_in6 *)&second_connect)->sin6_port
		    						= htons(port);
		else
		    ((struct sockaddr_in *)&second_connect)->sin_port
		    						= htons(port);
		if (connect(sock, (struct sockaddr *)&second_connect,
			fromlen) < 0) {
		    syslog(LOG_INFO, "connect second port %d: %m", port);
		    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, cmdbuflen, "command");
	if (!strcmp(locuser, "root")) paranoid = 1;

	hostname = findhostname((struct sockaddr *)fromp, fromlen,
		remuser, locuser, cmdbuf);

	pwd = doauth(remuser, hostname, locuser);
	if (pwd == NULL) {
		if (log_audit(remuser, -1, hostname, 0) > 0) {
			fail("Error sending audit event.\n", 
			     remuser, hostname, locuser, cmdbuf);
		}
		fail("Permission denied.\n", 
		     remuser, hostname, locuser, cmdbuf);
	}
	if (pwd->pw_uid != 0 && !access(_PATH_NOLOGIN, F_OK)) {
		error("Logins currently disabled.\n");
		exit(1);
	}
	if (log_audit(NULL, pwd->pw_uid, hostname, 1) > 0) {
		fail("Error sending audit event.\n", 
		     remuser, hostname, locuser, cmdbuf);
	}

	(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]);
	} else {
		pid = fork();
		if (pid == -1)  {
			error("Can't fork; try again.\n");
			exit(1);
		}
		if (pid) {
			waitpid(pid, NULL, 0);
#ifdef USE_PAM
			pam_close_session(pamh, 0);
			pam_end(pamh, PAM_SUCCESS);
#endif
			exit(0);
		}
		setpgrp();
	}
	theshell = pwd->pw_shell;
	if (!theshell || !*theshell) {
	    /* shouldn't we deny access? */
	    theshell = _PATH_BSHELL;
	}

#if (BSD > 43) && !defined(__CYGWIN__)
	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;

	if (chdir(pwd->pw_dir) < 0) {
		if(chdir("/") < 0)
		{
			error("No remote directory and can't chdir to root.\n");
			exit(1);
		}
		/*
		 * error("No remote directory.\n");
		 * exit(1);
		 */
	}

	strncat(homedir, pwd->pw_dir, sizeof(homedir)-6);
	homedir[sizeof(homedir)-1] = 0;

	strncat(remoteuser, remuser, sizeof(remoteuser)-12);
	remoteuser[sizeof(remoteuser)-1] = 0;

	strncat(remotehost, hostname, sizeof(remotehost)-12);
	remotehost[sizeof(remotehost)-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;
#ifdef USE_PAM
	env = pam_getenvlist(pamh);
	if (NULL != env)
		for(i = 0; env[i]; i++)
			putenv(env[i]);
#endif

	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, NULL);
	perror(theshell);
	exit(1);
}

static void network_init(int fd,
	struct sockaddr_storage *fromp, socklen_t *fromlenp)
{
	struct linger linger;
	char hostname[NI_MAXHOST];
	char portname[NI_MAXSERV];
	sa_family_t family;

	int error;
	int on=1;
	int port;

	if (getpeername(fd, (struct sockaddr *)fromp, fromlenp) < 0) {
		syslog(LOG_ERR, "getpeername: %m");
		_exit(1);
	}

	if (((struct sockaddr_in *) fromp)->sin_family == AF_INET6 &&
		IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *) fromp)->sin6_addr)) {

		struct addrinfo *res, hints = {};
		char client_addr[INET6_ADDRSTRLEN] = {};
		char client_port[6] = {};

		inet_ntop(AF_INET6, &((struct sockaddr_in6 *) fromp)->sin6_addr,
			client_addr, sizeof(client_addr));

		sprintf(client_port, "%d", ntohs(((struct sockaddr_in6 *) fromp)->sin6_port));

		hints.ai_family = AF_INET;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;

		getaddrinfo(client_addr, client_port, &hints, &res);

		memcpy(fromp, res->ai_addr, sizeof(struct sockaddr_in6));
		freeaddrinfo(res);
	}

	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");

	family = ((struct sockaddr *)fromp)->sa_family;
	if (family != AF_INET && family != AF_INET6) {
	    syslog(LOG_ERR, "malformed \"from\" address (af %d)\n",
	    	family);
	    exit(1);
	}

	error = getnameinfo((struct sockaddr *)fromp, *fromlenp,
		hostname, sizeof(hostname), portname, sizeof(portname),
		NI_NUMERICSERV);
	if (error && (error == EAI_AGAIN) && !check_all)
		error = getnameinfo ((struct sockaddr *)fromp, *fromlenp,
				     hostname, sizeof(hostname), portname,
				     sizeof(portname),
				     NI_NUMERICSERV|NI_NUMERICHOST);
	if (error) {
	    syslog(LOG_NOTICE|LOG_AUTH,
	    	"Failed to retrieve address and port of the connection: %s",
		gai_strerror(error));
	    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",
		       hostname, 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 = atoi(portname);
	if (! port) {
	    syslog(LOG_NOTICE|LOG_AUTH, "Unknown port %s", portname);
	    exit(1);
	}
	if (port >= IPPORT_RESERVED || port < IPPORT_RESERVED/2) {
	    syslog(LOG_NOTICE|LOG_AUTH,
	    	"Connection from %s from illegal port %s",
		   hostname, portname);
	    exit(1);
	}
}

int
main(int argc, char *argv[])
{
	int ch;
	struct sockaddr_storage from;
	socklen_t fromlen;
	_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 'D':
			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 */

	fromlen = sizeof(from);
	network_init(0, &from, &fromlen);
	doit(&from, fromlen);
	return 0;
}




