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