src/modules/geoip2/geoip2_pv.c
cdba71ea
 /**
  *
  * This file is part of Kamailio, a free SIP server.
  *
  * This file 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 file 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, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
 
 #include <assert.h>
 #include <ctype.h>
 #include <string.h>
 #include <stdlib.h>
 #include <time.h>
 
cf83221d
 #include "../../core/dprint.h"
 #include "../../core/hashes.h"
 #include "../../core/pvar.h"
2f78b97e
 #include "../../core/locking.h"
cdba71ea
 
 #include "geoip2_pv.h"
 
 typedef struct _sr_geoip2_record {
 	MMDB_lookup_result_s record;
 	str time_zone;
 	str zip;
 	str city;
 	str region_code;
 	str region_name;
 	str country;
4d1e4e81
 	str cont_code;
cdba71ea
 	char latitude[16];
 	char longitude[16];
 	char metro[16];
 	char nmask[8];
 	char tomatch[256];
 	int flags;
 } sr_geoip2_record_t;
 
 typedef struct _sr_geoip2_item {
 	str pvclass;
 	unsigned int hashid;
 	sr_geoip2_record_t r;
 	struct _sr_geoip2_item *next;
 } sr_geoip2_item_t;
 
 typedef struct _geoip2_pv {
 	sr_geoip2_item_t *item;
 	int type;
 } geoip2_pv_t;
 
2f78b97e
 static MMDB_s *_handle_GeoIP;
 static gen_lock_t *lock = NULL;
cdba71ea
 
 static sr_geoip2_item_t *_sr_geoip2_list = NULL;
 
 sr_geoip2_record_t *sr_geoip2_get_record(str *name)
 {
 	sr_geoip2_item_t *it = NULL;
 	unsigned int hashid = 0;
 
 	hashid =  get_hash1_raw(name->s, name->len);
 
 	it = _sr_geoip2_list;
 	while(it!=NULL)
 	{
 		if(it->hashid==hashid && it->pvclass.len == name->len
 				&& strncmp(it->pvclass.s, name->s, name->len)==0)
 			return &it->r;
 		it = it->next;
 	}
 	return NULL;
 }
 
 sr_geoip2_item_t *sr_geoip2_add_item(str *name)
 {
 	sr_geoip2_item_t *it = NULL;
 	unsigned int hashid = 0;
 
 	hashid =  get_hash1_raw(name->s, name->len);
 
 	it = _sr_geoip2_list;
 	while(it!=NULL)
 	{
 		if(it->hashid==hashid && it->pvclass.len == name->len
 				&& strncmp(it->pvclass.s, name->s, name->len)==0)
 			return it;
 		it = it->next;
 	}
 	/* add new */
 	it = (sr_geoip2_item_t*)pkg_malloc(sizeof(sr_geoip2_item_t));
 	if(it==NULL)
 	{
 		LM_ERR("no more pkg\n");
 		return NULL;
 	}
 	memset(it, 0, sizeof(sr_geoip2_item_t));
 	it->pvclass.s = (char*)pkg_malloc(name->len+1);
 	if(it->pvclass.s==NULL)
 	{
 		LM_ERR("no more pkg.\n");
 		pkg_free(it);
 		return NULL;
 	}
 	memcpy(it->pvclass.s, name->s, name->len);
 	it->pvclass.s[name->len] = '\0';
 	it->pvclass.len = name->len;
 	it->hashid = hashid;
 	it->next = _sr_geoip2_list;
 	_sr_geoip2_list = it;
 	return it;
 }
 
 
 int pv_parse_geoip2_name(pv_spec_p sp, str *in)
 {
 	geoip2_pv_t *gpv=NULL;
 	char *p;
 	str pvc;
 	str pvs;
 	if(sp==NULL || in==NULL || in->len<=0)
 		return -1;
 
 	gpv = (geoip2_pv_t*)pkg_malloc(sizeof(geoip2_pv_t));
 	if(gpv==NULL)
 		return -1;
 
 	memset(gpv, 0, sizeof(geoip2_pv_t));
 
 	p = in->s;
 
 	while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
 		p++;
 	if(p>in->s+in->len || *p=='\0')
 		goto error;
 	pvc.s = p;
 	while(p < in->s + in->len)
 	{
 		if(*p=='=' || *p==' ' || *p=='\t' || *p=='\n' || *p=='\r')
 			break;
 		p++;
 	}
 	if(p>in->s+in->len || *p=='\0')
 		goto error;
 	pvc.len = p - pvc.s;
 	if(*p!='=')
 	{
 		while(p<in->s+in->len && (*p==' ' || *p=='\t' || *p=='\n' || *p=='\r'))
 			p++;
 		if(p>in->s+in->len || *p=='\0' || *p!='=')
 			goto error;
 	}
 	p++;
 	if(*p!='>')
 		goto error;
 	p++;
 
 	pvs.len = in->len - (int)(p - in->s);
 	pvs.s = p;
 	LM_DBG("geoip2 [%.*s] - key [%.*s]\n", pvc.len, pvc.s,
 			pvs.len, pvs.s);
 
 	gpv->item = sr_geoip2_add_item(&pvc);
 	if(gpv->item==NULL)
 		goto error;
 
 	switch(pvs.len)
 	{
a5484f40
 		case 2:
cdba71ea
 			if(strncmp(pvs.s, "cc", 2)==0)
 				gpv->type = 0;
 			else if(strncmp(pvs.s, "tz", 2)==0)
 				gpv->type = 1;
 			else goto error;
 		break;
a5484f40
 		case 3:
cdba71ea
 			if(strncmp(pvs.s, "zip", 3)==0)
 				gpv->type = 2;
 			else if(strncmp(pvs.s, "lat", 3)==0)
 				gpv->type = 3;
 			else if(strncmp(pvs.s, "lon", 3)==0)
 				gpv->type = 4;
 			else goto error;
 		break;
a5484f40
 		case 4:
cdba71ea
 			if(strncmp(pvs.s, "city", 4)==0)
 				gpv->type = 8;
 			else if(strncmp(pvs.s, "regc", 4)==0)
 				gpv->type = 10;
 			else if(strncmp(pvs.s, "regn", 4)==0)
 				gpv->type = 11;
 			else goto error;
 		break;
a5484f40
 		case 5:
cdba71ea
 			if(strncmp(pvs.s, "metro", 5)==0)
 				gpv->type = 12;
 			else if(strncmp(pvs.s, "nmask", 5)==0)
 				gpv->type = 13;
4d1e4e81
 			else if(strncmp(pvs.s, "contc", 5)==0)
 				gpv->type = 6;
cdba71ea
 			else goto error;
 		break;
 		default:
 			goto error;
 	}
 	sp->pvp.pvn.u.dname = (void*)gpv;
 	sp->pvp.pvn.type = PV_NAME_OTHER;
 
 	return 0;
 
 error:
 	if(gpv!=NULL)
 		pkg_free(gpv);
 
 	LM_ERR("error at PV geoip2 name: %.*s\n", in->len, in->s);
 	return -1;
 }
 
 int pv_geoip2_get_strzval(struct sip_msg *msg, pv_param_t *param,
 		pv_value_t *res, char *sval)
 {
 	str s;
 	if(sval==NULL)
 		return pv_get_null(msg, param, res);
 
 	s.s = sval;
 	s.len = strlen(s.s);
 	return pv_get_strval(msg, param, res, &s);
 }
 
