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