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