decodenetnum.c revision 362716
1/*
2 * decodenetnum - return a net number (this is crude, but careful)
3 */
4#include <config.h>
5#include <sys/types.h>
6#include <ctype.h>
7#ifdef HAVE_SYS_SOCKET_H
8#include <sys/socket.h>
9#endif
10#ifdef HAVE_NETINET_IN_H
11#include <netinet/in.h>
12#endif
13
14#include "ntp.h"
15#include "ntp_stdlib.h"
16
17
18/* If the given string position points to a decimal digit, parse the
19 * number. If this is not possible, or the parsing did not consume the
20 * whole string, or if the result exceeds the maximum value, return the
21 * default value.
22 */
23static unsigned long
24_num_or_dflt(
25	char *		sval,
26	unsigned long	maxval,
27	unsigned long	defval
28	)
29{
30	char *		ep;
31	unsigned long	num;
32
33	if (!(sval && isdigit(*(unsigned char*)sval)))
34		return defval;
35
36	num = strtoul(sval, &ep, 10);
37	if (!*ep && num <= maxval)
38		return num;
39
40	return defval;
41}
42
43/* If the given string position is not NULL and does not point to the
44 * terminator, replace the character with NUL and advance the pointer.
45 * Return the resulting position.
46 */
47static inline char*
48_chop(
49	char * sp)
50{
51	if (sp && *sp)
52		*sp++ = '\0';
53	return sp;
54}
55
56/* If the given string position points to the given char, advance the
57 * pointer and return the result. Otherwise, return NULL.
58 */
59static inline char*
60_skip(
61	char * sp,
62	int    ch)
63{
64	if (sp && *(unsigned char*)sp == ch)
65		return (sp + 1);
66	return NULL;
67}
68
69/*
70 * decodenetnum		convert text IP address and port to sockaddr_u
71 *
72 * Returns FALSE (->0) for failure, TRUE (->1) for success.
73 */
74int
75decodenetnum(
76	const char *num,
77	sockaddr_u *net
78	)
79{
80	/* Building a parser is more fun in Haskell, but here we go...
81	 *
82	 * This works through 'inet_pton()' taking the brunt of the
83	 * work, after some string manipulations to split off URI
84	 * brackets, ports and scope identifiers. The heuristics are
85	 * simple but must hold for all _VALID_ addresses. inet_pton()
86	 * will croak on bad ones later, but replicating the whole
87	 * parser logic to detect errors is wasteful.
88	 */
89
90	sockaddr_u	netnum;
91	char		buf[64];	/* working copy of input */
92	char		*haddr=buf;
93	unsigned int	port=NTP_PORT, scope=0;
94	unsigned short	afam=AF_UNSPEC;
95
96	/* copy input to working buffer with length check */
97	if (strlcpy(buf, num, sizeof(buf)) >= sizeof(buf))
98		return FALSE;
99
100	/* Identify address family and possibly the port, if given.  If
101	 * this results in AF_UNSPEC, we will fail in the next step.
102	 */
103	if (*haddr == '[') {
104		char * endp = strchr(++haddr, ']');
105		if (endp) {
106			port = _num_or_dflt(_skip(_chop(endp), ':'),
107					      0xFFFFu, port);
108			afam = strchr(haddr, ':') ? AF_INET6 : AF_INET;
109		}
110	} else {
111		char *col = strchr(haddr, ':');
112		char *dot = strchr(haddr, '.');
113		if (col == dot) {
114			/* no dot, no colon: bad! */
115			afam = AF_UNSPEC;
116		} else if (!col) {
117			/* no colon, only dot: IPv4! */
118			afam = AF_INET;
119		} else if (!dot || col < dot) {
120			/* no dot or 1st colon before 1st dot: IPv6! */
121			afam = AF_INET6;
122		} else {
123			/* 1st dot before 1st colon: must be IPv4 with port */
124			afam = AF_INET;
125			port = _num_or_dflt(_chop(col), 0xFFFFu, port);
126		}
127	}
128
129	/* Since we don't know about additional members in the address
130	 * structures, we wipe the result buffer thoroughly:
131	 */
132	memset(&netnum, 0, sizeof(netnum));
133
134	/* For AF_INET6, evaluate and remove any scope suffix. Have
135	 * inet_pton() do the real work for AF_INET and AF_INET6, bail
136	 * out otherwise:
137	 */
138	switch (afam) {
139	case AF_INET:
140		if (inet_pton(afam, haddr, &netnum.sa4.sin_addr) <= 0)
141			return FALSE;
142		netnum.sa4.sin_port = htons((unsigned short)port);
143		break;
144
145	case AF_INET6:
146		scope = _num_or_dflt(_chop(strchr(haddr, '%')), 0xFFFFFFFFu, scope);
147		if (inet_pton(afam, haddr, &netnum.sa6.sin6_addr) <= 0)
148			return FALSE;
149		netnum.sa6.sin6_port = htons((unsigned short)port);
150		netnum.sa6.sin6_scope_id = scope;
151		break;
152
153	case AF_UNSPEC:
154	default:
155		return FALSE;
156	}
157
158	/* Collect the remaining pieces and feed the output, which was
159	 * not touched so far:
160	 */
161	netnum.sa.sa_family = afam;
162	memcpy(net, &netnum, sizeof(netnum));
163	return TRUE;
164}
165