/*
 * 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.
 *
 * files/gethostent.c -- "files" backend for nsswitch "hosts" database
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <netdb.h>
#include "files_common.h"
#include <string.h>
#include <strings.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <arpa/inet.h>
#include <ctype.h>

static int	check_name(nss_XbyY_args_t *, const char *, int,
			int, const char **, int *, void *, int *);
static char *do_aliases();
static char *strcasestr(const char *as1, const char *as2);
nss_status_t __nss_files_XY_hostbyname();
int __nss_files_2herrno();
static int	__nss_files_get_addr(int, const char *, int,
			void *, int, int *);

static int
check_name(nss_XbyY_args_t *argp, const char *line, int linelen,
	int type, const char **namep, int *namelen,
	void *addrp, int *addrsize)
{
	const char	*limit, *linep, *keyp, *addrstart;
	int		v6flag = 0, addrlen;

	linep = line;
	limit = line + linelen;

	/* Address */
	addrstart = linep;
	while (linep < limit && !isspace(*linep)) {
		if (*linep == ':')
			v6flag++;
		linep++;
	}
	addrlen = linep - addrstart;

	/* skip the delimiting spaces */
	while (linep < limit && isspace(*linep))
		linep++;

	/* Canonical name */
	keyp = argp->key.name;
	*namep = linep;
	while (*keyp && linep < limit && !isspace(*linep) &&
			tolower(*keyp) == tolower(*linep)) {
		keyp++;
		linep++;
	}
	if (*keyp == '\0' && (linep == limit || isspace(*linep))) {
		if (__nss_files_get_addr(type, addrstart, addrlen,
					addrp, v6flag, addrsize)) {
			*namelen = linep - *namep;
			return (1);
		}
	}
	while (linep < limit && !isspace(*linep))
		linep++;
	*namelen = linep - *namep;

	/* Aliases */
	while (linep < limit) {
		/* skip the delimiting spaces */
		while (linep < limit && isspace(*linep))
			linep++;

		/* compare name (case insensitive) */
		keyp = argp->key.name;
		while (*keyp && linep < limit && !isspace(*linep) &&
				tolower(*keyp) == tolower(*linep)) {
			keyp++;
			linep++;
		}
		if (*keyp == '\0' && (linep == limit || isspace(*linep)))
			return (__nss_files_get_addr(type, addrstart, addrlen,
					addrp, v6flag, addrsize));

		/* skip remainder of alias, if any */
		while (linep < limit && !isspace(*linep))
			linep++;
	}
	return (0);

}

static nss_status_t
getbyname(be, a)
	files_backend_ptr_t	be;
	void			*a;
{
	nss_XbyY_args_t		*argp = (nss_XbyY_args_t *)a;
	nss_status_t		res;

	res = __nss_files_XY_hostbyname(be, argp, argp->key.name, AF_INET);
	if (res != NSS_SUCCESS)
		argp->h_errno = __nss_files_2herrno(res);
	return (res);
}

static int
__nss_files_get_addr(int af, const char *addrstart, int addrlen,
	void *addrp, int v6flag, int *h_length)
{
	struct in_addr	addr_ipv4;
	struct in6_addr	*addrpv6;
	in_addr_t	*addrpv4;
	char		addrbuf[INET6_ADDRSTRLEN + 1];

	if (addrlen >= sizeof (addrbuf))
		return (0);
	(void) memcpy(addrbuf, addrstart, addrlen);
	addrbuf[addrlen] = '\0';

	if (af == AF_INET) {
		addrpv4 = (in_addr_t *)addrp;
		if ((*addrpv4 = inet_addr(addrbuf)) == 0xffffffffU)
			return (0);
		*h_length = sizeof (in_addr_t);
	} else if (af == AF_INET6) {
		addrpv6 = (struct in6_addr *)addrp;
		if (v6flag) {
			if (inet_pton(af, addrbuf, addrpv6) != 1)
				return (0);
		} else {
			if ((addr_ipv4.s_addr = inet_addr(addrbuf)) ==
							0xffffffffU)
				return (0);
			IN6_INADDR_TO_V4MAPPED(&addr_ipv4, addrpv6);
		}
		*h_length = sizeof (struct in6_addr);
	} else {
		return (0);
	}
	return (1);
}


