aboutsummaryrefslogtreecommitdiff
path: root/sys-fs/dmc/files/dmc
blob: 2a2febcb970b982d4c85d6c6176822121d3c2f0f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
#!/usr/bin/perl
# See below for documentation.
#
# TODO: fallocate is cool. maybe we even want an option for 'truncate'
#	aka using sparse files that factually get allocated on the fly?

use IPC::Open3;
use Term::ANSIColor qw(:constants);
use Pod::Usage qw( pod2usage );

use Getopt::Std;
getopt('tsumP');
# i liked 'sumpf' but it was not worth renaming -t into -f
my $user = $opt_u || $ENV{SUDO_USER};

# urandom is rather slow, zero is faster. by default however we use
# 'fallocate' instead of 'dd' which reserves an amount of disk space
# for the volume without changing it at all, which can be considered
# as slightly disguising. better and much faster than zeroing it out.
sub RND () { 'if=/dev/' . ($opt_R? 'urandom': 'zero') }
# remember that this is only relevant if you don't plan to fill the
# volume up completely - random bytes hide how much of the container
# has been used. if you're likely to fill the entire space anyway,
# you might aswell start with a zeroed or fallocated container.

# should i need to access my volumes from microsoft or macos i'd
# rather install ext4 on them than pick an awful "compatible" fs.
sub FS () { 'ext4' }

sub debug () { 0 }

# we use undeclared global variables. that's the privilege of using
# a rapid prototyping and scripting language and to be respected.
my $vol = shift;
my $mnt = shift;
my $recoll;

if ($opt_d and not $vol) {
	# blkid shows the proper dmc mapper device even if
	# the file system was mounted by its /dev/dm-# path
	open(M, '/sbin/blkid |') or die "blkid: $!";
	&findmounts;
	# under sane conditions these should already be
	# updated, but conditions are sometimes insane
	# my 4.0.x kernel just panicked on dmc -d
	&findmounts if open(M, '/proc/self/mounts');
	&findmounts if open(M, '/etc/mtab');
	exit $!;
}

my $kee = undef;
my $keef = undef;
my $keefr = undef;
my $fs = FS;
# just in case i use -f instead of -t
die pod2usage unless $vol and not $opt_f;

## HELPERS

sub sysmatch {
	my $match = shift;
	# why can't people honestly admit that it is in fact PINK
	print BOLD, MAGENTA, join(' ', @_), "\n", RESET unless $opt_q;
	return if $opt_n;
	open3(\*I, \*O, \*U, @_) or die "$_[0]: $!";
	$_ = <U>;
	close I, O, U;
	if ( /$match/ ) {
		print STDERR BOLD, RED, $_, RESET;
		return -1;
	}
	return 0;
}
sub sys2 {
	print BOLD, BLUE, join(' ', @_), "\n", RESET unless $opt_q;
	return system(@_) unless $opt_n;
}
sub sys {
	print BOLD, GREEN, join(' ', @_), "\n", RESET unless $opt_q;
	if (!$opt_n and system(@_)) {
		if ($? == -1) {
			print "\t{failed to execute: $!}\n";
		} elsif ($? & 127) {
			printf "\t{command died with sig %d, %s core dump}\n",
			 ($? & 127),  ($? & 128) ? 'with' : 'without';
		} else {
			printf "\t{command exited with value %d}\n", $? >> 8;
		}
		exit $? if $?;
		exit $@ if $@;
	}
}
sub prepare {
	unless ($nick = $opt_m) {
		$nick = $vol;
		$nick =~ s/\.\w+$//;			# remove a trailing extension
		$nick = $1 if $nick =~ /(\w+)$/;	# use last word of volume name
	}

	if (not $opt_d and -e $mapper) {
		srand;
		$r = int(rand(100));
		warn <<X;
Warning: $mapper already exists, using $mapper$r instead
X
		$mapper = $mapper . $r;
		$map = $map . $r;
		$nick = $nick . $r;
	}

	# /media is a good idea because the desktop environments will show
	# device icons for it and make it easy to use for GUI users.
	# unfortunately it stops the automounter from doing its job. why?
	if ( -w "/dev/shm" ) {
		$media = "/dev/shm";
	} else {
		$media = "/tmp";
	}
	$mnt = "$media/$nick" unless $mnt;
	$recoll = $mnt.'/.recoll';

	# the prefix is useful to automate dismounting of all dmcs
	$map = "dmc-$nick";
	$mapper = "/dev/mapper/$map";
}
sub unmount {
	print "\t{checking for $recoll}\n" if debug;
	if (not $opt_p and -d $recoll) {
		my $pid = $recoll."/index.pid";
		print "\t{checking pid $pid}\n" if debug;
		while (-r $pid) {
			open(P, $pid);
			$pid = <P>;
			close P;
			if ($pid) {
				chomp $pid;
				print "\t{shutting down recollindex $pid}\n";
				kill 'INT', $pid;
				sleep 2;
			} else {
				print STDERR RED, "\t{couldn't read recollindex pid}\n", RESET;
			}
		}
	}
	if ($opt_n or -r $mapper) {
		# ignore if already unmounted but stop if cannot be unmounted
		if ( sysmatch(' target is busy', '/bin/umount', $mnt) ) {
			system("lsof | fgrep $mnt") unless $nick =~ /work/;
			return -1;
		}
		# ignore if already closed but stop if cannot be closed
		return -1 if sysmatch('Device or resource busy', '/sbin/cryptsetup', 'luksClose', $map);
		sys2 '/bin/rmdir', $mnt;
	} else {
		print "\t{$mapper already dismounted}\n";
	}
	return 0;
}
my %unmounted = {};
sub findmounts {
	while (<M>) {
		# this matches either input format
		if (m!^/dev/mapper/dmc-(\S+?):? !) {
			$vol = $1;
			next if $unmounted{$vol};
			undef $mnt;
			&prepare;
			&unmount;
			++$unmounted{$vol};
		}
	}
	close M;
}
sub luksDump {
	sys '/sbin/cryptsetup', 'luksDump', $vol;
	print "\n";
}
sub wordFetch {
	my $f = shift;
	print "{found key in $f}\n" if debug;
	$keef = $f;
	# double braces allow me to use 'next' locally
	if (open (F, $f)) {{
		$kee = $_ = <F>;
		close F;
		warn "Failed to read $f somehow" if length() < 2;
		next unless s/\s+$//;
		open (F, ">$f") or
		    die "Cannot fix trailing whitespace in $f";
		print F $_;
		close F;
		print "{fixed trailing whitespace in $f}\n";
	}}
	return 1;
}

