xref: /curl/docs/examples/block_ip.c (revision a9d881c7)
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