2f78b97e
 static int get_mmdb_value2(MMDB_entry_s *entry, MMDB_entry_data_s *data,
 		const char *first, const char *second)
 {
 	int status = 0;
 
 	lock_get(lock);
 	status = MMDB_get_value(entry, data, first, second, NULL);
 	lock_release(lock);
 
 	return status;
 }
 
 static int get_mmdb_value3(MMDB_entry_s *entry, MMDB_entry_data_s *data,
 		const char *first, const char *second, const char *third)
 {
 	int status = 0;
 
 	lock_get(lock);
 	status = MMDB_get_value(entry, data, first, second, third, NULL);
 	lock_release(lock);
 
 	return status;
 }
 
 static int get_mmdb_value4(MMDB_entry_s *entry, MMDB_entry_data_s *data,
 		const char *first, const char *second, const char *third,
 		const char *fourth)
 {
 	int status = 0;
 
 	lock_get(lock);
 	status = MMDB_get_value(entry, data, first, second, third, fourth, NULL);
 	lock_release(lock);
 
 	return status;
 }
 
 
cdba71ea
 int pv_get_geoip2(struct sip_msg *msg, pv_param_t *param,
 		pv_value_t *res)
 {
 	geoip2_pv_t *gpv;
 	MMDB_entry_data_s entry_data;
 
 	if(msg==NULL || param==NULL)
 		return -1;
 
 	gpv = (geoip2_pv_t*)param->pvn.u.dname;
 	if(gpv==NULL)
 		return -1;
 	if(gpv->item==NULL)
 		return pv_get_null(msg, param, res);
 
 	switch(gpv->type)
 	{
 		case 1: /* tz */
 			if(gpv->item->r.time_zone.s==NULL)
 			{
 				if(gpv->item->r.flags&1)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"location","time_zone") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.time_zone.s = (char *)entry_data.utf8_string;
 					gpv->item->r.time_zone.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 1;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.time_zone);
 		case 2: /* zip */
 			if(gpv->item->r.zip.s==NULL)
 			{
 				if(gpv->item->r.flags&32)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"postal","code") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.zip.s = (char *)entry_data.utf8_string;
 					gpv->item->r.zip.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 32;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.zip);
 		case 3: /* lat */
 			if((gpv->item->r.flags&2)==0)
 			{
 				gpv->item->r.latitude[0] = '\0';
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"location","latitude") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
 					snprintf(gpv->item->r.latitude, 15, "%f", entry_data.double_value);
 				gpv->item->r.flags |= 2;
 			}
 			return pv_geoip2_get_strzval(msg, param, res,
 					gpv->item->r.latitude);
 		case 4: /* lon */
 			if((gpv->item->r.flags&4)==0)
 			{
 				gpv->item->r.latitude[0] = '\0';
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"location","longitude") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_DOUBLE)
 					snprintf(gpv->item->r.longitude, 15, "%f", entry_data.double_value);
 				gpv->item->r.flags |= 4;
 			}
 			return pv_geoip2_get_strzval(msg, param, res,
 					gpv->item->r.longitude);