## MAIN


&prepare;

die <<X if $nick =~ m:/:;

Invalid volume name. $0 needs volume names to end
in a word character string <name> or <name>.<extension>

X

if (opendir D, $media) {
	while (readdir D) {
		my $p = "$media/$_";
		# this also checks . and ..
		# but is that a problem?
		next unless -d $p;
		my $f = "$p/.words/$nick";
		print "{looking for key in $f}\n" if debug;
		next unless -r $f;
		last if &wordFetch($f);
	}
	closedir D;
}
unless ($keef) {
	my $f = "/root/.words/$nick";
	&wordFetch($f) if -r $f;
}

if ($opt_d) {
	&unmount;
	exit;
}

if ($opt_L) {
	&luksDump;
	goto ADEU;
}
if ($opt_A) {
	&luksDump;
	sys '/sbin/cryptsetup', 'luksAddKey', '--key-file', $keef, $vol if $keef;
	sys '/sbin/cryptsetup', 'luksAddKey', $vol unless $keef;
	&luksDump;
	exit;
}
if ($opt_C) {
	&luksDump;
	sys '/sbin/cryptsetup', 'luksChangeKey', '--key-file', $keef, $vol if $keef;
	sys '/sbin/cryptsetup', 'luksChangeKey', $vol unless $keef;
	&luksDump;
	exit;
}
if ($opt_D) {
	&luksDump;
	# Ignoring $keef for this one... as it might happen by mistake
	sys '/sbin/cryptsetup', 'luksRemoveKey', $vol;
	&luksDump;
	exit;
}
if ($opt_E) {
	&luksDump;
	print <<X;

If you really want to remove all access keys and make the
volume inaccessible, copy and paste this command:

/sbin/cryptsetup erase $vol && /bin/rm $vol

If you need to remove a certain key slot but leave the
others as they are, here's what you need:

/sbin/cryptsetup luksKillSlot $vol <slot-number>

X
	exit;
}

if ($opt_s) {
	$size = $opt_s;
	$unit = uc($1) if $size =~ s/(\w)$//i;
	$size = 1024 * $size if $unit eq 'K';
	$size = 1024 * 1024 * $size if $unit eq 'M';
	$size = 1024 * 1024 * 1024 * $size if $unit eq 'G';
	$size = 1024 * 1024 * 1024 * 1024 * $size if $unit eq 'T';

	# let's use a megabyte large buffer
	$unit = "bs=1M";
	$size = $size / (1024 * 1024);
	$size = "count=$size" if $size;
}

