472 lines
11 KiB
C
472 lines
11 KiB
C
/*
|
|
* 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 <sys/socket.h>
|
|
|
|
#include "internal.h"
|
|
#include "freeswan.h"
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
err_t /* NULL for success, else string literal */
|
|
ttoaddr(src, srclen, af, dst)
|
|
const char *src;
|
|
size_t srclen; /* 0 means "apply strlen" */
|
|
int af; /* address family */
|
|
ip_address *dst;
|
|
{
|
|
err_t oops;
|
|
# define HEXLEN 10 /* strlen("0x11223344") */
|
|
int nultermd;
|
|
|
|
if (srclen == 0) {
|
|
srclen = strlen(src);
|
|
if (srclen == 0)
|
|
return "empty string";
|
|
nultermd = 1;
|
|
} else
|
|
nultermd = 0; /* at least, not *known* to be terminated */
|
|
|
|
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 */
|
|
}
|
|
|
|
return tryname(src, srclen, nultermd, af, dst);
|
|
}
|
|
|
|
/*
|
|
- 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 addrinfo hints, *res;
|
|
struct netent *ne = NULL;
|
|
char namebuf[100]; /* enough for most DNS names */
|
|
const char *cp;
|
|
char *p = namebuf;
|
|
unsigned char *addr = NULL;
|
|
size_t n;
|
|
int error;
|
|
err_t err = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = af;
|
|
error = getaddrinfo(cp, NULL, &hints, &res);
|
|
if (error != 0)
|
|
{ /* getaddrinfo failed, try getnetbyname */
|
|
if (af == AF_INET)
|
|
{
|
|
ne = getnetbyname(cp);
|
|
if (ne != NULL)
|
|
{
|
|
ne->n_net = htonl(ne->n_net);
|
|
addr = (unsigned char*)&ne->n_net;
|
|
err = initaddr(addr, sizeof(ne->n_net), af, dst);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
struct addrinfo *r = res;
|
|
while (r)
|
|
{
|
|
size_t addr_len;
|
|
switch (r->ai_family)
|
|
{
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *in = (struct sockaddr_in*)r->ai_addr;
|
|
addr_len = 4;
|
|
addr = (unsigned char*)&in->sin_addr.s_addr;
|
|
break;
|
|
}
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *in6 = (struct sockaddr_in6*)r->ai_addr;
|
|
addr_len = 16;
|
|
addr = (unsigned char*)&in6->sin6_addr.s6_addr;
|
|
break;
|
|
}
|
|
default:
|
|
{ /* unknown family, try next result */
|
|
r = r->ai_next;
|
|
continue;
|
|
}
|
|
}
|
|
err = initaddr(addr, addr_len, r->ai_family, dst);
|
|
break;
|
|
}
|
|
freeaddrinfo(res);
|
|
}
|
|
|
|
if (p != namebuf)
|
|
{
|
|
FREE(p);
|
|
}
|
|
|
|
if (addr == NULL)
|
|
{
|
|
return "does not look numeric and name lookup failed";
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
- 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 = 0;
|
|
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;
|
|
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;
|
|
}
|