 /*
  * fromhost() determines the type of connection (datagram, stream), the name
  * and address of the host at the other end of standard input, and the
  * remote user name (if RFC 931 lookups are enabled). A host name of "stdin"
  * is returned if the program is run from a tty. The value "unknown" is
  * returned as a placeholder for information that could not be looked up.
  * All results are in static memory.
  * 
  * The return status is (-1) if the remote host pretends to have someone elses
  * host name, otherwise a zero status is returned.
  * 
  * Diagnostics are reported through syslog(3).
  * 
  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  */

#ifndef lint
static char sccsid[] = "@(#) fromhost.c 1.7 93/03/07 22:47:34";
#endif

/* System libraries. */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <syslog.h>
#include <errno.h>

extern char *inet_ntoa();
extern char *strncpy();
extern char *strcpy();

/* In case not defined in <sys/param.h>. */

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN	1024		/* string with host name */
#endif

/* Local stuff. */

#include "log_tcp.h"

/* Forward declarations. */

static int matchname();

/* The following are to be used in assignment context, not in comparisons. */

#define	GOOD	1
#define	BAD	0

/* Initially, we know nothing about the origin of the connection. */

static struct from_host from_unknown = {
    0,					/* connected/unconnected */
    FROM_UNKNOWN,			/* remote host name */
    FROM_UNKNOWN,			/* remote host address */
    "",					/* remote user name */
};

 /*
  * With early SunOS 5 versions, recvfrom() does not completely fill in the
  * source address structure when doing a non-destructive read. The following
  * code works around the problem. It does no harm on "normal" systems.
  */

#ifdef RECVFROM_BUG

static int fix_recvfrom(sock, buf, buflen, flags, from, fromlen)
int     sock;
char   *buf;
int     buflen;
int     flags;
struct sockaddr *from;
int     *fromlen;
{
    int     ret;

    /* Assume that both ends of a socket belong to the same address family. */

    if ((ret = recvfrom(sock, buf, buflen, flags, from, fromlen)) >= 0) {
	if (from->sa_family == 0) {
	    struct sockaddr my_addr;
	    int     my_addr_len = sizeof(my_addr);

	    if (getsockname(0, &my_addr, &my_addr_len)) {
		syslog(LOG_ERR, "getsockname: %m");
	    } else {
		from->sa_family = my_addr.sa_family;
	    }
	}
    }
    return (ret);
}

#define recvfrom fix_recvfrom
#endif

 /*
  * The Apollo SR10.3 and some SYSV4 getpeername(2) versions do not return an
  * error in case of a datagram-oriented socket. Instead, they claim that all
  * UDP requests come from address 0.0.0.0. The following code works around
  * the problem. It does no harm on "normal" systems.
  */

#ifdef GETPEERNAME_BUG

static int fix_getpeername(sock, sa, len)
int     sock;
struct sockaddr *sa;
int    *len;
{
    int     ret;
    struct sockaddr_in *sin = (struct sockaddr_in *) sa;

    if ((ret = getpeername(sock, sa, len)) >= 0
	&& sa->sa_family == AF_INET
	&& sin->sin_addr.s_addr == 0) {
	errno = ENOTCONN;
	return (-1);
    } else {
	return (ret);
    }
}

#define	getpeername	fix_getpeername
#endif

/* fromhost - find out what is at the other end of standard input */

int     fromhost(f)
struct from_host *f;
{
    static struct sockaddr sa;
    struct sockaddr_in *sin = (struct sockaddr_in *) (&sa);
    struct hostent *hp;
    int     length = sizeof(sa);
    char    buf[BUFSIZ];
    static char addr_buf[FROM_ADDRLEN];
    static char name_buf[MAXHOSTNAMELEN];

    /*
     * There are so many results and so many early returns that it seems
     * safest to first initialize all results to UNKNOWN.
     */

    *f = from_unknown;

    /*
     * Look up the remote host address. Hal R. Brand <BRAND@addvax.llnl.gov>
     * suggested how to get the remote host info in case of UDP connections:
     * peek at the first message without actually looking at its contents.
     */