if ($size and $opt_e) {
	die "$vol doesn't exist. Did you mean -c instead of -e? " unless -e $vol;
	# frequent use case, enlarge a currently mounted volume
	die if &unmount;
	if ($kee and not -r $keef) {
		# when the keef was in the volume itself
		$keefr = $keef = "/dev/shm/.keef-$$";
		open (F, 700, ">$keef") or
		    die "Cannot create temporary keyfile $keef";
		print F $kee;
		close F;
	}
	# fallocate does not respect -R requirements...
	if ($opt_R or sys2 '/usr/bin/fallocate', '-o', -s $vol, '-l', $opt_s, $vol) {
		print "Reverting to 'dd' ...\n" unless $opt_R;
		sys '/bin/dd', 'oflag=append', 'conv=notrunc', RND, $unit,
		    $size, "of=$vol";
	}
	# otherwise assume the user has resized the volume manually
} elsif ($opt_c) {
	# not sure about auto-unmount here
	if ($size) {
	    die "$vol already exists. Did you mean -e instead of -c? " if -e $vol;
	    if ($opt_R or sys2 '/usr/bin/fallocate', '-l', $opt_s, $vol) {
		print "Reverting to 'dd' ...\n" unless $opt_R;
		sys '/bin/dd', RND, $unit, $size, "of=$vol";
	    }
	}
	sys '/sbin/cryptsetup', 'luksFormat', '--batch-mode', $vol, $keef if $keef;
	sys '/sbin/cryptsetup', 'luksFormat', '--batch-mode', $vol unless $keef;
} elsif ($size) {
	die <<X;

Option -s is cool but you have to choose between -c and -e to go with it.

X
}

if ($opt_P) {
	# ugly, do not use
	sys "/bin/echo -n '$opt_P' | /sbin/cryptsetup luksOpen --key-file - '$vol' '$map'";
} elsif ($keef) {
	sys "/sbin/cryptsetup luksOpen --key-file '$keef' '$vol' '$map'";
} else {
	sys '/sbin/cryptsetup', 'luksOpen', $vol, $map;
}

unlink $keefr if $keefr;

if ($opt_c) {
	$fs = $opt_t || FS;
	# so logical that each file system had to have its own
	# mkfs flag to pass the device label name.. right?
	if ( $fs =~ /^ext/ ) {
		# optimizations of ext* filesystems depending on size happens
		# automatically as defined in /etc/mke2fs.conf
		sys '/sbin/mkfs.'. $fs, '-m', '0', '-L', $nick, $mapper;
		# -m is the percentage of root-reserved blocks. only makes sense
		# for file systems in production use (system, server etc)
	} elsif ($fs eq 'btrfs') {
		sys '/sbin/mkfs.'. $fs, '-L', $nick, $mapper;
	} elsif ($fs eq 'ntfs') {
		# zeroing out a virtual volume is silly: Q for quick
		sys '/sbin/mkfs.'. $fs, '-QIL', $nick, $mapper;
		# I disables content indexing
	} elsif ($fs eq 'reiserfs') {
		sys '/sbin/mkfs.'. $fs, '-l', $nick, $mapper;
	} elsif ($fs eq 'bfs') {
		sys '/sbin/mkfs.'. $fs, '-V', $nick, $mapper;
	} elsif ($fs eq 'minix') {
		sys '/sbin/mkfs.minix', $mapper;
	} else { # msdos, fat, vfat
		sys '/sbin/mkfs.'. $fs, '-n', $nick, $mapper;
	}
} else {
	open(B, '-|', '/sbin/blkid', $mapper) or die "blkid: $!";
	$_ = <B>;
	$fs = $1 if / TYPE="(\w+)"/;
	close B;
	die "Could not detect filesystem of $mapper" unless $fs or $opt_n;
}

if ($opt_e) {
	sys '/sbin/cryptsetup', 'resize', $mapper;
	# so reasonable there is no fs-independent resize command!
	# hey wait.. there is a shell script called fsadm that knows
	# half of the file systems.. but not the ones i need. great!
	if ($fs eq 'ntfs') {
		sys '/usr/sbin/ntfsresize', $mapper;
	} elsif ($fs eq 'btrfs') {
		# btrfs needs it AFTER mounting!
	} elsif ($fs eq 'xfs') {
		# xfs cannot be shrunk!
		sys '/usr/sbin/xfs_growfs', $mapper;
	} elsif ($fs eq 'reiserfs') {
		sys '/usr/sbin/resize_reiserfs', $mapper;
	} else {
		sys2 '/sbin/fsck', '-f', $mapper;
		# sys '/sbin/resize2fs', $mapper;
		sys '/sbin/fsadm', 'resize', $mapper;
	}
}

