1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 /* <DESC>
25 * Show how CURLOPT_OPENSOCKETFUNCTION can be used to block IP addresses.
26 * </DESC>
27 */
28 /* This is an advanced example that defines a whitelist or a blacklist to
29 * filter IP addresses.
30 */
31
32 #ifdef __AMIGA__
33 #include <stdio.h>
main(void)34 int main(void) { printf("AmigaOS is not supported.\n"); return 1; }
35 #else
36
37 #ifdef _WIN32
38 #ifndef _CRT_SECURE_NO_WARNINGS
39 #define _CRT_SECURE_NO_WARNINGS
40 #endif
41 #ifndef _CRT_NONSTDC_NO_DEPRECATE
42 #define _CRT_NONSTDC_NO_DEPRECATE
43 #endif
44 #ifndef _WIN32_WINNT
45 #define _WIN32_WINNT 0x0600
46 #endif
47 #include <winsock2.h>
48 #include <ws2tcpip.h>
49 #include <windows.h>
50 #else
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
55 #endif
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60
61 #include <curl/curl.h>
62
63 #ifndef TRUE
64 #define TRUE 1
65 #endif
66
67 #ifndef FALSE
68 #define FALSE 0
69 #endif
70
71 struct ip {
72 /* The user-provided IP address or network (use CIDR) to filter */
73 char *str;
74 /* IP address family AF_INET (IPv4) or AF_INET6 (IPv6) */
75 int family;
76 /* IP in network byte format */
77 union netaddr {
78 struct in_addr ipv4;
79 #ifdef AF_INET6
80 struct in6_addr ipv6;
81 #endif
82 } netaddr;
83 /* IP bits to match against.
84 * This is equal to the CIDR notation or max bits if no CIDR.
85 * For example if ip->str is 127.0.0.0/8 then ip->maskbits is 8.
86 */
87 int maskbits;
88 struct ip *next;
89 };
90
91 enum connection_filter_t {
92 CONNECTION_FILTER_BLACKLIST,
93 CONNECTION_FILTER_WHITELIST
94 };
95
96 struct connection_filter {
97 struct ip *list;
98 enum connection_filter_t type;
99 int verbose;
100 #ifdef AF_INET6
101 /* If the address being filtered is an IPv4-mapped IPv6 address then it is
102 * checked against IPv4 list entries as well, unless ipv6_v6only is set TRUE.
103 */
104 int ipv6_v6only;
105 #endif
106 };
107
ip_list_append(struct ip * list,const char * data)108 static struct ip *ip_list_append(struct ip *list, const char *data)
109 {
110 struct ip *ip, *last;
111 char *cidr;
112
113 ip = (struct ip *)calloc(1, sizeof(*ip));
114 if(!ip)
115 return NULL;
116
117 if(strchr(data, ':')) {
118 #ifdef AF_INET6
119 ip->family = AF_INET6;
120 #else
121 free(ip);
122 return NULL;
123 #endif
124 }
125 else
126 ip->family = AF_INET;
127
128 ip->str = strdup(data);
129 if(!ip->str) {
130 free(ip);
131 return NULL;
132 }
133
134 /* determine the number of bits that this IP will match against */
135 cidr = strchr(ip->str, '/');
136 if(cidr) {
137 ip->maskbits = atoi(cidr + 1);
138 if(ip->maskbits <= 0 ||
139 #ifdef AF_INET6
140 (ip->family == AF_INET6 && ip->maskbits > 128) ||
141 #endif
142 (ip->family == AF_INET && ip->maskbits > 32)) {
143 free(ip->str);
144 free(ip);
145 return NULL;
146 }
147 /* ignore the CIDR notation when converting ip->str to ip->netaddr */
148 *cidr = '\0';
149 }
150 else if(ip->family == AF_INET)
151 ip->maskbits = 32;
152 #ifdef AF_INET6
153 else if(ip->family == AF_INET6)
154 ip->maskbits = 128;
155 #endif
156
157 if(1 != inet_pton(ip->family, ip->str, &ip->netaddr)) {
158 free(ip->str);
159 free(ip);
160 return NULL;
161 }
162
163 if(cidr)
164 *cidr = '/';
165
166 if(!list)
167 return ip;
168 for(last = list; last->next; last = last->next)
169 ;
170 last->next = ip;
171 return list;
172 }
173
ip_list_free_all(struct ip * list)174 static void ip_list_free_all(struct ip *list)
175 {
176 struct ip *next;
177 while(list) {
178 next = list->next;
179 free(list->str);
180 free(list);
181 list = next;
182 }
183 }
184
free_connection_filter(struct connection_filter * filter)185 static void free_connection_filter(struct connection_filter *filter)
186 {
187 if(filter) {
188 ip_list_free_all(filter->list);
189 free(filter);
190 }
191 }
192
ip_match(struct ip * ip,void * netaddr)193 static int ip_match(struct ip *ip, void *netaddr)
194 {
195 int bytes, tailbits;
196 const unsigned char *x, *y;
197
198 x = (unsigned char *)&ip->netaddr;
199 y = (unsigned char *)netaddr;
200
201 for(bytes = ip->maskbits / 8; bytes; --bytes) {
202 if(*x++ != *y++)
203 return FALSE;
204 }
205
206 tailbits = ip->maskbits % 8;
207 if(tailbits) {
208 unsigned char tailmask = (unsigned char)((0xFF << (8 - tailbits)) & 0xFF);
209 if((*x & tailmask) != (*y & tailmask))
210 return FALSE;
211 }
212
213 return TRUE;
214 }
215
216 #ifdef AF_INET6
is_ipv4_mapped_ipv6_address(int family,void * netaddr)217 static int is_ipv4_mapped_ipv6_address(int family, void *netaddr)
218 {
219 if(family == AF_INET6) {
220 int i;
221 unsigned char *x = (unsigned char *)netaddr;
222 for(i = 0; i < 12; ++i) {
223 if(x[i])
224 break;
225 }
226 /* support formats ::x.x.x.x (deprecated) and ::ffff:x.x.x.x */
227 if((i == 12 && (x[i] || x[i + 1] || x[i + 2] || x[i + 3])) ||
228 (i == 10 && (x[i] == 0xFF && x[i + 1] == 0xFF)))
229 return TRUE;
230 }
231
232 return FALSE;
233 }
234 #endif /* AF_INET6 */
235
opensocket(void * clientp,curlsocktype purpose,struct curl_sockaddr * address)236 static curl_socket_t opensocket(void *clientp,
237 curlsocktype purpose,
238 struct curl_sockaddr *address)
239 {
240 /* filter the address */
241 if(purpose == CURLSOCKTYPE_IPCXN) {
242 void *cinaddr = NULL;
243
244 if(address->family == AF_INET)
245 cinaddr = &((struct sockaddr_in *)(void *)&address->addr)->sin_addr;
246 #ifdef AF_INET6
247 else if(address->family == AF_INET6)
248 cinaddr = &((struct sockaddr_in6 *)(void *)&address->addr)->sin6_addr;
249 #endif
250
251 if(cinaddr) {
252 struct ip *ip;
253 struct connection_filter *filter = (struct connection_filter *)clientp;
254 #ifdef AF_INET6
255 int mapped = !filter->ipv6_v6only &&
256 is_ipv4_mapped_ipv6_address(address->family, cinaddr);
257 #endif
258
259 for(ip = filter->list; ip; ip = ip->next) {
260 if(ip->family == address->family && ip_match(ip, cinaddr))
261 break;
262 #ifdef AF_INET6
263 if(mapped && ip->family == AF_INET && address->family == AF_INET6 &&
264 ip_match(ip, (unsigned char *)cinaddr + 12))
265 break;
266 #endif
267 }
268
269 if(ip && filter->type == CONNECTION_FILTER_BLACKLIST) {
270 if(filter->verbose) {
271 char buf[128] = {0};
272 inet_ntop(address->family, cinaddr, buf, sizeof(buf));
273 fprintf(stderr, "* Rejecting IP %s due to blacklist entry %s.\n",
274 buf, ip->str);
275 }
276 return CURL_SOCKET_BAD;
277 }
278 else if(!ip && filter->type == CONNECTION_FILTER_WHITELIST) {
279 if(filter->verbose) {
280 char buf[128] = {0};
281 inet_ntop(address->family, cinaddr, buf, sizeof(buf));
282 fprintf(stderr,
283 "* Rejecting IP %s due to missing whitelist entry.\n", buf);
284 }
285 return CURL_SOCKET_BAD;
286 }
287 }
288 }
289
290 return socket(address->family, address->socktype, address->protocol);
291 }
292
main(void)293 int main(void)
294 {
295 CURL *curl;
296 CURLcode res;
297 struct connection_filter *filter;
298
299 filter = (struct connection_filter *)calloc(1, sizeof(*filter));
300 if(!filter)
301 exit(1);
302
303 if(curl_global_init(CURL_GLOBAL_DEFAULT))
304 exit(1);
305
306 curl = curl_easy_init();
307 if(!curl)
308 exit(1);
309
310 /* Set the target URL */
311 curl_easy_setopt(curl, CURLOPT_URL, "http://localhost");
312
313 /* Define an IP connection filter.
314 * If an address has CIDR notation then it matches the network.
315 * For example 74.6.143.25/24 matches 74.6.143.0 - 74.6.143.255.
316 */
317 filter->type = CONNECTION_FILTER_BLACKLIST;
318 filter->list = ip_list_append(filter->list, "98.137.11.164");
319 filter->list = ip_list_append(filter->list, "127.0.0.0/8");
320 #ifdef AF_INET6
321 filter->list = ip_list_append(filter->list, "::1");
322 #endif
323
324 /* Set the socket function which does the filtering */
325 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket);
326 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, filter);
327
328 /* Verbose mode */
329 filter->verbose = TRUE;
330 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
331
332 /* Perform the request */
333 res = curl_easy_perform(curl);
334
335 /* Check for errors */
336 if(res != CURLE_OK) {
337 fprintf(stderr, "curl_easy_perform() failed: %s\n",
338 curl_easy_strerror(res));
339 }
340
341 /* Clean up */
342 curl_easy_cleanup(curl);
343 free_connection_filter(filter);
344
345 /* Clean up libcurl */
346 curl_global_cleanup();
347
348 return 0;
349 }
350 #endif
351