/*
 * conversion from text forms of addresses to internal ones
 * Copyright (C) 2000  Henry Spencer.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/lgpl.txt>.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
 * License for more details.
 *
 */
#include "internal.h"
#include "libreswan.h"

#if defined(__CYGWIN32__)
#define gethostbyname2(X, Y) gethostbyname(X)
#endif

/*
 * Legal ASCII characters in a domain name.  Underscore technically is not,
 * but is a common misunderstanding.  Non-ASCII characters are simply
 * exempted from checking at the moment, to allow for UTF-8 encoded stuff;
 * the purpose of this check is merely to catch blatant errors.
 */
static const char namechars[] = "abcdefghijklmnopqrstuvwxyz0123456789"
				"ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.";
#define ISASCII(c)      (((c) & 0x80) == 0)

static err_t tryname(const char *, size_t, int, int, ip_address *);
static err_t tryhex(const char *, size_t, int, ip_address *);
static err_t trydotted(const char *, size_t, ip_address *);
static err_t getbyte(const char **, const char *, int *);
static err_t colon(const char *, size_t, ip_address *);
static err_t getpiece(const char **, const char *, unsigned *);

/*
   - ttoaddr - convert text name or dotted-decimal address to binary address
 */
static err_t                           /* NULL for success, else string literal */
ttoaddr_base(const char *src,
	     size_t srclen,     /* 0 means "apply strlen" */
	     int af,            /* address family */
	     int   *allnumericfailed,
	     ip_address *dst)
{
	err_t oops;

#       define  HEXLEN  10      /* strlen("0x11223344") */

	switch (af) {
	case AF_INET:
	case AF_INET6:
	case 0:                  /* guess */
		break;

	default:
		return "invalid address family";
	}

	if (af == AF_INET && srclen == HEXLEN && *src == '0') {
		if (*(src + 1) == 'x' || *(src + 1) == 'X')
			return tryhex(src + 2, srclen - 2, 'x', dst);

		if (*(src + 1) == 'h' || *(src + 1) == 'H')
			return tryhex(src + 2, srclen - 2, 'h', dst);
	}

	if (memchr(src, ':', srclen) != NULL) {
		if (af == 0)
			af = AF_INET6;

		if (af != AF_INET6)
			return "non-ipv6 address may not contain `:'";

		return colon(src, srclen, dst);
	}

	if (af == 0 || af == AF_INET) {
		oops = trydotted(src, srclen, dst);
		if (oops == NULL)
			return NULL;            /* it worked */

		if (*oops != '?')
			return oops;            /* probably meant as d-d */
	}

	*allnumericfailed = 1;
	return "not numeric";
}

/*
   - tnatoaddr - convert text numeric address (only) to binary address
 */
err_t                           /* NULL for success, else string literal */
tnatoaddr(src, srclen, af, dst)
const char *src;
size_t srclen;                  /* 0 means "apply strlen" */
int af;                         /* address family */
ip_address *dst;
{
	err_t oops;

	if (srclen == 0) {
		srclen = strlen(src);
		if (srclen == 0)
			return "empty string";
	}

	switch (af) {
	case 0:  /* guess */
		oops = colon(src, srclen, dst);
		if (oops == NULL)
			return NULL;

		oops = trydotted(src, srclen, dst);
		if (oops == NULL)
			return NULL;

		return
			"does not appear to be either IPv4 or IPv6 numeric address";

		break;

	case AF_INET6:
		return colon(src, srclen, dst);

		break;
	case AF_INET:
		oops = trydotted(src, srclen, dst);
		if (oops == NULL)
			return NULL;            /* it worked */

		if (*oops != '?')
			return oops;            /* probably meant as d-d */

		return "does not appear to be numeric address";

		break;
	default:
		return "unknown address family in tnatoaddr";

		break;
	}
}

/*
   - tryname - try it as a name
 * Slightly complicated by lack of reliable NUL termination in source.
 */
