root/usr/src/lib/nametoaddr/straddr/common/straddr.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
 */

/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*       All Rights Reserved    */

#include <ctype.h>
#include <stdio.h>
#include <tiuser.h>
#include <netdir.h>
#include <netconfig.h>
#include <sys/utsname.h>
#include <sys/param.h>
#include <string.h>
#include <stdlib.h>

/*
 *      The generic name to address mappings for any transport that
 *      has strings for address (e.g., ISO Starlan).
 *
 *      Address in ISO Starlan consist of arbitrary strings of
 *      characters.  Because of this, the following routines
 *      create an "address" based on two strings, one gotten
 *      from a "host" file and one gotten from a "services" file.
 *      The two strings are catenated together (with a "." between
 *      them).  The hosts file is /etc/net/starlan/hosts and
 *      contain lines of the form:
 *
 *              arbitrary_string        machname
 *
 *      To make things simple, the "arbitrary string" should be the
 *      machine name.
 *
 *      The services file is /etc/net/starlan/services and has lines
 *      of the form:
 *
 *              service_name    arbitrary_string
 *
 *      Again, to make things easer, the "arbitrary name" should be the
 *      service name.
 */

#define HOSTFILE        "/etc/net/%s/hosts"
#define SERVICEFILE     "/etc/net/%s/services"
#define FIELD1          1
#define FIELD2          2
#define LOCALHOST       "localhost"

static int searchhost(struct netconfig *, char *, int, char *);
static int searchserv(struct netconfig *, char *, int, char *);

/*
 *      _netdir_getbyname() returns all of the addresses for
 *      a specified host and service.
 */

struct nd_addrlist *
_netdir_getbyname(struct netconfig *netconfigp,
    struct nd_hostserv *nd_hostservp)
{
        char   fulladdr[BUFSIZ];   /* holds the full address string        */
        struct nd_addrlist *retp;  /* the return structure                 */
        struct netbuf *netbufp;    /* indexes through the addresses        */

        /*
         *      HOST_BROADCAST is not supported.
         */

        if (strcmp(nd_hostservp->h_host, HOST_BROADCAST) == 0) {
                _nderror = ND_NOHOST;
                return (NULL);
        }

        if (searchhost(netconfigp, nd_hostservp->h_host, FIELD2,
            fulladdr) == 0) {
                _nderror = ND_NOHOST;
                return (NULL);
        }

        /*
         *      Now simply fill in the address by forming strings of the
         *      form "string_from_hosts.string_from_services"
         */

        if (nd_hostservp->h_serv &&
            (strcmp(nd_hostservp->h_serv, "rpcbind") == 0)) {
                (void) strcat(fulladdr, ".");
                (void) strcat(fulladdr, "rpc"); /* hard coded */
        } else {
                /*
                 *      Get the address from the services file
                 */

                if (nd_hostservp->h_serv && (nd_hostservp->h_serv[0] != '\0')) {
                        (void) strcat(fulladdr, ".");
                        if (searchserv(netconfigp, nd_hostservp->h_serv, FIELD1,
                            fulladdr + strlen(fulladdr)) == 0) {
                                _nderror = ND_NOSERV;
                                return (NULL);
                        }
                }
        }

        if ((retp = malloc(sizeof (struct nd_addrlist))) == NULL) {
                _nderror = ND_NOMEM;
                return (NULL);
        }

        /*
         *      We do not worry about multiple addresses here.  Loopbacks
         *      have only one interface.
         */

        retp->n_cnt = 1;
        if ((retp->n_addrs = malloc(sizeof (struct netbuf))) == NULL) {
                free(retp);
                _nderror = ND_NOMEM;
                return (NULL);
        }

        netbufp = retp->n_addrs;

        /*
         *      Don't include the terminating NULL character in the
         *      length.
         */

        netbufp->len = netbufp->maxlen = (int)strlen(fulladdr);
        if ((netbufp->buf = strdup(fulladdr)) == NULL) {
                free(netbufp);
                free(retp);
                _nderror = ND_NOMEM;
                return (NULL);
        }
        _nderror = ND_OK;
        return (retp);
}

