root/usr/src/uts/common/inet/ip/inet_ntop.c
/*
 * 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));
}