4d1e4e81
 		case 6: /* contc */
 			if(gpv->item->r.cont_code.s==NULL)
 			{
 				if(gpv->item->r.flags&16)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"continent","code") != MMDB_SUCCESS)
4d1e4e81
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.cont_code.s = (char *)entry_data.utf8_string;
 					gpv->item->r.cont_code.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 16;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.cont_code);
cdba71ea
 		case 8: /* city */
 			if(gpv->item->r.city.s==NULL)
 			{
 				if(gpv->item->r.flags&64)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value3(&gpv->item->r.record.entry, &entry_data,
 					"city","names","en") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.city.s = (char *)entry_data.utf8_string;
 					gpv->item->r.city.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 64;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.city);
 		case 10: /* regc */
 			if(gpv->item->r.region_code.s==NULL)
 			{
 				if(gpv->item->r.flags&128)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value3(&gpv->item->r.record.entry, &entry_data,
 					"subdivisions","0","iso_code") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.region_code.s = (char *)entry_data.utf8_string;
 					gpv->item->r.region_code.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 128;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.region_code);
 		case 11: /* regn */
 			if(gpv->item->r.region_name.s==NULL)
 			{
 				if(gpv->item->r.flags&16)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value4(&gpv->item->r.record.entry, &entry_data,
 					"subdivisions","0","names","en") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.region_name.s = (char *)entry_data.utf8_string;
 					gpv->item->r.region_name.len = entry_data.data_size;
 				}
 				gpv->item->r.flags |= 16;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.region_name);
 		case 12: /* metro */
 			if((gpv->item->r.flags&256)==0)
 			{
 				gpv->item->r.metro[0] = '\0';
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"location","metro_code") != MMDB_SUCCESS)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UINT16)
 					snprintf(gpv->item->r.metro, 15, "%hd", entry_data.uint16);
 				gpv->item->r.flags |= 256;
 			}
 			return pv_geoip2_get_strzval(msg, param, res,
 					gpv->item->r.metro);
 		case 13: /* nmask */
 			if((gpv->item->r.flags&1024)==0)
 			{
 				gpv->item->r.nmask[0] = '\0';
 				snprintf(gpv->item->r.nmask, 8, "%hd", gpv->item->r.record.netmask);
 				gpv->item->r.flags |= 1024;
 			}
 			return pv_geoip2_get_strzval(msg, param, res,
 					gpv->item->r.nmask);
 		default: /* cc */
 			if(gpv->item->r.country.s==NULL)
 			{
 				if(gpv->item->r.flags&512)
 					return pv_get_null(msg, param, res);
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"country","iso_code") != MMDB_SUCCESS
 					&& get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 						"registered_country","iso_code") != MMDB_SUCCESS
c49011c2
 					)
cdba71ea
 					return pv_get_null(msg, param, res);
 				if(entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_UTF8_STRING) {
 					gpv->item->r.country.s = (char *)entry_data.utf8_string;
 					gpv->item->r.country.len = entry_data.data_size;
 				}