/*
 *      _netdir_getbyaddr() takes an address (hopefully obtained from
 *      someone doing a _netdir_getbyname()) and returns all hosts with
 *      that address.
 */

struct nd_hostservlist *
_netdir_getbyaddr(struct netconfig *netconfigp, struct netbuf *netbufp)
{
        char   fulladdr[BUFSIZ];          /* a copy of the address string   */
        char   servbuf[BUFSIZ];           /* a buffer for service string    */
        char   hostbuf[BUFSIZ];           /* points to list of host names   */
        char   *hostname;                 /* the "first" path of the string */
        char   *servname;                 /* the "second" part of string    */
        struct nd_hostservlist *retp;     /* the return structure           */
        char   *serv;                     /* resultant service name obtained */
        int    nhost;                     /* the number of hosts in hostpp  */
        struct nd_hostserv *nd_hostservp; /* traverses the host structures  */
        char   *nexttok;                  /* next token to process          */

        /*
         *      Separate the two parts of the address string.
         */

        (void) strlcpy(fulladdr, netbufp->buf, sizeof (fulladdr));
        hostname = strtok_r(fulladdr, ".", &nexttok);
        if (hostname == NULL) {
                _nderror = ND_NOHOST;
                return (NULL);
        }
        servname = strtok_r(NULL, " \n\t", &nexttok);

        /*
         *      Search for all the hosts associated with the
         *      first part of the address string.
         */

        nhost = searchhost(netconfigp, hostname, FIELD1, hostbuf);
        if (nhost == 0) {
                _nderror = ND_NOHOST;
                return (NULL);
        }

        /*
         *      Search for the service associated with the second
         *      path of the address string.
         */

        if (servname == NULL) {
                _nderror = ND_NOSERV;
                return (NULL);
        }

        servbuf[0] = '\0';
        serv = servbuf;
        if (searchserv(netconfigp, servname, FIELD2, servbuf) == 0) {
                serv = _taddr2uaddr(netconfigp, netbufp);
                (void) strcpy(servbuf, serv);
                free(serv);
                serv = servbuf;
                while (*serv != '.')
                        serv++;
        }

        /*
         *      Allocate space to hold the return structure, set the number
         *      of hosts, and allocate space to hold them.
         */

        if ((retp = malloc(sizeof (struct nd_hostservlist))) == NULL) {
                _nderror = ND_NOMEM;
                return (NULL);
        }

        retp->h_cnt = nhost;
        retp->h_hostservs = calloc(nhost, sizeof (struct nd_hostserv));
        if (retp->h_hostservs == NULL) {
                free(retp);
                _nderror = ND_NOMEM;
                return (NULL);
        }

        /*
         *      Loop through the host structues and fill them in with
         *      each host name (and service name).
         */

        nd_hostservp = retp->h_hostservs;
        hostname = strtok_r(hostbuf, ",", &nexttok);
        while (hostname && nhost--) {
                if (((nd_hostservp->h_host = strdup(hostname)) == NULL) ||
                    ((nd_hostservp->h_serv = strdup(serv)) == NULL)) {
                        netdir_free(retp, ND_HOSTSERVLIST);
                        _nderror = ND_NOMEM;
                        return (NULL);
                }
                nd_hostservp++;
                hostname = strtok_r(NULL, ",", &nexttok);
        }

        _nderror = ND_OK;
        return (retp);
}

/*
 *      _taddr2uaddr() translates a address into a "universal" address.
 *      Since the address is a string, simply return the string as the
 *      universal address (but replace all non-printable characters with
 *      the \ddd form, where ddd is three octal digits).  The '\n' character
 *      is also replace by \ddd and the '\' character is placed as two
 *      '\' characters.
 */

