1 /* MaxMind, Inc., licenses this file to you under the Apache License, Version
2 * 2.0 (the "License"); you may not use this file except in compliance with
3 * the License. You may obtain a copy of the License at
4 *
5 * http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 * License for the specific language governing permissions and limitations
11 * under the License.
12 */
13
14 #include "php_maxminddb.h"
15
16 #ifdef HAVE_CONFIG_H
17 #include "config.h"
18 #endif
19
20 #include <php.h>
21 #include <zend.h>
22 #include "Zend/zend_exceptions.h"
23 #include <maxminddb.h>
24
25 #ifdef ZTS
26 #include <TSRM.h>
27 #endif
28
29 #define __STDC_FORMAT_MACROS
30 #include <inttypes.h>
31
32 #define PHP_MAXMINDDB_NS ZEND_NS_NAME("MaxMind", "Db")
33 #define PHP_MAXMINDDB_READER_NS ZEND_NS_NAME(PHP_MAXMINDDB_NS, "Reader")
34 #define PHP_MAXMINDDB_READER_EX_NS \
35 ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, \
36 "InvalidDatabaseException")
37
38 #ifdef ZEND_ENGINE_3
39 #define Z_MAXMINDDB_P(zv) php_maxminddb_fetch_object(Z_OBJ_P(zv))
40 #define _ZVAL_STRING ZVAL_STRING
41 #define _ZVAL_STRINGL ZVAL_STRINGL
42 typedef size_t strsize_t;
43 typedef zend_object free_obj_t;
44 #else
45 #define Z_MAXMINDDB_P(zv) (maxminddb_obj *) zend_object_store_get_object(zv TSRMLS_CC)
46 #define _ZVAL_STRING(a, b) ZVAL_STRING(a, b, 1)
47 #define _ZVAL_STRINGL(a, b, c) ZVAL_STRINGL(a, b, c, 1)
48 typedef int strsize_t;
49 typedef void free_obj_t;
50 #endif
51
52 #ifdef ZEND_ENGINE_3
53 typedef struct _maxminddb_obj {
54 MMDB_s *mmdb;
55 zend_object std;
56 } maxminddb_obj;
57 #else
58 typedef struct _maxminddb_obj {
59 zend_object std;
60 MMDB_s *mmdb;
61 } maxminddb_obj;
62 #endif
63
64 PHP_FUNCTION(maxminddb);
65
66 static const MMDB_entry_data_list_s *handle_entry_data_list(
67 const MMDB_entry_data_list_s *entry_data_list,
68 zval *z_value
69 TSRMLS_DC);
70 static const MMDB_entry_data_list_s *handle_array(
71 const MMDB_entry_data_list_s *entry_data_list,
72 zval *z_value TSRMLS_DC);
73 static const MMDB_entry_data_list_s *handle_map(
74 const MMDB_entry_data_list_s *entry_data_list,
75 zval *z_value TSRMLS_DC);
76 static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
77 zval *z_value TSRMLS_DC);
78 static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
79 zval *z_value TSRMLS_DC);
80 static zend_class_entry * lookup_class(const char *name TSRMLS_DC);
81
82 #define CHECK_ALLOCATED(val) \
83 if (!val ) { \
84 zend_error(E_ERROR, "Out of memory"); \
85 return; \
86 } \
87
88 #define THROW_EXCEPTION(name, ... ) \
89 { \
90 zend_class_entry *exception_ce = lookup_class(name TSRMLS_CC); \
91 zend_throw_exception_ex(exception_ce, 0 TSRMLS_CC, __VA_ARGS__); \
92 } \
93
94
95 #if PHP_VERSION_ID < 50399
96 #define object_properties_init(zo, class_type) \
97 { \
98 zval *tmp; \
99 zend_hash_copy((*zo).properties, \
100 &class_type->default_properties, \
101 (copy_ctor_func_t)zval_add_ref, \
102 (void *)&tmp, \
103 sizeof(zval *)); \
104 }
105 #endif
106
107 static zend_object_handlers maxminddb_obj_handlers;
108 static zend_class_entry *maxminddb_ce;
109
php_maxminddb_fetch_object(zend_object * obj TSRMLS_DC)110 static inline maxminddb_obj *php_maxminddb_fetch_object(zend_object *obj TSRMLS_DC){
111 #ifdef ZEND_ENGINE_3
112 return (maxminddb_obj *)((char*)(obj) - XtOffsetOf(maxminddb_obj, std));
113 #else
114 return (maxminddb_obj *)obj;
115 #endif
116 }
117
PHP_METHOD(MaxMind_Db_Reader,__construct)118 PHP_METHOD(MaxMind_Db_Reader, __construct){
119 char *db_file = NULL;
120 strsize_t name_len;
121 zval * _this_zval = NULL;
122
123 if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
124 &_this_zval, maxminddb_ce, &db_file, &name_len) == FAILURE) {
125 THROW_EXCEPTION("InvalidArgumentException",
126 "The constructor takes exactly one argument.");
127 return;
128 }
129
130 if (0 != php_check_open_basedir(db_file TSRMLS_CC) || 0 != access(db_file, R_OK)) {
131 THROW_EXCEPTION("InvalidArgumentException",
132 "The file \"%s\" does not exist or is not readable.",
133 db_file);
134 return;
135 }
136
137 MMDB_s *mmdb = (MMDB_s *)emalloc(sizeof(MMDB_s));
138 uint16_t status = MMDB_open(db_file, MMDB_MODE_MMAP, mmdb);
139
140 if (MMDB_SUCCESS != status) {
141 THROW_EXCEPTION(
142 PHP_MAXMINDDB_READER_EX_NS,
143 "Error opening database file (%s). Is this a valid MaxMind DB file?",
144 db_file);
145 efree(mmdb);
146 return;
147 }
148
149 maxminddb_obj *mmdb_obj = Z_MAXMINDDB_P(getThis());
150 mmdb_obj->mmdb = mmdb;
151 }
152
PHP_METHOD(MaxMind_Db_Reader,get)153 PHP_METHOD(MaxMind_Db_Reader, get){
154 char *ip_address = NULL;
155 strsize_t name_len;
156 zval * _this_zval = NULL;
157
158 if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
159 &_this_zval, maxminddb_ce, &ip_address, &name_len) == FAILURE) {
160 THROW_EXCEPTION("InvalidArgumentException",
161 "Method takes exactly one argument.");
162 return;
163 }
164
165 const maxminddb_obj *mmdb_obj =
166 (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
167
168 MMDB_s *mmdb = mmdb_obj->mmdb;
169
170 if (NULL == mmdb) {
171 THROW_EXCEPTION("BadMethodCallException",
172 "Attempt to read from a closed MaxMind DB.");
173 return;
174 }
175
176 int gai_error = 0;
177 int mmdb_error = MMDB_SUCCESS;
178 MMDB_lookup_result_s result =
179 MMDB_lookup_string(mmdb, ip_address, &gai_error,
180 &mmdb_error);
181
182 if (MMDB_SUCCESS != gai_error) {
183 THROW_EXCEPTION("InvalidArgumentException",
184 "The value \"%s\" is not a valid IP address.",
185 ip_address);
186 return;
187 }
188
189 if (MMDB_SUCCESS != mmdb_error) {
190 char *exception_name;
191 if (MMDB_IPV6_LOOKUP_IN_IPV4_DATABASE_ERROR == mmdb_error) {
192 exception_name = "InvalidArgumentException";
193 } else {
194 exception_name = PHP_MAXMINDDB_READER_EX_NS;
195 }
196 THROW_EXCEPTION(exception_name,
197 "Error looking up %s. %s",
198 ip_address, MMDB_strerror(mmdb_error));
199 return;
200 }
201
202 MMDB_entry_data_list_s *entry_data_list = NULL;
203
204 if (!result.found_entry) {
205 RETURN_NULL();
206 }
207
208 int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
209
210 if (MMDB_SUCCESS != status) {
211 THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
212 "Error while looking up data for %s. %s",
213 ip_address, MMDB_strerror(status));
214 MMDB_free_entry_data_list(entry_data_list);
215 return;
216 } else if (NULL == entry_data_list) {
217 THROW_EXCEPTION(
218 PHP_MAXMINDDB_READER_EX_NS,
219 "Error while looking up data for %s. Your database may be corrupt or you have found a bug in libmaxminddb.",
220 ip_address);
221 return;
222 }
223
224 handle_entry_data_list(entry_data_list, return_value TSRMLS_CC);
225 MMDB_free_entry_data_list(entry_data_list);
226 }
227
PHP_METHOD(MaxMind_Db_Reader,metadata)228 PHP_METHOD(MaxMind_Db_Reader, metadata){
229 if (ZEND_NUM_ARGS() != 0) {
230 THROW_EXCEPTION("InvalidArgumentException",
231 "Method takes no arguments.");
232 return;
233 }
234
235 const maxminddb_obj *const mmdb_obj =
236 (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
237
238 if (NULL == mmdb_obj->mmdb) {
239 THROW_EXCEPTION("BadMethodCallException",
240 "Attempt to read from a closed MaxMind DB.");
241 return;
242 }
243
244 const char *const name = ZEND_NS_NAME(PHP_MAXMINDDB_READER_NS, "Metadata");
245 zend_class_entry *metadata_ce = lookup_class(name TSRMLS_CC);
246
247 object_init_ex(return_value, metadata_ce);
248
249 #ifdef ZEND_ENGINE_3
250 zval _metadata_array;
251 zval *metadata_array = &_metadata_array;
252 ZVAL_NULL(metadata_array);
253 #else
254 zval *metadata_array;
255 ALLOC_INIT_ZVAL(metadata_array);
256 #endif
257
258 MMDB_entry_data_list_s *entry_data_list;
259 MMDB_get_metadata_as_entry_data_list(mmdb_obj->mmdb, &entry_data_list);
260
261 handle_entry_data_list(entry_data_list, metadata_array TSRMLS_CC);
262 MMDB_free_entry_data_list(entry_data_list);
263 #ifdef ZEND_ENGINE_3
264 zend_call_method_with_1_params(return_value, metadata_ce,
265 &metadata_ce->constructor,
266 ZEND_CONSTRUCTOR_FUNC_NAME,
267 NULL,
268 metadata_array);
269 zval_ptr_dtor(metadata_array);
270 #else
271 zend_call_method_with_1_params(&return_value, metadata_ce,
272 &metadata_ce->constructor,
273 ZEND_CONSTRUCTOR_FUNC_NAME,
274 NULL,
275 metadata_array);
276 zval_ptr_dtor(&metadata_array);
277 #endif
278 }
279
PHP_METHOD(MaxMind_Db_Reader,close)280 PHP_METHOD(MaxMind_Db_Reader, close){
281 if (ZEND_NUM_ARGS() != 0) {
282 THROW_EXCEPTION("InvalidArgumentException",
283 "Method takes no arguments.");
284 return;
285 }
286
287 maxminddb_obj *mmdb_obj =
288 (maxminddb_obj *)Z_MAXMINDDB_P(getThis());
289
290 if (NULL == mmdb_obj->mmdb) {
291 THROW_EXCEPTION("BadMethodCallException",
292 "Attempt to close a closed MaxMind DB.");
293 return;
294 }
295 MMDB_close(mmdb_obj->mmdb);
296 efree(mmdb_obj->mmdb);
297 mmdb_obj->mmdb = NULL;
298 }
299
handle_entry_data_list(const MMDB_entry_data_list_s * entry_data_list,zval * z_value TSRMLS_DC)300 static const MMDB_entry_data_list_s *handle_entry_data_list(
301 const MMDB_entry_data_list_s *entry_data_list,
302 zval *z_value
303 TSRMLS_DC)
304 {
305 switch (entry_data_list->entry_data.type) {
306 case MMDB_DATA_TYPE_MAP:
307 return handle_map(entry_data_list, z_value TSRMLS_CC);
308 case MMDB_DATA_TYPE_ARRAY:
309 return handle_array(entry_data_list, z_value TSRMLS_CC);
310 case MMDB_DATA_TYPE_UTF8_STRING:
311 _ZVAL_STRINGL(z_value,
312 (char *)entry_data_list->entry_data.utf8_string,
313 entry_data_list->entry_data.data_size);
314 break;
315 case MMDB_DATA_TYPE_BYTES:
316 _ZVAL_STRINGL(z_value, (char *)entry_data_list->entry_data.bytes,
317 entry_data_list->entry_data.data_size);
318 break;
319 case MMDB_DATA_TYPE_DOUBLE:
320 ZVAL_DOUBLE(z_value, entry_data_list->entry_data.double_value);
321 break;
322 case MMDB_DATA_TYPE_FLOAT:
323 ZVAL_DOUBLE(z_value, entry_data_list->entry_data.float_value);
324 break;
325 case MMDB_DATA_TYPE_UINT16:
326 ZVAL_LONG(z_value, entry_data_list->entry_data.uint16);
327 break;
328 case MMDB_DATA_TYPE_UINT32:
329 ZVAL_LONG(z_value, entry_data_list->entry_data.uint32);
330 break;
331 case MMDB_DATA_TYPE_BOOLEAN:
332 ZVAL_BOOL(z_value, entry_data_list->entry_data.boolean);
333 break;
334 case MMDB_DATA_TYPE_UINT64:
335 handle_uint64(entry_data_list, z_value TSRMLS_CC);
336 break;
337 case MMDB_DATA_TYPE_UINT128:
338 handle_uint128(entry_data_list, z_value TSRMLS_CC);
339 break;
340 case MMDB_DATA_TYPE_INT32:
341 ZVAL_LONG(z_value, entry_data_list->entry_data.int32);
342 break;
343 default:
344 THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
345 "Invalid data type arguments: %d",
346 entry_data_list->entry_data.type);
347 return NULL;
348 }
349 return entry_data_list;
350 }
351
handle_map(const MMDB_entry_data_list_s * entry_data_list,zval * z_value TSRMLS_DC)352 static const MMDB_entry_data_list_s *handle_map(
353 const MMDB_entry_data_list_s *entry_data_list,
354 zval *z_value TSRMLS_DC)
355 {
356 array_init(z_value);
357 const uint32_t map_size = entry_data_list->entry_data.data_size;
358
359 uint i;
360 for (i = 0; i < map_size && entry_data_list; i++ ) {
361 entry_data_list = entry_data_list->next;
362
363 char *key =
364 estrndup((char *)entry_data_list->entry_data.utf8_string,
365 entry_data_list->entry_data.data_size);
366 if (NULL == key) {
367 THROW_EXCEPTION(PHP_MAXMINDDB_READER_EX_NS,
368 "Invalid data type arguments");
369 return NULL;
370 }
371
372 entry_data_list = entry_data_list->next;
373 #ifdef ZEND_ENGINE_3
374 zval _new_value;
375 zval * new_value = &_new_value;
376 ZVAL_NULL(new_value);
377 #else
378 zval *new_value;
379 ALLOC_INIT_ZVAL(new_value);
380 #endif
381 entry_data_list = handle_entry_data_list(entry_data_list,
382 new_value TSRMLS_CC);
383 add_assoc_zval(z_value, key, new_value);
384 efree(key);
385 }
386 return entry_data_list;
387 }
388
handle_array(const MMDB_entry_data_list_s * entry_data_list,zval * z_value TSRMLS_DC)389 static const MMDB_entry_data_list_s *handle_array(
390 const MMDB_entry_data_list_s *entry_data_list,
391 zval *z_value TSRMLS_DC)
392 {
393 const uint32_t size = entry_data_list->entry_data.data_size;
394
395 array_init(z_value);
396
397 uint i;
398 for (i = 0; i < size && entry_data_list; i++) {
399 entry_data_list = entry_data_list->next;
400 #ifdef ZEND_ENGINE_3
401 zval _new_value;
402 zval * new_value = &_new_value;
403 ZVAL_NULL(new_value);
404 #else
405 zval *new_value;
406 ALLOC_INIT_ZVAL(new_value);
407 #endif
408 entry_data_list = handle_entry_data_list(entry_data_list,
409 new_value TSRMLS_CC);
410 add_next_index_zval(z_value, new_value);
411 }
412 return entry_data_list;
413 }
414
handle_uint128(const MMDB_entry_data_list_s * entry_data_list,zval * z_value TSRMLS_DC)415 static void handle_uint128(const MMDB_entry_data_list_s *entry_data_list,
416 zval *z_value TSRMLS_DC)
417 {
418 uint64_t high = 0;
419 uint64_t low = 0;
420 #if MMDB_UINT128_IS_BYTE_ARRAY
421 int i;
422 for (i = 0; i < 8; i++) {
423 high = (high << 8) | entry_data_list->entry_data.uint128[i];
424 }
425
426 for (i = 8; i < 16; i++) {
427 low = (low << 8) | entry_data_list->entry_data.uint128[i];
428 }
429 #else
430 high = entry_data_list->entry_data.uint128 >> 64;
431 low = (uint64_t)entry_data_list->entry_data.uint128;
432 #endif
433
434 char *num_str;
435 spprintf(&num_str, 0, "0x%016" PRIX64 "%016" PRIX64, high, low);
436 CHECK_ALLOCATED(num_str);
437
438 _ZVAL_STRING(z_value, num_str);
439 efree(num_str);
440 }
441
handle_uint64(const MMDB_entry_data_list_s * entry_data_list,zval * z_value TSRMLS_DC)442 static void handle_uint64(const MMDB_entry_data_list_s *entry_data_list,
443 zval *z_value TSRMLS_DC)
444 {
445 // We return it as a string because PHP uses signed longs
446 char *int_str;
447 spprintf(&int_str, 0, "%" PRIu64,
448 entry_data_list->entry_data.uint64);
449 CHECK_ALLOCATED(int_str);
450
451 _ZVAL_STRING(z_value, int_str);
452 efree(int_str);
453 }
454
lookup_class(const char * name TSRMLS_DC)455 static zend_class_entry *lookup_class(const char *name TSRMLS_DC)
456 {
457 #ifdef ZEND_ENGINE_3
458 zend_string *n = zend_string_init(name, strlen(name), 0);
459 zend_class_entry *ce = zend_lookup_class(n);
460 zend_string_release(n);
461 if( NULL == ce ) {
462 zend_error(E_ERROR, "Class %s not found", name);
463 }
464 return ce;
465 #else
466 zend_class_entry **ce;
467 if (FAILURE ==
468 zend_lookup_class(name, strlen(name),
469 &ce TSRMLS_CC)) {
470 zend_error(E_ERROR, "Class %s not found", name);
471 }
472 return *ce;
473 #endif
474 }
475
maxminddb_free_storage(free_obj_t * object TSRMLS_DC)476 static void maxminddb_free_storage(free_obj_t *object TSRMLS_DC)
477 {
478 maxminddb_obj *obj = php_maxminddb_fetch_object((zend_object *)object TSRMLS_CC);
479 if (obj->mmdb != NULL) {
480 MMDB_close(obj->mmdb);
481 efree(obj->mmdb);
482 }
483
484 zend_object_std_dtor(&obj->std TSRMLS_CC);
485 #ifndef ZEND_ENGINE_3
486 efree(object);
487 #endif
488 }
489
490 #ifdef ZEND_ENGINE_3
maxminddb_create_handler(zend_class_entry * type TSRMLS_DC)491 static zend_object *maxminddb_create_handler(
492 zend_class_entry *type TSRMLS_DC)
493 {
494 maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
495 zend_object_std_init(&obj->std, type TSRMLS_CC);
496 object_properties_init(&(obj->std), type);
497
498 obj->std.handlers = &maxminddb_obj_handlers;
499
500 return &obj->std;
501 }
502 #else
maxminddb_create_handler(zend_class_entry * type TSRMLS_DC)503 static zend_object_value maxminddb_create_handler(
504 zend_class_entry *type TSRMLS_DC)
505 {
506 zend_object_value retval;
507
508 maxminddb_obj *obj = (maxminddb_obj *)ecalloc(1, sizeof(maxminddb_obj));
509 zend_object_std_init(&obj->std, type TSRMLS_CC);
510 object_properties_init(&(obj->std), type);
511
512 retval.handle = zend_objects_store_put(obj, NULL,
513 maxminddb_free_storage,
514 NULL TSRMLS_CC);
515 retval.handlers = &maxminddb_obj_handlers;
516
517 return retval;
518 }
519 #endif
520
521 /* *INDENT-OFF* */
522 static zend_function_entry maxminddb_methods[] = {
523 PHP_ME(MaxMind_Db_Reader, __construct, NULL,
524 ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
525 PHP_ME(MaxMind_Db_Reader, close, NULL, ZEND_ACC_PUBLIC)
526 PHP_ME(MaxMind_Db_Reader, get, NULL, ZEND_ACC_PUBLIC)
527 PHP_ME(MaxMind_Db_Reader, metadata, NULL, ZEND_ACC_PUBLIC)
528 { NULL, NULL, NULL }
529 };
530 /* *INDENT-ON* */
531
PHP_MINIT_FUNCTION(maxminddb)532 PHP_MINIT_FUNCTION(maxminddb){
533 zend_class_entry ce;
534
535 INIT_CLASS_ENTRY(ce, PHP_MAXMINDDB_READER_NS, maxminddb_methods);
536 maxminddb_ce = zend_register_internal_class(&ce TSRMLS_CC);
537 maxminddb_ce->create_object = maxminddb_create_handler;
538 memcpy(&maxminddb_obj_handlers,
539 zend_get_std_object_handlers(), sizeof(zend_object_handlers));
540 maxminddb_obj_handlers.clone_obj = NULL;
541 #ifdef ZEND_ENGINE_3
542 maxminddb_obj_handlers.offset = XtOffsetOf(maxminddb_obj, std);
543 maxminddb_obj_handlers.free_obj = maxminddb_free_storage;
544 #endif
545
546 return SUCCESS;
547 }
548
549 zend_module_entry maxminddb_module_entry = {
550 STANDARD_MODULE_HEADER,
551 PHP_MAXMINDDB_EXTNAME,
552 NULL,
553 PHP_MINIT(maxminddb),
554 NULL,
555 NULL,
556 NULL,
557 NULL,
558 PHP_MAXMINDDB_VERSION,
559 STANDARD_MODULE_PROPERTIES
560 };
561
562 #ifdef COMPILE_DL_MAXMINDDB
563 ZEND_GET_MODULE(maxminddb)
564 #endif
565