1 /*
2  * Python bindings module for libmodi (pymodi)
3  *
4  * Copyright (C) 2012-2021, Joachim Metz <joachim.metz@gmail.com>
5  *
6  * Refer to AUTHORS for acknowledgements.
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21 
22 #include <common.h>
23 #include <narrow_string.h>
24 #include <types.h>
25 
26 #if defined( HAVE_STDLIB_H ) || defined( HAVE_WINAPI )
27 #include <stdlib.h>
28 #endif
29 
30 #include "pymodi.h"
31 #include "pymodi_error.h"
32 #include "pymodi_file_object_io_handle.h"
33 #include "pymodi_handle.h"
34 #include "pymodi_libbfio.h"
35 #include "pymodi_libcerror.h"
36 #include "pymodi_libmodi.h"
37 #include "pymodi_python.h"
38 #include "pymodi_unused.h"
39 
40 #if !defined( LIBMODI_HAVE_BFIO )
41 
42 LIBMODI_EXTERN \
43 int libmodi_check_file_signature_file_io_handle(
44      libbfio_handle_t *file_io_handle,
45      libmodi_error_t **error );
46 
47 #endif /* !defined( LIBMODI_HAVE_BFIO ) */
48 
49 /* The pymodi module methods
50  */
51 PyMethodDef pymodi_module_methods[] = {
52 	{ "get_version",
53 	  (PyCFunction) pymodi_get_version,
54 	  METH_NOARGS,
55 	  "get_version() -> String\n"
56 	  "\n"
57 	  "Retrieves the version." },
58 
59 	{ "check_file_signature",
60 	  (PyCFunction) pymodi_check_file_signature,
61 	  METH_VARARGS | METH_KEYWORDS,
62 	  "check_file_signature(filename) -> Boolean\n"
63 	  "\n"
64 	  "Checks if a file has a Mac OS disk image file signature." },
65 
66 	{ "check_file_signature_file_object",
67 	  (PyCFunction) pymodi_check_file_signature_file_object,
68 	  METH_VARARGS | METH_KEYWORDS,
69 	  "check_file_signature_file_object(file_object) -> Boolean\n"
70 	  "\n"
71 	  "Checks if a file has a Mac OS disk image file signature using a file-like object." },
72 
73 	{ "open",
74 	  (PyCFunction) pymodi_open_new_handle,
75 	  METH_VARARGS | METH_KEYWORDS,
76 	  "open(filename, mode='r') -> Object\n"
77 	  "\n"
78 	  "Opens a handle." },
79 
80 	{ "open_file_object",
81 	  (PyCFunction) pymodi_open_new_handle_with_file_object,
82 	  METH_VARARGS | METH_KEYWORDS,
83 	  "open_file_object(file_object, mode='r') -> Object\n"
84 	  "\n"
85 	  "Opens a handle using a file-like object." },
86 
87 	/* Sentinel */
88 	{ NULL, NULL, 0, NULL }
89 };
90 
91 /* Retrieves the pymodi/libmodi version
92  * Returns a Python object if successful or NULL on error
93  */
pymodi_get_version(PyObject * self PYMODI_ATTRIBUTE_UNUSED,PyObject * arguments PYMODI_ATTRIBUTE_UNUSED)94 PyObject *pymodi_get_version(
95            PyObject *self PYMODI_ATTRIBUTE_UNUSED,
96            PyObject *arguments PYMODI_ATTRIBUTE_UNUSED )
97 {
98 	const char *errors           = NULL;
99 	const char *version_string   = NULL;
100 	size_t version_string_length = 0;
101 
102 	PYMODI_UNREFERENCED_PARAMETER( self )
103 	PYMODI_UNREFERENCED_PARAMETER( arguments )
104 
105 	Py_BEGIN_ALLOW_THREADS
106 
107 	version_string = libmodi_get_version();
108 
109 	Py_END_ALLOW_THREADS
110 
111 	version_string_length = narrow_string_length(
112 	                         version_string );
113 
114 	/* Pass the string length to PyUnicode_DecodeUTF8
115 	 * otherwise it makes the end of string character is part
116 	 * of the string
117 	 */
118 	return( PyUnicode_DecodeUTF8(
119 	         version_string,
120 	         (Py_ssize_t) version_string_length,
121 	         errors ) );
122 }
123 
124 /* Checks if a file has a Mac OS disk image file signature
125  * Returns a Python object if successful or NULL on error
126  */
pymodi_check_file_signature(PyObject * self PYMODI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)127 PyObject *pymodi_check_file_signature(
128            PyObject *self PYMODI_ATTRIBUTE_UNUSED,
129            PyObject *arguments,
130            PyObject *keywords )
131 {
132 	PyObject *string_object     = NULL;
133 	libcerror_error_t *error    = NULL;
134 	const char *filename_narrow = NULL;
135 	static char *function       = "pymodi_check_file_signature";
136 	static char *keyword_list[] = { "filename", NULL };
137 	int result                  = 0;
138 
139 #if defined( HAVE_WIDE_SYSTEM_CHARACTER )
140 	const wchar_t *filename_wide = NULL;
141 #else
142 	PyObject *utf8_string_object = NULL;
143 #endif
144 
145 	PYMODI_UNREFERENCED_PARAMETER( self )
146 
147 	/* Note that PyArg_ParseTupleAndKeywords with "s" will force Unicode strings to be converted to narrow character string.
148 	 * On Windows the narrow character strings contains an extended ASCII string with a codepage. Hence we get a conversion
149 	 * exception. This will also fail if the default encoding is not set correctly. We cannot use "u" here either since that
150 	 * does not allow us to pass non Unicode string objects and Python (at least 2.7) does not seems to automatically upcast them.
151 	 */
152 	if( PyArg_ParseTupleAndKeywords(
153 	     arguments,
154 	     keywords,
155 	     "O|",
156 	     keyword_list,
157 	     &string_object ) == 0 )
158 	{
159 		return( NULL );
160 	}
161 	PyErr_Clear();
162 
163 	result = PyObject_IsInstance(
164 	          string_object,
165 	          (PyObject *) &PyUnicode_Type );
166 
167 	if( result == -1 )
168 	{
169 		pymodi_error_fetch_and_raise(
170 		 PyExc_RuntimeError,
171 		 "%s: unable to determine if string object is of type Unicode.",
172 		 function );
173 
174 		return( NULL );
175 	}
176 	else if( result != 0 )
177 	{
178 		PyErr_Clear();
179 
180 #if defined( HAVE_WIDE_SYSTEM_CHARACTER )
181 		filename_wide = (wchar_t *) PyUnicode_AsUnicode(
182 		                             string_object );
183 		Py_BEGIN_ALLOW_THREADS
184 
185 		result = libmodi_check_file_signature_wide(
186 		          filename_wide,
187 		          &error );
188 
189 		Py_END_ALLOW_THREADS
190 #else
191 		utf8_string_object = PyUnicode_AsUTF8String(
192 		                      string_object );
193 
194 		if( utf8_string_object == NULL )
195 		{
196 			pymodi_error_fetch_and_raise(
197 			 PyExc_RuntimeError,
198 			 "%s: unable to convert Unicode string to UTF-8.",
199 			 function );
200 
201 			return( NULL );
202 		}
203 #if PY_MAJOR_VERSION >= 3
204 		filename_narrow = PyBytes_AsString(
205 		                   utf8_string_object );
206 #else
207 		filename_narrow = PyString_AsString(
208 		                   utf8_string_object );
209 #endif
210 		Py_BEGIN_ALLOW_THREADS
211 
212 		result = libmodi_check_file_signature(
213 		          filename_narrow,
214 		          &error );
215 
216 		Py_END_ALLOW_THREADS
217 
218 		Py_DecRef(
219 		 utf8_string_object );
220 
221 #endif /* defined( HAVE_WIDE_SYSTEM_CHARACTER ) */
222 
223 		if( result == -1 )
224 		{
225 			pymodi_error_raise(
226 			 error,
227 			 PyExc_IOError,
228 			 "%s: unable to check file signature.",
229 			 function );
230 
231 			libcerror_error_free(
232 			 &error );
233 
234 			return( NULL );
235 		}
236 		if( result != 0 )
237 		{
238 			Py_IncRef(
239 			 (PyObject *) Py_True );
240 
241 			return( Py_True );
242 		}
243 		Py_IncRef(
244 		 (PyObject *) Py_False );
245 
246 		return( Py_False );
247 	}
248 	PyErr_Clear();
249 
250 #if PY_MAJOR_VERSION >= 3
251 	result = PyObject_IsInstance(
252 	          string_object,
253 	          (PyObject *) &PyBytes_Type );
254 #else
255 	result = PyObject_IsInstance(
256 	          string_object,
257 	          (PyObject *) &PyString_Type );
258 #endif
259 	if( result == -1 )
260 	{
261 		pymodi_error_fetch_and_raise(
262 		 PyExc_RuntimeError,
263 		 "%s: unable to determine if string object is of type string.",
264 		 function );
265 
266 		return( NULL );
267 	}
268 	else if( result != 0 )
269 	{
270 		PyErr_Clear();
271 
272 #if PY_MAJOR_VERSION >= 3
273 		filename_narrow = PyBytes_AsString(
274 		                   string_object );
275 #else
276 		filename_narrow = PyString_AsString(
277 		                   string_object );
278 #endif
279 		Py_BEGIN_ALLOW_THREADS
280 
281 		result = libmodi_check_file_signature(
282 		          filename_narrow,
283 		          &error );
284 
285 		Py_END_ALLOW_THREADS
286 
287 		if( result == -1 )
288 		{
289 			pymodi_error_raise(
290 			 error,
291 			 PyExc_IOError,
292 			 "%s: unable to check file signature.",
293 			 function );
294 
295 			libcerror_error_free(
296 			 &error );
297 
298 			return( NULL );
299 		}
300 		if( result != 0 )
301 		{
302 			Py_IncRef(
303 			 (PyObject *) Py_True );
304 
305 			return( Py_True );
306 		}
307 		Py_IncRef(
308 		 (PyObject *) Py_False );
309 
310 		return( Py_False );
311 	}
312 	PyErr_Format(
313 	 PyExc_TypeError,
314 	 "%s: unsupported string object type.",
315 	 function );
316 
317 	return( NULL );
318 }
319 
320 /* Checks if a file has a Mac OS disk image file signature using a file-like object
321  * Returns a Python object if successful or NULL on error
322  */
pymodi_check_file_signature_file_object(PyObject * self PYMODI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)323 PyObject *pymodi_check_file_signature_file_object(
324            PyObject *self PYMODI_ATTRIBUTE_UNUSED,
325            PyObject *arguments,
326            PyObject *keywords )
327 {
328 	PyObject *file_object            = NULL;
329 	libbfio_handle_t *file_io_handle = NULL;
330 	libcerror_error_t *error         = NULL;
331 	static char *function            = "pymodi_check_file_signature_file_object";
332 	static char *keyword_list[]      = { "file_object", NULL };
333 	int result                       = 0;
334 
335 	PYMODI_UNREFERENCED_PARAMETER( self )
336 
337 	if( PyArg_ParseTupleAndKeywords(
338 	     arguments,
339 	     keywords,
340 	     "O|",
341 	     keyword_list,
342 	     &file_object ) == 0 )
343 	{
344 		return( NULL );
345 	}
346 	if( pymodi_file_object_initialize(
347 	     &file_io_handle,
348 	     file_object,
349 	     &error ) != 1 )
350 	{
351 		pymodi_error_raise(
352 		 error,
353 		 PyExc_MemoryError,
354 		 "%s: unable to initialize file IO handle.",
355 		 function );
356 
357 		libcerror_error_free(
358 		 &error );
359 
360 		goto on_error;
361 	}
362 	Py_BEGIN_ALLOW_THREADS
363 
364 	result = libmodi_check_file_signature_file_io_handle(
365 	          file_io_handle,
366 	          &error );
367 
368 	Py_END_ALLOW_THREADS
369 
370 	if( result == -1 )
371 	{
372 		pymodi_error_raise(
373 		 error,
374 		 PyExc_IOError,
375 		 "%s: unable to check file signature.",
376 		 function );
377 
378 		libcerror_error_free(
379 		 &error );
380 
381 		goto on_error;
382 	}
383 	if( libbfio_handle_free(
384 	     &file_io_handle,
385 	     &error ) != 1 )
386 	{
387 		pymodi_error_raise(
388 		 error,
389 		 PyExc_MemoryError,
390 		 "%s: unable to free file IO handle.",
391 		 function );
392 
393 		libcerror_error_free(
394 		 &error );
395 
396 		goto on_error;
397 	}
398 	if( result != 0 )
399 	{
400 		Py_IncRef(
401 		 (PyObject *) Py_True );
402 
403 		return( Py_True );
404 	}
405 	Py_IncRef(
406 	 (PyObject *) Py_False );
407 
408 	return( Py_False );
409 
410 on_error:
411 	if( file_io_handle != NULL )
412 	{
413 		libbfio_handle_free(
414 		 &file_io_handle,
415 		 NULL );
416 	}
417 	return( NULL );
418 }
419 
420 /* Creates a new handle object and opens it
421  * Returns a Python object if successful or NULL on error
422  */
pymodi_open_new_handle(PyObject * self PYMODI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)423 PyObject *pymodi_open_new_handle(
424            PyObject *self PYMODI_ATTRIBUTE_UNUSED,
425            PyObject *arguments,
426            PyObject *keywords )
427 {
428 	pymodi_handle_t *pymodi_handle = NULL;
429 	static char *function          = "pymodi_open_new_handle";
430 
431 	PYMODI_UNREFERENCED_PARAMETER( self )
432 
433 	/* PyObject_New does not invoke tp_init
434 	 */
435 	pymodi_handle = PyObject_New(
436 	                 struct pymodi_handle,
437 	                 &pymodi_handle_type_object );
438 
439 	if( pymodi_handle == NULL )
440 	{
441 		PyErr_Format(
442 		 PyExc_MemoryError,
443 		 "%s: unable to create handle.",
444 		 function );
445 
446 		goto on_error;
447 	}
448 	if( pymodi_handle_init(
449 	     pymodi_handle ) != 0 )
450 	{
451 		goto on_error;
452 	}
453 	if( pymodi_handle_open(
454 	     pymodi_handle,
455 	     arguments,
456 	     keywords ) == NULL )
457 	{
458 		goto on_error;
459 	}
460 	return( (PyObject *) pymodi_handle );
461 
462 on_error:
463 	if( pymodi_handle != NULL )
464 	{
465 		Py_DecRef(
466 		 (PyObject *) pymodi_handle );
467 	}
468 	return( NULL );
469 }
470 
471 /* Creates a new handle object and opens it using a file-like object
472  * Returns a Python object if successful or NULL on error
473  */
pymodi_open_new_handle_with_file_object(PyObject * self PYMODI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)474 PyObject *pymodi_open_new_handle_with_file_object(
475            PyObject *self PYMODI_ATTRIBUTE_UNUSED,
476            PyObject *arguments,
477            PyObject *keywords )
478 {
479 	pymodi_handle_t *pymodi_handle = NULL;
480 	static char *function          = "pymodi_open_new_handle_with_file_object";
481 
482 	PYMODI_UNREFERENCED_PARAMETER( self )
483 
484 	/* PyObject_New does not invoke tp_init
485 	 */
486 	pymodi_handle = PyObject_New(
487 	                 struct pymodi_handle,
488 	                 &pymodi_handle_type_object );
489 
490 	if( pymodi_handle == NULL )
491 	{
492 		PyErr_Format(
493 		 PyExc_MemoryError,
494 		 "%s: unable to create handle.",
495 		 function );
496 
497 		goto on_error;
498 	}
499 	if( pymodi_handle_init(
500 	     pymodi_handle ) != 0 )
501 	{
502 		goto on_error;
503 	}
504 	if( pymodi_handle_open_file_object(
505 	     pymodi_handle,
506 	     arguments,
507 	     keywords ) == NULL )
508 	{
509 		goto on_error;
510 	}
511 	return( (PyObject *) pymodi_handle );
512 
513 on_error:
514 	if( pymodi_handle != NULL )
515 	{
516 		Py_DecRef(
517 		 (PyObject *) pymodi_handle );
518 	}
519 	return( NULL );
520 }
521 
522 #if PY_MAJOR_VERSION >= 3
523 
524 /* The pymodi module definition
525  */
526 PyModuleDef pymodi_module_definition = {
527 	PyModuleDef_HEAD_INIT,
528 
529 	/* m_name */
530 	"pymodi",
531 	/* m_doc */
532 	"Python libmodi module (pymodi).",
533 	/* m_size */
534 	-1,
535 	/* m_methods */
536 	pymodi_module_methods,
537 	/* m_reload */
538 	NULL,
539 	/* m_traverse */
540 	NULL,
541 	/* m_clear */
542 	NULL,
543 	/* m_free */
544 	NULL,
545 };
546 
547 #endif /* PY_MAJOR_VERSION >= 3 */
548 
549 /* Initializes the pymodi module
550  */
551 #if PY_MAJOR_VERSION >= 3
PyInit_pymodi(void)552 PyMODINIT_FUNC PyInit_pymodi(
553                 void )
554 #else
555 PyMODINIT_FUNC initpymodi(
556                 void )
557 #endif
558 {
559 	PyObject *module           = NULL;
560 	PyGILState_STATE gil_state = 0;
561 
562 #if defined( HAVE_DEBUG_OUTPUT )
563 	libmodi_notify_set_stream(
564 	 stderr,
565 	 NULL );
566 	libmodi_notify_set_verbose(
567 	 1 );
568 #endif
569 
570 	/* Create the module
571 	 * This function must be called before grabbing the GIL
572 	 * otherwise the module will segfault on a version mismatch
573 	 */
574 #if PY_MAJOR_VERSION >= 3
575 	module = PyModule_Create(
576 	          &pymodi_module_definition );
577 #else
578 	module = Py_InitModule3(
579 	          "pymodi",
580 	          pymodi_module_methods,
581 	          "Python libmodi module (pymodi)." );
582 #endif
583 	if( module == NULL )
584 	{
585 #if PY_MAJOR_VERSION >= 3
586 		return( NULL );
587 #else
588 		return;
589 #endif
590 	}
591 #if PY_VERSION_HEX < 0x03070000
592 	PyEval_InitThreads();
593 #endif
594 	gil_state = PyGILState_Ensure();
595 
596 	/* Setup the handle type object
597 	 */
598 	pymodi_handle_type_object.tp_new = PyType_GenericNew;
599 
600 	if( PyType_Ready(
601 	     &pymodi_handle_type_object ) < 0 )
602 	{
603 		goto on_error;
604 	}
605 	Py_IncRef(
606 	 (PyObject *) &pymodi_handle_type_object );
607 
608 	PyModule_AddObject(
609 	 module,
610 	 "handle",
611 	 (PyObject *) &pymodi_handle_type_object );
612 
613 	PyGILState_Release(
614 	 gil_state );
615 
616 #if PY_MAJOR_VERSION >= 3
617 	return( module );
618 #else
619 	return;
620 #endif
621 
622 on_error:
623 	PyGILState_Release(
624 	 gil_state );
625 
626 #if PY_MAJOR_VERSION >= 3
627 	return( NULL );
628 #else
629 	return;
630 #endif
631 }
632 
633