/* ARGSUSED */
char *
_taddr2uaddr(struct netconfig *netconfigp, struct netbuf *netbufp)
{
        char *retp;     /* pointer the return string                    */
        char *to;       /* traverses and populates the return string    */
        char *from;     /* traverses the string to be converted         */
        int i;          /* indexes through the given string             */

        /*
         * BUFSIZ is perhaps too big for this one and there is a better
         * way to optimize it, but for now we will just assume BUFSIZ
         */
        if ((retp = malloc(BUFSIZ)) == NULL) {
                _nderror = ND_NOMEM;
                return (NULL);
        }
        to = retp;
        from = netbufp->buf;

        for (i = 0; i < netbufp->len; i++) {
                if (*from == '\\') {
                        *to++ = '\\';
                        *to++ = '\\';
                } else {
                        if (*from == '\n' || !isprint((unsigned char)*from)) {
                                (void) sprintf(to, "\\%.3o", *from & 0xff);
                                to += 4;
                        } else {
                                *to++ = *from;
                        }
                }
                from++;
        }
        *to = '\0';
        return (retp);
}

/*
 *      _uaddr2taddr() translates a universal address back into a
 *      netaddr structure.  Since the universal address is a string,
 *      put that into the TLI buffer (making sure to change all \ddd
 *      characters back and strip off the trailing \0 character).
 */

/* ARGSUSED */
struct netbuf *
_uaddr2taddr(struct netconfig *netconfigp, char *uaddr)
{
        struct netbuf *retp;    /* the return structure                    */
        char *holdp;            /* holds the converted address             */
        char *to;               /* traverses and populates the new address */
        char *from;             /* traverses the universal address         */

        holdp = malloc(strlen(uaddr) + 1);
        if (holdp == NULL) {
                _nderror = ND_NOMEM;
                return (NULL);
        }
        from = uaddr;
        to = holdp;

        while (*from) {
                if (*from == '\\') {
                        if (*(from+1) == '\\') {
                                *to = '\\';
                                from += 2;
                        } else {
                                *to = ((*(from+1) - '0') << 6) +
                                    ((*(from+2) - '0') << 3) +
                                    (*(from+3) - '0');
                                from += 4;
                        }
                } else {
                        *to = *from++;
                }
                to++;
        }
        *to = '\0';

        if ((retp = malloc(sizeof (struct netbuf))) == NULL) {
                free(holdp);
                _nderror = ND_NOMEM;
                return (NULL);
        }
        retp->maxlen = retp->len = (int)(to - holdp);
        retp->buf = holdp;
        return (retp);
}

/*
 *      _netdir_options() is a "catch-all" routine that does
 *      transport specific things.  The only thing that these
 *      routines have to worry about is ND_MERGEADDR.
 */

/* ARGSUSED */
int
_netdir_options(struct netconfig *netconfigp, int option, int fd, void *par)
{
        struct nd_mergearg *argp;  /* the argument for mergeaddr */

        switch (option) {
        case ND_MERGEADDR:
                /*
                 *      Translate the universal address into something that
                 *      makes sense to the caller.  This is a no-op in
                 *      loopback's case, so just return the universal address.
                 */
                argp = (struct nd_mergearg *)par;
                argp->m_uaddr = strdup(argp->s_uaddr);
                if (argp->m_uaddr == NULL) {
                        _nderror = ND_NOMEM;
                        return (-1);
                }
                return (0);
        default:
                _nderror = ND_NOCTRL;
                return (-1);
        }
}

/*
 *      searchhost() looks for the specified token in the host file.
 *      The "field" parameter signifies which field to compare the token
 *      on, and returns all comma separated values associated with the token.
 */

