/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2017 Nexenta Systems, Inc.
 */

#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/sunddi.h>
#include <netinet/in.h>
#include <inet/led.h>

/*
 * v6 formats supported
 * General format xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
 * The short hand notation :: is used for COMPAT addr
 * Other forms : fe80::xxxx:xxxx:xxxx:xxxx
 */
static void
convert2ascii(char *buf, const in6_addr_t *addr)
{
	int		hexdigits;
	int		head_zero = 0;
	int		tail_zero = 0;
	/* tempbuf must be big enough to hold ffff:\0 */
	char		tempbuf[6];
	char		*ptr;
	uint16_t	*addr_component;
	size_t		len;
	boolean_t	first = B_FALSE;
	boolean_t	med_zero = B_FALSE;
	boolean_t	end_zero = B_FALSE;

	addr_component = (uint16_t *)addr;
	ptr = buf;

	/* First count if trailing zeroes higher in number */
	for (hexdigits = 0; hexdigits < 8; hexdigits++) {
		if (*addr_component == 0) {
			if (hexdigits < 4)
				head_zero++;
			else
				tail_zero++;
		}
		addr_component++;
	}
	addr_component = (uint16_t *)addr;
	if (tail_zero > head_zero && (head_zero + tail_zero) != 7)
		end_zero = B_TRUE;

	for (hexdigits = 0; hexdigits < 8; hexdigits++) {

		/* if entry is a 0 */

		if (*addr_component == 0) {
			if (!first && *(addr_component + 1) == 0) {
				if (end_zero && (hexdigits < 4)) {
					*ptr++ = '0';
					*ptr++ = ':';
				} else {
					/*
					 * address starts with 0s ..
					 * stick in leading ':' of pair
					 */
					if (hexdigits == 0)
						*ptr++ = ':';
					/* add another */
					*ptr++ = ':';
					first = B_TRUE;
					med_zero = B_TRUE;
				}
			} else if (first && med_zero) {
				if (hexdigits == 7)
					*ptr++ = ':';
				addr_component++;
				continue;
			} else {
				*ptr++ = '0';
				*ptr++ = ':';
			}
			addr_component++;
			continue;
		}
		if (med_zero)
			med_zero = B_FALSE;

		tempbuf[0] = '\0';
		(void) sprintf(tempbuf, "%x:", ntohs(*addr_component) & 0xffff);
		len = strlen(tempbuf);
		bcopy(tempbuf, ptr, len);
		ptr = ptr + len;
		addr_component++;
	}
	*--ptr = '\0';
}

/*
 * search for char c, terminate on trailing white space
 */
static char *
strchr_w(const char *sp, int c)
{
	/* skip leading white space */
	while (*sp && (*sp == ' ' || *sp == '\t')) {
		sp++;
	}

	do {
		if (*sp == (char)c)
			return ((char *)sp);
		if (*sp == ' ' || *sp == '\t')
			return (NULL);
	} while (*sp++);
	return (NULL);
}

static int
str2inet_addr(char *cp, ipaddr_t *addrp)
{
	char *end;
	long byte;
	int i;
	ipaddr_t addr = 0;

	for (i = 0; i < 4; i++) {
		if (ddi_strtol(cp, &end, 10, &byte) != 0 || byte < 0 ||
		    byte > 255) {
			return (0);
		}
		addr = (addr << 8) | (uint8_t)byte;
		if (i < 3) {
			if (*end != '.') {
				return (0);
			} else {
				cp = end + 1;
			}
		} else {
			cp = end;
		}
	}
	*addrp = addr;
	return (1);
}

/*
 * inet_ntop: Convert an IPv4 or IPv6 address in binary form into
 * printable form, and return a pointer to that string.  Caller should
 * provide a buffer of correct length to store string into.
 * Note: this routine is kernel version of inet_ntop.  It has similar
 * format as inet_ntop() defined in RFC 2553, but it does not do
 * error handling operations exactly as RFC 2553 defines.
 */
