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