static int
searchhost(struct netconfig *netconfigp, char *token, int field, char *hostbuf)
{
        char searchfile[MAXPATHLEN];  /* the name of file to be opened      */
        char buf[BUFSIZ];       /* holds each line of the file              */
        char *fileaddr;         /* the first token in each line             */
        char *filehost;         /* the second token in each line            */
        char *cmpstr;           /* the string to compare token to           */
        char *retstr;           /* the string to return if compare succeeds */
        char *nexttok;          /* next token to process                    */
        FILE *fp;               /* the opened searchfile                    */
        int   nelements = 0;    /* total number of elements found           */
        struct utsname utsname;

        /*
         *      Unless /etc/netconfig has been altered, the only transport that
         *      will use straddr.so is loopback.  In this case, we always
         *      return "localhost" if either our nodename, or "localhost", or
         *      some of special-case host names were passed, or we fail.
         */

        if ((strcmp(token, HOST_SELF_BIND) == 0) ||
            (strcmp(token, HOST_SELF_CONNECT) == 0) ||
            (strcmp(token, HOST_ANY) == 0) ||
            (strcmp(token, LOCALHOST) == 0) ||
            (uname(&utsname) >= 0 && strcmp(token, utsname.nodename) == 0)) {
                (void) strcpy(hostbuf, LOCALHOST);
                return (1);
        }

        if (strcmp(netconfigp->nc_protofmly, NC_LOOPBACK) == 0)
                return (0);

        /*
         *      We only get here if an administrator has modified
         *      /etc/netconfig to use straddr.so for a transport other than
         *      loopback (which is questionable but something we'll need to
         *      EOL at a later point in time).  In this case, we fallback to
         *      searching for the associated key in the appropriate hosts
         *      file (based on nc_netid).
         */

        (void) snprintf(searchfile, sizeof (searchfile), HOSTFILE,
            netconfigp->nc_netid);

        fp = fopen(searchfile, "rF");
        if (fp == NULL)
                return (0);

        /*
         *      Loop through the file looking for the tokens and creating
         *      the list of strings to be returned.
         */

        while (fgets(buf, BUFSIZ, fp) != NULL) {

                /*
                 *      Ignore comments and bad lines.
                 */

                fileaddr = strtok_r(buf, " \t\n", &nexttok);
                if (fileaddr == NULL || *fileaddr == '#')
                        continue;

                if ((filehost = strtok_r(NULL, " \t\n", &nexttok)) == NULL)
                        continue;

                /*
                 *      determine which to compare the token to, then
                 *      compare it, and if they match, add the return
                 *      string to the list.
                 */

                cmpstr = (field == FIELD1)? fileaddr : filehost;
                retstr = (field == FIELD1)? filehost : fileaddr;

                if (strcmp(token, cmpstr) == 0) {
                        nelements++;
                        if (field == FIELD2) {
                                /*
                                 * called by _netdir_getbyname
                                 */

                                (void) strcpy(hostbuf, retstr);
                                break;
                        }
                        if (nelements > 1) {
                                /*
                                 * Assuming that "," will never be a part
                                 * of any host name.
                                 */
                                (void) strcat(hostbuf, ",");
                        }
                        (void) strcat(hostbuf, retstr);
                }
        }

        (void) fclose(fp);
        return (nelements);
}

/*
 *      searchserv() looks for the specified token in the service file.
 *      The "field" parameter signifies which field to compare the token
 *      on, and returns the string associated with the token in servname.
 */

static int
searchserv(struct netconfig *netconfigp, char *token, int field, char *servname)
{
        char searchfile[MAXPATHLEN];  /* the name of file to be opened  */
        char buf[BUFSIZ];       /* buffer space for lines in file       */
        char *fileservice;      /* the first token in each line         */
        char *fileport;         /* the second token in each line        */
        char *cmpstr;           /* the string to compare the token to   */
        char *retstr;           /* temporarily hold token in line of file */
        char *nexttok;          /* next token to process                */
        FILE *fp;               /* the opened searchfile                */

        (void) snprintf(searchfile, sizeof (searchfile), SERVICEFILE,
            netconfigp->nc_netid);

        fp = fopen(searchfile, "rF");
        if (fp == NULL)
                return (0);

        /*
         *      Loop through the services file looking for the token.
         */

        while (fgets(buf, BUFSIZ, fp) != NULL) {
                /*
                 *      If comment or bad line, continue.
                 */
                fileservice = strtok_r(buf, " \t\n", &nexttok);
                if (fileservice == NULL || *fileservice == '#')
                        continue;

                if ((fileport = strtok_r(NULL, " \t\n", &nexttok)) == NULL)
                        continue;

                cmpstr = (field == FIELD1)? fileservice : fileport;
                retstr = (field == FIELD1)? fileport : fileservice;

                if (strcmp(token, cmpstr) == 0) {
                        (void) strcpy(servname, retstr);
                        (void) fclose(fp);
                        return (1);
                }
        }

        (void) fclose(fp);
        return (0);
}