xref: /freebsd/contrib/ntp/libntp/decodenetnum.c (revision 767173cec2b2041e1f847bc8896092f9c1481242)
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  */
23 static unsigned long
_num_or_dflt(char * sval,unsigned long maxval,unsigned long defval)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  */
47 static inline char*
_chop(char * sp)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  */
59 static inline char*
_skip(char * sp,int ch)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  */
74 int
decodenetnum(const char * num,sockaddr_u * net)75 decodenetnum(
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