xref: /freebsd/lib/libc/inet/inet_net_pton.c (revision 8f4a0d2f7b96099001dbc51e06114df1a0e6d291)
1 /*	$OpenBSD: inet_net_pton.c,v 1.14 2022/12/27 17:10:06 jmc Exp $	*/
2 
3 /*
4  * Copyright (c) 2012 by Gilles Chehade <gilles@openbsd.org>
5  * Copyright (c) 1996,1999 by Internet Software Consortium.
6  *
7  * SPDX-License-Identifier: ISC
8  *
9  * Permission to use, copy, modify, and distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
14  * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
16  * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
17  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
18  * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
19  * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
20  * SOFTWARE.
21  */
22 
23 #include "port_before.h"
24 
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <stdlib.h>
36 
37 #include "port_after.h"
38 
39 static int	inet_net_pton_ipv4(const char *, u_char *, size_t);
40 static int	inet_net_pton_ipv6(const char *, u_char *, size_t);
41 
42 /*
43  * static int
44  * inet_net_pton(af, src, dst, size)
45  *	convert network number from presentation to network format.
46  *	accepts hex octets, hex strings, decimal octets, and /CIDR.
47  *	"size" is in bytes and describes "dst".
48  * return:
49  *	number of bits, either imputed classfully or specified with /CIDR,
50  *	or -1 if some failure occurred (check errno).  ENOENT means it was
51  *	not a valid network specification.
52  * author:
53  *	Paul Vixie (ISC), June 1996
54  */
55 int
inet_net_pton(int af,const char * src,void * dst,size_t size)56 inet_net_pton(int af, const char *src, void *dst, size_t size)
57 {
58 	switch (af) {
59 	case AF_INET:
60 		return (inet_net_pton_ipv4(src, dst, size));
61 	case AF_INET6:
62 		return (inet_net_pton_ipv6(src, dst, size));
63 	default:
64 		errno = EAFNOSUPPORT;
65 		return (-1);
66 	}
67 }
68 
69 /*
70  * static int
71  * inet_net_pton_ipv4(src, dst, size)
72  *	convert IPv4 network number from presentation to network format.
73  *	accepts hex octets, hex strings, decimal octets, and /CIDR.
74  *	"size" is in bytes and describes "dst".
75  * return:
76  *	number of bits, either imputed classfully or specified with /CIDR,
77  *	or -1 if some failure occurred (check errno).  ENOENT means it was
78  *	not an IPv4 network specification.
79  * note:
80  *	network byte order assumed.  this means 192.5.5.240/28 has
81  *	0b11110000 in its fourth octet.
82  * author:
83  *	Paul Vixie (ISC), June 1996
84  */
85 static int
inet_net_pton_ipv4(const char * src,u_char * dst,size_t size)86 inet_net_pton_ipv4(const char *src, u_char *dst, size_t size)
87 {
88 	static const char
89 		xdigits[] = "0123456789abcdef",
90 		digits[] = "0123456789";
91 	int n, ch, tmp, dirty, bits;
92 	const u_char *odst = dst;
93 
94 	ch = (unsigned char)*src++;
95 	if (ch == '0' && (src[0] == 'x' || src[0] == 'X')
96 	    && isascii((unsigned char)src[1]) && isxdigit((unsigned char)src[1])) {
97 		/* Hexadecimal: Eat nybble string. */
98 		if (size == 0)
99 			goto emsgsize;
100 		tmp = 0, dirty = 0;
101 		src++;	/* skip x or X. */
102 		while ((ch = (unsigned char)*src++) != '\0' &&
103 		    isascii(ch) && isxdigit(ch)) {
104 			if (isupper(ch))
105 				ch = tolower(ch);
106 			n = strchr(xdigits, ch) - xdigits;
107 			assert(n >= 0 && n <= 15);
108 			if (dirty == 0)
109 				tmp = n;
110 			else
111 				tmp = (tmp << 4) | n;
112 			if (++dirty == 2) {
113 				if (size-- == 0)
114 					goto emsgsize;
115 				*dst++ = (u_char) tmp;
116 				dirty = 0;
117 			}
118 		}
119 		if (dirty) {  /* Odd trailing nybble? */
120 			if (size-- == 0)
121 				goto emsgsize;
122 			*dst++ = (u_char) (tmp << 4);
123 		}
124 	} else if (isascii(ch) && isdigit(ch)) {
125 		/* Decimal: eat dotted digit string. */
126 		for (;;) {
127 			tmp = 0;
128 			do {
129 				n = strchr(digits, ch) - digits;
130 				assert(n >= 0 && n <= 9);
131 				tmp *= 10;
132 				tmp += n;
133 				if (tmp > 255)
134 					goto enoent;
135 			} while ((ch = (unsigned char)*src++) != '\0' &&
136 				 isascii(ch) && isdigit(ch));
137 			if (size-- == 0)
138 				goto emsgsize;
139 			*dst++ = (u_char) tmp;
140 			if (ch == '\0' || ch == '/')
141 				break;
142 			if (ch != '.')
143 				goto enoent;
144 			ch = (unsigned char)*src++;
145 			if (!isascii(ch) || !isdigit(ch))
146 				goto enoent;
147 		}
148 	} else
149 		goto enoent;
150 
151 	bits = -1;
152 	if (ch == '/' && isascii((unsigned char)src[0]) &&
153 	    isdigit((unsigned char)src[0]) && dst > odst) {
154 		/* CIDR width specifier.  Nothing can follow it. */
155 		ch = (unsigned char)*src++;	/* Skip over the /. */
156 		bits = 0;
157 		do {
158 			n = strchr(digits, ch) - digits;
159 			assert(n >= 0 && n <= 9);
160 			bits *= 10;
161 			bits += n;
162 			if (bits > 32)
163 				goto emsgsize;
164 		} while ((ch = (unsigned char)*src++) != '\0' &&
165 			 isascii(ch) && isdigit(ch));
166 		if (ch != '\0')
167 			goto enoent;
168 	}
169 
170 	/* Fiery death and destruction unless we prefetched EOS. */
171 	if (ch != '\0')
172 		goto enoent;
173 
174 	/* If nothing was written to the destination, we found no address. */
175 	if (dst == odst)
176 		goto enoent;
177 	/* If no CIDR spec was given, infer width from net class. */
178 	if (bits == -1) {
179 		if (*odst >= 240)	/* Class E */
180 			bits = 32;
181 		else if (*odst >= 224)	/* Class D */
182 			bits = 4;
183 		else if (*odst >= 192)	/* Class C */
184 			bits = 24;
185 		else if (*odst >= 128)	/* Class B */
186 			bits = 16;
187 		else			/* Class A */
188 			bits = 8;
189 		/* If imputed mask is narrower than specified octets, widen. */
190 		if (bits < ((dst - odst) * 8))
191 			bits = (dst - odst) * 8;
192 	}
193 	/* Extend network to cover the actual mask. */
194 	while (bits > ((dst - odst) * 8)) {
195 		if (size-- == 0)
196 			goto emsgsize;
197 		*dst++ = '\0';
198 	}
199 	return (bits);
200 
201  enoent:
202 	errno = ENOENT;
203 	return (-1);
204 
205  emsgsize:
206 	errno = EMSGSIZE;
207 	return (-1);
208 }
209 
210 
211 static int
inet_net_pton_ipv6(const char * src,u_char * dst,size_t size)212 inet_net_pton_ipv6(const char *src, u_char *dst, size_t size)
213 {
214 	struct in6_addr	 in6;
215 	int		 ret;
216 	int		 bits;
217 	size_t		 bytes;
218 	char		 buf[INET6_ADDRSTRLEN + sizeof("/128")];
219 	char		*sep;
220 	const char	*errstr;
221 
222 	if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
223 		errno = EMSGSIZE;
224 		return (-1);
225 	}
226 
227 	sep = strchr(buf, '/');
228 	if (sep != NULL)
229 		*sep++ = '\0';
230 
231 	ret = inet_pton(AF_INET6, buf, &in6);
232 	if (ret != 1)
233 		return (-1);
234 
235 	if (sep == NULL)
236 		bits = 128;
237 	else {
238 		bits = strtonum(sep, 0, 128, &errstr);
239 		if (errstr) {
240 			errno = EINVAL;
241 			return (-1);
242 		}
243 	}
244 
245 	bytes = (bits + 7) / 8;
246 	if (bytes > size) {
247 		errno = EMSGSIZE;
248 		return (-1);
249 	}
250 	memcpy(dst, &in6.s6_addr, bytes);
251 	return (bits);
252 }
253 
254 /*
255  * Weak aliases for applications that use certain private entry points,
256  * and fail to include <arpa/inet.h>.
257  */
258 #undef inet_net_pton
259 __weak_reference(__inet_net_pton, inet_net_pton);
260