aboutsummaryrefslogtreecommitdiffstats
path: root/libntp/decodenetnum.c
diff options
context:
space:
mode:
Diffstat (limited to 'libntp/decodenetnum.c')
-rw-r--r--libntp/decodenetnum.c200
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;
}