1 /*
2  * Python bindings module for libvhdi (pyvhdi)
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 "pyvhdi.h"
31 #include "pyvhdi_disk_types.h"
32 #include "pyvhdi_error.h"
33 #include "pyvhdi_file.h"
34 #include "pyvhdi_file_object_io_handle.h"
35 #include "pyvhdi_libbfio.h"
36 #include "pyvhdi_libcerror.h"
37 #include "pyvhdi_libvhdi.h"
38 #include "pyvhdi_python.h"
39 #include "pyvhdi_unused.h"
40 
41 #if !defined( LIBVHDI_HAVE_BFIO )
42 
43 LIBVHDI_EXTERN \
44 int libvhdi_check_file_signature_file_io_handle(
45      libbfio_handle_t *file_io_handle,
46      libvhdi_error_t **error );
47 
48 #endif /* !defined( LIBVHDI_HAVE_BFIO ) */
49 
50 /* The pyvhdi module methods
51  */
52 PyMethodDef pyvhdi_module_methods[] = {
53 	{ "get_version",
54 	  (PyCFunction) pyvhdi_get_version,
55 	  METH_NOARGS,
56 	  "get_version() -> String\n"
57 	  "\n"
58 	  "Retrieves the version." },
59 
60 	{ "check_file_signature",
61 	  (PyCFunction) pyvhdi_check_file_signature,
62 	  METH_VARARGS | METH_KEYWORDS,
63 	  "check_file_signature(filename) -> Boolean\n"
64 	  "\n"
65 	  "Checks if a file has a Virtual Hard Disk (VHD) image file signature." },
66 
67 	{ "check_file_signature_file_object",
68 	  (PyCFunction) pyvhdi_check_file_signature_file_object,
69 	  METH_VARARGS | METH_KEYWORDS,
70 	  "check_file_signature_file_object(file_object) -> Boolean\n"
71 	  "\n"
72 	  "Checks if a file has a Virtual Hard Disk (VHD) image file signature using a file-like object." },
73 
74 	{ "open",
75 	  (PyCFunction) pyvhdi_open_new_file,
76 	  METH_VARARGS | METH_KEYWORDS,
77 	  "open(filename, mode='r') -> Object\n"
78 	  "\n"
79 	  "Opens a file." },
80 
81 	{ "open_file_object",
82 	  (PyCFunction) pyvhdi_open_new_file_with_file_object,
83 	  METH_VARARGS | METH_KEYWORDS,
84 	  "open_file_object(file_object, mode='r') -> Object\n"
85 	  "\n"
86 	  "Opens a file using a file-like object." },
87 
88 	/* Sentinel */
89 	{ NULL, NULL, 0, NULL }
90 };
91 
92 /* Retrieves the pyvhdi/libvhdi version
93  * Returns a Python object if successful or NULL on error
94  */
pyvhdi_get_version(PyObject * self PYVHDI_ATTRIBUTE_UNUSED,PyObject * arguments PYVHDI_ATTRIBUTE_UNUSED)95 PyObject *pyvhdi_get_version(
96            PyObject *self PYVHDI_ATTRIBUTE_UNUSED,
97            PyObject *arguments PYVHDI_ATTRIBUTE_UNUSED )
98 {
99 	const char *errors           = NULL;
100 	const char *version_string   = NULL;
101 	size_t version_string_length = 0;
102 
103 	PYVHDI_UNREFERENCED_PARAMETER( self )
104 	PYVHDI_UNREFERENCED_PARAMETER( arguments )
105 
106 	Py_BEGIN_ALLOW_THREADS
107 
108 	version_string = libvhdi_get_version();
109 
110 	Py_END_ALLOW_THREADS
111 
112 	version_string_length = narrow_string_length(
113 	                         version_string );
114 
115 	/* Pass the string length to PyUnicode_DecodeUTF8
116 	 * otherwise it makes the end of string character is part
117 	 * of the string
118 	 */
119 	return( PyUnicode_DecodeUTF8(
120 	         version_string,
121 	         (Py_ssize_t) version_string_length,
122 	         errors ) );
123 }
124 
125 /* Checks if a file has a Virtual Hard Disk (VHD) image file signature
126  * Returns a Python object if successful or NULL on error
127  */
pyvhdi_check_file_signature(PyObject * self PYVHDI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)128 PyObject *pyvhdi_check_file_signature(
129            PyObject *self PYVHDI_ATTRIBUTE_UNUSED,
130            PyObject *arguments,
131            PyObject *keywords )
132 {
133 	PyObject *string_object     = NULL;
134 	libcerror_error_t *error    = NULL;
135 	const char *filename_narrow = NULL;
136 	static char *function       = "pyvhdi_check_file_signature";
137 	static char *keyword_list[] = { "filename", NULL };
138 	int result                  = 0;
139 
140 #if defined( HAVE_WIDE_SYSTEM_CHARACTER )
141 	const wchar_t *filename_wide = NULL;
142 #else
143 	PyObject *utf8_string_object = NULL;
144 #endif
145 
146 	PYVHDI_UNREFERENCED_PARAMETER( self )
147 
148 	/* Note that PyArg_ParseTupleAndKeywords with "s" will force Unicode strings to be converted to narrow character string.
149 	 * On Windows the narrow character strings contains an extended ASCII string with a codepage. Hence we get a conversion
150 	 * exception. This will also fail if the default encoding is not set correctly. We cannot use "u" here either since that
151 	 * does not allow us to pass non Unicode string objects and Python (at least 2.7) does not seems to automatically upcast them.
152 	 */
153 	if( PyArg_ParseTupleAndKeywords(
154 	     arguments,
155 	     keywords,
156 	     "O|",
157 	     keyword_list,
158 	     &string_object ) == 0 )
159 	{
160 		return( NULL );
161 	}
162 	PyErr_Clear();
163 
164 	result = PyObject_IsInstance(
165 	          string_object,
166 	          (PyObject *) &PyUnicode_Type );
167 
168 	if( result == -1 )
169 	{
170 		pyvhdi_error_fetch_and_raise(
171 		 PyExc_RuntimeError,
172 		 "%s: unable to determine if string object is of type Unicode.",
173 		 function );
174 
175 		return( NULL );
176 	}
177 	else if( result != 0 )
178 	{
179 		PyErr_Clear();
180 
181 #if defined( HAVE_WIDE_SYSTEM_CHARACTER )
182 		filename_wide = (wchar_t *) PyUnicode_AsUnicode(
183 		                             string_object );
184 		Py_BEGIN_ALLOW_THREADS
185 
186 		result = libvhdi_check_file_signature_wide(
187 		          filename_wide,
188 		          &error );
189 
190 		Py_END_ALLOW_THREADS
191 #else
192 		utf8_string_object = PyUnicode_AsUTF8String(
193 		                      string_object );
194 
195 		if( utf8_string_object == NULL )
196 		{
197 			pyvhdi_error_fetch_and_raise(
198 			 PyExc_RuntimeError,
199 			 "%s: unable to convert Unicode string to UTF-8.",
200 			 function );
201 
202 			return( NULL );
203 		}
204 #if PY_MAJOR_VERSION >= 3
205 		filename_narrow = PyBytes_AsString(
206 		                   utf8_string_object );
207 #else
208 		filename_narrow = PyString_AsString(
209 		                   utf8_string_object );
210 #endif
211 		Py_BEGIN_ALLOW_THREADS
212 
213 		result = libvhdi_check_file_signature(
214 		          filename_narrow,
215 		          &error );
216 
217 		Py_END_ALLOW_THREADS
218 
219 		Py_DecRef(
220 		 utf8_string_object );
221 
222 #endif /* defined( HAVE_WIDE_SYSTEM_CHARACTER ) */
223 
224 		if( result == -1 )
225 		{
226 			pyvhdi_error_raise(
227 			 error,
228 			 PyExc_IOError,
229 			 "%s: unable to check file signature.",
230 			 function );
231 
232 			libcerror_error_free(
233 			 &error );
234 
235 			return( NULL );
236 		}
237 		if( result != 0 )
238 		{
239 			Py_IncRef(
240 			 (PyObject *) Py_True );
241 
242 			return( Py_True );
243 		}
244 		Py_IncRef(
245 		 (PyObject *) Py_False );
246 
247 		return( Py_False );
248 	}
249 	PyErr_Clear();
250 
251 #if PY_MAJOR_VERSION >= 3
252 	result = PyObject_IsInstance(
253 	          string_object,
254 	          (PyObject *) &PyBytes_Type );
255 #else
256 	result = PyObject_IsInstance(
257 	          string_object,
258 	          (PyObject *) &PyString_Type );
259 #endif
260 	if( result == -1 )
261 	{
262 		pyvhdi_error_fetch_and_raise(
263 		 PyExc_RuntimeError,
264 		 "%s: unable to determine if string object is of type string.",
265 		 function );
266 
267 		return( NULL );
268 	}
269 	else if( result != 0 )
270 	{
271 		PyErr_Clear();
272 
273 #if PY_MAJOR_VERSION >= 3
274 		filename_narrow = PyBytes_AsString(
275 		                   string_object );
276 #else
277 		filename_narrow = PyString_AsString(
278 		                   string_object );
279 #endif
280 		Py_BEGIN_ALLOW_THREADS
281 
282 		result = libvhdi_check_file_signature(
283 		          filename_narrow,
284 		          &error );
285 
286 		Py_END_ALLOW_THREADS
287 
288 		if( result == -1 )
289 		{
290 			pyvhdi_error_raise(
291 			 error,
292 			 PyExc_IOError,
293 			 "%s: unable to check file signature.",
294 			 function );
295 
296 			libcerror_error_free(
297 			 &error );
298 
299 			return( NULL );
300 		}
301 		if( result != 0 )
302 		{
303 			Py_IncRef(
304 			 (PyObject *) Py_True );
305 
306 			return( Py_True );
307 		}
308 		Py_IncRef(
309 		 (PyObject *) Py_False );
310 
311 		return( Py_False );
312 	}
313 	PyErr_Format(
314 	 PyExc_TypeError,
315 	 "%s: unsupported string object type.",
316 	 function );
317 
318 	return( NULL );
319 }
320 
321 /* Checks if a file has a Virtual Hard Disk (VHD) image file signature using a file-like object
322  * Returns a Python object if successful or NULL on error
323  */
pyvhdi_check_file_signature_file_object(PyObject * self PYVHDI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)324 PyObject *pyvhdi_check_file_signature_file_object(
325            PyObject *self PYVHDI_ATTRIBUTE_UNUSED,
326            PyObject *arguments,
327            PyObject *keywords )
328 {
329 	PyObject *file_object            = NULL;
330 	libbfio_handle_t *file_io_handle = NULL;
331 	libcerror_error_t *error         = NULL;
332 	static char *function            = "pyvhdi_check_file_signature_file_object";
333 	static char *keyword_list[]      = { "file_object", NULL };
334 	int result                       = 0;
335 
336 	PYVHDI_UNREFERENCED_PARAMETER( self )
337 
338 	if( PyArg_ParseTupleAndKeywords(
339 	     arguments,
340 	     keywords,
341 	     "O|",
342 	     keyword_list,
343 	     &file_object ) == 0 )
344 	{
345 		return( NULL );
346 	}
347 	if( pyvhdi_file_object_initialize(
348 	     &file_io_handle,
349 	     file_object,
350 	     &error ) != 1 )
351 	{
352 		pyvhdi_error_raise(
353 		 error,
354 		 PyExc_MemoryError,
355 		 "%s: unable to initialize file IO handle.",
356 		 function );
357 
358 		libcerror_error_free(
359 		 &error );
360 
361 		goto on_error;
362 	}
363 	Py_BEGIN_ALLOW_THREADS
364 
365 	result = libvhdi_check_file_signature_file_io_handle(
366 	          file_io_handle,
367 	          &error );
368 
369 	Py_END_ALLOW_THREADS
370 
371 	if( result == -1 )
372 	{
373 		pyvhdi_error_raise(
374 		 error,
375 		 PyExc_IOError,
376 		 "%s: unable to check file signature.",
377 		 function );
378 
379 		libcerror_error_free(
380 		 &error );
381 
382 		goto on_error;
383 	}
384 	if( libbfio_handle_free(
385 	     &file_io_handle,
386 	     &error ) != 1 )
387 	{
388 		pyvhdi_error_raise(
389 		 error,
390 		 PyExc_MemoryError,
391 		 "%s: unable to free file IO handle.",
392 		 function );
393 
394 		libcerror_error_free(
395 		 &error );
396 
397 		goto on_error;
398 	}
399 	if( result != 0 )
400 	{
401 		Py_IncRef(
402 		 (PyObject *) Py_True );
403 
404 		return( Py_True );
405 	}
406 	Py_IncRef(
407 	 (PyObject *) Py_False );
408 
409 	return( Py_False );
410 
411 on_error:
412 	if( file_io_handle != NULL )
413 	{
414 		libbfio_handle_free(
415 		 &file_io_handle,
416 		 NULL );
417 	}
418 	return( NULL );
419 }
420 
421 /* Creates a new file object and opens it
422  * Returns a Python object if successful or NULL on error
423  */
pyvhdi_open_new_file(PyObject * self PYVHDI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)424 PyObject *pyvhdi_open_new_file(
425            PyObject *self PYVHDI_ATTRIBUTE_UNUSED,
426            PyObject *arguments,
427            PyObject *keywords )
428 {
429 	pyvhdi_file_t *pyvhdi_file = NULL;
430 	static char *function      = "pyvhdi_open_new_file";
431 
432 	PYVHDI_UNREFERENCED_PARAMETER( self )
433 
434 	/* PyObject_New does not invoke tp_init
435 	 */
436 	pyvhdi_file = PyObject_New(
437 	               struct pyvhdi_file,
438 	               &pyvhdi_file_type_object );
439 
440 	if( pyvhdi_file == NULL )
441 	{
442 		PyErr_Format(
443 		 PyExc_MemoryError,
444 		 "%s: unable to create file.",
445 		 function );
446 
447 		goto on_error;
448 	}
449 	if( pyvhdi_file_init(
450 	     pyvhdi_file ) != 0 )
451 	{
452 		goto on_error;
453 	}
454 	if( pyvhdi_file_open(
455 	     pyvhdi_file,
456 	     arguments,
457 	     keywords ) == NULL )
458 	{
459 		goto on_error;
460 	}
461 	return( (PyObject *) pyvhdi_file );
462 
463 on_error:
464 	if( pyvhdi_file != NULL )
465 	{
466 		Py_DecRef(
467 		 (PyObject *) pyvhdi_file );
468 	}
469 	return( NULL );
470 }
471 
472 /* Creates a new file object and opens it using a file-like object
473  * Returns a Python object if successful or NULL on error
474  */
pyvhdi_open_new_file_with_file_object(PyObject * self PYVHDI_ATTRIBUTE_UNUSED,PyObject * arguments,PyObject * keywords)475 PyObject *pyvhdi_open_new_file_with_file_object(
476            PyObject *self PYVHDI_ATTRIBUTE_UNUSED,
477            PyObject *arguments,
478            PyObject *keywords )
479 {
480 	pyvhdi_file_t *pyvhdi_file = NULL;
481 	static char *function      = "pyvhdi_open_new_file_with_file_object";
482 
483 	PYVHDI_UNREFERENCED_PARAMETER( self )
484 
485 	/* PyObject_New does not invoke tp_init
486 	 */
487 	pyvhdi_file = PyObject_New(
488 	               struct pyvhdi_file,
489 	               &pyvhdi_file_type_object );
490 
491 	if( pyvhdi_file == NULL )
492 	{
493 		PyErr_Format(
494 		 PyExc_MemoryError,
495 		 "%s: unable to create file.",
496 		 function );
497 
498 		goto on_error;
499 	}
500 	if( pyvhdi_file_init(
501 	     pyvhdi_file ) != 0 )
502 	{
503 		goto on_error;
504 	}
505 	if( pyvhdi_file_open_file_object(
506 	     pyvhdi_file,
507 	     arguments,
508 	     keywords ) == NULL )
509 	{
510 		goto on_error;
511 	}
512 	return( (PyObject *) pyvhdi_file );
513 
514 on_error:
515 	if( pyvhdi_file != NULL )
516 	{
517 		Py_DecRef(
518 		 (PyObject *) pyvhdi_file );
519 	}
520 	return( NULL );
521 }
522 
523 #if PY_MAJOR_VERSION >= 3
524 
525 /* The pyvhdi module definition
526  */
527 PyModuleDef pyvhdi_module_definition = {
528 	PyModuleDef_HEAD_INIT,
529 
530 	/* m_name */
531 	"pyvhdi",
532 	/* m_doc */
533 	"Python libvhdi module (pyvhdi).",
534 	/* m_size */
535 	-1,
536 	/* m_methods */
537 	pyvhdi_module_methods,
538 	/* m_reload */
539 	NULL,
540 	/* m_traverse */
541 	NULL,
542 	/* m_clear */
543 	NULL,
544 	/* m_free */
545 	NULL,
546 };
547 
548 #endif /* PY_MAJOR_VERSION >= 3 */
549 
550 /* Initializes the pyvhdi module
551  */
552 #if PY_MAJOR_VERSION >= 3
PyInit_pyvhdi(void)553 PyMODINIT_FUNC PyInit_pyvhdi(
554                 void )
555 #else
556 PyMODINIT_FUNC initpyvhdi(
557                 void )
558 #endif
559 {
560 	PyObject *module           = NULL;
561 	PyGILState_STATE gil_state = 0;
562 
563 #if defined( HAVE_DEBUG_OUTPUT )
564 	libvhdi_notify_set_stream(
565 	 stderr,
566 	 NULL );
567 	libvhdi_notify_set_verbose(
568 	 1 );
569 #endif
570 
571 	/* Create the module
572 	 * This function must be called before grabbing the GIL
573 	 * otherwise the module will segfault on a version mismatch
574 	 */
575 #if PY_MAJOR_VERSION >= 3
576 	module = PyModule_Create(
577 	          &pyvhdi_module_definition );
578 #else
579 	module = Py_InitModule3(
580 	          "pyvhdi",
581 	          pyvhdi_module_methods,
582 	          "Python libvhdi module (pyvhdi)." );
583 #endif
584 	if( module == NULL )
585 	{
586 #if PY_MAJOR_VERSION >= 3
587 		return( NULL );
588 #else
589 		return;
590 #endif
591 	}
592 #if PY_VERSION_HEX < 0x03070000
593 	PyEval_InitThreads();
594 #endif
595 	gil_state = PyGILState_Ensure();
596 
597 	/* Setup the disk_types type object
598 	 */
599 	pyvhdi_disk_types_type_object.tp_new = PyType_GenericNew;
600 
601 	if( pyvhdi_disk_types_init_type(
602 	     &pyvhdi_disk_types_type_object ) != 1 )
603 	{
604 		goto on_error;
605 	}
606 	if( PyType_Ready(
607 	     &pyvhdi_disk_types_type_object ) < 0 )
608 	{
609 		goto on_error;
610 	}
611 	Py_IncRef(
612 	 (PyObject *) &pyvhdi_disk_types_type_object );
613 
614 	PyModule_AddObject(
615 	 module,
616 	 "disk_types",
617 	 (PyObject *) &pyvhdi_disk_types_type_object );
618 
619 	/* Setup the file type object
620 	 */
621 	pyvhdi_file_type_object.tp_new = PyType_GenericNew;
622 
623 	if( PyType_Ready(
624 	     &pyvhdi_file_type_object ) < 0 )
625 	{
626 		goto on_error;
627 	}
628 	Py_IncRef(
629 	 (PyObject *) &pyvhdi_file_type_object );
630 
631 	PyModule_AddObject(
632 	 module,
633 	 "file",
634 	 (PyObject *) &pyvhdi_file_type_object );
635 
636 	PyGILState_Release(
637 	 gil_state );
638 
639 #if PY_MAJOR_VERSION >= 3
640 	return( module );
641 #else
642 	return;
643 #endif
644 
645 on_error:
646 	PyGILState_Release(
647 	 gil_state );
648 
649 #if PY_MAJOR_VERSION >= 3
650 	return( NULL );
651 #else
652 	return;
653 #endif
654 }
655 
656