static err_t tryname(src, srclen, nultermd, af, dst)
const char *src;
size_t srclen;
int nultermd;                   /* is it known to be NUL-terminated? */
int af;
ip_address *dst;
{
	struct hostent *h;
	struct netent *ne = NULL;
	char namebuf[100];      /* enough for most DNS names */
	const char *cp;
	char *p = namebuf;
	size_t n;

	for (cp = src, n = srclen; n > 0; cp++, n--)
		if (ISASCII(*cp) && strchr(namechars, *cp) == NULL)
			return "illegal (non-DNS-name) character in name";

	if (nultermd) {
		cp = src;
	} else {
		if (srclen + 1 > sizeof(namebuf)) {
			p = (char *) MALLOC(srclen + 1);
			if (p == NULL)
				return "unable to get temporary space for name";


		}
		p[0] = '\0';    /* strncpy semantics are wrong */
		strncat(p, src, srclen);
		cp = (const char *)p;
	}

	h = gethostbyname2(cp, af);
#if !defined(__CYGWIN32__)
	/* like, windows even has an /etc/networks? */
	if (h == NULL && af == AF_INET)
		ne = getnetbyname(cp);
#endif
	if (p != namebuf)
		FREE(p);
	if (h == NULL && ne == NULL)
		return
			"does not look numeric and name lookup failed (no validation performed)";



	if (h != NULL) {
		if (h->h_addrtype != af)
			return "address-type mismatch from gethostbyname2!!!";

		return initaddr((unsigned char *)h->h_addr, h->h_length, af,
				dst);
	} else {
		if (ne->n_addrtype != af)
			return "address-type mismatch from getnetbyname!!!";

		ne->n_net = htonl(ne->n_net);
		return initaddr((unsigned char *)&ne->n_net, sizeof(ne->n_net),
				af, dst);
	}
}

/*
   - tryhex - try conversion as an eight-digit hex number (AF_INET only)
 */
static err_t tryhex(src, srclen, flavor, dst)
const char *src;
size_t srclen;                  /* should be 8 */
int flavor;                     /* 'x' for network order, 'h' for host order */
ip_address *dst;
{
	err_t oops;
	unsigned long ul;
	union {
		uint32_t addr;
		unsigned char buf[4];
	} u;

	if (srclen != 8)
		return "internal error, tryhex called with bad length";

	oops = ttoul(src, srclen, 16, &ul);
	if (oops != NULL)
		return oops;

	u.addr = (flavor == 'h') ? ul : htonl(ul);
	return initaddr(u.buf, sizeof(u.buf), AF_INET, dst);
}

/*
   - trydotted - try conversion as dotted decimal (AF_INET only)
 *
 * If the first char of a complaint is '?', that means "didn't look like
 * dotted decimal at all".
 */
static err_t trydotted(src, srclen, dst)
const char *src;
size_t srclen;
ip_address *dst;
{
	const char *stop = src + srclen;        /* just past end */
	int byte;
	err_t oops;
#       define  NBYTES  4
	unsigned char buf[NBYTES];
	int i;

	memset(buf, 0, sizeof(buf));
	for (i = 0; i < NBYTES && src < stop; i++) {
		oops = getbyte(&src, stop, &byte);
		if (oops != NULL) {
			if (*oops != '?')
				return oops;    /* bad number */

			if (i > 1)
				return oops + 1;        /* failed number */

			return oops;                    /* with leading '?' */
		}
		buf[i] = byte;
		if (i < 3 && src < stop && *src++ != '.') {
			if (i == 0)
				return
					"?syntax error in dotted-decimal address";


			else
				return "syntax error in dotted-decimal address";


		}
	}
	if (src != stop)
		return "extra garbage on end of dotted-decimal address";

	return initaddr(buf, sizeof(buf), AF_INET, dst);
}

/*
   - getbyte - try to scan a byte in dotted decimal
 * A subtlety here is that all this arithmetic on ASCII digits really is
 * highly portable -- ANSI C guarantees that digits 0-9 are contiguous.
 * It's easier to just do it ourselves than set up for a call to ttoul().
 *
 * If the first char of a complaint is '?', that means "didn't look like a
 * number at all".
 */
err_t getbyte(srcp, stop, retp)
const char **srcp;              /* *srcp is updated */
const char *stop;               /* first untouchable char */
int *retp;                      /* return-value pointer */
{
	char c;
	const char *p;
	int no;

	if (*srcp >= stop)
		return "?empty number in dotted-decimal address";

	no = 0;
	p = *srcp;
	while (p < stop && no <= 255 && (c = *p) >= '0' && c <= '9') {
		no = no * 10 + (c - '0');
		p++;
	}
	if (p == *srcp)
		return "?non-numeric component in dotted-decimal address";

	*srcp = p;
	if (no > 255)
		return "byte overflow in dotted-decimal address";

	*retp = no;
	return NULL;
}

