/* * maxminddb.c - MaxMind GeoIP2 dlfunc for Exim * https://mta.org.ua/exim-4.94-conf/dlfunc/maxminddb/maxminddb.c * * Copyright (C) 2018-2023 Victor Ustugov * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include /* libmaxmind utils: */ #include "maxminddb-compat-util.h" //#define DLFUNC_IMPL /* Exim4 dlfunc API header: */ //#include "local_scan.h" //#include "macros.h" #include "exim.h" //# define string_copy(s) string_copy_function(s) //# define string_copyn(s, n) string_copyn_function((s), (n)) //# define string_copy_taint(s, t) string_copy_taint_function((s), (t)) extern uschar * string_copy_function(const uschar *); extern uschar * string_copyn_function(const uschar *, int n); extern uschar * string_copy_taint_function(const uschar *, BOOL tainted); #define string_sprintf(fmt, ...) \ string_sprintf_trc(fmt, US __FUNCTION__, __LINE__, __VA_ARGS__) extern uschar *string_sprintf_trc(const char *, const uschar *, unsigned, ...) ALMOST_PRINTF(1,4); /***************************************************************************** * Configuration settings: * *****************************************************************************/ /* default code returned when country is unknown: */ #define RESULT_UNKNOWN US"--" /* default code returned on lookup failures: */ #define RESULT_LOOKUP_FAILED US"--" //------------------------------------------------------------------------- int maxminddb(uschar **yield, int argc, uschar *argv[]) { char *filename = (char *)argv[0]; char *ip_address = (char *)argv[1]; uschar *search_type = NULL; int defer_ok = 0; int status; MMDB_s mmdb; if (argc < 2) { debug_printf("maxminddb dlfunc: Invalid number of arguments\n"); *yield = string_copy(US"Invalid number of arguments"); return ERROR; } if (argc >= 3) { search_type = argv[2]; } if (argc < 4) { defer_ok = 0; } else if ( (strcmpic(argv[3], US"1") == 0) || (strcmpic(argv[3], US"yes") == 0) || (strcmpic(argv[3], US"true") == 0) || (strcmpic(argv[3], US"defer_ok") == 0) ) { defer_ok = 1; } else { defer_ok = 0; } debug_printf("maxminddb dlfunc: defer_ok: %d\n", defer_ok); status = MMDB_open(filename, MMDB_MODE_MMAP, &mmdb); if (status != MMDB_SUCCESS) { if (defer_ok) { if (status == MMDB_IO_ERROR) { debug_printf("maxminddb dlfunc: IO error: %s\n", strerror(errno)); log_write(0, LOG_PANIC, "maxminddb dlfunc: IO error: %s", strerror(errno)); } else { debug_printf("maxminddb dlfunc: Can't open %s - %s\n", filename, MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Can't open %s - %s", filename, MMDB_strerror(status)); } goto LOOKUP_FAILED_DEFER_OK; } else { if (status == MMDB_IO_ERROR) { *yield = string_sprintf("IO error: %s", strerror(errno)); } else { *yield = string_sprintf("Can't open %s - %s", filename, MMDB_strerror(status)); } return ERROR; } } int gai_error, mmdb_error; MMDB_lookup_result_s result = MMDB_lookup_string(&mmdb, ip_address, &gai_error, &mmdb_error); if (gai_error != 0) { if (defer_ok) { debug_printf("maxminddb dlfunc: Error from getaddrinfo for %s - %s\n", ip_address, gai_strerror(gai_error)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Error from getaddrinfo for %s - %s", ip_address, gai_strerror(gai_error)); goto LOOKUP_FAILED_DEFER_OK; } else { *yield = string_sprintf("Error from getaddrinfo for %s - %s", ip_address, gai_strerror(gai_error)); return ERROR; } } if ( mmdb_error != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error from libmaxminddb: %s\n", MMDB_strerror(mmdb_error)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error from libmaxminddb: %s", MMDB_strerror(mmdb_error)); goto LOOKUP_FAILED_DEFER_OK; } else { *yield = string_sprintf("Got an error from libmaxminddb: %s", MMDB_strerror(mmdb_error)); return ERROR; } } MMDB_entry_data_list_s *entry_data_list = NULL; MMDB_entry_data_s entry_data; char *country = NULL; char *registered_country = NULL; char *continent = NULL; uschar *asn = NULL; if (result.found_entry) { if ((search_type == NULL) || (strstric(search_type, US"country", FALSE) != NULL)) { debug_printf("maxminddb dlfunc: Try to get the entry data for country ISO code (IP address %s)\n", ip_address); status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL); if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { debug_printf("maxminddb dlfunc: No country ISO code entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_MAIN, "maxminddb dlfunc: No country ISO code entry found (IP address %s)", ip_address); } else if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for country ISO code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for country ISO code (IP address %s) - %s", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an error looking up the entry data for country ISO code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if ((status == MMDB_SUCCESS) && (entry_data.has_data)) { if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) { country = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size); if (country == NULL) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an memory error - %s", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an memory error - %s\n","maxminddb", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_MMDB_CLOSE; } } debug_printf("maxminddb dlfunc: country has found: %s\n", country); } else { debug_printf("maxminddb dlfunc: Unexpected data type for country ISO code entry found (IP address %s)\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for country ISO code entry found (IP address %s)", ip_address); } } } if (strstric(search_type, US"registered_country", FALSE) != NULL) { debug_printf("maxminddb dlfunc: Try to get the entry data for registered_country ISO code (IP address %s)\n", ip_address); status = MMDB_get_value(&result.entry, &entry_data, "registered_country", "iso_code", NULL); if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { debug_printf("maxminddb dlfunc: No registered_country ISO code entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_MAIN, "maxminddb dlfunc: No registered_country ISO code entry found (IP address %s)", ip_address); } else if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for registered_country ISO code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for registered_country ISO code (IP address %s) - %s", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an error looking up the entry data for registered_country ISO code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if ((status == MMDB_SUCCESS) && (entry_data.has_data)) { if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) { registered_country = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size); if (registered_country == NULL) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an memory error - %s", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an memory error - %s\n","maxminddb", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_MMDB_CLOSE; } } debug_printf("maxminddb dlfunc: registered_country has found: %s\n", registered_country); } else { debug_printf("maxminddb dlfunc: Unexpected data type for registered_country ISO code entry found (IP address %s)\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for registered_country ISO code entry found (IP address %s)", ip_address); } } } if (strstric(search_type, US"continent", FALSE) != NULL) { debug_printf("maxminddb dlfunc: Try to get the entry data for continent code (IP address %s)\n", ip_address); status = MMDB_get_value(&result.entry, &entry_data, "continent", "code", NULL); if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { debug_printf("maxminddb dlfunc: No continent code entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_MAIN, "maxminddb dlfunc: No continent code entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); } else if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for continent code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for continent code (IP address %s) - %s", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an error looking up the entry data for continent code (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if ((status == MMDB_SUCCESS) && (entry_data.has_data)) { if (entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) { continent = mmdb_strndup((char *)entry_data.utf8_string, entry_data.data_size); if (continent == NULL) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an memory error - %s", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_MMDB_CLOSE; } } debug_printf("maxminddb dlfunc: continent has found: %s\n", continent); } else { debug_printf("maxminddb dlfunc: Unexpected data type for continent code entry found (IP address %s)\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for continent code entry found (IP address %s)", ip_address); } } } if (strstric(search_type, US"asn", FALSE) != NULL) { debug_printf("maxminddb dlfunc: Try to get the entry data for AS number (IP address %s)\n", ip_address); status = MMDB_get_value(&result.entry, &entry_data, "autonomous_system_number", NULL); if (status == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) { debug_printf("maxminddb dlfunc: No AS number entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_MAIN, "maxminddb dlfunc: No AS number entry found (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); } else if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for AS number (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for AS number (IP address %s) - %s", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an error looking up the entry data for AS number (IP address %s) - %s\n", ip_address, MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if ((status == MMDB_SUCCESS) && (entry_data.has_data)) { if (entry_data.type == MMDB_DATA_TYPE_UINT32) { asn = string_sprintf("%u", entry_data.uint32); if (asn == NULL) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an memory error - %s", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE; } else { *yield = string_sprintf("Got an memory error - %s\n", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_MMDB_CLOSE; } } debug_printf("maxminddb dlfunc: asn has found: %s\n", asn); } else { debug_printf("maxminddb dlfunc: Unexpected data type for AS number entry found (IP address %s)\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for AS number entry found (IP address %s)", ip_address); } } } } else { debug_printf("maxminddb dlfunc: No entry found (IP address %s)\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: No entry found (IP address %s)", ip_address); } MMDB_close(&mmdb); *yield = string_sprintf("country=\"%s\" registered_country=\"%s\" continent=\"%s\" asn=\"%s\"", (country == NULL ? (char *)RESULT_UNKNOWN : country), (registered_country == NULL ? (char *)RESULT_UNKNOWN : registered_country), (continent == NULL ? (char *)RESULT_UNKNOWN : continent), (asn == NULL ? (char *)RESULT_UNKNOWN : (char *)asn) ); if (country != NULL) free(country); if (registered_country != NULL) free(registered_country); if (continent != NULL) free(continent); return OK; LOOKUP_FAILED_DEFER_OK_MMDB_CLOSE: MMDB_close(&mmdb); LOOKUP_FAILED_DEFER_OK: *yield = string_sprintf("country=\"%s\" registered_country=\"%s\" continent=\"%s\" asn=\"%s\"", (country == NULL ? (char *)RESULT_UNKNOWN : country), (registered_country == NULL ? (char *)RESULT_UNKNOWN : registered_country), (continent == NULL ? (char *)RESULT_UNKNOWN : continent), (asn == NULL ? (char *)RESULT_UNKNOWN : (char *)asn) ); if (country != NULL) free(country); if (registered_country != NULL) free(registered_country); if (continent != NULL) free(continent); return OK; LOOKUP_FAILED_MMDB_CLOSE: MMDB_close(&mmdb); if (country != NULL) free(country); if (registered_country != NULL) free(registered_country); if (continent != NULL) free(continent); return ERROR; }