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