root/usr/src/lib/libwrap/tli.c
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

 /*
  * tli_host() determines the type of transport (connected, connectionless),
  * the transport address of a client host, and the transport address of a
  * server endpoint. In addition, it provides methods to map a transport
  * address to a printable host name or address. Socket address results are
  * in static memory; tli structures are allocated from the heap.
  *
  * The result from the hostname lookup method is STRING_PARANOID when a host
  * pretends to have someone elses name, or when a host name is available but
  * could not be verified.
  *
  * Diagnostics are reported through syslog(3).
  *
  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  */

#ifndef lint
static char sccsid[] = "@(#) tli.c 1.15 97/03/21 19:27:25";
#endif

#ifdef TLI

/* System libraries. */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stream.h>
#include <sys/stat.h>
#include <sys/mkdev.h>
#include <sys/tiuser.h>
#include <sys/timod.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <netconfig.h>
#include <netdir.h>
#include <string.h>

extern char *nc_sperror();
extern int errno;
extern int t_errno;
extern char *t_errlist[];
extern int t_nerr;

/* Local stuff. */

#include "tcpd.h"

/* Forward declarations. */

static void tli_endpoints();
static struct netconfig *tli_transport();
static void tli_hostname();
static void tli_hostaddr();
static void tli_cleanup();
static char *tli_error();
static void tli_sink();

/* tli_host - look up endpoint addresses and install conversion methods */

void    tli_host(request)
struct request_info *request;
{
    static struct sockaddr_gen client;
    static struct sockaddr_gen server;

    /*
     * If we discover that we are using an IP transport, pretend we never
     * were here. Otherwise, use the transport-independent method and stick
     * to generic network addresses. XXX hard-coded protocol family name.
     */

    tli_endpoints(request);
    if ((request->config = tli_transport(request->fd)) != 0
        && (STR_EQ(request->config->nc_protofmly, "inet")
#ifdef HAVE_IPV6
            || STR_EQ(request->config->nc_protofmly, "inet6")
#endif
        )) {
        if (request->client->unit != 0) {
            memcpy(&client, request->client->unit->addr.buf,
                SGSOCKADDRSZ((struct sockaddr_gen*)
                                request->client->unit->addr.buf));
            request->client->sin = &client;
            sockgen_simplify(&client);
        }
        if (request->server->unit != 0) {
            memcpy(&server, request->server->unit->addr.buf,
                SGSOCKADDRSZ((struct sockaddr_gen*)
                                request->server->unit->addr.buf));
            request->server->sin = &server;
            sockgen_simplify(&server);
        }
        tli_cleanup(request);
        sock_methods(request);
    } else {
        request->hostname = tli_hostname;
        request->hostaddr = tli_hostaddr;
        request->cleanup = tli_cleanup;
    }
}

/* tli_cleanup - cleanup some dynamically-allocated data structures */

static void tli_cleanup(request)
struct request_info *request;
{
    if (request->config != 0)
        freenetconfigent(request->config);
    if (request->client->unit != 0)
        t_free((char *) request->client->unit, T_UNITDATA);
    if (request->server->unit != 0)
        t_free((char *) request->server->unit, T_UNITDATA);
}

/* tli_endpoints - determine TLI client and server endpoint information */

static void tli_endpoints(request)
struct request_info *request;
{
    struct t_unitdata *server;
    struct t_unitdata *client;
    int     fd = request->fd;
    int     flags;

    /*
     * Determine the client endpoint address. With unconnected services, peek
     * at the sender address of the pending protocol data unit without
     * popping it off the receive queue. This trick works because only the
     * address member of the unitdata structure has been allocated.
     *
     * Beware of successful returns with zero-length netbufs (for example,
     * Solaris 2.3 with ticlts transport). The netdir(3) routines can't
     * handle that. Assume connection-less transport when TI_GETPEERNAME
     * produces no usable result, even when t_rcvudata() is unable to figure
     * out the peer address. Better to hang than to loop.
     */

    if ((client = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ADDR)) == 0) {
        tcpd_warn("t_alloc: %s", tli_error());
        return;
    }
    if (ioctl(fd, TI_GETPEERNAME, &client->addr) < 0 || client->addr.len == 0) {
        request->sink = tli_sink;
        if (t_rcvudata(fd, client, &flags) < 0 || client->addr.len == 0) {
            tcpd_warn("can't get client address: %s", tli_error());
            t_free((void *) client, T_UNITDATA);
            return;
        }
    }
    request->client->unit = client;

    /*
     * Look up the server endpoint address. This can be used for filtering on
     * server address or name, or to look up the client user.
     */

    if ((server = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ADDR)) == 0) {
        tcpd_warn("t_alloc: %s", tli_error());
        return;
    }
    if (ioctl(fd, TI_GETMYNAME, &server->addr) < 0) {
        tcpd_warn("TI_GETMYNAME: %m");
        t_free((void *) server, T_UNITDATA);
        return;
    }
    request->server->unit = server;
}