2f78b97e
 				if(get_mmdb_value2(&gpv->item->r.record.entry, &entry_data,
 					"traits","is_anonymous_proxy") == MMDB_SUCCESS
cdba71ea
 					&& entry_data.has_data && entry_data.type == MMDB_DATA_TYPE_BOOLEAN
 					&& entry_data.boolean) {
 					gpv->item->r.country.s = "A1";
 					gpv->item->r.country.len = 2;
 				}
 				gpv->item->r.flags |= 512;
 			}
 			return pv_get_strval(msg, param, res, &gpv->item->r.country);
 	}
 }
 
2f78b97e
 static int init_shmlock(void)
 {
 	lock = lock_alloc();
 	if (!lock) {
 		LM_CRIT("cannot allocate memory for lock\n");
 		return -1;
 	}
 	if (lock_init(lock) == 0) {
 		LM_CRIT("cannot initialize lock\n");
 		return -1;
 	}
 
 	return 0;
 }
 
 static void destroy_shmlock(void)
 {
 	if (lock) {
 		lock_destroy(lock);
 		lock_dealloc((void *)lock);
 		lock = NULL;
 	}
 }
 
cdba71ea
 int geoip2_init_pv(char *path)
 {
2f78b97e
 	int status;
 	_handle_GeoIP = shm_malloc(sizeof(struct MMDB_s));
6a001d3f
 	memset(_handle_GeoIP, 0, sizeof(struct MMDB_s));
 
2f78b97e
 	if (_handle_GeoIP == NULL)
 	{
 		SHM_MEM_ERROR;
 		return -1;
 	}
 
 	status = MMDB_open(path, MMDB_MODE_MMAP, _handle_GeoIP);
a5484f40
 
cdba71ea
 	if(MMDB_SUCCESS != status)
 	{
 		LM_ERR("cannot open GeoIP database file at: %s\n", path);
 		return -1;
 	}
2f78b97e
 
 	if (init_shmlock() != 0)
 	{
 		LM_ERR("cannot create GeoIP database lock\n");
 		return -1;
 	}
cdba71ea
 	return 0;
 }
 
2f78b97e
 int geoip2_reload_pv(char *path)
 {
 	int status = 0;
 
 	lock_get(lock);
 	MMDB_close(_handle_GeoIP);
 	status = MMDB_open(path, MMDB_MODE_MMAP, _handle_GeoIP);
 	if(MMDB_SUCCESS != status)
 	{
 		LM_ERR("cannot reload GeoIP database file at: %s\n", path);
6a001d3f
 
 	}
 	else
 	{
 		LM_INFO("reloaded GeoIP database file at: %s\n", path);
2f78b97e
 	}
 	lock_release(lock);
 
 	return status;
 }
 
cdba71ea
 void geoip2_destroy_list(void)
 {
 }
 
 void geoip2_destroy_pv(void)
 {
2f78b97e
 	MMDB_close(_handle_GeoIP);
 	shm_free(_handle_GeoIP);
 	_handle_GeoIP = NULL;
 	destroy_shmlock();
cdba71ea
 }
 
 void geoip2_pv_reset(str *name)
 {
 	sr_geoip2_record_t *gr = NULL;
a5484f40
 
cdba71ea
 	gr = sr_geoip2_get_record(name);
 
 	if(gr==NULL)
 		return;
 	memset(gr, 0, sizeof(struct _sr_geoip2_record));
 }
 
 int geoip2_update_pv(str *tomatch, str *name)
 {
 	sr_geoip2_record_t *gr = NULL;
 	int gai_error, mmdb_error;
a5484f40
 
cdba71ea
 	if(tomatch->len>255)
 	{
0c16d682
 		LM_ERR("target too long (max 255): %s\n", tomatch->s);
cdba71ea
 		return -3;
 	}
a5484f40
 
cdba71ea
 	gr = sr_geoip2_get_record(name);
 	if(gr==NULL)
 	{
0c16d682
 		LM_ERR("container not found: %s\n", tomatch->s);
cdba71ea
 		return - 4;
 	}
 
 	strncpy(gr->tomatch, tomatch->s, tomatch->len);
 	tomatch->s[tomatch->len] = '\0';
2f78b97e
 	lock_get(lock);
 	gr->record = MMDB_lookup_string(_handle_GeoIP,
cdba71ea
 			(const char*)gr->tomatch,
 			&gai_error, &mmdb_error
 			);
2f78b97e
 	lock_release(lock);
cdba71ea
 	LM_DBG("attempt to match: %s\n", gr->tomatch);
 	if (gai_error || MMDB_SUCCESS != mmdb_error || !gr->record.found_entry)
 	{
 		LM_DBG("no match for: %s\n", gr->tomatch);
 		return -2;
 	}
 	LM_DBG("geoip2 PV updated for: %s\n", gr->tomatch);
 
 	return 1;
 }