1 /*
2 *
3 * IP-address/hostname to country converter.
4 *
5 * Problem; you want to know where IP a.b.c.d is located.
6 *
7 * Use ares_gethostbyname ("d.c.b.a.zz.countries.nerd.dk")
8 * and get the CNAME (host->h_name). Result will be:
9 * CNAME = zz<CC>.countries.nerd.dk with address 127.0.x.y (ver 1) or
10 * CNAME = <a.b.c.d>.zz.countries.nerd.dk with address 127.0.x.y (ver 2)
11 *
12 * The 2 letter country code is in <CC> and the ISO-3166 country
13 * number is in x.y (number = x*256 + y). Version 2 of the protocol is missing
14 * the <CC> number.
15 *
16 * Ref: http://countries.nerd.dk/more.html
17 *
18 * Written by G. Vanem <gvanem@yahoo.no> 2006, 2007
19 *
20 * NB! This program may not be big-endian aware.
21 *
22 * Permission to use, copy, modify, and distribute this
23 * software and its documentation for any purpose and without
24 * fee is hereby granted, provided that the above copyright
25 * notice appear in all copies and that both that copyright
26 * notice and this permission notice appear in supporting
27 * documentation, and that the name of M.I.T. not be used in
28 * advertising or publicity pertaining to distribution of the
29 * software without specific, written prior permission.
30 * M.I.T. makes no representations about the suitability of
31 * this software for any purpose. It is provided "as is"
32 * without express or implied warranty.
33 */
34
35 #include "ares_setup.h"
36
37 #ifdef HAVE_STRINGS_H
38 #include <strings.h>
39 #endif
40
41 #if defined(WIN32) && !defined(WATT32)
42 #include <winsock.h>
43 #else
44 #include <arpa/inet.h>
45 #include <netinet/in.h>
46 #include <netdb.h>
47 #endif
48
49 #include "ares.h"
50 #include "ares_getopt.h"
51 #include "ares_nowarn.h"
52
53 #ifndef HAVE_STRDUP
54 # include "ares_strdup.h"
55 # define strdup(ptr) ares_strdup(ptr)
56 #endif
57
58 #ifndef HAVE_STRCASECMP
59 # include "ares_strcasecmp.h"
60 # define strcasecmp(p1,p2) ares_strcasecmp(p1,p2)
61 #endif
62
63 #ifndef HAVE_STRNCASECMP
64 # include "ares_strcasecmp.h"
65 # define strncasecmp(p1,p2,n) ares_strncasecmp(p1,p2,n)
66 #endif
67
68 #ifndef INADDR_NONE
69 #define INADDR_NONE 0xffffffff
70 #endif
71
72 /* By using a double cast, we can get rid of the bogus warning of
73 * warning: cast from 'const struct sockaddr *' to 'const struct sockaddr_in6 *' increases required alignment from 1 to 4 [-Wcast-align]
74 */
75 #define CARES_INADDR_CAST(type, var) ((type)((void *)var))
76
77 static const char *usage = "acountry [-?hdv] {host|addr} ...\n";
78 static const char nerd_fmt[] = "%u.%u.%u.%u.zz.countries.nerd.dk";
79 static const char *nerd_ver1 = nerd_fmt + 14; /* .countries.nerd.dk */
80 static const char *nerd_ver2 = nerd_fmt + 11; /* .zz.countries.nerd.dk */
81 static int verbose = 0;
82
83 #define TRACE(fmt) do { \
84 if (verbose > 0) \
85 printf fmt ; \
86 } WHILE_FALSE
87
88 static void wait_ares(ares_channel channel);
89 static void callback(void *arg, int status, int timeouts, struct hostent *host);
90 static void callback2(void *arg, int status, int timeouts, struct hostent *host);
91 static void find_country_from_cname(const char *cname, struct in_addr addr);
92
Abort(const char * fmt,...)93 static void Abort(const char *fmt, ...)
94 {
95 va_list args;
96 va_start(args, fmt);
97 vfprintf(stderr, fmt, args);
98 va_end(args);
99 exit(1);
100 }
101
main(int argc,char ** argv)102 int main(int argc, char **argv)
103 {
104 ares_channel channel;
105 int ch, status;
106
107 #if defined(WIN32) && !defined(WATT32)
108 WORD wVersionRequested = MAKEWORD(USE_WINSOCK,USE_WINSOCK);
109 WSADATA wsaData;
110 WSAStartup(wVersionRequested, &wsaData);
111 #endif
112
113 status = ares_library_init(ARES_LIB_INIT_ALL);
114 if (status != ARES_SUCCESS)
115 {
116 fprintf(stderr, "ares_library_init: %s\n", ares_strerror(status));
117 return 1;
118 }
119
120 while ((ch = ares_getopt(argc, argv, "dvh?")) != -1)
121 switch (ch)
122 {
123 case 'd':
124 #ifdef WATT32
125 dbug_init();
126 #endif
127 break;
128 case 'v':
129 verbose++;
130 break;
131 case 'h':
132 case '?':
133 default:
134 Abort(usage);
135 }
136
137 argc -= optind;
138 argv += optind;
139 if (argc < 1)
140 Abort(usage);
141
142 status = ares_init(&channel);
143 if (status != ARES_SUCCESS)
144 {
145 fprintf(stderr, "ares_init: %s\n", ares_strerror(status));
146 return 1;
147 }
148
149 /* Initiate the queries, one per command-line argument. */
150 for ( ; *argv; argv++)
151 {
152 struct in_addr addr;
153 char buf[100];
154
155 /* If this fails, assume '*argv' is a host-name that
156 * must be resolved first
157 */
158 if (ares_inet_pton(AF_INET, *argv, &addr) != 1)
159 {
160 ares_gethostbyname(channel, *argv, AF_INET, callback2, &addr);
161 wait_ares(channel);
162 if (addr.s_addr == INADDR_NONE)
163 {
164 printf("Failed to lookup %s\n", *argv);
165 continue;
166 }
167 }
168
169 sprintf(buf, nerd_fmt,
170 (unsigned int)(addr.s_addr >> 24),
171 (unsigned int)((addr.s_addr >> 16) & 255),
172 (unsigned int)((addr.s_addr >> 8) & 255),
173 (unsigned int)(addr.s_addr & 255));
174 TRACE(("Looking up %s...", buf));
175 fflush(stdout);
176 ares_gethostbyname(channel, buf, AF_INET, callback, buf);
177 }
178
179 wait_ares(channel);
180 ares_destroy(channel);
181
182 ares_library_cleanup();
183
184 #if defined(WIN32) && !defined(WATT32)
185 WSACleanup();
186 #endif
187
188 return 0;
189 }
190
191 /*
192 * Wait for the queries to complete.
193 */
wait_ares(ares_channel channel)194 static void wait_ares(ares_channel channel)
195 {
196 for (;;)
197 {
198 struct timeval *tvp, tv;
199 fd_set read_fds, write_fds;
200 int nfds;
201
202 FD_ZERO(&read_fds);
203 FD_ZERO(&write_fds);
204 nfds = ares_fds(channel, &read_fds, &write_fds);
205 if (nfds == 0)
206 break;
207 tvp = ares_timeout(channel, NULL, &tv);
208 nfds = select(nfds, &read_fds, &write_fds, NULL, tvp);
209 if (nfds < 0)
210 continue;
211 ares_process(channel, &read_fds, &write_fds);
212 }
213 }
214
215 /*
216 * This is the callback used when we have the IP-address of interest.
217 * Extract the CNAME and figure out the country-code from it.
218 */
callback(void * arg,int status,int timeouts,struct hostent * host)219 static void callback(void *arg, int status, int timeouts, struct hostent *host)
220 {
221 const char *name = (const char*)arg;
222 const char *cname;
223 char buf[20];
224
225 (void)timeouts;
226
227 if (!host || status != ARES_SUCCESS)
228 {
229 printf("Failed to lookup %s: %s\n", name, ares_strerror(status));
230 return;
231 }
232
233 TRACE(("\nFound address %s, name %s\n",
234 ares_inet_ntop(AF_INET,(const char*)host->h_addr,buf,sizeof(buf)),
235 host->h_name));
236
237 cname = host->h_name; /* CNAME gets put here */
238 if (!cname)
239 printf("Failed to get CNAME for %s\n", name);
240 else
241 find_country_from_cname(cname, *(CARES_INADDR_CAST(struct in_addr *, host->h_addr)));
242 }
243
244 /*
245 * This is the callback used to obtain the IP-address of the host of interest.
246 */
callback2(void * arg,int status,int timeouts,struct hostent * host)247 static void callback2(void *arg, int status, int timeouts, struct hostent *host)
248 {
249 struct in_addr *addr = (struct in_addr*) arg;
250
251 (void)timeouts;
252 if (!host || status != ARES_SUCCESS)
253 memset(addr, INADDR_NONE, sizeof(*addr));
254 else
255 memcpy(addr, host->h_addr, sizeof(*addr));
256 }
257
258 struct search_list {
259 int country_number; /* ISO-3166 country number */
260 char short_name[3]; /* A2 short country code */
261 const char *long_name; /* normal country name */
262 };
263
list_lookup(int number,const struct search_list * list,int num)264 static const struct search_list *list_lookup(int number, const struct search_list *list, int num)
265 {
266 while (num > 0 && list->long_name)
267 {
268 if (list->country_number == number)
269 return (list);
270 num--;
271 list++;
272 }
273 return (NULL);
274 }
275
276 /*
277 * Ref: ftp://ftp.ripe.net/iso3166-countrycodes.txt
278 */
279 static const struct search_list country_list[] = {
280 { 4, "af", "Afghanistan" },
281 { 248, "ax", "Åland Island" },
282 { 8, "al", "Albania" },
283 { 12, "dz", "Algeria" },
284 { 16, "as", "American Samoa" },
285 { 20, "ad", "Andorra" },
286 { 24, "ao", "Angola" },
287 { 660, "ai", "Anguilla" },
288 { 10, "aq", "Antarctica" },
289 { 28, "ag", "Antigua & Barbuda" },
290 { 32, "ar", "Argentina" },
291 { 51, "am", "Armenia" },
292 { 533, "aw", "Aruba" },
293 { 36, "au", "Australia" },
294 { 40, "at", "Austria" },
295 { 31, "az", "Azerbaijan" },
296 { 44, "bs", "Bahamas" },
297 { 48, "bh", "Bahrain" },
298 { 50, "bd", "Bangladesh" },
299 { 52, "bb", "Barbados" },
300 { 112, "by", "Belarus" },
301 { 56, "be", "Belgium" },
302 { 84, "bz", "Belize" },
303 { 204, "bj", "Benin" },
304 { 60, "bm", "Bermuda" },
305 { 64, "bt", "Bhutan" },
306 { 68, "bo", "Bolivia" },
307 { 70, "ba", "Bosnia & Herzegowina" },
308 { 72, "bw", "Botswana" },
309 { 74, "bv", "Bouvet Island" },
310 { 76, "br", "Brazil" },
311 { 86, "io", "British Indian Ocean Territory" },
312 { 96, "bn", "Brunei Darussalam" },
313 { 100, "bg", "Bulgaria" },
314 { 854, "bf", "Burkina Faso" },
315 { 108, "bi", "Burundi" },
316 { 116, "kh", "Cambodia" },
317 { 120, "cm", "Cameroon" },
318 { 124, "ca", "Canada" },
319 { 132, "cv", "Cape Verde" },
320 { 136, "ky", "Cayman Islands" },
321 { 140, "cf", "Central African Republic" },
322 { 148, "td", "Chad" },
323 { 152, "cl", "Chile" },
324 { 156, "cn", "China" },
325 { 162, "cx", "Christmas Island" },
326 { 166, "cc", "Cocos Islands" },
327 { 170, "co", "Colombia" },
328 { 174, "km", "Comoros" },
329 { 178, "cg", "Congo" },
330 { 180, "cd", "Congo" },
331 { 184, "ck", "Cook Islands" },
332 { 188, "cr", "Costa Rica" },
333 { 384, "ci", "Cote d'Ivoire" },
334 { 191, "hr", "Croatia" },
335 { 192, "cu", "Cuba" },
336 { 196, "cy", "Cyprus" },
337 { 203, "cz", "Czech Republic" },
338 { 208, "dk", "Denmark" },
339 { 262, "dj", "Djibouti" },
340 { 212, "dm", "Dominica" },
341 { 214, "do", "Dominican Republic" },
342 { 218, "ec", "Ecuador" },
343 { 818, "eg", "Egypt" },
344 { 222, "sv", "El Salvador" },
345 { 226, "gq", "Equatorial Guinea" },
346 { 232, "er", "Eritrea" },
347 { 233, "ee", "Estonia" },
348 { 231, "et", "Ethiopia" },
349 { 238, "fk", "Falkland Islands" },
350 { 234, "fo", "Faroe Islands" },
351 { 242, "fj", "Fiji" },
352 { 246, "fi", "Finland" },
353 { 250, "fr", "France" },
354 { 249, "fx", "France, Metropolitan" },
355 { 254, "gf", "French Guiana" },
356 { 258, "pf", "French Polynesia" },
357 { 260, "tf", "French Southern Territories" },
358 { 266, "ga", "Gabon" },
359 { 270, "gm", "Gambia" },
360 { 268, "ge", "Georgia" },
361 { 276, "de", "Germany" },
362 { 288, "gh", "Ghana" },
363 { 292, "gi", "Gibraltar" },
364 { 300, "gr", "Greece" },
365 { 304, "gl", "Greenland" },
366 { 308, "gd", "Grenada" },
367 { 312, "gp", "Guadeloupe" },
368 { 316, "gu", "Guam" },
369 { 320, "gt", "Guatemala" },
370 { 324, "gn", "Guinea" },
371 { 624, "gw", "Guinea-Bissau" },
372 { 328, "gy", "Guyana" },
373 { 332, "ht", "Haiti" },
374 { 334, "hm", "Heard & Mc Donald Islands" },
375 { 336, "va", "Vatican City" },
376 { 340, "hn", "Honduras" },
377 { 344, "hk", "Hong kong" },
378 { 348, "hu", "Hungary" },
379 { 352, "is", "Iceland" },
380 { 356, "in", "India" },
381 { 360, "id", "Indonesia" },
382 { 364, "ir", "Iran" },
383 { 368, "iq", "Iraq" },
384 { 372, "ie", "Ireland" },
385 { 376, "il", "Israel" },
386 { 380, "it", "Italy" },
387 { 388, "jm", "Jamaica" },
388 { 392, "jp", "Japan" },
389 { 400, "jo", "Jordan" },
390 { 398, "kz", "Kazakhstan" },
391 { 404, "ke", "Kenya" },
392 { 296, "ki", "Kiribati" },
393 { 408, "kp", "Korea (north)" },
394 { 410, "kr", "Korea (south)" },
395 { 414, "kw", "Kuwait" },
396 { 417, "kg", "Kyrgyzstan" },
397 { 418, "la", "Laos" },
398 { 428, "lv", "Latvia" },
399 { 422, "lb", "Lebanon" },
400 { 426, "ls", "Lesotho" },
401 { 430, "lr", "Liberia" },
402 { 434, "ly", "Libya" },
403 { 438, "li", "Liechtenstein" },
404 { 440, "lt", "Lithuania" },
405 { 442, "lu", "Luxembourg" },
406 { 446, "mo", "Macao" },
407 { 807, "mk", "Macedonia" },
408 { 450, "mg", "Madagascar" },
409 { 454, "mw", "Malawi" },
410 { 458, "my", "Malaysia" },
411 { 462, "mv", "Maldives" },
412 { 466, "ml", "Mali" },
413 { 470, "mt", "Malta" },
414 { 584, "mh", "Marshall Islands" },
415 { 474, "mq", "Martinique" },
416 { 478, "mr", "Mauritania" },
417 { 480, "mu", "Mauritius" },
418 { 175, "yt", "Mayotte" },
419 { 484, "mx", "Mexico" },
420 { 583, "fm", "Micronesia" },
421 { 498, "md", "Moldova" },
422 { 492, "mc", "Monaco" },
423 { 496, "mn", "Mongolia" },
424 { 500, "ms", "Montserrat" },
425 { 504, "ma", "Morocco" },
426 { 508, "mz", "Mozambique" },
427 { 104, "mm", "Myanmar" },
428 { 516, "na", "Namibia" },
429 { 520, "nr", "Nauru" },
430 { 524, "np", "Nepal" },
431 { 528, "nl", "Netherlands" },
432 { 530, "an", "Netherlands Antilles" },
433 { 540, "nc", "New Caledonia" },
434 { 554, "nz", "New Zealand" },
435 { 558, "ni", "Nicaragua" },
436 { 562, "ne", "Niger" },
437 { 566, "ng", "Nigeria" },
438 { 570, "nu", "Niue" },
439 { 574, "nf", "Norfolk Island" },
440 { 580, "mp", "Northern Mariana Islands" },
441 { 578, "no", "Norway" },
442 { 512, "om", "Oman" },
443 { 586, "pk", "Pakistan" },
444 { 585, "pw", "Palau" },
445 { 275, "ps", "Palestinian Territory" },
446 { 591, "pa", "Panama" },
447 { 598, "pg", "Papua New Guinea" },
448 { 600, "py", "Paraguay" },
449 { 604, "pe", "Peru" },
450 { 608, "ph", "Philippines" },
451 { 612, "pn", "Pitcairn" },
452 { 616, "pl", "Poland" },
453 { 620, "pt", "Portugal" },
454 { 630, "pr", "Puerto Rico" },
455 { 634, "qa", "Qatar" },
456 { 638, "re", "Reunion" },
457 { 642, "ro", "Romania" },
458 { 643, "ru", "Russia" },
459 { 646, "rw", "Rwanda" },
460 { 659, "kn", "Saint Kitts & Nevis" },
461 { 662, "lc", "Saint Lucia" },
462 { 670, "vc", "Saint Vincent" },
463 { 882, "ws", "Samoa" },
464 { 674, "sm", "San Marino" },
465 { 678, "st", "Sao Tome & Principe" },
466 { 682, "sa", "Saudi Arabia" },
467 { 686, "sn", "Senegal" },
468 { 891, "cs", "Serbia and Montenegro" },
469 { 690, "sc", "Seychelles" },
470 { 694, "sl", "Sierra Leone" },
471 { 702, "sg", "Singapore" },
472 { 703, "sk", "Slovakia" },
473 { 705, "si", "Slovenia" },
474 { 90, "sb", "Solomon Islands" },
475 { 706, "so", "Somalia" },
476 { 710, "za", "South Africa" },
477 { 239, "gs", "South Georgia" },
478 { 724, "es", "Spain" },
479 { 144, "lk", "Sri Lanka" },
480 { 654, "sh", "St. Helena" },
481 { 666, "pm", "St. Pierre & Miquelon" },
482 { 736, "sd", "Sudan" },
483 { 740, "sr", "Suriname" },
484 { 744, "sj", "Svalbard & Jan Mayen Islands" },
485 { 748, "sz", "Swaziland" },
486 { 752, "se", "Sweden" },
487 { 756, "ch", "Switzerland" },
488 { 760, "sy", "Syrian Arab Republic" },
489 { 626, "tl", "Timor-Leste" },
490 { 158, "tw", "Taiwan" },
491 { 762, "tj", "Tajikistan" },
492 { 834, "tz", "Tanzania" },
493 { 764, "th", "Thailand" },
494 { 768, "tg", "Togo" },
495 { 772, "tk", "Tokelau" },
496 { 776, "to", "Tonga" },
497 { 780, "tt", "Trinidad & Tobago" },
498 { 788, "tn", "Tunisia" },
499 { 792, "tr", "Turkey" },
500 { 795, "tm", "Turkmenistan" },
501 { 796, "tc", "Turks & Caicos Islands" },
502 { 798, "tv", "Tuvalu" },
503 { 800, "ug", "Uganda" },
504 { 804, "ua", "Ukraine" },
505 { 784, "ae", "United Arab Emirates" },
506 { 826, "gb", "United Kingdom" },
507 { 840, "us", "United States" },
508 { 581, "um", "United States Minor Outlying Islands" },
509 { 858, "uy", "Uruguay" },
510 { 860, "uz", "Uzbekistan" },
511 { 548, "vu", "Vanuatu" },
512 { 862, "ve", "Venezuela" },
513 { 704, "vn", "Vietnam" },
514 { 92, "vg", "Virgin Islands (British)" },
515 { 850, "vi", "Virgin Islands (US)" },
516 { 876, "wf", "Wallis & Futuna Islands" },
517 { 732, "eh", "Western Sahara" },
518 { 887, "ye", "Yemen" },
519 { 894, "zm", "Zambia" },
520 { 716, "zw", "Zimbabwe" }
521 };
522
523 /*
524 * Check if start of 'str' is simply an IPv4 address.
525 */
526 #define BYTE_OK(x) ((x) >= 0 && (x) <= 255)
527
is_addr(char * str,char ** end)528 static int is_addr(char *str, char **end)
529 {
530 int a0, a1, a2, a3, num, rc = 0, length = 0;
531
532 num = sscanf(str,"%3d.%3d.%3d.%3d%n",&a0,&a1,&a2,&a3,&length);
533 if( (num == 4) &&
534 BYTE_OK(a0) && BYTE_OK(a1) && BYTE_OK(a2) && BYTE_OK(a3) &&
535 length >= (3+4))
536 {
537 rc = 1;
538 *end = str + length;
539 }
540 return rc;
541 }
542
543 /*
544 * Find the country-code and name from the CNAME. E.g.:
545 * version 1: CNAME = zzno.countries.nerd.dk with address 127.0.2.66
546 * yields ccode_A" = "no" and cnumber 578 (2.66).
547 * version 2: CNAME = <a.b.c.d>.zz.countries.nerd.dk with address 127.0.2.66
548 * yields cnumber 578 (2.66). ccode_A is "";
549 */
find_country_from_cname(const char * cname,struct in_addr addr)550 static void find_country_from_cname(const char *cname, struct in_addr addr)
551 {
552 const struct search_list *country;
553 char ccode_A2[3], *ccopy, *dot_4;
554 int cnumber, z0, z1, ver_1, ver_2;
555 unsigned long ip;
556
557 ip = ntohl(addr.s_addr);
558 z0 = TOLOWER(cname[0]);
559 z1 = TOLOWER(cname[1]);
560 ccopy = strdup(cname);
561 dot_4 = NULL;
562
563 ver_1 = (z0 == 'z' && z1 == 'z' && !strcasecmp(cname+4,nerd_ver1));
564 ver_2 = (is_addr(ccopy,&dot_4) && !strcasecmp(dot_4,nerd_ver2));
565
566 if (ver_1)
567 {
568 const char *dot = strchr(cname, '.');
569 if (dot != cname+4)
570 {
571 printf("Unexpected CNAME %s (ver_1)\n", cname);
572 free(ccopy);
573 return;
574 }
575 }
576 else if (ver_2)
577 {
578 z0 = TOLOWER(dot_4[1]);
579 z1 = TOLOWER(dot_4[2]);
580 if (z0 != 'z' && z1 != 'z')
581 {
582 printf("Unexpected CNAME %s (ver_2)\n", cname);
583 free(ccopy);
584 return;
585 }
586 }
587 else
588 {
589 printf("Unexpected CNAME %s (ver?)\n", cname);
590 free(ccopy);
591 return;
592 }
593
594 if (ver_1)
595 {
596 ccode_A2[0] = (char)TOLOWER(cname[2]);
597 ccode_A2[1] = (char)TOLOWER(cname[3]);
598 ccode_A2[2] = '\0';
599 }
600 else
601 ccode_A2[0] = '\0';
602
603 cnumber = ip & 0xFFFF;
604
605 TRACE(("Found country-code `%s', number %d\n",
606 ver_1 ? ccode_A2 : "<n/a>", cnumber));
607
608 country = list_lookup(cnumber, country_list,
609 sizeof(country_list) / sizeof(country_list[0]));
610 if (!country)
611 printf("Name for country-number %d not found.\n", cnumber);
612 else
613 {
614 if (ver_1)
615 {
616 if ((country->short_name[0] != ccode_A2[0]) ||
617 (country->short_name[1] != ccode_A2[1]) ||
618 (country->short_name[2] != ccode_A2[2]))
619 printf("short-name mismatch; %s vs %s\n",
620 country->short_name, ccode_A2);
621 }
622 printf("%s (%s), number %d.\n",
623 country->long_name, country->short_name, cnumber);
624 }
625 free(ccopy);
626 }
627