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 <stdint.h>
49 #include <netdb.h>
50 #include <stdlib.h>
51 #include <strings.h>
52 
53 int
inet_matchaddr(const void * sa,const char * name)54 inet_matchaddr(const void *sa, const char *name)
55 {
56 	int ret = -1;
57 	char *lname, *mp, *p;
58 	char *ep;
59 	int serrno = errno;
60 	uint32_t claddr4 = 0;
61 
62 	if ((p = lname = strdup(name)) == NULL) {
63 		errno = ENOMEM;
64 		return (-1);
65 	}
66 
67 	if ((mp = strchr(p, '/')) != NULL)
68 		*mp++ = '\0';
69 
70 	switch (((struct sockaddr_in *)sa)->sin_family) {
71 	case AF_INET6: {
72 		char *pp;
73 		ipaddr_t ipaddr4;
74 		struct in6_addr hcaddr6;
75 		struct in6_addr *claddr6 =
76 		    &((struct sockaddr_in6 *)sa)->sin6_addr;
77 
78 		if (!IN6_IS_ADDR_V4MAPPED(claddr6)) {
79 			/* IPv6 address */
80 			if (*p != '[') {
81 				errno = EINVAL;
82 				break;
83 			}
84 			p++;
85 
86 			if ((pp = strchr(p, ']')) == NULL ||
87 			    (mp != NULL && pp != mp - 2) ||
88 			    (mp == NULL && *(pp + 1) != '\0')) {
89 				errno = EINVAL;
90 				break;
91 			}
92 			*pp = '\0';
93 
94 			if (inet_pton(AF_INET6, p, &hcaddr6) != 1) {
95 				errno = EINVAL;
96 				break;
97 			}
98 
99 			if (mp != NULL) {
100 				/* Match only first prefix bits */
101 				long prefix6;
102 
103 				errno = 0;
104 				prefix6 = strtol(mp, &ep, 10);
105 				if (errno != 0 || prefix6 < 0 ||
106 				    prefix6 > 128 || *ep != '\0') {
107 					errno = EINVAL;
108 					break;
109 				}
110 				ret = IN6_ARE_PREFIXEDADDR_EQUAL(claddr6,
111 				    &hcaddr6, prefix6) ? 1 : 0;
112 				break;
113 			} else {
114 				/* No prefix, exact match */
115 				ret = IN6_ARE_ADDR_EQUAL(claddr6,
116 				    &hcaddr6) ? 1 : 0;
117 				break;
118 			}
119 		} else {
120 			/* IPv4-mapped IPv6 address, fallthrough to IPv4 */
121 			IN6_V4MAPPED_TO_IPADDR(claddr6, ipaddr4);
122 			claddr4 = ntohl(ipaddr4);
123 		}
124 	}
125 	/*FALLTHROUGH*/
126 	case AF_INET: {
127 		int i;
128 		uint32_t hcaddr4 = 0, mask4;
129 
130 		if (claddr4 == 0) {
131 			claddr4 = ntohl(
132 			    ((struct sockaddr_in *)sa)->sin_addr.s_addr);
133 		}
134 
135 		for (i = 0; i < 4; i++) {
136 			long qaddr4;
137 
138 			errno = 0;
139 			qaddr4 = strtol(p, &ep, 10);
140 			if (errno != 0 || qaddr4 < 0 || qaddr4 > 255 ||
141 			    (*ep != '.' && *ep != '\0')) {
142 				errno = EINVAL;
143 				break;
144 			}
145 			hcaddr4 |= qaddr4 << ((3 - i) * 8);
146 			if (*ep == '\0')
147 				break;
148 			p = ep + 1;
149 		}
150 
151 		if (errno != 0)
152 			break;
153 
154 		if (mp != NULL) {
155 			/* Mask is specified explicitly */
156 			long mb;
157 
158 			errno = 0;
159 			mb = strtol(mp, &ep, 10);
160 			if (errno != 0 || mb < 0 || mb > 32 || *ep != '\0') {
161 				errno = EINVAL;
162 				break;
163 			}
164 			if (mb != 0) {
165 				mask4 =
166 				    UINT32_MAX <<
167 				    ((sizeof (struct in_addr) * NBBY) - mb);
168 			} else {
169 				mask4 = 0;
170 			}
171 			hcaddr4 &= mask4;
172 		} else {
173 			/*
174 			 * Use old-fashioned implicit netmasking by checking
175 			 * for lower-end zeroes. On the off chance we don't
176 			 * match any well-known prefixes, return an exact-
177 			 * match prefix which is misleadingly labelled as
178 			 * IN_CLASSE_NET.
179 			 */
180 			if ((hcaddr4 & IN_CLASSA_HOST) == 0)
181 				mask4 = IN_CLASSA_NET;
182 			else if ((hcaddr4 & IN_CLASSB_HOST) == 0)
183 				mask4 = IN_CLASSB_NET;
184 			else if ((hcaddr4 & IN_CLASSC_HOST) == 0)
185 				mask4 = IN_CLASSC_NET;
186 			else
187 				mask4 = IN_CLASSE_NET;
188 		}
189 
190 		ret = ((claddr4 & mask4) == hcaddr4) ? 1 : 0;
191 		break;
192 	}
193 	}
194 
195 	free(lname);
196 
197 	if (ret != -1)
198 		errno = serrno;
199 	return (ret);
200 }
201