/*
   - colon - convert IPv6 "numeric" address
 */
static err_t colon(src, srclen, dst)
const char *src;
size_t srclen;                  /* known to be >0 */
ip_address *dst;
{
	const char *stop = src + srclen;        /* just past end */
	unsigned piece;
	int gapat;                              /* where was empty piece seen */
	err_t oops;
#       define  NPIECES 8
	unsigned char buf[NPIECES * 2];   /* short may have wrong byte order */
	int i;
	int j;
#       define  IT      "IPv6 numeric address"
	int naftergap;

	/* leading or trailing :: becomes single empty field */
	if (*src == ':') {              /* legal only if leading :: */
		if (srclen == 1 || *(src + 1) != ':')
			return "illegal leading `:' in " IT;

		if (srclen == 2) {
			unspecaddr(AF_INET6, dst);
			return NULL;
		}
		src++;          /* past first but not second */
		srclen--;
	}
	if (*(stop - 1) == ':') {         /* legal only if trailing :: */
		if (srclen == 1 || *(stop - 2) != ':')
			return "illegal trailing `:' in " IT;

		srclen--;               /* leave one */
	}

	gapat = -1;
	piece = 0;
	for (i = 0; i < NPIECES && src < stop; i++) {
		oops = getpiece(&src, stop, &piece);
		if (oops != NULL && *oops == ':') {     /* empty field */
			if (gapat >= 0)
				return "more than one :: in " IT;

			gapat = i;
		} else if (oops != NULL) {
			return oops;
		}
		buf[2 * i] = piece >> 8;
		buf[2 * i + 1] = piece & 0xff;
		if (i < NPIECES - 1) {    /* there should be more input */
			if (src == stop && gapat < 0)
				return IT " ends prematurely";

			if (src != stop && *src++ != ':')
				return "syntax error in " IT;
		}
	}
	if (src != stop)
		return "extra garbage on end of " IT;

	if (gapat < 0 && i < NPIECES)   /* should have been caught earlier */
		return "incomplete " IT " (internal error)";

	if (gapat >= 0 && i == NPIECES)
		return "non-abbreviating empty field in " IT;

	if (gapat >= 0) {
		naftergap = i - (gapat + 1);
		for (i--, j = NPIECES - 1; naftergap > 0;
		     i--, j--, naftergap--) {
			buf[2 * j] = buf[2 * i];
			buf[2 * j + 1] = buf[2 * i + 1];
		}
		for (; j >= gapat; j--)
			buf[2 * j] = buf[2 * j + 1] = 0;
	}

	return initaddr(buf, sizeof(buf), AF_INET6, dst);
}

/*
   - getpiece - try to scan one 16-bit piece of an IPv6 address
 */
err_t                           /* ":" means "empty field seen" */
getpiece(srcp, stop, retp)
const char **srcp;              /* *srcp is updated */
const char *stop;               /* first untouchable char */
unsigned *retp;                 /* return-value pointer */
{
	const char *p;
#       define  NDIG    4
	int d;
	unsigned long ret;
	err_t oops;

	if (*srcp >= stop || **srcp == ':') {   /* empty field */
		*retp = 0;
		return ":";
	}

	p = *srcp;
	d = 0;
	while (p < stop && d < NDIG && isxdigit(*p)) {
		p++;
		d++;
	}
	if (d == 0)
		return "non-hex field in IPv6 numeric address";

	if (p < stop && d == NDIG && isxdigit(*p))
		return "field in IPv6 numeric address longer than 4 hex digits";



	oops = ttoul(*srcp, d, 16, &ret);
	if (oops != NULL)       /* shouldn't happen, really... */
		return oops;

	*srcp = p;
	*retp = ret;
	return NULL;
}