int
__nss_files_check_addr(int af, nss_XbyY_args_t *argp, const char *line,
		int linelen)
{
	const char	*limit, *linep, *addrstart;
	int		v6flag = 0, addrlen, h_length;
	in_addr_t	addr_ipv4;
	struct in6_addr	addr_ipv6;
	char		*h_addrp;

	/* Compare the address type */
	if (argp->key.hostaddr.type != af)
		return (0);

	/* Retrieve the address */
	if (af == AF_INET)
		h_addrp = (char *)&addr_ipv4;
	else
		h_addrp = (char *)&addr_ipv6;
	linep = line;
	limit = line + linelen;
	addrstart = linep;
	while (linep < limit && !isspace(*linep)) {
		if (*linep == ':')
			v6flag++;
		linep++;
	}
	addrlen = linep - addrstart;
	if (__nss_files_get_addr(af, addrstart, addrlen, h_addrp,
			v6flag, &h_length) == 0)
		return (0);

	/* Compare the address */
	return (h_length == argp->key.hostaddr.len &&
		memcmp(h_addrp, argp->key.hostaddr.addr,
			argp->key.hostaddr.len) == 0);
}

static int
check_addr(nss_XbyY_args_t *argp, const char *line, int linelen)
{
	return (__nss_files_check_addr(AF_INET, argp, line, linelen));
}

static nss_status_t
getbyaddr(be, a)
	files_backend_ptr_t	be;
	void			*a;
{
	nss_XbyY_args_t		*argp	= (nss_XbyY_args_t *)a;
	nss_status_t		res;

	res = _nss_files_XY_all(be, argp, 1, 0, check_addr);
	if (res != NSS_SUCCESS)
		argp->h_errno = __nss_files_2herrno(res);
	return (res);
}

int _inet_aton(const char *cp, struct in_addr *addr);
/*
 * filter_ipv6
 *
 * Return - NSS_STR_PARSE_SUCCESS: An IPv4 address
 *          NSS_STR_PARSE_PARSE: An IPv6 address or other errors
 */
static int
filter_ipv6(char *instr, int lenstr) {
	char	*p, *addrstart, *limit, c;
	int	rc;
	struct in_addr	addr;

	p = instr;
	limit = p + lenstr;

	addrstart = p;

	/* parse IP address */
	while (p < limit && !isspace(*p)) {
		if (*p == ':')
			/* IPv6 */
			return (NSS_STR_PARSE_PARSE);
		else
			p++;
	}

	if (p >= limit)
		/* invalid IP */
		return (NSS_STR_PARSE_PARSE);

	/* extract IP address */
	c = *p;
	*p = '\0';
	rc = _inet_aton(addrstart, &addr);
	*p = c;

	if (rc == 0)
		/* invalid IP */
		return (NSS_STR_PARSE_PARSE);
	else
		/* IPv4 */
		return (NSS_STR_PARSE_SUCCESS);


}
static nss_status_t
getent_hosts(files_backend_ptr_t be, void *a)
{
	nss_XbyY_args_t	*args = (nss_XbyY_args_t *)a;
	nss_status_t	rc = NSS_SUCCESS;

	if (args->buf.result != NULL) {
		return (_nss_files_XY_all(be, args, 1, 0, 0));
	} else {
		/*
		 * Called by nscd
		 */
		/*CONSTCOND*/
		while (1) {
			rc = _nss_files_XY_all(be, args, 1, 0, 0);
			/*
			 * NSS_NOTFOUND, end of file or other errors.
			 */
			if (rc != NSS_SUCCESS)
				break;
			/*
			 * /etc/hosts and /etc/ipnodes are merged and
			 * /etc/hosts can contain IPv6 addresses.
			 * These addresses have to be filtered.
			 */
			if (filter_ipv6(args->returnval, args->returnlen)
					== NSS_STR_PARSE_SUCCESS)
				break;
			/*
			 * The entry is an IPv6 address or other errors.
			 * Skip it and continue to find next one.
			 */
			args->returnval = NULL;
			args->returnlen = 0;

		}
		return (rc);
	}

}

static files_backend_op_t host_ops[] = {
	_nss_files_destr,
	_nss_files_endent,
	_nss_files_setent,
	getent_hosts,
	getbyname,
	getbyaddr,
};