/* tli_transport - find out TLI transport type */

static struct netconfig *tli_transport(fd)
int     fd;
{
    struct stat from_client;
    struct stat from_config;
    void   *handlep;
    struct netconfig *config;

    /*
     * Assuming that the network device is a clone device, we must compare
     * the major device number of stdin to the minor device number of the
     * devices listed in the netconfig table.
     */

    if (fstat(fd, &from_client) != 0) {
        tcpd_warn("fstat(fd %d): %m", fd);
        return (0);
    }
    if ((handlep = setnetconfig()) == 0) {
        tcpd_warn("setnetconfig: %m");
        return (0);
    }
    while (config = getnetconfig(handlep)) {
        if (stat(config->nc_device, &from_config) == 0) {
            if (minor(from_config.st_rdev) == major(from_client.st_rdev) ||
                /* XXX: Solaris 8 no longer has clone devices for IP */
                major(from_config.st_rdev) == major(from_client.st_rdev))
                break;
        }
    }
    if (config == 0) {
        tcpd_warn("unable to identify transport protocol");
        return (0);
    }

    /*
     * Something else may clobber our getnetconfig() result, so we'd better
     * acquire our private copy.
     */

    if ((config = getnetconfigent(config->nc_netid)) == 0) {
        tcpd_warn("getnetconfigent(%s): %s", config->nc_netid, nc_sperror());
        return (0);
    }
    return (config);
}

/* tli_hostaddr - map TLI transport address to printable address */

static void tli_hostaddr(host)
struct host_info *host;
{
    struct request_info *request = host->request;
    struct netconfig *config = request->config;
    struct t_unitdata *unit = host->unit;
    char   *uaddr;

    if (config != 0 && unit != 0
        && (uaddr = taddr2uaddr(config, &unit->addr)) != 0) {
        STRN_CPY(host->addr, uaddr, sizeof(host->addr));
        free(uaddr);
    }
}

/* tli_hostname - map TLI transport address to hostname */

static void tli_hostname(host)
struct host_info *host;
{
    struct request_info *request = host->request;
    struct netconfig *config = request->config;
    struct t_unitdata *unit = host->unit;
    struct nd_hostservlist *servlist;

    if (config != 0 && unit != 0
        && netdir_getbyaddr(config, &servlist, &unit->addr) == ND_OK) {

        struct nd_hostserv *service = servlist->h_hostservs;
        struct nd_addrlist *addr_list;
        int     found = 0;

        if (netdir_getbyname(config, service, &addr_list) != ND_OK) {

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

            tcpd_warn("can't verify hostname: netdir_getbyname(%.*s) failed",
                      STRING_LENGTH, service->h_host);

        } else {

            /*
             * Look up the host address in the address list we just got. The
             * comparison is done on the textual representation, because the
             * transport address is an opaque structure that may have holes
             * with uninitialized garbage. This approach obviously loses when
             * the address does not have a textual representation.
             */

            char   *uaddr = eval_hostaddr(host);
            char   *ua;
            int     i;

            for (i = 0; found == 0 && i < addr_list->n_cnt; i++) {
                if ((ua = taddr2uaddr(config, &(addr_list->n_addrs[i]))) != 0) {
                    found = !strcmp(ua, uaddr);
                    free(ua);
                }
            }
            netdir_free((void *) addr_list, ND_ADDRLIST);

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

            if (found == 0)
                tcpd_warn("host name/address mismatch: %s != %.*s",
                          host->addr, STRING_LENGTH, service->h_host);
        }
        STRN_CPY(host->name, found ? service->h_host : paranoid,
                 sizeof(host->name));
        netdir_free((void *) servlist, ND_HOSTSERVLIST);
    }
}

/* tli_error - convert tli error number to text */

static char *tli_error()
{
    static char buf[40];

    if (t_errno != TSYSERR) {
        if (t_errno < 0 || t_errno >= t_nerr) {
            snprintf(buf, sizeof (buf), "Unknown TLI error %d", t_errno);
            return (buf);
        } else {
            return (t_errlist[t_errno]);
        }
    } else {
        STRN_CPY(buf, strerror(errno), sizeof (buf));
        return (buf);
    }
}

/* tli_sink - absorb unreceived datagram */

static void tli_sink(fd)
int     fd;
{
    struct t_unitdata *unit;
    int     flags;

    /*
     * Something went wrong. Absorb the datagram to keep inetd from looping.
     * Allocate storage for address, control and data. If that fails, sleep
     * for a couple of seconds in an attempt to keep inetd from looping too
     * fast.
     */

    if ((unit = (struct t_unitdata *) t_alloc(fd, T_UNITDATA, T_ALL)) == 0) {
        tcpd_warn("t_alloc: %s", tli_error());
        sleep(5);
    } else {
        (void) t_rcvudata(fd, unit, &flags);
        t_free((void *) unit, T_UNITDATA);
    }
}

#endif /* TLI */