static char *
__inet_ntop(int af, const void *addr, char *buf, int addrlen, int compat)
{
	static char	*badaf = "<badfamily>";
	in6_addr_t	*v6addr;
	uchar_t		*v4addr;
	char		*caddr;

	VERIFY(addr != NULL);
	VERIFY(OK_32PTR(addr));
	VERIFY(buf != NULL);

	buf[0] = '\0';

#define	UC(b)	(((int)b) & 0xff)
	switch (af) {
	case AF_INET:
		ASSERT(addrlen >= INET_ADDRSTRLEN);
		v4addr = (uchar_t *)addr;
		(void) sprintf(buf,
		    (compat) ? "%03d.%03d.%03d.%03d" : "%d.%d.%d.%d",
		    UC(v4addr[0]), UC(v4addr[1]), UC(v4addr[2]), UC(v4addr[3]));
		return (buf);
	case AF_INET6:
		ASSERT(addrlen >= INET6_ADDRSTRLEN);
		v6addr = (in6_addr_t *)addr;
		if (IN6_IS_ADDR_V4MAPPED(v6addr)) {
			caddr = (char *)addr;
			(void) sprintf(buf, "::ffff:%d.%d.%d.%d",
			    UC(caddr[12]), UC(caddr[13]),
			    UC(caddr[14]), UC(caddr[15]));
		} else if (IN6_IS_ADDR_V4COMPAT(v6addr)) {
			caddr = (char *)addr;
			(void) sprintf(buf, "::%d.%d.%d.%d",
			    UC(caddr[12]), UC(caddr[13]), UC(caddr[14]),
			    UC(caddr[15]));
		} else if (IN6_IS_ADDR_UNSPECIFIED(v6addr)) {
			(void) sprintf(buf, "::");
		} else {
			convert2ascii(buf, v6addr);
		}
		return (buf);

	default:
		return (badaf);
	}
#undef UC
}

/*
 * Provide fixed inet_ntop() implementation.
 */
char *
_inet_ntop(int af, const void *addr, char *buf, int addrlen)
{
	return (__inet_ntop(af, addr, buf, addrlen, 0));
}

/*
 * Provide old inet_ntop() implementation by default for binary
 * compatibility.
 */
char *
inet_ntop(int af, const void *addr, char *buf, int addrlen)
{
	static char	local_buf[INET6_ADDRSTRLEN];
	static char	*badaddr = "<badaddr>";

	if (addr == NULL || !(OK_32PTR(addr)))
		return (badaddr);

	if (buf == NULL) {
		buf = local_buf;
		addrlen = sizeof (local_buf);
	}

	return (__inet_ntop(af, addr, buf, addrlen, 1));
}

/*
 * inet_pton: This function takes string format IPv4 or IPv6 address and
 * converts it to binary form. The format of this function corresponds to
 * inet_pton() in the socket library.
 *
 * Return values:
 *  0 invalid IPv4 or IPv6 address
 *  1 successful conversion
 * -1 af is not AF_INET or AF_INET6
 */
