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 #include <memory.h>
61
62 #include <curl/curl.h>
63
64 #ifndef TRUE
65 #define TRUE 1
66 #endif
67
68 #ifndef FALSE
69 #define FALSE 0
70 #endif
71
72 struct ip {
73 /* The user-provided IP address or network (use CIDR) to filter */
74 char *str;
75 /* IP address family AF_INET (IPv4) or AF_INET6 (IPv6) */
76 int family;
77 /* IP in network byte format */
78 union netaddr {
79 struct in_addr ipv4;
80 #ifdef AF_INET6
81 struct in6_addr ipv6;
82 #endif
83 } netaddr;
84 /* IP bits to match against.
85 * This is equal to the CIDR notation or max bits if no CIDR.
86 * For example if ip->str is 127.0.0.0/8 then ip->maskbits is 8.
87 */
88 int maskbits;
89 struct ip *next;
90 };
91
92 enum connection_filter_t {
93 CONNECTION_FILTER_BLACKLIST,
94 CONNECTION_FILTER_WHITELIST
95 };
96
97 struct connection_filter {
98 struct ip *list;
99 enum connection_filter_t type;
100 int verbose;
101 #ifdef AF_INET6
102 /* If the address being filtered is an IPv4-mapped IPv6 address then it is
103 * checked against IPv4 list entries as well, unless ipv6_v6only is set TRUE.
104 */
105 int ipv6_v6only;
106 #endif
107 };
108
ip_list_append(struct ip * list,const char * data)109 static struct ip *ip_list_append(struct ip *list, const char *data)
110 {
111 struct ip *ip, *last;
112 char *cidr;
113
114 ip = (struct ip *)calloc(1, sizeof(*ip));
115 if(!ip)
116 return NULL;
117
118 if(strchr(data, ':')) {
119 #ifdef AF_INET6
120 ip->family = AF_INET6;
121 #else
122 free(ip);
123 return NULL;
124 #endif
125 }
126 else
127 ip->family = AF_INET;
128
129 ip->str = strdup(data);
130 if(!ip->str) {
131 free(ip);
132 return NULL;
133 }
134
135 /* determine the number of bits that this IP will match against */
136 cidr = strchr(ip->str, '/');
137 if(cidr) {
138 ip->maskbits = atoi(cidr + 1);
139 if(ip->maskbits <= 0 ||
140 #ifdef AF_INET6
141 (ip->family == AF_INET6 && ip->maskbits > 128) ||
142 #endif
143 (ip->family == AF_INET && ip->maskbits > 32)) {
144 free(ip->str);
145 free(ip);
146 return NULL;
147 }
148 /* ignore the CIDR notation when converting ip->str to ip->netaddr */
149 *cidr = '\0';
150 }
151 else if(ip->family == AF_INET)
152 ip->maskbits = 32;
153 #ifdef AF_INET6
154 else if(ip->family == AF_INET6)
155 ip->maskbits = 128;
156 #endif
157
158 if(1 != inet_pton(ip->family, ip->str, &ip->netaddr)) {
159 free(ip->str);
160 free(ip);
161 return NULL;
162 }
163
164 if(cidr)
165 *cidr = '/';
166
167 if(!list)
168 return ip;
169 for(last = list; last->next; last = last->next)
170 ;
171 last->next = ip;
172 return list;
173 }
174
ip_list_free_all(struct ip * list)175 static void ip_list_free_all(struct ip *list)
176 {
177 struct ip *next;
178 while(list) {
179 next = list->next;
180 free(list->str);
181 free(list);
182 list = next;
183 }
184 }
185
free_connection_filter(struct connection_filter * filter)186 static void free_connection_filter(struct connection_filter *filter)
187 {
188 if(filter) {
189 ip_list_free_all(filter->list);
190 free(filter);
191 }
192 }
193
ip_match(struct ip * ip,void * netaddr)194 static int ip_match(struct ip *ip, void *netaddr)
195 {
196 int bytes, tailbits;
197 const unsigned char *x, *y;
198
199 x = (unsigned char *)&ip->netaddr;
200 y = (unsigned char *)netaddr;
201
202 for(bytes = ip->maskbits / 8; bytes; --bytes) {
203 if(*x++ != *y++)
204 return FALSE;
205 }
206
207 tailbits = ip->maskbits % 8;
208 if(tailbits) {
209 unsigned char tailmask = (unsigned char)((0xFF << (8 - tailbits)) & 0xFF);
210 if((*x & tailmask) != (*y & tailmask))
211 return FALSE;
212 }
213
214 return TRUE;
215 }
216
217 #ifdef AF_INET6
is_ipv4_mapped_ipv6_address(int family,void * netaddr)218 static int is_ipv4_mapped_ipv6_address(int family, void *netaddr)
219 {
220 if(family == AF_INET6) {
221 int i;
222 unsigned char *x = (unsigned char *)netaddr;
223 for(i = 0; i < 12; ++i) {
224 if(x[i])
225 break;
226 }
227 /* support formats ::x.x.x.x (deprecated) and ::ffff:x.x.x.x */
228 if((i == 12 && (x[i] || x[i + 1] || x[i + 2] || x[i + 3])) ||
229 (i == 10 && (x[i] == 0xFF && x[i + 1] == 0xFF)))
230 return TRUE;
231 }
232
233 return FALSE;
234 }
235 #endif /* AF_INET6 */
236
opensocket(void * clientp,curlsocktype purpose,struct curl_sockaddr * address)237 static curl_socket_t opensocket(void *clientp,
238 curlsocktype purpose,
239 struct curl_sockaddr *address)
240 {
241 /* filter the address */
242 if(purpose == CURLSOCKTYPE_IPCXN) {
243 void *cinaddr = NULL;
244
245 if(address->family == AF_INET)
246 cinaddr = &((struct sockaddr_in *)(void *)&address->addr)->sin_addr;
247 #ifdef AF_INET6
248 else if(address->family == AF_INET6)
249 cinaddr = &((struct sockaddr_in6 *)(void *)&address->addr)->sin6_addr;
250 #endif
251
252 if(cinaddr) {
253 struct ip *ip;
254 struct connection_filter *filter = (struct connection_filter *)clientp;
255 #ifdef AF_INET6
256 int mapped = !filter->ipv6_v6only &&
257 is_ipv4_mapped_ipv6_address(address->family, cinaddr);
258 #endif
259
260 for(ip = filter->list; ip; ip = ip->next) {
261 if(ip->family == address->family && ip_match(ip, cinaddr))
262 break;
263 #ifdef AF_INET6
264 if(mapped && ip->family == AF_INET && address->family == AF_INET6 &&
265 ip_match(ip, (unsigned char *)cinaddr + 12))
266 break;
267 #endif
268 }
269
270 if(ip && filter->type == CONNECTION_FILTER_BLACKLIST) {
271 if(filter->verbose) {
272 char buf[128] = {0};
273 inet_ntop(address->family, cinaddr, buf, sizeof(buf));
274 fprintf(stderr, "* Rejecting IP %s due to blacklist entry %s.\n",
275 buf, ip->str);
276 }
277 return CURL_SOCKET_BAD;
278 }
279 else if(!ip && filter->type == CONNECTION_FILTER_WHITELIST) {
280 if(filter->verbose) {
281 char buf[128] = {0};
282 inet_ntop(address->family, cinaddr, buf, sizeof(buf));
283 fprintf(stderr,
284 "* Rejecting IP %s due to missing whitelist entry.\n", buf);
285 }
286 return CURL_SOCKET_BAD;
287 }
288 }
289 }
290
291 return socket(address->family, address->socktype, address->protocol);
292 }
293
main(void)294 int main(void)
295 {
296 CURL *curl;
297 CURLcode res;
298 struct connection_filter *filter;
299
300 filter = (struct connection_filter *)calloc(1, sizeof(*filter));
301 if(!filter)
302 exit(1);
303
304 if(curl_global_init(CURL_GLOBAL_DEFAULT))
305 exit(1);
306
307 curl = curl_easy_init();
308 if(!curl)
309 exit(1);
310
311 /* Set the target URL */
312 curl_easy_setopt(curl, CURLOPT_URL, "http://localhost");
313
314 /* Define an IP connection filter.
315 * If an address has CIDR notation then it matches the network.
316 * For example 74.6.143.25/24 matches 74.6.143.0 - 74.6.143.255.
317 */
318 filter->type = CONNECTION_FILTER_BLACKLIST;
319 filter->list = ip_list_append(filter->list, "98.137.11.164");
320 filter->list = ip_list_append(filter->list, "127.0.0.0/8");
321 #ifdef AF_INET6
322 filter->list = ip_list_append(filter->list, "::1");
323 #endif
324
325 /* Set the socket function which does the filtering */
326 curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket);
327 curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, filter);
328
329 /* Verbose mode */
330 filter->verbose = TRUE;
331 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
332
333 /* Perform the request */
334 res = curl_easy_perform(curl);
335
336 /* Check for errors */
337 if(res != CURLE_OK) {
338 fprintf(stderr, "curl_easy_perform() failed: %s\n",
339 curl_easy_strerror(res));
340 }
341
342 /* Clean up */
343 curl_easy_cleanup(curl);
344 free_connection_filter(filter);
345
346 /* Clean up libcurl */
347 curl_global_cleanup();
348
349 return 0;
350 }
351 #endif
352