/*
 * 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);
}