// SPDX-License-Identifier: BSD-2-Clause
/*
 * Copyright (c) 2005, 2006
 * Matthew D. Fuller <fullermd@over-yonder.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/*
 * cidr_to_str() - Generate a textual representation of the given CIDR
 * subnet.
 */
#include "config.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cidr.h"
#include "abstract_mem.h"

char *cidr_to_str(const CIDR * block, int flags)
{
	int i;
	int zst, zcur, zlen, zmax;
	short pflen;
	short lzer;		/* Last zero */
	char *toret;
	char tmpbuf[128];	/* We shouldn't need more than ~5 anywhere */
	CIDR *nmtmp;
	char *nmstr;
	int nmflags;
	uint8_t moct;
	uint16_t v6sect;

	/* Just in case */
	if (block->proto == CIDR_NOPROTO) {
		errno = EINVAL;
		return (NULL);
	}

	/*
	 * Sanity: If we have both ONLYADDR and ONLYPFLEN, we really don't
	 * have anything to *DO*...
	 */
	if ((flags & CIDR_ONLYADDR) && (flags & CIDR_ONLYPFLEN)) {
		errno = EINVAL;
		return (NULL);
	}

	/*
	 * Now, in any case, there's a maximum length for any address, which
	 * is the completely expanded form of a v6-{mapped,compat} address
	 * with a netmask instead of a prefix.  That's 8 pieces of 4
	 * characters each (32), separated by :'s (+7=39), plus the slash
	 * (+1=40), plus another separated-8*4 (+39=79), plus the trailing
	 * null (+1=80).  We'll just allocate 128 for kicks.
	 *
	 * I'm not, at this time anyway, going to try and allocate only and
	 * exactly as much as we need for any given address.  Whether
	 * consumers of the library can count on this behavior...  well, I
	 * haven't decided yet.  Lemme alone.
	 */
	toret = gsh_calloc(1, 128);

	/*
	 * If it's a v4 address, we mask off everything but the last 4
	 * octets, and just proceed from there.
	 */
	if ((block->proto == CIDR_IPV4 && !(flags & CIDR_FORCEV6))
	    || (flags & CIDR_FORCEV4)) {
		/* First off, creating the in-addr.arpa form is special */
		if (flags & CIDR_REVERSE) {
			/*
			 * Build the d.c.b.a.in-addr.arpa form.  Note that we ignore
			 * flags like CIDR_VERBOSE and the like here, since they lead
			 * to non-valid reverse paths (or at least, paths that no DNS
			 * implementation will look for).  So it pretty much always
			 * looks exactly the same.  Also, we don't mess with dealing
			 * with netmaks or anything here; we just assume it's a
			 * host address, and treat it as such.
			 */

			(void) sprintf(toret, "%d.%d.%d.%d.in-addr.arpa",
				       block->addr[15], block->addr[14],
				       block->addr[13], block->addr[12]);
			return (toret);
		}

		/* Are we bothering to show the address? */
		if (!(flags & CIDR_ONLYPFLEN)) {
			/* If we're USEV6'ing, add whatever prefixes we need */
			if (flags & CIDR_USEV6) {
				if (flags & CIDR_NOCOMPACT) {
					if (flags & CIDR_VERBOSE)
						strcat(toret,
						       "0000:0000:0000:0000:0000:");
					else
						strcat(toret, "0:0:0:0:0:");
				} else
					strcat(toret, "::");

				if (flags & CIDR_USEV4COMPAT) {
					if (flags & CIDR_NOCOMPACT) {
						if (flags & CIDR_VERBOSE)
							strcat(toret, "0000:");
						else
							strcat(toret, "0:");
					}
				} else
					strcat(toret, "ffff:");
			}

			/* USEV6 */
			/* Now, slap on the v4 address */
			for (i = 12; i <= 15; i++) {
				(void) sprintf(tmpbuf, "%u", (block->addr)[i]);
				strcat(toret, tmpbuf);
				if (i < 15)
					strcat(toret, ".");
			}
		}

		/* ! ONLYPFLEN */
		/* Are we bothering to show the pf/mask? */
		if (!(flags & CIDR_ONLYADDR)) {
			/*
			 * And the prefix/netmask.  Don't show the '/' if we're only
			 * showing the pflen/mask.
			 */
			if (!(flags & CIDR_ONLYPFLEN))
				strcat(toret, "/");

			/* Which are we showing? */
			if (flags & CIDR_NETMASK) {
				/*
				 * In this case, we can just print out like the address
				 * above.
				 */
				for (i = 12; i <= 15; i++) {
					moct = (block->mask)[i];
					if (flags & CIDR_WILDCARD)
						moct = ~(moct);
					(void) sprintf(tmpbuf, "%u", moct);
					strcat(toret, tmpbuf);
					if (i < 15)
						strcat(toret, ".");
				}
			} else {
				/*
				 * For this, iterate over each octet,
				 * then each bit within the octet.
				 */
				pflen = cidr_get_pflen(block);
				if (pflen == -1) {
					gsh_free(toret);
					return (NULL);	/* Preserve errno */
				}
				/* Special handling for forced modes */
				if (block->proto == CIDR_IPV6
				    && (flags & CIDR_FORCEV4))
					pflen -= 96;

				(void) sprintf(tmpbuf, "%u",
					       (flags & CIDR_USEV6) ? pflen +
								96 : pflen);

				strcat(toret, tmpbuf);
			}
		}

		/* ! ONLYADDR */
		/* That's it for a v4 address, in any of our forms */
	} else if ((block->proto == CIDR_IPV6 && !(flags & CIDR_FORCEV4))
		   || (flags & CIDR_FORCEV6)) {
		/* First off, creating the .ip6.arpa form is special */
		if (flags & CIDR_REVERSE) {
			/*
			 * Build the ...ip6.arpa form.  See notes in the CIDR_REVERSE
			 * section of PROTO_IPV4 above for various notes.
			 */
			(void) sprintf(toret,
				"%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
				"%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x."
				"%x.%x.%x.%x.%x.ip6.arpa",
				block->addr[15] & 0x0f, block->addr[15] >> 4,
				block->addr[14] & 0x0f, block->addr[14] >> 4,
				block->addr[13] & 0x0f, block->addr[13] >> 4,
				block->addr[12] & 0x0f, block->addr[12] >> 4,
				block->addr[11] & 0x0f, block->addr[11] >> 4,
				block->addr[10] & 0x0f, block->addr[10] >> 4,
				block->addr[9] & 0x0f, block->addr[9] >> 4,
				block->addr[8] & 0x0f, block->addr[8] >> 4,
				block->addr[7] & 0x0f, block->addr[7] >> 4,
				block->addr[6] & 0x0f, block->addr[6] >> 4,
				block->addr[5] & 0x0f, block->addr[5] >> 4,
				block->addr[4] & 0x0f, block->addr[4] >> 4,
				block->addr[3] & 0x0f, block->addr[3] >> 4,
				block->addr[2] & 0x0f, block->addr[2] >> 4,
				block->addr[1] & 0x0f, block->addr[1] >> 4,
				block->addr[0] & 0x0f, block->addr[0] >> 4);
			return (toret);
		}
		/* Are we showing the address part? */
		if (!(flags & CIDR_ONLYPFLEN)) {
			/* It's a simple, boring, normal v6 address */

			/* First, find the longest string of 0's, if there is one */
			zst = zcur = -1;
			zlen = zmax = 0;
			for (i = 0; i <= 15; i += 2) {
				if (block->addr[i] == 0
				    && block->addr[i + 1] == 0) {
					/* This section is zero */
					if (zcur != -1) {
						/* We're already in a block of 0's */
						zlen++;
					} else {
						/* Starting a new block */
						zcur = i;
						zlen = 1;
					}
				} else {
					/* This section is non-zero */
					if (zcur != -1) {
						/*
						 * We were in 0's.  See if we set a new record,
						 * and if we did, note it and move on.
						 */
						if (zlen > zmax) {
							zst = zcur;
							zmax = zlen;
						}

						/* We're out of 0's, so reset start */
						zcur = -1;
					}
				}
			}

			/*
			 * If zcur is !=-1, we were in 0's when the loop ended.  Redo
			 * the "if we have a record, update" logic.
			 */
			if (zcur != -1 && zlen > zmax) {
				zst = zcur;
				zmax = zlen;
			}

			/*
			 * Now, what makes it HARD is the options we have.  To make
			 * some things simpler, we'll take two octets at a time for
			 * our run through.
			 */
			lzer = 0;
			for (i = 0; i <= 15; i += 2) {
				/*
				 * Start with a cheat; if this begins our already-found
				 * longest block of 0's, and we're not NOCOMPACT'ing,
				 * stick in a ::, increment past them, and keep on
				 * playing.
				 */
				if (i == zst && !(flags & CIDR_NOCOMPACT)) {
					strcat(toret, "::");
					i += (zmax * 2) - 2;
					lzer = 1;
					continue;
				}

				/*
				 * First, if we're not the first set, we may need a :
				 * before us.  If we're not compacting, we always want
				 * it.  If we ARE compacting, we want it unless the
				 * previous octet was a 0 that we're minimizing.
				 */
				if (i != 0
				    && ((flags & CIDR_NOCOMPACT) || lzer == 0))
					strcat(toret, ":");
				lzer = 0;	/* Reset */

				/*
				 * From here on, we no longer have to worry about
				 * CIDR_NOCOMPACT.
				 */

				/* Combine the pair of octets into one number */
				v6sect = 0;
				v6sect |= (block->addr)[i] << 8;
				v6sect |= (block->addr)[i + 1];

				/*
				 * If we're being VERBOSE, use leading 0's.  Otherwise,
				 * only use as many digits as we need.
				 */
				if (flags & CIDR_VERBOSE)
					(void) sprintf(tmpbuf, "%.4x", v6sect);
				else
					(void) sprintf(tmpbuf, "%x", v6sect);
				strcat(toret, tmpbuf);

				/* And loop back around to the next 2-octet set */
			}	/* for(each 16-bit set) */
		}

		/* ! ONLYPFLEN */
		/* Prefix/netmask */
		if (!(flags & CIDR_ONLYADDR)) {
			/* Only show the / if we're not showing just the prefix */
			if (!(flags & CIDR_ONLYPFLEN))
				strcat(toret, "/");

			if (flags & CIDR_NETMASK) {
				/*
				 * We already wrote how to build the whole v6 form, so
				 * just call ourselves recursively for this.
				 */
				nmtmp = cidr_alloc();

				nmtmp->proto = block->proto;
				for (i = 0; i <= 15; i++)
					if (flags & CIDR_WILDCARD)
						nmtmp->addr[i] =
						    ~(block->mask[i]);
					else
						nmtmp->addr[i] = block->mask[i];

				/*
				 * Strip flags:
				 * - CIDR_NETMASK would make us recurse forever.
				 * - CIDR_ONLYPFLEN would not show the address bit, which
				 *   is the part we want here.
				 * Add flag CIDR_ONLYADDR because that's the bit we care
				 * about.
				 */
				nmflags = flags;
				nmflags &= ~(CIDR_NETMASK) & ~(CIDR_ONLYPFLEN);
				nmflags |= CIDR_ONLYADDR;
				nmstr = cidr_to_str(nmtmp, nmflags);
				cidr_free(nmtmp);
				if (nmstr == NULL) {
					gsh_free(toret);
					return (NULL);	/* Preserve errno */
				}

				/* No need to strip the prefix, it doesn't have it */

				/* Just add it on */
				strcat(toret, nmstr);
				gsh_free(nmstr);
			} else {
				/* Just figure the and show prefix length */
				pflen = cidr_get_pflen(block);
				if (pflen == -1) {
					gsh_free(toret);
					return (NULL);	/* Preserve errno */
				}
				/* Special handling for forced modes */
				if (block->proto == CIDR_IPV4
				    && (flags & CIDR_FORCEV6))
					pflen += 96;

				(void) sprintf(tmpbuf, "%u", pflen);
				strcat(toret, tmpbuf);
			}
		}		/* ! ONLYADDR */
	} else {
		/* Well, *I* dunno what the fuck it is */
		gsh_free(toret);
		errno = ENOENT;	/* Bad choice of errno */
		return (NULL);
	}

	/* Give back the string */
	return (toret);
}
