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