#define _GNU_SOURCE #include #include #include #include #include #include "ipcalc.h" #ifdef USE_MAXMIND #include #ifndef MAXMINDDB_LOCATION_COUNTRY #define MAXMINDDB_LOCATION_COUNTRY "/usr/share/GeoIP/GeoLite2-Country.mmdb" #endif #ifndef MAXMINDDB_LOCATION_CITY #define MAXMINDDB_LOCATION_CITY "/usr/share/GeoIP/GeoLite2-City.mmdb" #endif #ifdef USE_RUNTIME_LINKING # include # define LIBNAME LIBPATH"/libmaxminddb.so.0" typedef int (*MMDB_open_fn) (const char *const filename, uint32_t flags, MMDB_s *const mmdb); typedef void (*MMDB_close_fn) (MMDB_s *const mmdb); typedef MMDB_lookup_result_s (*MMDB_lookup_string_fn) (MMDB_s *const mmdb, const char *const ipstr, int *const gai_error, int *const mmdb_error); typedef int (*MMDB_get_value_fn) (MMDB_entry_s *const start, MMDB_entry_data_s *const entry_data, ...); static MMDB_close_fn pMMDB_close; static MMDB_get_value_fn pMMDB_get_value; static MMDB_lookup_string_fn pMMDB_lookup_string; static MMDB_open_fn pMMDB_open; int geo_setup(void) { static void *ld = NULL; static int ret = 0; static char err[256] = {0}; if (ld != NULL || ret != 0) { if (!beSilent && err[0] != 0) { fprintf(stderr, "%s", err); } return ret; } ld = dlopen(LIBNAME, RTLD_LAZY); if (ld == NULL) { snprintf(err, sizeof(err), "ipcalc: could not open %s\n", LIBNAME); ret = -1; goto exit; } pMMDB_close = dlsym(ld, "MMDB_close"); pMMDB_get_value = dlsym(ld, "MMDB_get_value"); pMMDB_lookup_string = dlsym(ld, "MMDB_lookup_string"); pMMDB_open = dlsym(ld, "MMDB_open"); if(pMMDB_close == NULL || pMMDB_get_value == NULL || pMMDB_lookup_string == NULL || pMMDB_open == NULL) { snprintf(err, sizeof(err), "ipcalc: could not find symbols in libmaxmind\n"); ret = -1; goto exit; } ret = 0; exit: return ret; } #else #define pMMDB_close MMDB_close #define pMMDB_get_value MMDB_get_value #define pMMDB_lookup_string MMDB_lookup_string #define pMMDB_open MMDB_open #endif void process_result_from_mmdb_lookup(MMDB_entry_data_s *entry_data, int status, char **output) { if (MMDB_SUCCESS == status) { if (entry_data->has_data) { if (entry_data->type == MMDB_DATA_TYPE_UTF8_STRING) { *output = (char *) calloc(entry_data->data_size + 1, sizeof(char)); if (NULL != *output) { memcpy(*output, entry_data->utf8_string, entry_data->data_size); } else { fprintf(stderr, "Memory allocation failure line %d\n", __LINE__); } } } } /* Else fail silently */ } void geo_ip_lookup(const char *ip, char **country, char **ccode, char **city, char **coord) { MMDB_s mmdb; MMDB_entry_data_s entry_data; int gai_error, mmdb_error, status, coordinates=0; double latitude, longitude; /* Open the system maxmind database with countries */ status = pMMDB_open(MAXMINDDB_LOCATION_COUNTRY, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS == status) { /* Lookup IP address in the database */ MMDB_lookup_result_s result = pMMDB_lookup_string(&mmdb, ip, &gai_error, &mmdb_error); if (MMDB_SUCCESS == mmdb_error) { /* If the lookup was successfull and an entry was found */ if (result.found_entry) { memset(&entry_data, 0, sizeof(MMDB_entry_data_s)); /* Travel the path in the tree like structure of the MMDB and store the value if found */ status = pMMDB_get_value(&result.entry, &entry_data, "country", "names", "en", NULL); process_result_from_mmdb_lookup(&entry_data, status, country); memset(&entry_data, 0, sizeof(MMDB_entry_data_s)); status = pMMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL); process_result_from_mmdb_lookup(&entry_data, status, ccode); } } /* Else fail silently */ pMMDB_close(&mmdb); } /* Else fail silently */ /* Open the system maxmind database with cities - which actually does not contain names of the cities */ status = pMMDB_open(MAXMINDDB_LOCATION_CITY, MMDB_MODE_MMAP, &mmdb); if (MMDB_SUCCESS == status) { /* Lookup IP address in the database */ MMDB_lookup_result_s result = pMMDB_lookup_string(&mmdb, ip, &gai_error, &mmdb_error); if (MMDB_SUCCESS == mmdb_error) { /* If the lookup was successfull and an entry was found */ if (result.found_entry) { memset(&entry_data, 0, sizeof(MMDB_entry_data_s)); // NOTE: Information about the city is not available in the free database, so there is not way // for me to implement this functionality right now, but it should be easy to add for anyone with // access to the paid databases. status = pMMDB_get_value(&result.entry, &entry_data, "location", "latitude", NULL); if (MMDB_SUCCESS == status) { if (entry_data.has_data) { if (entry_data.type == MMDB_DATA_TYPE_DOUBLE) { latitude = entry_data.double_value; ++coordinates; } } } status = pMMDB_get_value(&result.entry, &entry_data, "location", "longitude", NULL); if (MMDB_SUCCESS == status) { if (entry_data.has_data) { if (entry_data.type == MMDB_DATA_TYPE_DOUBLE) { longitude = entry_data.double_value; ++coordinates; } } } if (coordinates == 2) { safe_asprintf(coord, "%f,%f", latitude, longitude); } } } /* Else fail silently */ pMMDB_close(&mmdb); } /* Else fail silently */ } #endif