From 960032e065e20789669bf6c79599a2b74b837a73 Mon Sep 17 00:00:00 2001 From: Nikos Mavrogiannopoulos Date: Mon, 14 Oct 2019 14:54:40 +0200 Subject: [PATCH] occtl: use maxminddb when available Signed-off-by: Nikos Mavrogiannopoulos --- NEWS | 1 + configure.ac | 18 ++++- src/Makefile.am | 13 +++- src/occtl/maxmind.c | 171 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 src/occtl/maxmind.c diff --git a/NEWS b/NEWS index 150709c2..93410839 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ at the same time. - occtl: fixed json output of show status command. Introduced tests for checking its json output using yajl (#220). +- occtl: use maxminddb when available. * Version 0.12.4 (released 2019-07-03) diff --git a/configure.ac b/configure.ac index 7b436120..eea3e93c 100644 --- a/configure.ac +++ b/configure.ac @@ -163,13 +163,28 @@ if test "$test_for_libnl" = yes;then fi fi +AC_ARG_WITH(maxmind, + AS_HELP_STRING([--without-maxmind], [do not try to use the maxmind library]), + test_for_maxmind=$withval, + test_for_maxmind=yes) + +have_maxmind=no +if test "$test_for_maxmind" = yes;then + PKG_CHECK_MODULES(MAXMIND, libmaxminddb >= 1.0.0, [have_maxmind=yes], [have_maxmind=no]) + if test "$have_maxmind" = yes;then + AC_DEFINE(HAVE_MAXMIND, 1, [have libmaxminddb]) + fi +fi + +AM_CONDITIONAL(HAVE_MAXMIND, test "x$have_maxmind" != xno) + AC_ARG_WITH(geoip, AS_HELP_STRING([--without-geoip], [do not try to use the geoip library]), test_for_geoip=$withval, test_for_geoip=yes) have_geoip=no -if test "$test_for_geoip" = yes;then +if test "$test_for_geoip" = yes && test "$have_maxmind" != yes;then PKG_CHECK_MODULES(GEOIP, geoip >= 1.6.0, [have_geoip=yes], [have_geoip=no]) if test "$have_geoip" = yes;then AC_DEFINE(HAVE_GEOIP, 1, [have libgeoip]) @@ -601,6 +616,7 @@ Summary of build options: libnl3: ${have_libnl3} liboath: ${have_liboath} libgeoip: ${have_geoip} + libmaxminddb: ${have_maxmind} glibc (sha2crypt): ${have_glibc} local talloc: ${with_local_talloc} local protobuf-c: ${with_local_protobuf_c} diff --git a/src/Makefile.am b/src/Makefile.am index dfe5e3a2..439d1a14 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,15 +98,22 @@ clean-local: bin_PROGRAMS = occtl/occtl -occtl_occtl_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/occtl $(LIBNL3_CFLAGS) $(GEOIP_CFLAGS) +occtl_occtl_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/occtl $(LIBNL3_CFLAGS) $(GEOIP_CFLAGS) $(MAXMIND_CFLAGS) occtl_occtl_SOURCES = occtl/occtl.c occtl/pager.c occtl/occtl.h occtl/time.c occtl/cache.c \ occtl/ip-cache.c occtl/nl.c occtl/ctl.h occtl/print.c occtl/json.c occtl/json.h \ - occtl/hex.c occtl/hex.h occtl/unix.c occtl/geoip.c occtl/geoip.h \ + occtl/hex.c occtl/hex.h occtl/unix.c occtl/geoip.h \ occtl/session-cache.c + +if HAVE_MAXMIND +occtl_occtl_SOURCES += occtl/maxmind.c +else +occtl_occtl_SOURCES += occtl/geoip.c +endif + occtl_occtl_LDADD = ../gl/libgnu.a libcommon.a $(LIBREADLINE_LIBS) \ $(LIBNL3_LIBS) $(NEEDED_LIBPROTOBUF_LIBS) $(LIBTALLOC_LIBS) libccan.a \ libipc.a $(NEEDED_LIBPROTOBUF_LIBS) $(CODE_COVERAGE_LDFLAGS) \ - $(LIBNETTLE_LIBS) $(GEOIP_LIBS) + $(LIBNETTLE_LIBS) $(MAXMIND_LIBS) $(GEOIP_LIBS) # Ocpasswd diff --git a/src/occtl/maxmind.c b/src/occtl/maxmind.c new file mode 100644 index 00000000..1f29dbc8 --- /dev/null +++ b/src/occtl/maxmind.c @@ -0,0 +1,171 @@ +#include +#include +#include +#include +#include +#include + +#include "geoip.h" + +#ifdef HAVE_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 + +#define pMMDB_close MMDB_close +#define pMMDB_get_value MMDB_get_value +#define pMMDB_lookup_string MMDB_lookup_string +#define pMMDB_open MMDB_open + +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 */ +} + +char *geo_lookup(const char *ip, char *buf, unsigned buf_size) +{ + MMDB_s mmdb; + MMDB_entry_data_s entry_data; + int gai_error, mmdb_error, status, coordinates = 0; + double latitude, longitude; + char *country = NULL, *ccode = NULL; + char *coord = NULL; + unsigned found = 0; + + /* 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) { + asprintf(&coord, "%f,%f", latitude, + longitude); + } + } + + } + pMMDB_close(&mmdb); + } + + if (country && coord) { + snprintf(buf, buf_size, "%s (%s)", country, coord); + found = 1; + } else if (ccode && coord) { + snprintf(buf, buf_size, "%s (%s)", ccode, coord); + found = 1; + } else if (country) { + snprintf(buf, buf_size, "%s", country); + found = 1; + } else if (ccode) { + snprintf(buf, buf_size, "%s", ccode); + found = 1; + } + + free(ccode); + free(country); + free(coord); + + if (found) + return buf; + else + return "unknown"; +} + +#endif