/*
* ProFTPD: mod_geoip2 -- a module for looking up country/city/etc for clients
* Copyright (c) 2019 TJ Saunders
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
*
* As a special exemption, TJ Saunders and other respective copyright holders
* give permission to link this program with OpenSSL, and distribute the
* resulting executable, without including the source code for OpenSSL in the
* source distribution.
*
* This is mod_geoip2, contrib software for proftpd 1.3.x and above.
* For more information contact TJ Saunders <tj@castaglia.org>.
*
* --- DO NOT DELETE BELOW THIS LINE ----
* $Libraries: -lmaxminddb$
*/
#include "conf.h"
#include "privs.h"
/* A lot of ideas for this module were liberally borrowed from the mod_geoip
* module for Apache.
*/
#define MOD_GEOIP2_VERSION "mod_geoip2/0.1"
/* Make sure the version of proftpd is as necessary. */
#if PROFTPD_VERSION_NUMBER < 0x0001030402
# error "ProFTPD 1.3.4rc2 or later required"
#endif
#include <maxminddb.h>
module geoip2_module;
static int geoip2_engine = FALSE;
static int geoip2_logfd = -1;
static pool *geoip2_pool = NULL;
static array_header *geoip2_mmdbs = NULL;
/* The types of data that GeoIP can provide, and that we care about. */
static const char *geoip_city = NULL;
static const char *geoip_postal_code = NULL;
static const char *geoip_latitude = NULL;
static const char *geoip_longitude = NULL;
static const char *geoip_org = NULL;
static const char *geoip_country_code2 = NULL;
static const char *geoip_country_name = NULL;
static const char *geoip_region_code = NULL;
static const char *geoip_region_name = NULL;
static const char *geoip_continent_code = NULL;
static const char *geoip_asn = NULL;
static const char *geoip_timezone = NULL;
/* Names of supported GeoIP values */
#define GEOIP_FILTER_KEY_COUNTRY_CODE 100
#define GEOIP_FILTER_KEY_COUNTRY_NAME 101
#define GEOIP_FILTER_KEY_REGION_CODE 102
#define GEOIP_FILTER_KEY_REGION_NAME 103
#define GEOIP_FILTER_KEY_CONTINENT 104
#define GEOIP_FILTER_KEY_ORGANIZATION 105
#define GEOIP_FILTER_KEY_CITY 106
#define GEOIP_FILTER_KEY_POSTAL_CODE 107
#define GEOIP_FILTER_KEY_LATITUDE 108
#define GEOIP_FILTER_KEY_LONGITUDE 109
#define GEOIP_FILTER_KEY_ASN 110
#define GEOIP_FILTER_KEY_TIMEZONE 111
struct geoip_filter_key {
const char *filter_name;
int filter_id;
};
static struct geoip_filter_key geoip_filter_keys[] = {
{ "CountryCode", GEOIP_FILTER_KEY_COUNTRY_CODE },
{ "CountryName", GEOIP_FILTER_KEY_COUNTRY_NAME },
{ "RegionCode", GEOIP_FILTER_KEY_REGION_CODE },
{ "RegionName", GEOIP_FILTER_KEY_REGION_NAME },
{ "Continent", GEOIP_FILTER_KEY_CONTINENT },
{ "Organization", GEOIP_FILTER_KEY_ORGANIZATION },
{ "City", GEOIP_FILTER_KEY_CITY },
{ "PostalCode", GEOIP_FILTER_KEY_POSTAL_CODE },
{ "Latitude", GEOIP_FILTER_KEY_LATITUDE },
{ "Longitude", GEOIP_FILTER_KEY_LONGITUDE },
{ "ASN", GEOIP_FILTER_KEY_ASN },
{ "Timezone", GEOIP_FILTER_KEY_TIMEZONE },
{ NULL, -1 }
};
#if PR_USE_REGEX
/* GeoIP filter */
struct geoip_filter {
int filter_id;
const char *filter_pattern;
pr_regex_t *filter_re;
};
#endif /* PR_USE_REGEX */
/* GeoIP policies */
typedef enum {
GEOIP_POLICY_ALLOW_DENY,
GEOIP_POLICY_DENY_ALLOW
} geoip_policy_e;
static geoip_policy_e geoip_policy = GEOIP_POLICY_ALLOW_DENY;
static const char *trace_channel = "geoip2";
static const char *get_geoip_filter_name(int);
static const char *get_geoip_filter_value(int);
static int get_filter_id(const char *filter_name) {
register unsigned int i;
int filter_id = -1;
for (i = 0; geoip_filter_keys[i].filter_name != NULL; i++) {
if (strcasecmp(filter_name, geoip_filter_keys[i].filter_name) == 0) {
filter_id = geoip_filter_keys[i].filter_id;
break;
}
}
return filter_id;
}
#if PR_USE_REGEX
static int get_filter(pool *p, const char *pattern, pr_regex_t **pre) {
int res;
*pre = pr_regexp_alloc(&geoip2_module);
res = pr_regexp_compile(*pre, pattern, REG_EXTENDED|REG_NOSUB|REG_ICASE);
if (res != 0) {
char errstr[256];
memset(errstr, '\0', sizeof(errstr));
pr_regexp_error(res, *pre, errstr, sizeof(errstr)-1);
pr_regexp_free(&geoip2_module, *pre);
*pre = NULL;
pr_log_pri(PR_LOG_DEBUG, MOD_GEOIP2_VERSION
": pattern '%s' failed regex compilation: %s", pattern, errstr);
errno = EINVAL;
return -1;
}
return res;
}
static struct geoip_filter *make_filter(pool *p, const char *filter_name,
const char *pattern) {
struct geoip_filter *filter;
int filter_id;
pr_regex_t *pre = NULL;
filter_id = get_filter_id(filter_name);
if (filter_id < 0) {
pr_log_debug(DEBUG0, MOD_GEOIP2_VERSION ": unknown GeoIP filter name '%s'",
filter_name);
return NULL;
}
if (get_filter(p, pattern, &pre) < 0) {
return NULL;
}
filter = pcalloc(p, sizeof(struct geoip_filter));
filter->filter_id = filter_id;
filter->filter_pattern = pstrdup(p, pattern);
filter->filter_re = pre;
return filter;
}
static array_header *get_sql_filters(pool *p, const char *query_name) {
register unsigned int i;
cmdtable *sql_cmdtab = NULL;
cmd_rec *sql_cmd = NULL;
modret_t *sql_res = NULL;
array_header *sql_data = NULL;
const char **values = NULL;
array_header *sql_filters = NULL;
sql_cmdtab = pr_stash_get_symbol2(PR_SYM_HOOK, "sql_lookup", NULL, NULL,
NULL);
if (sql_cmdtab == NULL) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"unable to execute SQLNamedQuery '%s': mod_sql not loaded", query_name);
errno = EPERM;
return NULL;
}
sql_cmd = pr_cmd_alloc(p, 2, "sql_lookup", query_name);
sql_res = pr_module_call(sql_cmdtab->m, sql_cmdtab->handler, sql_cmd);
if (sql_res == NULL ||
MODRET_ISERROR(sql_res)) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"error processing SQLNamedQuery '%s'; check mod_sql logs for details",
query_name);
errno = EPERM;
return NULL;
}
sql_data = sql_res->data;
pr_trace_msg(trace_channel, 9, "SQLNamedQuery '%s' returned item count %d",
query_name, sql_data->nelts);
if (sql_data->nelts == 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"SQLNamedQuery '%s' returned no values", query_name);
errno = ENOENT;
return NULL;
}
if (sql_data->nelts % 2 == 1) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"SQLNamedQuery '%s' returned odd number of values (%d), "
"expected even number", query_name, sql_data->nelts);
errno = EINVAL;
return NULL;
}
values = sql_data->elts;
sql_filters = make_array(p, 0, sizeof(struct geoip_filter));
for (i = 0; i < sql_data->nelts; i += 2) {
const char *filter_name, *pattern = NULL;
struct geoip_filter *filter;
filter_name = values[i];
pattern = values[i+1];
filter = make_filter(p, filter_name, pattern);
if (filter == NULL) {
pr_trace_msg(trace_channel, 3, "unable to use '%s %s' as filter: %s",
filter_name, pattern, strerror(errno));
continue;
}
*((struct geoip_filter **) push_array(sql_filters)) = filter;
}
return sql_filters;
}
#endif /* PR_USE_REGEX */
static void resolve_deferred_patterns(pool *p, const char *directive) {
#if PR_USE_REGEX
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, directive, FALSE);
while (c != NULL) {
register unsigned int i;
array_header *deferred_filters, *filters;
pr_signals_handle();
filters = c->argv[0];
deferred_filters = c->argv[1];
for (i = 0; i < deferred_filters->nelts; i++) {
const char *query_name;
array_header *sql_filters;
query_name = ((const char **) deferred_filters->elts)[i];
sql_filters = get_sql_filters(p, query_name);
if (sql_filters == NULL) {
continue;
}
array_cat(filters, sql_filters);
}
c = find_config_next(c, c->next, CONF_PARAM, directive, FALSE);
}
#endif /* PR_USE_REGEX */
}
static void resolve_deferred_filters(pool *p) {
resolve_deferred_patterns(p, "GeoIPAllowFilter");
resolve_deferred_patterns(p, "GeoIPDenyFilter");
}
static int check_geoip_filters(geoip_policy_e policy) {
int allow_conn = 0, matched_allow_filter = -1, matched_deny_filter = -1;
#if PR_USE_REGEX
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "GeoIPAllowFilter", FALSE);
while (c != NULL) {
register unsigned int i;
int matched = TRUE;
array_header *filters;
pr_signals_handle();
if (matched_allow_filter == -1) {
matched_allow_filter = FALSE;
}
filters = c->argv[0];
for (i = 0; i < filters->nelts; i++) {
int filter_id, res;
struct geoip_filter *filter;
pr_regex_t *filter_re;
const char *filter_name, *filter_pattern, *filter_value;
filter = ((struct geoip_filter **) filters->elts)[i];
filter_id = filter->filter_id;
filter_pattern = filter->filter_pattern;
filter_re = filter->filter_re;
filter_value = get_geoip_filter_value(filter_id);
if (filter_value == NULL) {
matched = FALSE;
break;
}
filter_name = get_geoip_filter_name(filter_id);
res = pr_regexp_exec(filter_re, filter_value, 0, NULL, 0, 0, 0);
pr_trace_msg(trace_channel, 12,
"%s filter value %s %s GeoIPAllowFilter pattern '%s'",
filter_name, filter_value, res == 0 ? "matched" : "did not match",
filter_pattern);
if (res == 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"%s filter value '%s' matched GeoIPAllowFilter pattern '%s'",
filter_name, filter_value, filter_pattern);
} else {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"%s filter value '%s' did not match GeoIPAllowFilter pattern '%s'",
filter_name, filter_value, filter_pattern);
matched = FALSE;
break;
}
}
if (matched == TRUE) {
matched_allow_filter = TRUE;
break;
}
c = find_config_next(c, c->next, CONF_PARAM, "GeoIPAllowFilter", FALSE);
}
c = find_config(main_server->conf, CONF_PARAM, "GeoIPDenyFilter", FALSE);
while (c != NULL) {
register unsigned int i;
int matched = TRUE;
array_header *filters;
pr_signals_handle();
if (matched_deny_filter == -1) {
matched_deny_filter = FALSE;
}
filters = c->argv[0];
for (i = 0; i < filters->nelts; i++) {
int filter_id, res;
struct geoip_filter *filter;
pr_regex_t *filter_re;
const char *filter_name, *filter_pattern, *filter_value;
filter = ((struct geoip_filter **) filters->elts)[i];
filter_id = filter->filter_id;
filter_pattern = filter->filter_pattern;
filter_re = filter->filter_re;
filter_value = get_geoip_filter_value(filter_id);
if (filter_value == NULL) {
matched = FALSE;
break;
}
filter_name = get_geoip_filter_name(filter_id);
res = pr_regexp_exec(filter_re, filter_value, 0, NULL, 0, 0, 0);
pr_trace_msg(trace_channel, 12,
"%s filter value %s %s GeoIPDenyFilter pattern '%s'",
filter_name, filter_value, res == 0 ? "matched" : "did not match",
filter_pattern);
if (res == 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"%s filter value '%s' matched GeoIPDenyFilter pattern '%s'",
filter_name, filter_value, filter_pattern);
} else {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"%s filter value '%s' did not match GeoIPDenyFilter pattern '%s'",
filter_name, filter_value, filter_pattern);
matched = FALSE;
break;
}
}
if (matched == TRUE) {
matched_deny_filter = TRUE;
break;
}
c = find_config_next(c, c->next, CONF_PARAM, "GeoIPDenyFilter", FALSE);
}
#endif /* !HAVE_REGEX_H or !HAVE_REGCOMP */
switch (policy) {
case GEOIP_POLICY_ALLOW_DENY:
if (matched_deny_filter == TRUE &&
matched_allow_filter != TRUE) {
/* If we explicitly matched any deny filters AND have NOT explicitly
* matched any allow filters, the connection is rejected, otherwise,
* it is allowed.
*/
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"client matched GeoIPDenyFilter, rejecting connection");
allow_conn = -1;
} else {
pr_trace_msg(trace_channel, 9,
"allowing client connection (policy 'allow,deny')");
}
break;
case GEOIP_POLICY_DENY_ALLOW:
if (matched_allow_filter == FALSE) {
/* If we have not explicitly matched any allow filters, then
* reject the connection.
*/
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"client did not match any GeoIPAllowFilters, rejecting connection");
allow_conn = -1;
} else {
pr_trace_msg(trace_channel, 9,
"allowing client connection (policy 'deny,allow')");
}
break;
}
return allow_conn;
}
static const char *get_geoip_filter_name(int filter_id) {
register unsigned int i;
for (i = 0; geoip_filter_keys[i].filter_name != NULL; i++) {
if (geoip_filter_keys[i].filter_id == filter_id) {
return geoip_filter_keys[i].filter_name;
}
}
errno = ENOENT;
return NULL;
}
static const char *get_geoip_filter_value(int filter_id) {
switch (filter_id) {
case GEOIP_FILTER_KEY_COUNTRY_CODE:
if (geoip_country_code2 != NULL) {
return geoip_country_code2;
}
break;
case GEOIP_FILTER_KEY_COUNTRY_NAME:
if (geoip_country_name != NULL) {
return geoip_country_name;
}
break;
case GEOIP_FILTER_KEY_REGION_NAME:
if (geoip_region_name != NULL) {
return geoip_region_name;
}
break;
case GEOIP_FILTER_KEY_CONTINENT:
if (geoip_continent_code != NULL) {
return geoip_continent_code;
}
break;
case GEOIP_FILTER_KEY_ORGANIZATION:
if (geoip_org != NULL) {
return geoip_org;
}
break;
case GEOIP_FILTER_KEY_CITY:
if (geoip_city != NULL) {
return geoip_city;
}
break;
case GEOIP_FILTER_KEY_POSTAL_CODE:
if (geoip_postal_code != NULL) {
return geoip_postal_code;
}
break;
case GEOIP_FILTER_KEY_LATITUDE:
if (geoip_latitude != NULL) {
return geoip_latitude;
}
break;
case GEOIP_FILTER_KEY_LONGITUDE:
if (geoip_longitude != NULL) {
return geoip_longitude;
}
break;
case GEOIP_FILTER_KEY_ASN:
if (geoip_asn != NULL) {
return geoip_asn;
}
break;
case GEOIP_FILTER_KEY_TIMEZONE:
if (geoip_timezone != NULL) {
return geoip_timezone;
}
break;
}
errno = ENOENT;
return NULL;
}
static void get_geoip_tables(void) {
config_rec *c;
c = find_config(main_server->conf, CONF_PARAM, "GeoIPTable", FALSE);
while (c != NULL) {
MMDB_s *mmdb = NULL;
const char *path;
uint32_t flags;
int res, xerrno = 0;
pr_signals_handle();
path = c->argv[0];
flags = *((uint32_t *) c->argv[1]);
mmdb = pcalloc(geoip2_pool, sizeof(MMDB_s));
PRIVS_ROOT
res = MMDB_open(path, flags, mmdb);
xerrno = errno;
PRIVS_RELINQUISH
if (res == MMDB_SUCCESS) {
char build_date[64];
time_t build_epoch;
*((MMDB_s **) push_array(geoip2_mmdbs)) = mmdb;
build_epoch = mmdb->metadata.build_epoch;
strftime(build_date, sizeof(build_date), "%F %T UTC",
gmtime(&build_epoch));
pr_trace_msg(trace_channel, 15,
"loaded GeoIP table '%s': %s (IP version = IPv%d, format version = "
"%d.%d, built = %s)", path, mmdb->metadata.database_type,
mmdb->metadata.ip_version, mmdb->metadata.binary_format_major_version,
mmdb->metadata.binary_format_minor_version, build_date);
} else {
if (res != MMDB_IO_ERROR) {
pr_log_pri(PR_LOG_WARNING, MOD_GEOIP2_VERSION
": warning: unable to open/use GeoIPTable '%s': %s", path,
MMDB_strerror(res));
} else {
pr_log_pri(PR_LOG_WARNING, MOD_GEOIP2_VERSION
": warning: unable to open/use GeoIPTable '%s': %s (%s)", path,
MMDB_strerror(res), strerror(xerrno));
}
}
c = find_config_next(c, c->next, CONF_PARAM, "GeoIPTable", FALSE);
}
}
static void remove_geoip_tables(void) {
register unsigned int i;
MMDB_s **mmdbs;
if (geoip2_mmdbs == NULL ||
geoip2_mmdbs->nelts == 0) {
return;
}
mmdbs = geoip2_mmdbs->elts;
for (i = 0; i < geoip2_mmdbs->nelts; i++) {
if (mmdbs[i] != NULL) {
MMDB_close(mmdbs[i]);
mmdbs[i] = NULL;
}
}
}
static const char *get_geoip_data_text(pool *p, MMDB_lookup_result_s *lookup,
const char **lookup_path, int filter_id) {
int res, xerrno = 0;
const char *text = NULL;
MMDB_entry_data_s entry_data;
res = MMDB_aget_value(&(lookup->entry), &entry_data, lookup_path);
xerrno = errno;
if (res != MMDB_SUCCESS) {
const char *lookup_name;
lookup_name = get_geoip_filter_name(filter_id);
switch (res) {
case MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR:
/* Ignored. */
errno = ENOENT;
break;
case MMDB_IO_ERROR:
pr_trace_msg(trace_channel, 3, "error getting data for %s: %s (%s)",
lookup_name, MMDB_strerror(res), strerror(xerrno));
errno = xerrno;
break;
default:
pr_trace_msg(trace_channel, 3, "error getting data for %s: %s",
lookup_name, MMDB_strerror(res));
errno = EPERM;
break;
}
return NULL;
}
if (!entry_data.has_data) {
errno = ENOENT;
return NULL;
}
switch (entry_data.type) {
case MMDB_DATA_TYPE_UTF8_STRING:
text = pstrndup(p, entry_data.utf8_string, entry_data.data_size);
break;
case MMDB_DATA_TYPE_UINT32: {
char buf[64];
memset(buf, '\0', sizeof(buf));
pr_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long) entry_data.uint32);
text = pstrdup(p, buf);
break;
}
case MMDB_DATA_TYPE_DOUBLE: {
char buf[64];
memset(buf, '\0', sizeof(buf));
pr_snprintf(buf, sizeof(buf)-1, "%f", entry_data.double_value);
text = pstrdup(p, buf);
break;
}
default:
pr_trace_msg(trace_channel, 3,
"unknown/unsupported entry data type (%lu), ignoring",
(unsigned long) entry_data.type);
errno = EINVAL;
return NULL;
}
return text;
}
static void get_geoip_data(void) {
register unsigned int i;
const char *ip_addr, *text;
const char *lookup_path[5] = { NULL, NULL, NULL, NULL, NULL };
MMDB_s **mmdbs;
ip_addr = pr_netaddr_get_ipstr(session.c->remote_addr);
mmdbs = geoip2_mmdbs->elts;
for (i = 0; i < geoip2_mmdbs->nelts; i++) {
MMDB_s *mmdb;
MMDB_lookup_result_s lookup;
int gai_error = 0, mmdb_error = 0;
pr_signals_handle();
if (mmdbs[i] == NULL) {
continue;
}
mmdb = mmdbs[i];
lookup = MMDB_lookup_string(mmdb, ip_addr, &gai_error, &mmdb_error);
if (mmdb_error != MMDB_SUCCESS) {
pr_trace_msg(trace_channel, 2,
"error looking up '%s' in GeoIPTable '%s': %s", ip_addr,
mmdb->filename, MMDB_strerror(mmdb_error));
continue;
}
if (!lookup.found_entry) {
pr_trace_msg(trace_channel, 2,
"no entry found for '%s' in GeoIPTable '%s'", ip_addr, mmdb->filename);
continue;
}
/* XXX This cries out to be done in a table-driven fashion. */
lookup_path[0] = "country";
lookup_path[1] = "iso_code";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_COUNTRY_CODE);
if (text != NULL) {
geoip_country_code2 = text;
}
/* "country" already set as first element above; no need to duplicate
* it again.
*/
lookup_path[1] = "names";
lookup_path[2] = "en";
lookup_path[3] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_COUNTRY_NAME);
if (text != NULL) {
geoip_country_name = text;
}
lookup_path[0] = "continent";
lookup_path[1] = "code";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_CONTINENT);
if (text != NULL) {
geoip_continent_code = text;
}
lookup_path[0] = "subdivisions";
lookup_path[1] = "0";
lookup_path[2] = "iso_code";
lookup_path[3] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_REGION_CODE);
if (text != NULL) {
geoip_region_code = text;
}
/* "subdivisions" already set as first element above; no need to duplicate
* it again.
*/
lookup_path[1] = "0";
lookup_path[2] = "names";
lookup_path[3] = "en";
lookup_path[4] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_REGION_NAME);
if (text != NULL) {
geoip_region_name = text;
}
lookup_path[0] = "city";
lookup_path[1] = "names";
lookup_path[2] = "en";
lookup_path[3] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_CITY);
if (text != NULL) {
geoip_city = text;
}
lookup_path[0] = "postal";
lookup_path[1] = "code";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_POSTAL_CODE);
if (text != NULL) {
geoip_postal_code = text;
}
lookup_path[0] = "locations";
lookup_path[1] = "latitude";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_LATITUDE);
if (text != NULL) {
geoip_latitude = text;
}
/* "locations" already set as first element above; no need to duplicate
* it again.
*/
lookup_path[1] = "longitude";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_LONGITUDE);
if (text != NULL) {
geoip_longitude = text;
}
/* "locations" already set as first element above; no need to duplicate
* it again.
*/
lookup_path[1] = "time_zone";
lookup_path[2] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_TIMEZONE);
if (text != NULL) {
geoip_longitude = text;
}
lookup_path[0] = "autonomous_system_number";
lookup_path[1] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_ASN);
if (text != NULL) {
geoip_asn = text;
}
lookup_path[0] = "autonomous_system_organization";
lookup_path[1] = NULL;
text = get_geoip_data_text(geoip2_pool, &lookup, lookup_path,
GEOIP_FILTER_KEY_ORGANIZATION);
if (text != NULL) {
geoip_org = text;
}
}
}
static void get_geoip_info(void) {
const char *ip_addr;
get_geoip_data();
ip_addr = pr_netaddr_get_ipstr(session.c->remote_addr);
if (geoip_country_code2 != NULL) {
pr_trace_msg(trace_channel, 8, "%s: 2-Letter country code: %s", ip_addr,
geoip_country_code2);
}
if (geoip_country_name != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Country name: %s", ip_addr,
geoip_country_name);
}
if (geoip_region_code != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Region code: %s", ip_addr,
geoip_region_code);
}
if (geoip_region_name != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Region name: %s", ip_addr,
geoip_region_name);
}
if (geoip_timezone != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Timezone: %s", ip_addr, geoip_timezone);
}
if (geoip_continent_code != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Continent code: %s", ip_addr,
geoip_continent_code);
}
if (geoip_org != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Organization: %s", ip_addr, geoip_org);
}
if (geoip_city != NULL) {
pr_trace_msg(trace_channel, 8, "%s: City: %s", ip_addr, geoip_city);
}
if (geoip_postal_code != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Postal code: %s", ip_addr,
geoip_postal_code);
}
if (geoip_latitude != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Latitude: %s", ip_addr,
geoip_latitude);
}
if (geoip_longitude != NULL) {
pr_trace_msg(trace_channel, 8, "%s: Longitude: %s", ip_addr,
geoip_longitude);
}
if (geoip_asn != NULL) {
pr_trace_msg(trace_channel, 8, "%s: ASN: %s", ip_addr, geoip_asn);
}
}
static void set_geoip_value(const char *key, const char *value) {
int res;
res = pr_env_set(session.pool, key, value);
if (res < 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"error setting %s environment variable: %s", key, strerror(errno));
}
res = pr_table_add_dup(session.notes, pstrdup(session.pool, key),
(char *) value, 0);
if (res < 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"error adding %s session note: %s", key, strerror(errno));
}
}
static void set_geoip_values(void) {
if (geoip_country_code2 != NULL) {
set_geoip_value("GEOIP_COUNTRY_CODE", geoip_country_code2);
}
if (geoip_country_name != NULL) {
set_geoip_value("GEOIP_COUNTRY_NAME", geoip_country_name);
}
if (geoip_region_code != NULL) {
set_geoip_value("GEOIP_REGION", geoip_region_code);
}
if (geoip_region_name != NULL) {
set_geoip_value("GEOIP_REGION_NAME", geoip_region_name);
}
if (geoip_continent_code != NULL) {
set_geoip_value("GEOIP_CONTINENT_CODE", geoip_continent_code);
}
if (geoip_org != NULL) {
set_geoip_value("GEOIP_ORGANIZATION", geoip_org);
}
if (geoip_city != NULL) {
set_geoip_value("GEOIP_CITY", geoip_city);
}
if (geoip_postal_code != NULL) {
set_geoip_value("GEOIP_POSTAL_CODE", geoip_postal_code);
}
if (geoip_latitude != NULL) {
set_geoip_value("GEOIP_LATITUDE", geoip_latitude);
}
if (geoip_longitude != NULL) {
set_geoip_value("GEOIP_LONGITUDE", geoip_longitude);
}
if (geoip_asn != NULL) {
set_geoip_value("GEOIP_ASN", geoip_asn);
}
if (geoip_timezone != NULL) {
set_geoip_value("GEOIP_TIMEZONE", geoip_timezone);
}
}
/* Configuration handlers
*/
/* usage:
* GeoIPAllowFilter key1 regex1 [key2 regex2 ...]
* sql:/...
* GeoIPDenyFilter key1 regex1 [key2 regex2 ...]
* sql:/...
*/
MODRET set_geoipfilter(cmd_rec *cmd) {
#if PR_USE_REGEX
config_rec *c;
array_header *deferred_patterns, *filters;
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (cmd->argc == 1) {
CONF_ERROR(cmd, "wrong number of parameters");
}
/* IFF the first parameter starts with "sql:/", then we expect ONLY one
* parameter. If not, then we expect an even number of parameters.
*/
if (strncmp(cmd->argv[1], "sql:/", 5) == 0) {
if (cmd->argc > 2) {
CONF_ERROR(cmd, "wrong number of parameters");
}
} else {
if ((cmd->argc-1) % 2 != 0) {
CONF_ERROR(cmd, "wrong number of parameters");
}
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
filters = make_array(c->pool, 0, sizeof(struct geoip_filter *));
deferred_patterns = make_array(c->pool, 0, sizeof(char *));
if (cmd->argc == 2) {
const char *pattern;
pattern = cmd->argv[1];
/* Advance past the "sql:/" prefix. */
*((char **) push_array(deferred_patterns)) = pstrdup(c->pool, pattern + 5);
} else {
register unsigned int i;
for (i = 1; i < cmd->argc; i += 2) {
const char *filter_name, *pattern = NULL;
struct geoip_filter *filter;
filter_name = cmd->argv[i];
pattern = cmd->argv[i+1];
filter = make_filter(c->pool, filter_name, pattern);
if (filter == NULL) {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unable to use '",
filter_name, " ", pattern, "' as filter: ", strerror(errno), NULL));
}
*((struct geoip_filter **) push_array(filters)) = filter;
}
}
c->argv[0] = filters;
c->argv[1] = deferred_patterns;
return PR_HANDLED(cmd);
#else /* no regular expression support at the moment */
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "The ", cmd->argv[0],
" directive cannot be used on this system, as you do not have POSIX "
"compliant regex support", NULL));
#endif
}
/* usage: GeoIPEngine on|off */
MODRET set_geoipengine(cmd_rec *cmd) {
int engine = -1;
config_rec *c = NULL;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
engine = get_boolean(cmd, 1);
if (engine == -1) {
CONF_ERROR(cmd, "expected Boolean parameter");
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(int));
*((int *) c->argv[0]) = engine;
return PR_HANDLED(cmd);
}
/* usage: GeoIPLog path|"none" */
MODRET set_geoiplog(cmd_rec *cmd) {
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
(void) add_config_param_str(cmd->argv[0], 1, cmd->argv[1]);
return PR_HANDLED(cmd);
}
/* usage: GeoIPPolicy "allow,deny"|"deny,allow" */
MODRET set_geoippolicy(cmd_rec *cmd) {
geoip_policy_e policy;
config_rec *c;
CHECK_ARGS(cmd, 1);
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (strcasecmp(cmd->argv[1], "allow,deny") == 0) {
policy = GEOIP_POLICY_ALLOW_DENY;
} else if (strcasecmp(cmd->argv[1], "deny,allow") == 0) {
policy = GEOIP_POLICY_DENY_ALLOW;
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": '", cmd->argv[1],
"' is not one of the approved GeoIPPolicy settings", NULL));
}
c = add_config_param(cmd->argv[0], 1, NULL);
c->argv[0] = pcalloc(c->pool, sizeof(geoip_policy_e));
*((geoip_policy_e *) c->argv[0]) = policy;
return PR_HANDLED(cmd);
}
/* usage: GeoIPTable path [flags] */
MODRET set_geoiptable(cmd_rec *cmd) {
config_rec *c;
uint32_t flags = 0;
char *path;
CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
if (cmd->argc < 2) {
CONF_ERROR(cmd, "wrong number of parameters");
}
path = cmd->argv[1];
if (cmd->argc > 2) {
register unsigned int i;
for (i = 2; i < cmd->argc; i++) {
/* Most of these are ignored, for backward compatibility with the
* mod_geoip flags.
*/
if (strcasecmp(cmd->argv[i], "Standard") == 0) {
/* Ignored. */
} else if (strcasecmp(cmd->argv[i], "MemoryCache") == 0) {
/* Ignored. */
} else if (strcasecmp(cmd->argv[i], "MMapCache") == 0) {
flags |= MMDB_MODE_MMAP;
} else if (strcasecmp(cmd->argv[i], "IndexCache") == 0) {
/* Ignored. */
} else if (strcasecmp(cmd->argv[i], "CheckCache") == 0) {
/* Ignored. */
} else if (strcasecmp(cmd->argv[i], "UTF8") == 0) {
/* Ignored. */
} else {
CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown GeoIPTable flag '",
cmd->argv[i], "'", NULL));
}
}
}
c = add_config_param(cmd->argv[0], 2, NULL, NULL);
c->argv[0] = pstrdup(c->pool, path);
c->argv[1] = palloc(c->pool, sizeof(uint32_t));
*((uint32_t *) c->argv[1]) = flags;
return PR_HANDLED(cmd);
}
/* Command handlers
*/
MODRET geoip2_post_pass(cmd_rec *cmd) {
int res;
if (geoip2_engine == FALSE) {
return PR_DECLINED(cmd);
}
/* Scan for any deferred GeoIP filters and resolve them. */
resolve_deferred_filters(cmd->tmp_pool);
/* Modules such as mod_ifsession may have added new filters; check the
* filters again.
*/
res = check_geoip_filters(geoip_policy);
if (res < 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"connection from %s denied due to GeoIP filter/policy",
pr_netaddr_get_ipstr(session.c->remote_addr));
pr_log_pri(PR_LOG_NOTICE, MOD_GEOIP2_VERSION
": Connection denied to %s due to GeoIP filter/policy",
pr_netaddr_get_ipstr(session.c->remote_addr));
pr_event_generate("mod_geoip.connection-denied", NULL);
pr_session_disconnect(&geoip2_module, PR_SESS_DISCONNECT_CONFIG_ACL,
"GeoIP Filters");
}
return PR_DECLINED(cmd);
}
/* Event handlers
*/
#if defined(PR_SHARED_MODULE)
static void geoip2_mod_unload_ev(const void *event_data, void *user_data) {
if (strcmp("mod_geoip2.c", (const char *) event_data) == 0) {
remove_geoip_tables();
destroy_pool(geoip2_pool);
/* Unregister ourselves from all events. */
pr_event_unregister(&geoip2_module, NULL, NULL);
}
}
#endif /* PR_SHARED_MODULE */
static void geoip2_postparse_ev(const void *event_data, void *user_data) {
pr_log_debug(DEBUG8, MOD_GEOIP2_VERSION ": loading static GeoIP tables");
get_geoip_tables();
}
static void geoip2_restart_ev(const void *event_data, void *user_data) {
remove_geoip_tables();
destroy_pool(geoip2_pool);
geoip2_pool = make_sub_pool(permanent_pool);
pr_pool_tag(geoip2_pool, MOD_GEOIP2_VERSION);
geoip2_mmdbs = make_array(geoip2_pool, 0, sizeof(MMDB_s *));
}
/* Initialization functions
*/
static int geoip2_init(void) {
/* Make sure that mod_geoip is NOT loaded. If it is, error out. There
* can be only one. (Make sure the docs note this, too.)
*/
if (pr_module_exists("mod_geoip.c") == TRUE) {
pr_log_pri(PR_LOG_NOTICE, MOD_GEOIP2_VERSION
": mod_geoip and mod_geoip2 cannot be used at the same time");
errno = EPERM;
return -1;
}
geoip2_pool = make_sub_pool(permanent_pool);
pr_pool_tag(geoip2_pool, MOD_GEOIP2_VERSION);
geoip2_mmdbs = make_array(geoip2_pool, 0, sizeof(MMDB_s *));
#if defined(PR_SHARED_MODULE)
pr_event_register(&geoip2_module, "core.module-unload", geoip2_mod_unload_ev,
NULL);
#endif /* PR_SHARED_MODULE */
pr_event_register(&geoip2_module, "core.postparse", geoip2_postparse_ev,
NULL);
pr_event_register(&geoip2_module, "core.restart", geoip2_restart_ev, NULL);
pr_log_debug(DEBUG2, MOD_GEOIP2_VERSION ": using libmaxmindb-%s",
MMDB_lib_version());
return 0;
}
static int geoip2_sess_init(void) {
config_rec *c;
int res;
pool *tmp_pool;
c = find_config(main_server->conf, CONF_PARAM, "GeoIPEngine", FALSE);
if (c != NULL) {
geoip2_engine = *((int *) c->argv[0]);
}
if (geoip2_engine == FALSE) {
return 0;
}
c = find_config(main_server->conf, CONF_PARAM, "GeoIPLog", FALSE);
if (c != NULL) {
char *path;
path = c->argv[0];
if (strcasecmp(path, "none") != 0) {
int xerrno;
pr_signals_block();
PRIVS_ROOT
res = pr_log_openfile(path, &geoip2_logfd, PR_LOG_SYSTEM_MODE);
xerrno = errno;
PRIVS_RELINQUISH
pr_signals_unblock();
if (res < 0) {
if (res == -1) {
pr_log_pri(PR_LOG_NOTICE, MOD_GEOIP2_VERSION
": notice: unable to open GeoIPLog '%s': %s", path,
strerror(xerrno));
} else if (res == PR_LOG_WRITABLE_DIR) {
pr_log_pri(PR_LOG_WARNING, MOD_GEOIP2_VERSION
": notice: unable to open GeoIPLog '%s': parent directory is "
"world-writable", path);
} else if (res == PR_LOG_SYMLINK) {
pr_log_pri(PR_LOG_WARNING, MOD_GEOIP2_VERSION
": notice: unable to open GeoIPLog '%s': cannot log to a symlink",
path);
}
}
}
}
tmp_pool = make_sub_pool(geoip2_pool);
pr_pool_tag(tmp_pool, "GeoIP Session Pool");
if (geoip2_mmdbs->nelts == 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"no usable GeoIPTable files found, skipping GeoIP lookups");
(void) close(geoip2_logfd);
destroy_pool(tmp_pool);
return 0;
}
get_geoip_info();
c = find_config(main_server->conf, CONF_PARAM, "GeoIPPolicy", FALSE);
if (c != NULL) {
geoip_policy = *((geoip_policy_e *) c->argv[0]);
}
switch (geoip_policy) {
case GEOIP_POLICY_ALLOW_DENY:
pr_trace_msg(trace_channel, 8,
"using policy of allowing connections unless rejected by "
"GeoIPDenyFilters");
break;
case GEOIP_POLICY_DENY_ALLOW:
pr_trace_msg(trace_channel, 8,
"using policy of rejecting connections unless allowed by "
"GeoIPAllowFilters");
break;
}
res = check_geoip_filters(geoip_policy);
if (res < 0) {
(void) pr_log_writefile(geoip2_logfd, MOD_GEOIP2_VERSION,
"connection from %s denied due to GeoIP filter/policy",
pr_netaddr_get_ipstr(session.c->remote_addr));
pr_log_pri(PR_LOG_NOTICE, MOD_GEOIP2_VERSION
": Connection denied to %s due to GeoIP filter/policy",
pr_netaddr_get_ipstr(session.c->remote_addr));
pr_event_generate("mod_geoip.connection-denied", NULL);
/* XXX send_geoip_mesg(tmp_pool, mesg) */
destroy_pool(tmp_pool);
errno = EACCES;
return -1;
}
set_geoip_values();
destroy_pool(tmp_pool);
return 0;
}
/* Module API tables
*/
static conftable geoip2_conftab[] = {
{ "GeoIPAllowFilter", set_geoipfilter, NULL },
{ "GeoIPDenyFilter", set_geoipfilter, NULL },
{ "GeoIPEngine", set_geoipengine, NULL },
{ "GeoIPLog", set_geoiplog, NULL },
{ "GeoIPPolicy", set_geoippolicy, NULL },
{ "GeoIPTable", set_geoiptable, NULL },
{ NULL }
};
static cmdtable geoip2_cmdtab[] = {
{ POST_CMD, C_PASS, G_NONE, geoip2_post_pass, FALSE, FALSE },
{ 0, NULL },
};
module geoip2_module = {
NULL, NULL,
/* Module API version 2.0 */
0x20,
/* Module name */
"geoip2",
/* Module configuration handler table */
geoip2_conftab,
/* Module command handler table */
geoip2_cmdtab,
/* Module authentication handler table */
NULL,
/* Module initialization function */
geoip2_init,
/* Session initialization function */
geoip2_sess_init,
/* Module version */
MOD_GEOIP2_VERSION
};