err_t                   /* NULL for success, else string literal */
ttoaddr(const char *src,
	size_t srclen,  /* 0 means "apply strlen" */
	int af,         /* address family */
	ip_address *dst)
{
	int nultermd;
	int numfailed = 0;
	err_t err;

	if (srclen == 0) {
		srclen = strlen(src);
		if (srclen == 0)
			return "empty string";

		nultermd = 1;
	} else {
		nultermd = 0;   /* at least, not *known* to be terminated */

	}
	err = ttoaddr_base(src, srclen, af, &numfailed, dst);

	if (err && numfailed) {
		if (af == 0) {
			err = tryname(src, srclen, nultermd, AF_INET6, dst);
			if (err)
				err = tryname(src, srclen, nultermd, AF_INET,
					      dst);
		} else {
			err = tryname(src, srclen, nultermd, af, dst);
		}
		return err;
	}

	return err;
}

err_t                           /* NULL for success, else string literal */
ttoaddr_num(const char *src,
	    size_t srclen,      /* 0 means "apply strlen" */
	    int af,             /* address family */
	    ip_address *dst)
{
	int numfailed = 0;

	if (srclen == 0) {
		srclen = strlen(src);
		if (srclen == 0)
			return "empty string";
	}

	return ttoaddr_base(src, srclen, af, &numfailed, dst);
}

#ifdef TTOADDR_MAIN

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void regress(void);

int main(int argc, char *argv[])
{
	if (argc < 2) {
		fprintf(stderr, "Usage: %s {addr|net/mask|begin...end|-r}\n",
			argv[0]);
		exit(2);
	}

	if (strcmp(argv[1], "-r") == 0) {
		regress();
		fprintf(stderr, "regress() returned?!?\n");
		exit(1);
	}
	exit(0);
}

struct rtab {
	char *input;
	char numonly;
	char format;
	char expectfailure;
	char *output;                   /* NULL means error expected */
} rtab[] = {
	{ "1.2.3.0",     0,      0,   0,       "1.2.3.0" },
	{ "1:2::3:4",    0,      0,   0,       "1:2::3:4" },
	{ "1:2::3:4",    0,      'Q', 0,       "1:2:0:0:0:0:3:4" },
	{ "1:2:0:0:3:4:0:0", 0,    0, 0,       "1:2::3:4:0:0" },
	{ "www.libreswan.org", 0,   0, 0,       "193.110.157.129" },
	{ "www.libreswan.org", 1,   0, 'F',     "1.2.3.4" },
	{ "1.2.3.4",         0,   'r', 0,       "4.3.2.1.IN-ADDR.ARPA." },
	/*                                   0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f */
	{ "1:2::3:4",        0,   'r', 0,
	  "4.0.0.0.3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.1.0.0.0.IP6.ARPA." },
	{ NULL,              0,  0, 0, NULL }
};

void regress()
{
	struct rtab *r;
	int status = 0;
	ip_address a;
	char in[100];
	char buf[100];
	const char *oops;
	size_t n;
	int count = 0;

	for (r = rtab; r->input != NULL; r++) {
		++count;
		memset(&a, 0, sizeof(a));
		strcpy(in, r->input);

		if (r->numonly) {
			/* convert it *to* internal format (no DNS) */
			oops = ttoaddr_num(in, strlen(in), 0, &a);
		} else {
			/* convert it *to* internal format */
			oops = ttoaddr(in, strlen(in), 0, &a);
		}

		if (r->expectfailure && oops == NULL) {
			printf("%u: '%s' expected failure, but it succeeded\n",
			       count, r->input);
			status++;
			continue;
		}

		if (oops) {
			if (r->expectfailure)
				continue;
			printf("%u: '%s' failed to parse: %s\n",
			       count, r->input, oops);
			status++;
			continue;
		}

		/* now convert it back */

		n = addrtot(&a, r->format, buf, sizeof(buf));

		if (n == 0 && r->output == NULL) {
		}                       /* okay, error expected */
		else if (n == 0) {
			printf("`%s' atoasr failed\n", r->input);
			status++;

		} else if (r->output == NULL) {
			printf("`%s' atoasr succeeded unexpectedly '%c'\n",
			       r->input, r->format);
			status++;
		} else {
			if (strcasecmp(r->output, buf) != 0) {
				printf("`%s' '%u' gave `%s', expected `%s'\n",
				       r->input, r->format, buf, r->output);
				status++;
			}
		}
	}
	exit(status);
}

#endif /* ADDRTOT_MAIN */