/*ARGSUSED*/
nss_backend_t *
_nss_files_hosts_constr(dummy1, dummy2, dummy3)
	const char	*dummy1, *dummy2, *dummy3;
{
	return (_nss_files_constr(host_ops,
				sizeof (host_ops) / sizeof (host_ops[0]),
				_PATH_HOSTS,
				NSS_LINELEN_HOSTS,
				NULL));
}


/*
 * XXX - this duplicates code from files_common.c because we need to keep
 * going after we've found a match to satisfy the multihomed host case.
 */
nss_status_t
__nss_files_XY_hostbyname(be, args, filter, type)
	files_backend_ptr_t be;
	nss_XbyY_args_t *args;
	const char *filter;		/* hint for name string */
	int type;
{
	nss_status_t	res;
	char		*abuf = NULL, *abuf_start = NULL, *abuf_end;
	char		*first, *last, *buffer;
	int		parsestat, i, nhosts = 0, buflen;
	const char	*namep;
	char		*h_name;
	int		h_namelen, namelen;
	struct hostent	*hp;
	in_addr_t	*taddr = NULL;
	struct in6_addr	*taddr6 = NULL;
	size_t		ntaddr;
	void		*addrp;
	char		*alias_end = NULL;

	if (be->buf == 0 && (be->buf = malloc(be->minbuf)) == 0) {
		return (NSS_UNAVAIL);
	}

	if (be->f == 0) {
		if ((res = _nss_files_setent(be, 0)) != NSS_SUCCESS)
			return (res);
	}

	ntaddr = MAXADDRS;
	if (type == AF_INET) {
		taddr = (in_addr_t *)calloc(ntaddr, sizeof (*taddr));
		if (taddr == NULL)
			return (NSS_UNAVAIL);
	} else {
		taddr6 = (struct in6_addr *)calloc(ntaddr, sizeof (*taddr6));
		if (taddr6 == NULL)
			return (NSS_UNAVAIL);
	}

	res = NSS_NOTFOUND;
	args->returnval = (char *)0;
	args->returnlen = 0;
	hp = (struct hostent *)args->buf.result;
	buffer = args->buf.buffer;
	buflen = args->buf.buflen;
	h_namelen = 0;
	h_name = NULL;

	for (;;) {
		char *instr = be->buf;
		int linelen;

		if ((linelen = _nss_files_read_line(be->f,
		    instr, be->minbuf)) < 0) {
			break;		/* EOF */
		}

		/*
		 * This check avoids a malloc()/free() for the common
		 * case. Also, if we're trying to match an alias and an
		 * already matched entry doesn't share a canonical name
		 * with the current one, bail.
		 */
		if (nhosts == 0 && strcasestr(instr, filter) == 0) {
			continue;
		}

		if ((last = strchr(instr, '#')) == 0)
			last = instr + linelen;
		*last-- = '\0';
		for (first = instr;  isspace(*first);  first++)
			;
		/* Ignore blank and comment lines */
		if (*first == '\0')
			continue;

		while (isspace(*last))
			--last;
		linelen = last - first + 1;
		if (first != instr)
			instr = first;

		/* Bail out if the canonical name does not match */
		if (nhosts && strcasestr(instr, h_name) == 0) {
			continue;
		}

		/*
		 * Still need to check, strcasestr() above is just a hint.
		 */
		addrp = (type == AF_INET)?
				(void *)&taddr[nhosts]:
				(void *)&taddr6[nhosts];

		if (check_name(args, instr, linelen,
				type, &namep, &namelen,
				addrp, &i)) {

			/*
			 * If we've already matched once and have a possible
			 * match on this line, copy the aliases where they're
			 * safe from being overwritten when we look at the
			 * next entry. They're saved as a string of blank
			 * separated names for the alias parser. On errors,
			 * we return failure whether or not we have already
			 * obtained a valid address.
			 */
			if (nhosts == 1 && hp) {
				if (h_namelen + 1 > args->buf.buflen) {
					args->erange = 1;
					res = NSS_NOTFOUND;
					break;
				}
				abuf = (char *)malloc(args->buf.buflen);
				if (abuf == NULL) {
					res = NSS_UNAVAIL;
					break;
				}
				abuf_start = abuf;
				abuf_end = abuf_start + args->buf.buflen;
				(void) memcpy(abuf, h_name, h_namelen);
				abuf += h_namelen;
				*abuf = '\0';
				abuf = do_aliases(hp, abuf, abuf_end);
				if (abuf == NULL) {
					args->erange = 1;
					res = NSS_NOTFOUND;
					break;
				}
			}

			if (hp != NULL) {
				/* inside the application */
				parsestat = (*args->str2ent)(instr, linelen,
						hp, buffer, buflen);
				if (parsestat != NSS_STR_PARSE_SUCCESS) {
					if (parsestat == NSS_STR_PARSE_ERANGE)
						args->erange = 1;
					(void) memset(buffer, 0, buflen);
					continue;
				}
			} else {
				/* inside nscd */
				int	alen, cplen, erange = 0;
				char	*ap;

				/* Add alias to the first line if any */
				if (nhosts > 0) {

					/* get to the start of alias */
					ap = (char *)namep + namelen;
					/* see if there's any alias */
					if (ap == instr + linelen)
						alen = 0;
					else
						alen = linelen - (ap - instr);
					if (alen + 1 >= buflen)
						erange  = 1;
					if (erange == 0 && alen != 0) {
						/* make room for the alias */
						if (alias_end != NULL)
						(void) memmove(alias_end +
						alen, alias_end, buffer -
						alias_end);
						/* copy in the alias */
						(void) memmove(alias_end,
							ap, alen);
						buffer += alen;
						buflen -= alen;
						args->returnlen += alen;
						alias_end += alen;
					}

					/* Add delimiter to the buffer */
					*buffer++ = '\n';
					buflen--;
					args->returnlen++;
				}

				/* copy just the addr if not first one */
				if (alias_end == NULL)
					cplen = linelen;
				else
					cplen = namep - instr;

				if (cplen >= buflen || erange == 1) {
					args->erange = 1;
					if (nhosts > 0) {
						*(--buffer) = '\0';
						buflen++;
						args->returnlen--;
					}
					continue;
				}

				(void) memcpy(buffer, instr, cplen);
				/* Adjust buffer */
				buffer += cplen;
				*buffer = '\0';
				buflen -= cplen;
				args->returnlen += cplen;
				if (alias_end == NULL)
					alias_end = buffer;
			}

			/*
			 * If this is the first one, save the canonical
			 * name for future matches and continue.
			 */
			if (++nhosts == 1) {
				h_name = malloc(namelen + 1);
				if (h_name == NULL) {
					res = NSS_UNAVAIL;
					break;
				}
				res = NSS_SUCCESS;
				(void) memcpy(h_name, namep, namelen);
				h_name[namelen] = '\0';
				h_namelen = namelen;
				if (hp)
					args->returnval = hp;
				else
					args->returnval = args->buf.buffer;
				continue;
			}


			/* Extend the array */
			if (nhosts >= ntaddr) {
				ntaddr *= 2;
				if (type == AF_INET) {
					addrp = realloc(taddr,
						sizeof (*taddr) * ntaddr);
					if (addrp == NULL) {
						res = NSS_UNAVAIL;
						break;
					}
					taddr = (in_addr_t *)addrp;
				} else {
					addrp = realloc(taddr6,
						sizeof (*taddr6) * ntaddr);
					if (addrp == NULL) {
						res = NSS_UNAVAIL;
						break;
					}
					taddr6 = (struct in6_addr *)addrp;
				}
			}

			/*
			 * For non-nscd, save aliases in a temporary buffer
			 * Don't have to do this for nscd as 'buffer' already
			 * contains the required data in the appropriate
			 * format
			 */
			if (hp) {
				abuf = do_aliases(hp, abuf, abuf_end);
				if (abuf == NULL) {
					args->erange = 1;
					res = NSS_NOTFOUND;
					break;
				}
			}
		} else if (namep && h_namelen == namelen &&
		    strncasecmp(h_name, namep, namelen) == 0) {
			/*
			 * This line didn't have the requested name but
			 * is part of the same multihomed host (i.e. it
			 * has the same canonical name as the previous
			 * line), so march on...
			 */
			continue;
		} else if (nhosts) {
			break;
		}
	}

	if (abuf && res == NSS_SUCCESS) {

		/* abuf != NULL implies hp and abuf_start != NULL */

		struct in_addr *addrp;
		struct in6_addr *addrp6;

		if (type == AF_INET) {
			addrp = (struct in_addr *)(ROUND_DOWN(args->buf.buffer +
			    args->buf.buflen, sizeof (*addrp)));
			hp->h_addr_list = (char **)(ROUND_DOWN(addrp -
			    ((nhosts + 1) * sizeof (char *) +
			    (nhosts * sizeof (*addrp))), sizeof (char *)));
			for (i = 0, --addrp; i < nhosts; i++, --addrp) {
				(*(in_addr_t *)addrp) = taddr[i];
				hp->h_addr_list[i] = (char *)addrp;
			}
		} else {
			addrp6 = (struct in6_addr *)
			(ROUND_DOWN(args->buf.buffer + args->buf.buflen,
			sizeof (*addrp6)));
			hp->h_addr_list = (char **)(ROUND_DOWN(addrp6 -
			    ((nhosts + 1) * sizeof (char *) +
			    (nhosts * sizeof (*addrp6))), sizeof (char *)));
			for (i = 0, --addrp6; i < nhosts; i++, --addrp6) {
				(void) memcpy(addrp6, &taddr6[i],
						sizeof (struct in6_addr));
				hp->h_addr_list[i] = (char *)addrp6;
			}
		}

		hp->h_addr_list[nhosts] = 0;
		hp->h_aliases = _nss_netdb_aliases(abuf_start,
		    abuf - abuf_start, args->buf.buffer,
		    (char *)hp->h_addr_list - args->buf.buffer);
		if (hp->h_aliases == 0) {
			args->erange = 1;
			res = NSS_NOTFOUND;
		} else {
			hp->h_name = hp->h_aliases[0];
			hp->h_aliases++;
		}
	}

	/*
	 * stayopen is set to 0 by default in order to close the opened
	 * file.  Some applications may break if it is set to 1.
	 */
	if (!args->stayopen)
		(void) _nss_files_endent(be, 0);

	if (taddr)
		free(taddr);
	if (taddr6)
		free(taddr6);
	if (h_name)
		free(h_name);
	if (abuf_start)
		free(abuf_start);

	return (res);
}

