xref: /illumos-gate/usr/src/lib/libnsl/nss/inet_matchaddr.c (revision be4e997e05c92f444c81d2d197b79e67ebee2786)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2016 Nexenta Systems, Inc.
14  */
15 
16 /*
17  * inet_matchaddr
18  *
19  * Match IPv4 or IPv6 address provided in sa (sockaddr_in/sockaddr_in6)
20  * against standard text representation specified in name:
21  *
22  * IPv4:
23  *	IPv4
24  *	IPv4/netmask
25  *
26  * IPv6:
27  *	[IPv6]
28  *	[IPv6]/prefix
29  *
30  * Return values:
31  *
32  * 0		mismatch
33  * 1		match
34  * -1		error occured, caller should check errno:
35  * 		EINVAL	access list entry is invalid
36  *		ENOMEM	failed to allocate memory
37  */
38 
39 #include <sys/socket.h>
40 #include <sys/types.h>
41 
42 #include <arpa/inet.h>
43 
44 #include <netinet/in.h>
45 
46 #include <ctype.h>
47 #include <errno.h>
48 #include <netdb.h>
49 #include <stdlib.h>
50 #include <strings.h>
51 
52 int
53 inet_matchaddr(const void *sa, const char *name)
54 {
55 	int ret = -1;
56 	char *lname, *mp, *p;
57 	char *ep;
58 	int serrno = errno;
59 	uint32_t claddr4 = 0;
60 
61 	if ((p = lname = strdup(name)) == NULL) {
62 		errno = ENOMEM;
63 		return (-1);
64 	}
65 
66 	if ((mp = strchr(p, '/')) != NULL)
67 		*mp++ = '\0';
68 
69 	switch (((struct sockaddr_in *)sa)->sin_family) {
70 	case AF_INET6: {
71 		char *pp;
72 		ipaddr_t ipaddr4;
73 		struct in6_addr hcaddr6;
74 		struct in6_addr *claddr6 =
75 		    &((struct sockaddr_in6 *)sa)->sin6_addr;
76 
77 		if (!IN6_IS_ADDR_V4MAPPED(claddr6)) {
78 			/* IPv6 address */
79 			if (*p != '[') {
80 				errno = EINVAL;
81 				break;
82 			}
83 			p++;
84 
85 			if ((pp = strchr(p, ']')) == NULL ||
86 			    (mp != NULL && pp != mp - 2) ||
87 			    (mp == NULL && *(pp + 1) != '\0')) {
88 				errno = EINVAL;
89 				break;
90 			}
91 			*pp = '\0';
92 
93 			if (inet_pton(AF_INET6, p, &hcaddr6) != 1) {
94 				errno = EINVAL;
95 				break;
96 			}
97 
98 			if (mp != NULL) {
99 				/* Match only first prefix bits */
100 				long prefix6;
101 
102 				errno = 0;
103 				prefix6 = strtol(mp, &ep, 10);
104 				if (errno != 0 || prefix6 < 0 ||
105 				    prefix6 > 128 || *ep != '\0') {
106 					errno = EINVAL;
107 					break;
108 				}
109 				ret = IN6_ARE_PREFIXEDADDR_EQUAL(claddr6,
110 				    &hcaddr6, prefix6) ? 1 : 0;
111 				break;
112 			} else {
113 				/* No prefix, exact match */
114 				ret = IN6_ARE_ADDR_EQUAL(claddr6,
115 				    &hcaddr6) ? 1 : 0;
116 				break;
117 			}
118 		} else {
119 			/* IPv4-mapped IPv6 address, fallthrough to IPv4 */
120 			IN6_V4MAPPED_TO_IPADDR(claddr6, ipaddr4);
121 			claddr4 = ntohl(ipaddr4);
122 		}
123 		/*FALLTHROUGH*/
124 	}
125 	case AF_INET: {
126 		int i;
127 		uint32_t hcaddr4 = 0, mask4;
128 
129 		if (claddr4 == 0) {
130 			claddr4 = ntohl(
131 			    ((struct sockaddr_in *)sa)->sin_addr.s_addr);
132 		}
133 
134 		for (i = 0; i < 4; i++) {
135 			long qaddr4;
136 
137 			errno = 0;
138 			qaddr4 = strtol(p, &ep, 10);
139 			if (errno != 0 || qaddr4 < 0 || qaddr4 > 255 ||
140 			    (*ep != '.' && *ep != '\0')) {
141 				errno = EINVAL;
142 				break;
143 			}
144 			hcaddr4 |= qaddr4 << ((3 - i) * 8);
145 			if (*ep == '\0')
146 				break;
147 			p = ep + 1;
148 		}
149 
150 		if (errno != 0)
151 			break;
152 
153 		if (mp != NULL) {
154 			/* Mask is specified explicitly */
155 			long mb;
156 
157 			errno = 0;
158 			mb = strtol(mp, &ep, 10);
159 			if (errno != 0 || mb < 0 || mb > 32 || *ep != '\0') {
160 				errno = EINVAL;
161 				break;
162 			}
163 			mask4 = mb ? ~0 << ((sizeof (struct in_addr) * NBBY)
164 			    - mb) : 0;
165 			hcaddr4 &= mask4;
166 		} else {
167 			/*
168 			 * Use old-fashioned implicit netmasking by checking
169 			 * for lower-end zeroes. On the off chance we don't
170 			 * match any well-known prefixes, return an exact-
171 			 * match prefix which is misleadingly labelled as
172 			 * IN_CLASSE_NET.
173 			 */
174 			if ((hcaddr4 & IN_CLASSA_HOST) == 0)
175 				mask4 = IN_CLASSA_NET;
176 			else if ((hcaddr4 & IN_CLASSB_HOST) == 0)
177 				mask4 = IN_CLASSB_NET;
178 			else if ((hcaddr4 & IN_CLASSC_HOST) == 0)
179 				mask4 = IN_CLASSC_NET;
180 			else
181 				mask4 = IN_CLASSE_NET;
182 		}
183 
184 		ret = ((claddr4 & mask4) == hcaddr4) ? 1 : 0;
185 		break;
186 	}
187 	}
188 
189 	free(lname);
190 
191 	if (ret != -1)
192 		errno = serrno;
193 	return (ret);
194 }
195