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