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