diff options
Diffstat (limited to 'src/vmod_geoip.c')
-rw-r--r-- | src/vmod_geoip.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/vmod_geoip.c b/src/vmod_geoip.c new file mode 100644 index 0000000..6ad23e2 --- /dev/null +++ b/src/vmod_geoip.c @@ -0,0 +1,260 @@ +#include <config.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include "vcl.h" +#include "vrt.h" +#include "vcc_if.h" +#include "bin/varnishd/cache/cache.h" +#include <maxminddb.h> +#include <assert.h> + +#define DEFAULT_DIR "/usr/share/GeoIP" +#define DEFAULT_FILE "GeoLite2-City.mmdb" +#define DEFAULT_DATABASE DEFAULT_DIR "/" DEFAULT_FILE + +struct geoip_config { + char *database; +}; + +int +geoip_event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e) +{ + if (e == VCL_EVENT_LOAD) { + struct geoip_config *conf = calloc(1, sizeof(*conf)); + AN(conf); + priv->priv = conf; + } + return 0; +} + +void +vmod_init(VRT_CTX, struct vmod_priv *priv, const char *dir) +{ + struct geoip_config *conf = priv->priv; + struct stat st; + char *name; + + if (stat (dir, &st)) { + fprintf (stderr, "geoip.init: can't stat \"%s\": %s\n", + dir, strerror (errno)); + abort (); + } + + if (S_ISDIR(st.st_mode)) { + size_t dlen = strlen (dir); + size_t len; + + if (dir[dlen-1] == '/') + dlen--; + name = malloc (dlen + 1 + sizeof (DEFAULT_FILE)); + AN(name); + memcpy (name, dir, dlen); + name[dlen++] = '/'; + strcpy (name + dlen, DEFAULT_FILE); + } else if (S_ISREG (st.st_mode)) { + name = strdup (dir); + AN(name); + } else { + fprintf (stderr, "geoip.init: \"%s\": bad file type\n", dir); + abort (); + } + + free(conf->database); + conf->database = name; +} + +static int +open_geoip_database(struct geoip_config *conf, MMDB_s *dbptr) +{ + char *database = conf->database ? conf->database : DEFAULT_DATABASE; + int rc; + + rc = MMDB_open(database, MMDB_MODE_MMAP, dbptr); + if (rc != MMDB_SUCCESS) { + fprintf(stderr, "can't open database \"%s\": %s\n", + database, MMDB_strerror(rc)); + return -1; + } + return 0; +} + +static char * +conv_utf_string (struct ws *ws, MMDB_entry_data_s *dptr) +{ + char *retval = WS_Alloc(ws, dptr->data_size + 1); + AN(retval); + memcpy(retval, dptr->utf8_string, dptr->data_size); + retval[dptr->data_size] = 0; + return retval; +} + +static char *(*entry_conv[]) (struct ws *, MMDB_entry_data_s *) = { + [MMDB_DATA_TYPE_UTF8_STRING] = conv_utf_string +}; + +static char * +lookup_geoip_database(struct ws *ws, + MMDB_s *dbfile, VCL_STRING ipstr, + char const *pathstr, char **path) +{ + MMDB_lookup_result_s result; + int gai_error, mmdb_error; + int rc; + MMDB_entry_data_s entry_data; + char *retval; + + result = MMDB_lookup_string(dbfile, ipstr, &gai_error, &mmdb_error); + if (gai_error) { + fprintf(stderr, "%s %s: GAI %s\n", + pathstr, ipstr, gai_strerror(gai_error)); + return NULL; + } + + if (mmdb_error != MMDB_SUCCESS) { + fprintf(stderr, "%s %s: MMDB %s\n", + pathstr, ipstr, MMDB_strerror(mmdb_error)); + return NULL; + } + + if (!result.found_entry) + return NULL; + + rc = MMDB_aget_value(&result.entry, &entry_data, (const char * const* const) path); + if (rc != MMDB_SUCCESS) { + fprintf(stderr, "%s %s: MMDB_aget_value %s\n", + pathstr, ipstr, MMDB_strerror(rc)); + return NULL; + } + + if (!entry_data.has_data) + return NULL; + + if (entry_data.type >= 0 + && entry_data.type <= sizeof (entry_conv) / sizeof (entry_conv[0]) + && entry_conv[entry_data.type]) { + retval = entry_conv[entry_data.type] (ws, &entry_data); + } else + retval = NULL; + + return retval; +} + +static char ** +split(char const *path) +{ + int c = 0; + int i, j; + char *base; + char **retv; + char *str; + + c = 1; + for (i = 0; path[i]; i++) + if (path[i] == '.') + c++; + base = malloc(i + 1 + (c + 1) * sizeof(char*)); + AN(base); + retv = (char**) base; + str = (char*) &retv[c+1]; + strcpy(str, path); + + i = 0; + j = 0; + retv[j++] = str; + while (str[i]) { + if (str[i] == '.') { + str[i] = 0; + retv[j++] = str + i + 1; + } + i++; + } + retv[j] = NULL; + return retv; +} + +VCL_STRING +vmod_get(VRT_CTX, struct vmod_priv *priv, VCL_STRING ipstr, VCL_STRING path) +{ + struct geoip_config *conf = priv->priv; + MMDB_s dbfile; + char *retval = NULL; + char **pathv; + + if (open_geoip_database(conf, &dbfile)) + return NULL; + pathv = split(path); + retval = lookup_geoip_database(ctx->ws, &dbfile, ipstr, path, pathv); + free(pathv); + MMDB_close(&dbfile); + return retval; +} + +VCL_STRING +vmod_country_code(VRT_CTX, struct vmod_priv *priv, VCL_STRING ipstr) +{ + struct geoip_config *conf = priv->priv; + MMDB_s dbfile; + char *retval = NULL; + static char *country_code_path[] = { + "country", + "iso_code", + NULL + }; + + if (open_geoip_database(conf, &dbfile)) + return NULL; + retval = lookup_geoip_database(ctx->ws, &dbfile, ipstr, + "country.iso_code", country_code_path); + MMDB_close(&dbfile); + return retval; +} + +VCL_STRING +vmod_country_name(VRT_CTX, struct vmod_priv *priv, VCL_STRING ipstr) +{ + struct geoip_config *conf = priv->priv; + MMDB_s dbfile; + char *retval = NULL; + static char *country_name_path[] = { + "country", + "names", + "en", + NULL + }; + + if (open_geoip_database(conf, &dbfile)) + return NULL; + retval = lookup_geoip_database(ctx->ws, &dbfile, ipstr, + "country.names.en", + country_name_path); + MMDB_close(&dbfile); + return retval; +} + +VCL_STRING +vmod_city_name(VRT_CTX, struct vmod_priv *priv, VCL_STRING ipstr) +{ + struct geoip_config *conf = priv->priv; + MMDB_s dbfile; + char *retval = NULL; + static char *city_name_path[] = { + "city", + "names", + "en", + NULL + }; + + if (open_geoip_database(conf, &dbfile)) + return NULL; + retval = lookup_geoip_database(ctx->ws, &dbfile, ipstr, + "city.names.en", + city_name_path); + MMDB_close(&dbfile); + return retval; +} |