1 /*  Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include "knot/modules/geoip/geodb.h"
18 #include "contrib/strtonum.h"
19 #include "contrib/string.h"
20 
21 #if HAVE_MAXMINDDB
22 static const uint16_t type_map[] = {
23 	[GEODB_KEY_ID]  = MMDB_DATA_TYPE_UINT32,
24 	[GEODB_KEY_TXT] = MMDB_DATA_TYPE_UTF8_STRING
25 };
26 #endif
27 
parse_geodb_path(geodb_path_t * path,const char * input)28 int parse_geodb_path(geodb_path_t *path, const char *input)
29 {
30 	if (path == NULL || input == NULL) {
31 		return -1;
32 	}
33 
34 	// Parse optional type of key.
35 	path->type = GEODB_KEY_TXT;
36 	const char *delim = input;
37 	if (input[0] == '(') {
38 		delim = strchr(input, ')');
39 		if (delim == NULL) {
40 			return -1;
41 		}
42 		input++;
43 		char *type = sprintf_alloc("%.*s", (int)(delim - input), input);
44 		const knot_lookup_t *table = knot_lookup_by_name(geodb_key_types, type);
45 		free(type);
46 		if (table == NULL) {
47 			return -1;
48 		}
49 		path->type = table->id;
50 		input = delim + 1;
51 	}
52 
53 	// Parse the path.
54 	uint16_t len = 0;
55 	while (1) {
56 		delim = strchr(input, '/');
57 		if (delim == NULL) {
58 			delim = input + strlen(input);
59 		}
60 		path->path[len] = malloc(delim - input + 1);
61 		if (path->path[len] == NULL) {
62 			return -1;
63 		}
64 		memcpy(path->path[len], input, delim - input);
65 		path->path[len][delim - input] = '\0';
66 		len++;
67 		if (*delim == 0 || len == GEODB_MAX_PATH_LEN) {
68 			break;
69 		}
70 		input = delim + 1;
71 	}
72 
73 	return 0;
74 }
75 
parse_geodb_data(const char * input,void ** geodata,uint32_t * geodata_len,uint8_t * geodepth,geodb_path_t * path,uint16_t path_cnt)76 int parse_geodb_data(const char *input, void **geodata, uint32_t *geodata_len,
77                      uint8_t *geodepth, geodb_path_t *path, uint16_t path_cnt)
78 {
79 	for (uint16_t i = 0; i < path_cnt; i++) {
80 		const char *delim = strchr(input, ';');
81 		if (delim == NULL) {
82 			delim = input + strlen(input);
83 		}
84 		uint16_t key_len = delim - input;
85 		if (key_len > 0 && !(key_len == 1 && *input == '*')) {
86 			*geodepth = i + 1;
87 			switch (path[i].type) {
88 			case GEODB_KEY_TXT:
89 				geodata[i] = malloc(key_len + 1);
90 				if (geodata[i] == NULL) {
91 					return -1;
92 				}
93 				memcpy(geodata[i], input, key_len);
94 				((char *)geodata[i])[key_len] = '\0';
95 				geodata_len[i] = key_len;
96 				break;
97 			case GEODB_KEY_ID:
98 				geodata[i] = malloc(sizeof(uint32_t));
99 				if (geodata[i] == NULL) {
100 					return -1;
101 				}
102 				if (str_to_u32(input, (uint32_t *)geodata[i]) != KNOT_EOK) {
103 					return -1;
104 				}
105 				geodata_len[i] = sizeof(uint32_t);
106 				break;
107 			default:
108 				assert(0);
109 				return -1;
110 			}
111 		}
112 		if (*delim == '\0') {
113 			break;
114 		}
115 		input = delim + 1;
116 	}
117 
118 	return 0;
119 }
120 
geodb_available(void)121 bool geodb_available(void)
122 {
123 #if HAVE_MAXMINDDB
124 	return true;
125 #else
126 	return false;
127 #endif
128 }
129 
geodb_open(const char * filename)130 geodb_t *geodb_open(const char *filename)
131 {
132 #if HAVE_MAXMINDDB
133 	MMDB_s *db = calloc(1, sizeof(MMDB_s));
134 	if (db == NULL) {
135 		return NULL;
136 	}
137 	int mmdb_error = MMDB_open(filename, MMDB_MODE_MMAP, db);
138 	if (mmdb_error != MMDB_SUCCESS) {
139 		free(db);
140 		return NULL;
141 	}
142 	return db;
143 #else
144 	return NULL;
145 #endif
146 }
147 
geodb_close(geodb_t * geodb)148 void geodb_close(geodb_t *geodb)
149 {
150 #if HAVE_MAXMINDDB
151 	MMDB_close(geodb);
152 #endif
153 }
154 
geodb_query(geodb_t * geodb,geodb_data_t * entries,struct sockaddr * remote,geodb_path_t * paths,uint16_t path_cnt,uint16_t * netmask)155 int geodb_query(geodb_t *geodb, geodb_data_t *entries, struct sockaddr *remote,
156                 geodb_path_t *paths, uint16_t path_cnt, uint16_t *netmask)
157 {
158 #if HAVE_MAXMINDDB
159 	int mmdb_error = 0;
160 	MMDB_lookup_result_s res;
161 	res = MMDB_lookup_sockaddr(geodb, remote, &mmdb_error);
162 	if (mmdb_error != MMDB_SUCCESS || !res.found_entry) {
163 		return -1;
164 	}
165 
166 	// Save netmask.
167 	*netmask = res.netmask;
168 
169 	for (uint16_t i = 0; i < path_cnt; i++) {
170 		// Get the value of the next key.
171 		mmdb_error = MMDB_aget_value(&res.entry, &entries[i], (const char *const*)paths[i].path);
172 		if (mmdb_error != MMDB_SUCCESS && mmdb_error != MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR) {
173 			return -1;
174 		}
175 		if (mmdb_error == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR || !entries[i].has_data) {
176 			entries[i].has_data = false;
177 			continue;
178 		}
179 		// Check the type.
180 		if (entries[i].type != type_map[paths[i].type]) {
181 			entries[i].has_data = false;
182 			continue;
183 		}
184 	}
185 	return 0;
186 #else
187 	return -1;
188 #endif
189 }
190 
geodb_fill_geodata(geodb_data_t * entries,uint16_t path_cnt,void ** geodata,uint32_t * geodata_len,uint8_t * geodepth)191 void geodb_fill_geodata(geodb_data_t *entries, uint16_t path_cnt,
192                         void **geodata, uint32_t *geodata_len, uint8_t *geodepth)
193 {
194 #if HAVE_MAXMINDDB
195 	for (int i = 0; i < path_cnt; i++) {
196 		if (entries[i].has_data) {
197 			*geodepth = i + 1;
198 			switch (entries[i].type) {
199 			case MMDB_DATA_TYPE_UTF8_STRING:
200 				geodata[i] = (void *)entries[i].utf8_string;
201 				geodata_len[i] = entries[i].data_size;
202 				break;
203 			case MMDB_DATA_TYPE_UINT32:
204 				geodata[i] = (void *)&entries[i].uint32;
205 				geodata_len[i] = sizeof(uint32_t);
206 				break;
207 			default:
208 				assert(0);
209 				break;
210 			}
211 		}
212 	}
213 #else
214 	return;
215 #endif
216 }
217