static int
__inet_pton(int af, char *inp, void *outp, int compat)
{
	int i;
	long byte;
	char *end;

	switch (af) {
	case AF_INET:
		if (str2inet_addr(inp, (ipaddr_t *)outp) != 0) {
			if (!compat)
				*(uint32_t *)outp = htonl(*(uint32_t *)outp);
			return (1);
		} else {
			return (0);
		}
	case AF_INET6: {
		union v6buf_u {
			uint16_t v6words_u[8];
			in6_addr_t v6addr_u;
		} v6buf, *v6outp;
		uint16_t	*dbl_col = NULL;
		char lastbyte = '\0';

		v6outp = (union v6buf_u *)outp;

		if (strchr_w(inp, '.') != NULL) {
			int ret = 0;

			/* v4 mapped or v4 compatable */
			if (strncmp(inp, "::ffff:", 7) == 0) {
				ipaddr_t ipv4_all_zeroes = 0;
				/* mapped - first init prefix and then fill */
				IN6_IPADDR_TO_V4MAPPED(ipv4_all_zeroes,
				    &v6outp->v6addr_u);
				ret = str2inet_addr(inp + 7,
				    &(v6outp->v6addr_u.s6_addr32[3]));
			} else if (strncmp(inp, "::", 2) == 0) {
				/* v4 compatable - prefix all zeroes */
				bzero(&v6outp->v6addr_u, sizeof (in6_addr_t));
				ret = str2inet_addr(inp + 2,
				    &(v6outp->v6addr_u.s6_addr32[3]));
			}
			if (ret > 0 && !compat) {
				v6outp->v6addr_u.s6_addr32[3] =
				    htonl(v6outp->v6addr_u.s6_addr32[3]);
			}
			return (ret);
		}
		for (i = 0; i < 8; i++) {
			int error;
			/*
			 * if ddi_strtol() fails it could be because
			 * the string is "::".  That is valid and
			 * checked for below so just set the value to
			 * 0 and continue.
			 */
			if ((error = ddi_strtol(inp, &end, 16, &byte)) != 0) {
				if (error == ERANGE)
					return (0);
				byte = 0;
			}
			if (byte < 0 || byte > 0x0ffff) {
				return (0);
			}
			if (compat) {
				v6buf.v6words_u[i] = (uint16_t)byte;
			} else {
				v6buf.v6words_u[i] = htons((uint16_t)byte);
			}
			if (*end == '\0' || i == 7) {
				inp = end;
				break;
			}
			if (inp == end) {	/* not a number must be */
				if (*inp == ':' &&
				    ((i == 0 && *(inp + 1) == ':') ||
				    lastbyte == ':')) {
					if (dbl_col) {
						return (0);
					}
					if (byte != 0)
						i++;
					dbl_col = &v6buf.v6words_u[i];
					if (i == 0)
						inp++;
				} else if (*inp == '\0' || *inp == ' ' ||
				    *inp == '\t') {
					break;
				} else {
					return (0);
				}
			} else {
				inp = end;
			}
			if (*inp != ':') {
				return (0);
			}
			inp++;
			if (*inp == '\0' || *inp == ' ' || *inp == '\t') {
				break;
			}
			lastbyte = *inp;
		}
		if (*inp != '\0' && *inp != ' ' && *inp != '\t') {
			return (0);
		}
		/*
		 * v6words now contains the bytes we could translate
		 * dbl_col points to the word (should be 0) where
		 * a double colon was found
		 */
		if (i == 7) {
			v6outp->v6addr_u = v6buf.v6addr_u;
		} else {
			int rem;
			int word;
			int next;
			if (dbl_col == NULL) {
				return (0);
			}
			bzero(&v6outp->v6addr_u, sizeof (in6_addr_t));
			rem = dbl_col - &v6buf.v6words_u[0];
			for (next = 0; next < rem; next++) {
				v6outp->v6words_u[next] = v6buf.v6words_u[next];
			}
			next++;	/* skip dbl_col 0 */
			rem = i - rem;
			word = 8 - rem;
			while (rem > 0) {
				v6outp->v6words_u[word] = v6buf.v6words_u[next];
				word++;
				rem--;
				next++;
			}
		}
		return (1);	/* Success */
	}
	}	/* switch */
	return (-1);	/* return -1 for default case */
}

/*
 * Provide fixed inet_pton() implementation.
 */
int
_inet_pton(int af, char *inp, void *outp)
{
	return (__inet_pton(af, inp, outp, 0));
}

/*
 * Provide broken inet_pton() implementation by default for binary
 * compatibility.
 */
int
inet_pton(int af, char *inp, void *outp)
{
	return (__inet_pton(af, inp, outp, 1));
}