sys2 '/bin/mkdir', '-p', $mnt unless -d $mnt;

chdir ('/');	# nothing as annoying as having mounted A in dependency of B

if ($fs eq 'ntfs') {
	$ro = sysmatch ' mounted read-only', '/bin/mount', '-v', '-t', $fs, $mapper, $mnt;
} else {
	unless ($opt_c or $opt_e) {
		# would have spared me a system crash recently...
		sys2 '/sbin/fsck', $mapper if $fs ne 'btrfs';
		# fsck returns 1 even if it just created lost+found
		# so we'll ignore the return code. trying to mount it
		# will be the next thing we want to do anyhow
		#
		#sys2 '/sbin/btrfs', 'check', $mapper if $fs eq 'btrfs';
		# madness. btrfs check takes ages and cannot be sped up.
	}
	$ro = sysmatch ' mounted read-only', '/bin/mount', $mapper, $mnt;
}

sys2 "/sbin/btrfs filesystem resize max $mnt" if $opt_e and $fs eq 'btrfs';
sys '/bin/chown', $user, $mnt if $opt_c and $user;

ADEU:

sys2 '/sbin/cryptsetup', 'status', $mapper unless $opt_q;
sys '/bin/df', '-Th', $mnt if -d $mnt;
# -m(onitor) -(ignore)x(11) -n(oincrementalscan)
# apparently -n or -m don't do their jobs properly
sys '/usr/bin/recollindex', '-mx', '-c', $recoll
    if not $opt_p and not $ro and -w $recoll and -r $recoll.'/recoll.conf';


__END__

=pod

=head1 NAME

dmc - Command line UX frontend to dm-crypt's cryptsetup with support for multiple file systems

=head1 SYNOPSIS

 dmc <volume> [<mountpath>]		# mount a volume
 dmc -d					# dismount all dmc volumes
 dmc -d <volume> [<mountpath>]		# dismount a volume by filename
 dmc -d <mapper> [<mountpath>]		# dismount a volume by mapper name
 dmc -c <volume> [<mountpath>]		# format and mount an existing volume
 dmc -cs <size> <volume> [<mountpath>]	# create a volume of given size
 dmc -es <size> <volume> [<mountpath>]	# enlarge a volume by given size
 dmc -e <volume> [<mountpath>]		# adapt fs to a manually resized volume
 dmc [-A|-C|-D] <volume>		# add, change or delete a password
 dmc -L <volume>			# list key slots and show status
 dmc -E <volume>			# erase some or all passwords and keys

 <size> may end in M for megabytes, G for gigabytes, T for terabytes...
 If <mountpath> is omitted, /dev/shm/<mapper> is created instead.
 Combining action flags such as -c, -e and -d produces unuseful effects.

 Options:
	-R		# use /dev/urandom for volume creation (slow, but
			#   hides the amount of data stored in the volume)
	-t <fstype>	# use other file system type than $fs (only needed with -c)
	-u <name>	# specify user to chown file system to
	-m <name>	# use this specific mapper nickname
	-n		# no operation, just pretend
	-q		# quiet, do not show commands to be executed
	-p		# paranoid, do not act on any content of the volume

=head1 DESCRIPTION

dm-crypt tool with vaguely similar command line syntax to truecrypt.
Simplifies management of volumes with LUKS and a file system inside.

Reasons to use LUKS instead of truecrypt are: you can resize file
systems + dm-crypt behaves a bit more reliably when doing heavy duty
storage transactions. There is nothing wrong with truecrypt's
cryptography though!

Reasons to use this instead of jaromil's excellent tomb(8):
Not many, unless you need to use a file system other than ext4.
Maybe you like my screen colors or flag UX.

dmc will automatically fire up or shut down a recollindex process
if a .recoll directory is found on the top level of the volume.
This can be disabled with -p if you don't trust the volume.

# It is expected that no modifications were made to the volume while
# recoll wasn't running. <- Apparently that doesn't work.

=head1 AUTHORS

carlo von lynX.

=head1 COPYRIGHT

This program is free software, published under the Affero GNU Public
License. A disclaimer isn't necessary in my country, nor do I need
to mention the current year to assert a copyright.

=head1 FUNFACTS

Jaromil and I wrote very similar functionality at the same time, I
just postponed releasing my tool since his is actually smarter for
most purposes.

=head1 UPDATES

Latest version available from the youbroketheinternet overlay:
    https://gnunet.org/git/youbroketheinternet-overlay.git/plain/sys-fs/dmc/files/dmc