diff options
Diffstat (limited to 'libntp/decodenetnum.c')
-rw-r--r-- | libntp/decodenetnum.c | 200 |
1 files changed, 123 insertions, 77 deletions
diff --git a/libntp/decodenetnum.c b/libntp/decodenetnum.c index 35e839aafb09..8ff67625202c 100644 --- a/libntp/decodenetnum.c +++ b/libntp/decodenetnum.c @@ -13,106 +13,152 @@ #include "ntp.h" #include "ntp_stdlib.h" -#include "ntp_assert.h" -#define PORTSTR(x) _PORTSTR(x) -#define _PORTSTR(x) #x -static int -isnumstr( - const char *s +/* If the given string position points to a decimal digit, parse the + * number. If this is not possible, or the parsing did not consume the + * whole string, or if the result exceeds the maximum value, return the + * default value. + */ +static unsigned long +_num_or_dflt( + char * sval, + unsigned long maxval, + unsigned long defval ) { - while (*s >= '0' && *s <= '9') - ++s; - return !*s; + char * ep; + unsigned long num; + + if (!(sval && isdigit(*(unsigned char*)sval))) + return defval; + + num = strtoul(sval, &ep, 10); + if (!*ep && num <= maxval) + return num; + + return defval; +} + +/* If the given string position is not NULL and does not point to the + * terminator, replace the character with NUL and advance the pointer. + * Return the resulting position. + */ +static inline char* +_chop( + char * sp) +{ + if (sp && *sp) + *sp++ = '\0'; + return sp; +} + +/* If the given string position points to the given char, advance the + * pointer and return the result. Otherwise, return NULL. + */ +static inline char* +_skip( + char * sp, + int ch) +{ + if (sp && *(unsigned char*)sp == ch) + return (sp + 1); + return NULL; } /* * decodenetnum convert text IP address and port to sockaddr_u * - * Returns 0 for failure, 1 for success. + * Returns FALSE (->0) for failure, TRUE (->1) for success. */ int decodenetnum( const char *num, - sockaddr_u *netnum + sockaddr_u *net ) { - static const char * const servicename = "ntp"; - static const char * const serviceport = PORTSTR(NTP_PORT); + /* Building a parser is more fun in Haskell, but here we go... + * + * This works through 'inet_pton()' taking the brunt of the + * work, after some string manipulations to split off URI + * brackets, ports and scope identifiers. The heuristics are + * simple but must hold for all _VALID_ addresses. inet_pton() + * will croak on bad ones later, but replicating the whole + * parser logic to detect errors is wasteful. + */ - struct addrinfo hints, *ai = NULL; - int err; - const char *host_str; - const char *port_str; - char *pp; - char *np; - char nbuf[80]; - - REQUIRE(num != NULL); - - if (strlen(num) >= sizeof(nbuf)) { - printf("length error\n"); + sockaddr_u netnum; + char buf[64]; /* working copy of input */ + char *haddr=buf; + unsigned int port=NTP_PORT, scope=0; + unsigned short afam=AF_UNSPEC; + + /* copy input to working buffer with length check */ + if (strlcpy(buf, num, sizeof(buf)) >= sizeof(buf)) return FALSE; - } - port_str = servicename; - if ('[' != num[0]) { - /* - * to distinguish IPv6 embedded colons from a port - * specification on an IPv4 address, assume all - * legal IPv6 addresses have at least two colons. - */ - pp = strchr(num, ':'); - if (NULL == pp) - host_str = num; /* no colons */ - else if (NULL != strchr(pp + 1, ':')) - host_str = num; /* two or more colons */ - else { /* one colon */ - strlcpy(nbuf, num, sizeof(nbuf)); - host_str = nbuf; - pp = strchr(nbuf, ':'); - *pp = '\0'; - port_str = pp + 1; + /* Identify address family and possibly the port, if given. If + * this results in AF_UNSPEC, we will fail in the next step. + */ + if (*haddr == '[') { + char * endp = strchr(++haddr, ']'); + if (endp) { + port = _num_or_dflt(_skip(_chop(endp), ':'), + 0xFFFFu, port); + afam = strchr(haddr, ':') ? AF_INET6 : AF_INET; } } else { - host_str = np = nbuf; - while (*++num && ']' != *num) - *np++ = *num; - *np = 0; - if (']' == num[0] && ':' == num[1] && '\0' != num[2]) - port_str = &num[2]; + char *col = strchr(haddr, ':'); + char *dot = strchr(haddr, '.'); + if (col == dot) { + /* no dot, no colon: bad! */ + afam = AF_UNSPEC; + } else if (!col) { + /* no colon, only dot: IPv4! */ + afam = AF_INET; + } else if (!dot || col < dot) { + /* no dot or 1st colon before 1st dot: IPv6! */ + afam = AF_INET6; + } else { + /* 1st dot before 1st colon: must be IPv4 with port */ + afam = AF_INET; + port = _num_or_dflt(_chop(col), 0xFFFFu, port); + } } - if ( ! *host_str) + + /* Since we don't know about additional members in the address + * structures, we wipe the result buffer thoroughly: + */ + memset(&netnum, 0, sizeof(netnum)); + + /* For AF_INET6, evaluate and remove any scope suffix. Have + * inet_pton() do the real work for AF_INET and AF_INET6, bail + * out otherwise: + */ + switch (afam) { + case AF_INET: + if (inet_pton(afam, haddr, &netnum.sa4.sin_addr) <= 0) + return FALSE; + netnum.sa4.sin_port = htons((unsigned short)port); + break; + + case AF_INET6: + scope = _num_or_dflt(_chop(strchr(haddr, '%')), 0xFFFFFFFFu, scope); + if (inet_pton(afam, haddr, &netnum.sa6.sin6_addr) <= 0) + return FALSE; + netnum.sa6.sin6_port = htons((unsigned short)port); + netnum.sa6.sin6_scope_id = scope; + break; + + case AF_UNSPEC: + default: return FALSE; - if ( ! *port_str) - port_str = servicename; - - ZERO(hints); - hints.ai_flags |= Z_AI_NUMERICHOST; - if (isnumstr(port_str)) - hints.ai_flags |= Z_AI_NUMERICSERV; - err = getaddrinfo(host_str, port_str, &hints, &ai); - /* retry with default service name if the service lookup failed */ - if (err == EAI_SERVICE && strcmp(port_str, servicename)) { - hints.ai_flags &= ~Z_AI_NUMERICSERV; - port_str = servicename; - err = getaddrinfo(host_str, port_str, &hints, &ai); } - /* retry another time with default service port if the service lookup failed */ - if (err == EAI_SERVICE && strcmp(port_str, serviceport)) { - hints.ai_flags |= Z_AI_NUMERICSERV; - port_str = serviceport; - err = getaddrinfo(host_str, port_str, &hints, &ai); - } - if (err != 0) - return FALSE; - - INSIST(ai->ai_addrlen <= sizeof(*netnum)); - ZERO(*netnum); - memcpy(netnum, ai->ai_addr, ai->ai_addrlen); - freeaddrinfo(ai); + /* Collect the remaining pieces and feed the output, which was + * not touched so far: + */ + netnum.sa.sa_family = afam; + memcpy(net, &netnum, sizeof(netnum)); return TRUE; } |