/* * maxminddb.c - MaxMind GeoIP2 dlfunc for Exim * https://mta.org.ua/exim-4.88-conf/dlfunc/maxminddb/maxminddb.c * * Copyright (C) 2018 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" /* Exim4 dlfunc API header: */ //#include "local_scan.h" #include "exim.h" /***************************************************************************** * Configuration settings: * *****************************************************************************/ /* default code returned when country is unknown: */ #define RESULT_UNKNOWN US"--" /* default code returned on lookup failures: */ #define RESULT_LOOKUP_FAILED US"--" /************************************************* * Case-independent strcmp() function * * from Exim sources * *************************************************/ /* Arguments: s first string t second string Returns: < 0, = 0, or > 0, according to the comparison */ int strcmpic(const uschar *s, const uschar *t) { while (*s != 0) { int c = tolower(*s++) - tolower(*t++); if (c != 0) return c; } return *t; } /***************************************************************************** * Country code lookup function: * *****************************************************************************/ int maxminddb(uschar **yield, int argc, uschar *argv[]) { char *filename = (char *)argv[0]; char *ip_address = (char *)argv[1]; 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) { defer_ok = 0; } else if ( (strcmpic(argv[2], US"1") == 0) || (strcmpic(argv[2], US"yes") == 0) || (strcmpic(argv[2], US"true") == 0) || (strcmpic(argv[2], 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 *continent = NULL; if (result.found_entry) { 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: Got an error looking up the entry data for country ISO code - %s; Try to find \"country\" ISO code instead of \"registered_country\" ISO code\n", MMDB_strerror(status)); status = MMDB_get_value(&result.entry, &entry_data, "country", "iso_code", NULL); } if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for country ISO code - %s\n", MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for country ISO code - %s", 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 - %s\n", MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if (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", MMDB_strerror(MMDB_OUT_OF_MEMORY_ERROR)); goto LOOKUP_FAILED_MMDB_CLOSE; } } debug_printf("maxminddb dlfunc: country is found: %s\n", country); } else { debug_printf("maxminddb dlfunc: Unexpected data type for country ISO code entry for this IP address (%s) was found\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for country ISO code entry for this IP address (%s) was found", ip_address); } } else { debug_printf("maxminddb dlfunc: No country ISO code entry for this IP address (%s) was found\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: No country ISO code entry for this IP address (%s) was found", ip_address); } status = MMDB_get_value(&result.entry, &entry_data, "continent", "code", NULL); if (status != MMDB_SUCCESS) { if (defer_ok) { debug_printf("maxminddb dlfunc: Got an error looking up the entry data for continent code - %s\n", MMDB_strerror(status)); log_write(0, LOG_PANIC, "maxminddb dlfunc: Got an error looking up the entry data for continent code - %s", 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 - %s\n", MMDB_strerror(status)); goto LOOKUP_FAILED_MMDB_CLOSE; } } if (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 is found: %s\n", continent); } else { debug_printf("maxminddb dlfunc: Unexpected data type for continent code entry for this IP address (%s) was found\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: Unexpected data type for continent code entry for this IP address (%s) was found", ip_address); } } else { debug_printf("maxminddb dlfunc: No continent code entry for this IP address (%s) was found\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: No continent code entry for this IP address (%s) was found", ip_address); } } else { debug_printf("maxminddb dlfunc: No entry for this IP address (%s) was found\n", ip_address); log_write(0, LOG_MAIN, "maxminddb dlfunc: No entry for this IP address (%s) was found", ip_address); } MMDB_close(&mmdb); *yield = string_sprintf("country=\"%s\" continent=\"%s\"", (country == NULL ? (char *)RESULT_UNKNOWN : country), (continent == NULL ? (char *)RESULT_UNKNOWN : continent)); if (country != NULL) free(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\" continent=\"%s\"", RESULT_LOOKUP_FAILED, RESULT_LOOKUP_FAILED); if (country != NULL) free(country); if (continent != NULL) free(continent); return OK; LOOKUP_FAILED_MMDB_CLOSE: MMDB_close(&mmdb); if (country != NULL) free(country); if (continent != NULL) free(continent); return ERROR; }