    if (getpeername(0, &sa, &length) >= 0) {	/* assume TCP request */
	f->sock_type = FROM_CONNECTED;
    } else {
	switch (errno) {
	default:
	    if (isatty(0))			/* stdin is not a socket */
		f->name = "stdin";
	    else
		syslog(LOG_ERR, "getpeername: %m");	/* other, punt */
	    return (0);
	case ENOTCONN:				/* assume UDP request */
	    length = sizeof(sa);
	    if (recvfrom(0, buf, sizeof(buf), MSG_PEEK, &sa, &length) < 0) {
		syslog(LOG_ERR, "recvfrom: %m");
		return (0);
	    }
#ifdef really_paranoid
	    memset(buf, 0 sizeof(buf));
#endif
	    f->sock_type = FROM_UNCONNECTED;
	    break;
	}
    }

    /*
     * At present, we can only deal with the AF_INET address family. Some
     * implementations of System V religion never fill in the address family
     * field in case of UDP connections. If that happens, you may want to
     * take the chance and assume that we're dealing with TCP/IP anyway.
     */

#ifdef ADDRESS_FAMILY_BUG
    if (sa.sa_family == 0)
	sa.sa_family = AF_INET;
#endif
    if (sa.sa_family != AF_INET) {
	syslog(LOG_ERR, "unexpected address family %ld", (long) sa.sa_family);
	return (0);
    }
    /* Save the host address. A later inet_ntoa() call may clobber it. */

    f->sin = sin;
    f->addr = strcpy(addr_buf, inet_ntoa(sin->sin_addr));

    /* Look up the remote user name. Does not work for UDP services. */

#if defined(RFC931) && !defined(USER_AT_HOST) && !defined(RFC931_OPTION)
    if (f->sock_type == FROM_CONNECTED)
	f->user = rfc931_name(sin);
#endif

    /* Look up the remote host name. */

    if ((hp = gethostbyaddr((char *) &sin->sin_addr,
			    sizeof(sin->sin_addr),
			    AF_INET)) == 0) {
	return (0);
    }
    /* Save the host name. A later gethostbyxxx() call may clobber it. */

    f->name = strncpy(name_buf, hp->h_name, sizeof(name_buf) - 1);
    name_buf[sizeof(name_buf) - 1] = 0;

    /*
     * Verify that the host name does not belong to someone else. If host
     * name verification fails, pretend that the host name lookup failed.
     */

    if (matchname(f->name, sin->sin_addr)) {
	return (0);
    } else {
	f->name = FROM_UNKNOWN;
	return (-1);				/* verification failed */
    }
}

/* matchname - determine if host name matches IP address */

static int matchname(remotehost, addr)
char   *remotehost;
struct in_addr addr;
{
    struct hostent *hp;
    int     i;

    if ((hp = gethostbyname(remotehost)) == 0) {

	/*
	 * Unable to verify that the host name matches the address. This may
	 * be a transient problem or a botched name server setup. We decide
	 * to play safe.
	 */

	syslog(LOG_ERR, "gethostbyname(%s): lookup failure", remotehost);
	return (BAD);

    } else {

	/*
	 * Make sure that gethostbyname() returns the "correct" host name.
	 * Unfortunately, gethostbyname("localhost") sometimes yields
	 * "localhost.domain". Since the latter host name comes from the
	 * local DNS, we just have to trust it (all bets are off if the local
	 * DNS is perverted). We always check the address list, though.
	 */

	if (strcasecmp(remotehost, hp->h_name)
	    && strcasecmp(remotehost, "localhost")) {
	    syslog(LOG_ERR, "host name/name mismatch: %s != %s",
		   remotehost, hp->h_name);
	    return (BAD);
	}
	/* Look up the host address in the address list we just got. */

	for (i = 0; hp->h_addr_list[i]; i++) {
	    if (memcmp(hp->h_addr_list[i], (caddr_t) & addr, sizeof(addr)) == 0)
		return (GOOD);
	}

	/*
	 * The host name does not map to the original host address. Perhaps
	 * someone has compromised a name server. More likely someone botched
	 * it, but that could be dangerous, too.
	 */

	syslog(LOG_ERR, "host name/address mismatch: %s != %s",
	       inet_ntoa(addr), hp->h_name);
	return (BAD);
    }
}

#ifdef TEST

/* Code for stand-alone testing. */

main(argc, argv)
int     argc;
char  **argv;
{
    struct from_host from;

#ifdef LOG_MAIL
    (void) openlog(argv[0], LOG_PID, FACILITY);
#else
    (void) openlog(argv[0], LOG_PID);
#endif
    (void) fromhost(&from);
    printf("%s\n", hosts_info(&from));
    return (0);
}

#endif