/*
 * A case-insensitive version of strstr().
 */
static char *
strcasestr(const char *as1, const char *as2)
{
	int c2;
	register const char *tptr;
	register const char *s1, *s2;

	s1 = as1;
	s2 = as2;

	if (s2 == NULL || *s2 == '\0')
		return (0);

	while (*s1) {
		if (tolower(*s1++) == tolower(c2 = *s2)) {
			tptr = s1;
			while ((tolower(c2 = *++s2) ==
			    tolower(*s1++)) && c2 != 0)
				;
			if (c2 == 0)
				return ((char *)tptr - 1);
			s1 = tptr;
			s2 = as2;
		}
	}
	return (0);
}


static char *
do_aliases(struct hostent *hp, char *abuf, char *end)
{
	char	**cp;
	size_t	len;

	if ((cp = hp->h_aliases) == NULL)
		return (abuf);

	for (; *cp; cp++) {
		len = strlen(*cp);
		if (abuf+len+1 >= end) {
			return (NULL);
		}
		*abuf++ = ' ';
		(void) memcpy(abuf, *cp, len);
		abuf += len;
	}
	*abuf = '\0';

	return (abuf);
}


/*
 * This is a copy of a routine in libnsl/nss/netdir_inet.c.  It is
 * here because /etc/lib/nss_files.so.1 cannot call routines
 * in libnsl.  Care should be taken to keep the two copies in sync.
 */
int
__nss_files_2herrno(nsstat)
	nss_status_t nsstat;
{
	switch (nsstat) {
	case NSS_SUCCESS:
		/* no macro-defined success code for h_errno */
		return (0);
	case NSS_NOTFOUND:
		return (HOST_NOT_FOUND);
	case NSS_TRYAGAIN:
		return (TRY_AGAIN);
	case NSS_UNAVAIL:
		return (NO_RECOVERY);
	}
	/* anything else */
	return (NO_RECOVERY);
}