/*
* 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 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 */
#pragma ident "%Z%%M% %I% %E% SMI"
#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>
#include <synch.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
static int searchhost(struct netconfig *, char *, int, char *);
static int searchserv(struct netconfig *, char *, int, char *);
static const char *nodename(void);
/*
* _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);
return (argp->m_uaddr == NULL? -1 : 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 */
const char *myname; /* my own nodename */
myname = nodename();
/*
* Unless /etc/netconfig has been altered, the only transport
* that will use straddr.so is loopback. In this case, we
* always return our nodename if that's what we were passed,
* or we fail (note that we'd like to return a constant like
* "localhost" so that changes to the machine name won't cause
* problems, but things like autofs actually assume that we're
* using our nodename).
*/
if ((strcmp(token, HOST_SELF_BIND) == 0) ||
(strcmp(token, HOST_SELF_CONNECT) == 0) ||
(strcmp(token, HOST_ANY) == 0) ||
(myname != NULL && (strcmp(token, myname) == 0))) {
if (myname == NULL)
return (0);
(void) strcpy(hostbuf, myname);
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);
}
static const char *
nodename(void)
{
static mutex_t nodename_lock = DEFAULTMUTEX;
static const char *myname;
struct utsname utsname;
(void) mutex_lock(&nodename_lock);
if (myname != NULL) {
(void) mutex_unlock(&nodename_lock);
return (myname);
}
if (uname(&utsname) == -1) {
(void) mutex_unlock(&nodename_lock);
_nderror = ND_SYSTEM;
return (NULL);
}
myname = strdup(utsname.nodename);
if (myname == NULL)
_nderror = ND_NOMEM;
(void) mutex_unlock(&nodename_lock);
return (myname);
}