1 /*
2  * Mounts a BitLocker Drive Encrypted (BDE) volume
3  *
4  * Copyright (C) 2011-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 <file_stream.h>
24 #include <memory.h>
25 #include <system_string.h>
26 #include <types.h>
27 
28 #include <stdio.h>
29 
30 #if defined( HAVE_IO_H ) || defined( WINAPI )
31 #include <io.h>
32 #endif
33 
34 #if defined( HAVE_STDLIB_H ) || defined( WINAPI )
35 #include <stdlib.h>
36 #endif
37 
38 #if defined( HAVE_UNISTD_H )
39 #include <unistd.h>
40 #endif
41 
42 #include "bdetools_getopt.h"
43 #include "bdetools_i18n.h"
44 #include "bdetools_libbde.h"
45 #include "bdetools_libcerror.h"
46 #include "bdetools_libclocale.h"
47 #include "bdetools_libcnotify.h"
48 #include "bdetools_output.h"
49 #include "bdetools_signal.h"
50 #include "bdetools_unused.h"
51 #include "mount_dokan.h"
52 #include "mount_fuse.h"
53 #include "mount_handle.h"
54 
55 mount_handle_t *bdemount_mount_handle = NULL;
56 int bdemount_abort                    = 0;
57 
58 /* Prints usage information
59  */
usage_fprint(FILE * stream)60 void usage_fprint(
61       FILE *stream )
62 {
63 	if( stream == NULL )
64 	{
65 		return;
66 	}
67 	fprintf( stream, "Use bdemount to mount a BitLocker Drive Encrypted (BDE) volume\n\n" );
68 
69 	fprintf( stream, "Usage: bdemount [ -k keys ] [ -o offset ] [ -p password ]\n"
70 	                 "                [ -r recovery_password ] [ -s startup_key_path ]\n"
71 	                 "                [ -X extended_options ] [ -hvV ] volume mount_point\n\n" );
72 
73 	fprintf( stream, "\tvolume:      a BitLocker Drive Encrypted (BDE) volume\n\n" );
74 	fprintf( stream, "\tmount_point: the directory to serve as mount point\n\n" );
75 
76 	fprintf( stream, "\t-h:          shows this help\n" );
77 	fprintf( stream, "\t-k:          specify the full volume encryption key and tweak key formatted in\n"
78 	                 "\t             base16 and separated by a : character e.g. FVEK:TWEAK\n" );
79 	fprintf( stream, "\t-o:          specify the volume offset in bytes\n" );
80 	fprintf( stream, "\t-p:          specify the password/passphrase\n" );
81 	fprintf( stream, "\t-r:          specify the recovery password/passphrase\n" );
82 	fprintf( stream, "\t-s:          specify the path of the file containing the startup key. Typically\n"
83 	                 "\t             this file has the extension .BEK\n" );
84 	fprintf( stream, "\t-v:          verbose output to stderr, while bdemount will remain running in the\n"
85 	                 "\t             foreground\n" );
86 	fprintf( stream, "\t-V:          print version\n" );
87 	fprintf( stream, "\t-X:          extended options to pass to sub system\n" );
88 }
89 
90 /* Signal handler for bdemount
91  */
bdemount_signal_handler(bdetools_signal_t signal BDETOOLS_ATTRIBUTE_UNUSED)92 void bdemount_signal_handler(
93       bdetools_signal_t signal BDETOOLS_ATTRIBUTE_UNUSED )
94 {
95 	libcerror_error_t *error = NULL;
96 	static char *function    = "bdemount_signal_handler";
97 
98 	BDETOOLS_UNREFERENCED_PARAMETER( signal )
99 
100 	bdemount_abort = 1;
101 
102 	if( bdemount_mount_handle != NULL )
103 	{
104 		if( mount_handle_signal_abort(
105 		     bdemount_mount_handle,
106 		     &error ) != 1 )
107 		{
108 			libcnotify_printf(
109 			 "%s: unable to signal mount handle to abort.\n",
110 			 function );
111 
112 			libcnotify_print_error_backtrace(
113 			 error );
114 			libcerror_error_free(
115 			 &error );
116 		}
117 	}
118 	/* Force stdin to close otherwise any function reading it will remain blocked
119 	 */
120 #if defined( WINAPI ) && !defined( __CYGWIN__ )
121 	if( _close(
122 	     0 ) != 0 )
123 #else
124 	if( close(
125 	     0 ) != 0 )
126 #endif
127 	{
128 		libcnotify_printf(
129 		 "%s: unable to close stdin.\n",
130 		 function );
131 	}
132 }
133 
134 /* The main program
135  */
136 #if defined( HAVE_WIDE_SYSTEM_CHARACTER )
wmain(int argc,wchar_t * const argv[])137 int wmain( int argc, wchar_t * const argv[] )
138 #else
139 int main( int argc, char * const argv[] )
140 #endif
141 {
142 	libbde_error_t *error                        = NULL;
143 	system_character_t *mount_point              = NULL;
144 	system_character_t *option_extended_options  = NULL;
145 	system_character_t *option_keys              = NULL;
146 	system_character_t *option_offset            = NULL;
147 	system_character_t *option_password          = NULL;
148 	system_character_t *option_recovery_password = NULL;
149 	system_character_t *option_startup_key_path  = NULL;
150 	const system_character_t *path_prefix        = NULL;
151 	system_character_t *source                   = NULL;
152 	char *program                                = "bdemount";
153 	system_integer_t option                      = 0;
154 	size_t path_prefix_size                      = 0;
155 	int result                                   = 0;
156 	int verbose                                  = 0;
157 
158 #if defined( HAVE_LIBFUSE ) || defined( HAVE_LIBOSXFUSE )
159 	struct fuse_operations bdemount_fuse_operations;
160 
161 	struct fuse_args bdemount_fuse_arguments     = FUSE_ARGS_INIT(0, NULL);
162 	struct fuse_chan *bdemount_fuse_channel      = NULL;
163 	struct fuse *bdemount_fuse_handle            = NULL;
164 
165 #elif defined( HAVE_LIBDOKAN )
166 	DOKAN_OPERATIONS bdemount_dokan_operations;
167 	DOKAN_OPTIONS bdemount_dokan_options;
168 #endif
169 
170 	libcnotify_stream_set(
171 	 stderr,
172 	 NULL );
173 	libcnotify_verbose_set(
174 	 1 );
175 
176 	if( libclocale_initialize(
177 	     "bdetools",
178 	     &error ) != 1 )
179 	{
180 		fprintf(
181 		 stderr,
182 		 "Unable to initialize locale values.\n" );
183 
184 		goto on_error;
185 	}
186 	if( bdetools_output_initialize(
187 	     _IONBF,
188 	     &error ) != 1 )
189 	{
190 		fprintf(
191 		 stderr,
192 		 "Unable to initialize output settings.\n" );
193 
194 		goto on_error;
195 	}
196 	bdetools_output_version_fprint(
197 	 stdout,
198 	 program );
199 
200 	while( ( option = bdetools_getopt(
201 	                   argc,
202 	                   argv,
203 	                   _SYSTEM_STRING( "hk:o:p:r:s:vVX:" ) ) ) != (system_integer_t) -1 )
204 	{
205 		switch( option )
206 		{
207 			case (system_integer_t) '?':
208 			default:
209 				fprintf(
210 				 stderr,
211 				 "Invalid argument: %" PRIs_SYSTEM "\n",
212 				 argv[ optind - 1 ] );
213 
214 				usage_fprint(
215 				 stdout );
216 
217 				return( EXIT_FAILURE );
218 
219 			case (system_integer_t) 'h':
220 				usage_fprint(
221 				 stdout );
222 
223 				return( EXIT_SUCCESS );
224 
225 			case (system_integer_t) 'k':
226 				option_keys = optarg;
227 
228 				break;
229 
230 			case (system_integer_t) 'o':
231 				option_offset = optarg;
232 
233 				break;
234 
235 			case (system_integer_t) 'p':
236 				option_password = optarg;
237 
238 				break;
239 
240 			case (system_integer_t) 'r':
241 				option_recovery_password = optarg;
242 
243 				break;
244 
245 			case (system_integer_t) 's':
246 				option_startup_key_path = optarg;
247 
248 				break;
249 
250 			case (system_integer_t) 'v':
251 				verbose = 1;
252 
253 				break;
254 
255 			case (system_integer_t) 'V':
256 				bdetools_output_copyright_fprint(
257 				 stdout );
258 
259 				return( EXIT_SUCCESS );
260 
261 			case (system_integer_t) 'X':
262 				option_extended_options = optarg;
263 
264 				break;
265 		}
266 	}
267 	if( optind == argc )
268 	{
269 		fprintf(
270 		 stderr,
271 		 "Missing source volume.\n" );
272 
273 		usage_fprint(
274 		 stdout );
275 
276 		return( EXIT_FAILURE );
277 	}
278 	source = argv[ optind++ ];
279 
280 	if( optind == argc )
281 	{
282 		fprintf(
283 		 stderr,
284 		 "Missing mount point.\n" );
285 
286 		usage_fprint(
287 		 stdout );
288 
289 		return( EXIT_FAILURE );
290 	}
291 	mount_point = argv[ optind ];
292 
293 	libcnotify_verbose_set(
294 	 verbose );
295 	libbde_notify_set_stream(
296 	 stderr,
297 	 NULL );
298 	libbde_notify_set_verbose(
299 	 verbose );
300 
301 	if( mount_handle_initialize(
302 	     &bdemount_mount_handle,
303 	     &error ) != 1 )
304 	{
305 		fprintf(
306 		 stderr,
307 		 "Unable to initialize mount handle.\n" );
308 
309 		goto on_error;
310 	}
311 	if( option_keys != NULL )
312 	{
313 		if( mount_handle_set_keys(
314 		     bdemount_mount_handle,
315 		     option_keys,
316 		     &error ) != 1 )
317 		{
318 			fprintf(
319 			 stderr,
320 			 "Unable to set keys.\n" );
321 
322 			goto on_error;
323 		}
324 	}
325 	if( option_offset != NULL )
326 	{
327 		if( mount_handle_set_offset(
328 		     bdemount_mount_handle,
329 		     option_offset,
330 		     &error ) != 1 )
331 		{
332 			fprintf(
333 			 stderr,
334 			 "Unable to set volume offset.\n" );
335 
336 			goto on_error;
337 		}
338 	}
339 	if( option_password != NULL )
340 	{
341 		if( mount_handle_set_password(
342 		     bdemount_mount_handle,
343 		     option_password,
344 		     &error ) != 1 )
345 		{
346 			fprintf(
347 			 stderr,
348 			 "Unable to set password.\n" );
349 
350 			goto on_error;
351 		}
352 	}
353 	if( option_recovery_password != NULL )
354 	{
355 		if( mount_handle_set_recovery_password(
356 		     bdemount_mount_handle,
357 		     option_recovery_password,
358 		     &error ) != 1 )
359 		{
360 			fprintf(
361 			 stderr,
362 			 "Unable to set recovery password.\n" );
363 
364 			goto on_error;
365 		}
366 	}
367 	if( option_startup_key_path != NULL )
368 	{
369 		if( mount_handle_set_startup_key(
370 		     bdemount_mount_handle,
371 		     option_startup_key_path,
372 		     &error ) != 1 )
373 		{
374 			fprintf(
375 			 stderr,
376 			 "Unable to set startup key.\n" );
377 
378 			goto on_error;
379 		}
380 	}
381 #if defined( WINAPI )
382 	path_prefix = _SYSTEM_STRING( "\\BDE" );
383 #else
384 	path_prefix = _SYSTEM_STRING( "/bde" );
385 #endif
386 	path_prefix_size = 1 + system_string_length(
387 	                        path_prefix );
388 
389 	if( mount_handle_set_path_prefix(
390 	     bdemount_mount_handle,
391 	     path_prefix,
392 	     path_prefix_size,
393 	     &error ) != 1 )
394 	{
395 		fprintf(
396 		 stderr,
397 		 "Unable to set path prefix.\n" );
398 
399 		goto on_error;
400 	}
401 	if( mount_handle_open(
402 	     bdemount_mount_handle,
403 	     source,
404 	     &error ) != 1 )
405 	{
406 		fprintf(
407 		 stderr,
408 		 "Unable to open source volume\n" );
409 
410 		goto on_error;
411 	}
412 	if( mount_handle_is_locked(
413 	     bdemount_mount_handle,
414 	     &error ) != 0 )
415 	{
416 		fprintf(
417 		 stderr,
418 		 "Unable to unlock source volume\n" );
419 
420 		goto on_error;
421 	}
422 #if defined( HAVE_LIBFUSE ) || defined( HAVE_LIBOSXFUSE )
423 	if( option_extended_options != NULL )
424 	{
425 		/* This argument is required but ignored
426 		 */
427 		if( fuse_opt_add_arg(
428 		     &bdemount_fuse_arguments,
429 		     "" ) != 0 )
430 		{
431 			fprintf(
432 			 stderr,
433 			 "Unable add fuse arguments.\n" );
434 
435 			goto on_error;
436 		}
437 		if( fuse_opt_add_arg(
438 		     &bdemount_fuse_arguments,
439 		     "-o" ) != 0 )
440 		{
441 			fprintf(
442 			 stderr,
443 			 "Unable add fuse arguments.\n" );
444 
445 			goto on_error;
446 		}
447 		if( fuse_opt_add_arg(
448 		     &bdemount_fuse_arguments,
449 		     option_extended_options ) != 0 )
450 		{
451 			fprintf(
452 			 stderr,
453 			 "Unable add fuse arguments.\n" );
454 
455 			goto on_error;
456 		}
457 	}
458 	if( memory_set(
459 	     &bdemount_fuse_operations,
460 	     0,
461 	     sizeof( struct fuse_operations ) ) == NULL )
462 	{
463 		fprintf(
464 		 stderr,
465 		 "Unable to clear fuse operations.\n" );
466 
467 		goto on_error;
468 	}
469 	bdemount_fuse_operations.open       = &mount_fuse_open;
470 	bdemount_fuse_operations.read       = &mount_fuse_read;
471 	bdemount_fuse_operations.release    = &mount_fuse_release;
472 	bdemount_fuse_operations.opendir    = &mount_fuse_opendir;
473 	bdemount_fuse_operations.readdir    = &mount_fuse_readdir;
474 	bdemount_fuse_operations.releasedir = &mount_fuse_releasedir;
475 	bdemount_fuse_operations.getattr    = &mount_fuse_getattr;
476 	bdemount_fuse_operations.destroy    = &mount_fuse_destroy;
477 
478 	bdemount_fuse_channel = fuse_mount(
479 	                         mount_point,
480 	                         &bdemount_fuse_arguments );
481 
482 	if( bdemount_fuse_channel == NULL )
483 	{
484 		fprintf(
485 		 stderr,
486 		 "Unable to create fuse channel.\n" );
487 
488 		goto on_error;
489 	}
490 	bdemount_fuse_handle = fuse_new(
491 	                        bdemount_fuse_channel,
492 	                        &bdemount_fuse_arguments,
493 	                        &bdemount_fuse_operations,
494 	                        sizeof( struct fuse_operations ),
495 	                        bdemount_mount_handle );
496 
497 	if( bdemount_fuse_handle == NULL )
498 	{
499 		fprintf(
500 		 stderr,
501 		 "Unable to create fuse handle.\n" );
502 
503 		goto on_error;
504 	}
505 	if( verbose == 0 )
506 	{
507 		if( fuse_daemonize(
508 		     0 ) != 0 )
509 		{
510 			fprintf(
511 			 stderr,
512 			 "Unable to daemonize fuse.\n" );
513 
514 			goto on_error;
515 		}
516 	}
517 	result = fuse_loop(
518 	          bdemount_fuse_handle );
519 
520 	if( result != 0 )
521 	{
522 		fprintf(
523 		 stderr,
524 		 "Unable to run fuse loop.\n" );
525 
526 		goto on_error;
527 	}
528 	fuse_destroy(
529 	 bdemount_fuse_handle );
530 
531 	fuse_opt_free_args(
532 	 &bdemount_fuse_arguments );
533 
534 	return( EXIT_SUCCESS );
535 
536 #elif defined( HAVE_LIBDOKAN )
537 	if( memory_set(
538 	     &bdemount_dokan_operations,
539 	     0,
540 	     sizeof( DOKAN_OPERATIONS ) ) == NULL )
541 	{
542 		fprintf(
543 		 stderr,
544 		 "Unable to clear dokan operations.\n" );
545 
546 		goto on_error;
547 	}
548 	if( memory_set(
549 	     &bdemount_dokan_options,
550 	     0,
551 	     sizeof( DOKAN_OPTIONS ) ) == NULL )
552 	{
553 		fprintf(
554 		 stderr,
555 		 "Unable to clear dokan options.\n" );
556 
557 		goto on_error;
558 	}
559 	bdemount_dokan_options.Version     = DOKAN_VERSION;
560 	bdemount_dokan_options.ThreadCount = 0;
561 	bdemount_dokan_options.MountPoint  = mount_point;
562 
563 	if( verbose != 0 )
564 	{
565 		bdemount_dokan_options.Options |= DOKAN_OPTION_STDERR;
566 #if defined( HAVE_DEBUG_OUTPUT )
567 		bdemount_dokan_options.Options |= DOKAN_OPTION_DEBUG;
568 #endif
569 	}
570 /* This will only affect the drive properties
571 	bdemount_dokan_options.Options |= DOKAN_OPTION_REMOVABLE;
572 */
573 
574 #if ( DOKAN_VERSION >= 600 ) && ( DOKAN_VERSION < 800 )
575 	bdemount_dokan_options.Options |= DOKAN_OPTION_KEEP_ALIVE;
576 
577 	bdemount_dokan_operations.CreateFile           = &mount_dokan_CreateFile;
578 	bdemount_dokan_operations.OpenDirectory        = &mount_dokan_OpenDirectory;
579 	bdemount_dokan_operations.CreateDirectory      = NULL;
580 	bdemount_dokan_operations.Cleanup              = NULL;
581 	bdemount_dokan_operations.CloseFile            = &mount_dokan_CloseFile;
582 	bdemount_dokan_operations.ReadFile             = &mount_dokan_ReadFile;
583 	bdemount_dokan_operations.WriteFile            = NULL;
584 	bdemount_dokan_operations.FlushFileBuffers     = NULL;
585 	bdemount_dokan_operations.GetFileInformation   = &mount_dokan_GetFileInformation;
586 	bdemount_dokan_operations.FindFiles            = &mount_dokan_FindFiles;
587 	bdemount_dokan_operations.FindFilesWithPattern = NULL;
588 	bdemount_dokan_operations.SetFileAttributes    = NULL;
589 	bdemount_dokan_operations.SetFileTime          = NULL;
590 	bdemount_dokan_operations.DeleteFile           = NULL;
591 	bdemount_dokan_operations.DeleteDirectory      = NULL;
592 	bdemount_dokan_operations.MoveFile             = NULL;
593 	bdemount_dokan_operations.SetEndOfFile         = NULL;
594 	bdemount_dokan_operations.SetAllocationSize    = NULL;
595 	bdemount_dokan_operations.LockFile             = NULL;
596 	bdemount_dokan_operations.UnlockFile           = NULL;
597 	bdemount_dokan_operations.GetFileSecurity      = NULL;
598 	bdemount_dokan_operations.SetFileSecurity      = NULL;
599 	bdemount_dokan_operations.GetDiskFreeSpace     = NULL;
600 	bdemount_dokan_operations.GetVolumeInformation = &mount_dokan_GetVolumeInformation;
601 	bdemount_dokan_operations.Unmount              = &mount_dokan_Unmount;
602 
603 #else
604 	bdemount_dokan_operations.ZwCreateFile         = &mount_dokan_ZwCreateFile;
605 	bdemount_dokan_operations.Cleanup              = NULL;
606 	bdemount_dokan_operations.CloseFile            = &mount_dokan_CloseFile;
607 	bdemount_dokan_operations.ReadFile             = &mount_dokan_ReadFile;
608 	bdemount_dokan_operations.WriteFile            = NULL;
609 	bdemount_dokan_operations.FlushFileBuffers     = NULL;
610 	bdemount_dokan_operations.GetFileInformation   = &mount_dokan_GetFileInformation;
611 	bdemount_dokan_operations.FindFiles            = &mount_dokan_FindFiles;
612 	bdemount_dokan_operations.FindFilesWithPattern = NULL;
613 	bdemount_dokan_operations.SetFileAttributes    = NULL;
614 	bdemount_dokan_operations.SetFileTime          = NULL;
615 	bdemount_dokan_operations.DeleteFile           = NULL;
616 	bdemount_dokan_operations.DeleteDirectory      = NULL;
617 	bdemount_dokan_operations.MoveFile             = NULL;
618 	bdemount_dokan_operations.SetEndOfFile         = NULL;
619 	bdemount_dokan_operations.SetAllocationSize    = NULL;
620 	bdemount_dokan_operations.LockFile             = NULL;
621 	bdemount_dokan_operations.UnlockFile           = NULL;
622 	bdemount_dokan_operations.GetFileSecurity      = NULL;
623 	bdemount_dokan_operations.SetFileSecurity      = NULL;
624 	bdemount_dokan_operations.GetDiskFreeSpace     = NULL;
625 	bdemount_dokan_operations.GetVolumeInformation = &mount_dokan_GetVolumeInformation;
626 	bdemount_dokan_operations.Unmounted            = NULL;
627 	bdemount_dokan_operations.FindStreams          = NULL;
628 	bdemount_dokan_operations.Mounted              = NULL;
629 
630 #endif /* ( DOKAN_VERSION >= 600 ) && ( DOKAN_VERSION < 800 ) */
631 
632 	result = DokanMain(
633 	          &bdemount_dokan_options,
634 	          &bdemount_dokan_operations );
635 
636 	switch( result )
637 	{
638 		case DOKAN_SUCCESS:
639 			break;
640 
641 		case DOKAN_ERROR:
642 			fprintf(
643 			 stderr,
644 			 "Unable to run dokan main: generic error\n" );
645 			break;
646 
647 		case DOKAN_DRIVE_LETTER_ERROR:
648 			fprintf(
649 			 stderr,
650 			 "Unable to run dokan main: bad drive letter\n" );
651 			break;
652 
653 		case DOKAN_DRIVER_INSTALL_ERROR:
654 			fprintf(
655 			 stderr,
656 			 "Unable to run dokan main: unable to load driver\n" );
657 			break;
658 
659 		case DOKAN_START_ERROR:
660 			fprintf(
661 			 stderr,
662 			 "Unable to run dokan main: driver error\n" );
663 			break;
664 
665 		case DOKAN_MOUNT_ERROR:
666 			fprintf(
667 			 stderr,
668 			 "Unable to run dokan main: unable to assign drive letter\n" );
669 			break;
670 
671 		case DOKAN_MOUNT_POINT_ERROR:
672 			fprintf(
673 			 stderr,
674 			 "Unable to run dokan main: mount point error\n" );
675 			break;
676 
677 		default:
678 			fprintf(
679 			 stderr,
680 			 "Unable to run dokan main: unknown error: %d\n",
681 			 result );
682 			break;
683 	}
684 	return( EXIT_SUCCESS );
685 
686 #else
687 	fprintf(
688 	 stderr,
689 	 "No sub system to mount BDE format.\n" );
690 
691 	return( EXIT_FAILURE );
692 
693 #endif /* defined( HAVE_LIBFUSE ) || defined( HAVE_LIBOSXFUSE ) */
694 
695 on_error:
696 	if( error != NULL )
697 	{
698 		libcnotify_print_error_backtrace(
699 		 error );
700 		libcerror_error_free(
701 		 &error );
702 	}
703 #if defined( HAVE_LIBFUSE ) || defined( HAVE_LIBOSXFUSE )
704 	if( bdemount_fuse_handle != NULL )
705 	{
706 		fuse_destroy(
707 		 bdemount_fuse_handle );
708 	}
709 	fuse_opt_free_args(
710 	 &bdemount_fuse_arguments );
711 #endif
712 	if( bdemount_mount_handle != NULL )
713 	{
714 		mount_handle_free(
715 		 &bdemount_mount_handle,
716 		 NULL );
717 	}
718 	return( EXIT_FAILURE );
719 }
720 
721