-
Notifications
You must be signed in to change notification settings - Fork 0
/
mgcfs
executable file
·235 lines (215 loc) · 7.2 KB
/
mgcfs
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
#!/usr/bin/env bash
set +vx
# mgcfs - Manage access to gocryptfs encrypted directory
# The directory `$dir/$name` holds the latest gocryptfs-encrypted files,
# and `$dir/$name.tar` is the previous backup.
# Required: gocryptfs[github/com/rfjakob/gocryptfs] fuse grep procps
# tar coreutils(cat type ls mktemp stat shred mkdir chmod sleep sync) $run
# Optional: zenity/whiptail/cryptsetup(askpass)
# Optional environment variables: $MGCFS_MOUNT/DIR/NAME/RUN/PLAIN
# (MGCFS_DIR and MGCFS_NAME can be overridden on the commandline)
# Gocryptfs binary, will be downloaded if not specified here nor found in PATH
gcfs=
# Mountpoint, must start with '/'; can be overridden with $MGCFS_MOUNT
mount="$HOME/Private"
# Containing directory; can be overridden with $MGCFS_DIR
dir="$HOME/SECURE"
# Name of encrypted directory; can be overridden with $MGCFS_NAME
name=SECRET
# <run> is executed after mounting; can be overridden with $MGCFS_RUN
run=
# Option used for Init: use plain (1) or encrypted (0) filenames;
# can be overridden with $MGCFS_PLAIN
plain=0
# Logfile for gocryptfs operations
log="$HOME/gocryptfs.log"
Help(){
cat <<-EOS
mgcfs - Manage access to gocryptfs encrypted directory
USAGE: mgcfs [-c|--console] | [-w|--whiptail] [-i|--init [<dir> [<name>]] |
[-v|--verbose] [-u|--unmount <sleeplength>[<unit>]] | [-h|--help]
-c/--console: display through echo is forced instead of whiptail
-w/--whiptail: display through whiptail is forced instead of zenity
-i/--init: setting up, <dir> must exist and <name> not or be empty
-v/--verbose: echoing the masterpassword to the terminal on mounting
-u/--unmount: auto-unmount after <sleeplength>[<unit>]
-h/--help: display this help text
Either set or adjust <mount>, <dir> and <name> as hardcoded in this script
(and optionally <run>), or set the corresponding environment variables
MGCFS_MOUNT, MGCFS_DIR, MGCFS_NAME (and optionally MGCFS_RUN);
As a run-by-run backup, <dir>/<name>.tar will be used.
When during 'init' MGCFS_PLAIN is 1, filenames will not be encrypted.
<sleeplength> is in minutes if no unit is given; <unit> can be:
s (seconds), m (minutes), h (hours), d (days).
EOS
exit 0
}
Info(){ # $1:Message I:zenity,whiptail
case 1 in
$zenity) zenity --info --width=360 --title="$title" --text="<b>$1</b>" 2>/dev/null ;;
$whiptail) whiptail --title="$title" --msgbox "$1" 11 78 ;;
*) echo -e "=== $title ===\n$1"
esac
}
Abort(){ # $1:Message
Info "ERROR: $1"
exit 2
}
Unmount(){ # I:mount,name
## When mounted: keep trying to unmount
if grep "$mount" <<<"$(mount)" |grep -q ' fuse\.gocryptfs '
then
while ! fusermount -zqu "$mount"
do
Info "Unable to unmount $name:\n\n$mount\n\nTrying again in a minute."
sleep 1m
done
Info "Now unmounted $name from:\n\n$mount"
exit 0
fi
}
Password(){ # $1:Text I:zenity,whiptail,title,pass
local cancel=0 console=0
if ((zenity))
then
zenity --entry --hide-text --width=360 --title="$title" --text="$1" 2>/dev/null ||
cancel=1
elif ((whiptail))
then
whiptail --clear --title "$title" --passwordbox "$1" 16 78 3>&1 1>&2 2>&3 ||
cancel=1
else
console=1
echo -ne "=== $title ===\n$1" 1>&2
/lib/cryptsetup/askpass ""
echo
fi >"$pass"
# Cancel -c/--console mode with empty password
((console)) && [[ $(echo $(hexdump -b "$pass")) = "0000000 012 0000001" ]] &&
echo "Exited: empty password" && cancel=1
((cancel)) || return 0
shred -fu -- "$pass"
sync
exit 1
}
# Main
self=$(basename $0)
mount=${MGCFS_MOUNT:-$mount}
dir=${MGCFS_DIR:-$dir}
name=${MGCFS_NAME:-$name}
run=${MGCFS_RUN:-$run}
plain=${MGCFS_PLAIN:-$plain}
title="Manage encryption of $name"
# Try zenity unless -c or -w or not present or no GUI
zenity=1 init=0 sleep= q='-q'
type -P zenity >/dev/null && xrandr &>/dev/null || zenity=0
while (($#))
do
case $1 in
-h|--help) Help ;;
-v|--verbose) q= ;;
-c|--console) zenity=0 whiptail=0 ;;
-w|--whiptail) zenity=0 whiptail=1 ;;
-u|--unmount) [[ $2 ]] || Abort "Must have a sleeplength after '$1'"
sleep=$2
shift
shopt -s extglob
len='^[0-9]*([.][0-9]*)?[smhd]?$' dot='^[.][smhd]?$' unit='[smhd]'
[[ $sleep =~ $len && ! $sleep =~ $dot ]] ||
Abort "Bad sleeplength after -u/--unmount: '$sleep'"
[[ ${sleep: -1:1} =~ $unit ]] || sleep=${sleep}m ;;
-i|--init) init=1
if [[ $2 && ! ${2:0:1} = "-" ]]
then
dir=$2
shift
[[ $2 && ! ${2:0:1} = "-" ]] && name=$2 && shift
fi ;;
*) Abort "Unrecognized argument: '$1'"
esac
shift
done
dn="$dir/$name"
((whiptail)) && ! type -P whiptail >/dev/null && whiptail=0
[[ $gcfs && ! -x $gcfs ]] && Abort "Can't execute specified binary '$gcfs'"
if [[ -z $gcfs ]] && ! gcfs=$(type -p gocryptfs)
then
url=https://github.com/rfjakob/gocryptfs/releases/download/v1.8.0
file=gocryptfs_v1.8.0_linux-static_amd64.tar.gz
wget -q "$url/$file"
tar xf "$file" gocryptfs
chmod +x gocryptfs
gcfs=./gocryptfs
fi
trap "[[ -s '$log' ]] || rm -f -- '$log'" EXIT QUIT
if ((init))
then
[[ $dir ]] ||
Abort "Either set $MGCFS_DIR or $dir, or put a directory after '-i/--init'"
[[ $name ]] ||
Abort "Either set $MGCFS_NAME or $name, or put a name after '-i/--init <dir>'"
[[ -d $dn ]] || mkdir -p "$dn" ||
Abort "Initialize failed, unable to create directory '$dn'"
[[ $(ls -A "$dn") ]] && Abort "Initialize failed, directory '$dn' not empty"
[[ $MGCFS_PLAIN = 1 ]] && ptn="-plaintextnames " || ptn=
pass=$(mktemp)
cue=
while :
do
Password "${cue}Enter new password:"
(($(stat -c '%s' "$pass")<2)) && cue="Password cannot be empty!\n" && continue
first=$(cat "$pass")
Password "Confirm password:"
[[ "$(cat "$pass")" = "$first" ]] && break || cue="Passwords not the same!\n"
done
"$gcfs" -passfile "$pass" -init $ptn -- "$dn" >"$log" &&
shred -fu -- "$pass" &&
Info "Directory '$dn' initialized as gocryptfs directory" &&
exit 0 ||
Abort "Initializing '$dn' as gocryptfs directory failed"
fi
# When already mounted: unmount+exit
Unmount
# Directory not FUSE-mounted
if mkdir -p "$mount" 2>/dev/null
then # Directory now exists
chmod 700 "$mount" ## Force access restriction
else # $mount could not be created: abort
Abort "Mount point $mount could not be created or\nalready exists and isn't a directory"
fi
pass=$(mktemp)
[[ $sleep ]] && cue="(Will auto-unmount $name after $sleep)" ||
cue="(Run again to manually unmount $name!)"
cue="Mounting $name on $mount\n$cue\n\nPassword:"
Password "$cue"
# If $name is no directory or is empty, unpack $name.tar if present
dnt=$dn.tar dnbt=$dn.backup.tar
if [[ ! -d $dn || -z $(ls -A "$dn") ]]
then
if [[ -f $dnt ]]
then
Info "No files in '$dn'\n,unpacking '$dnt'"
tar xf "$dnt" -C "$dir"
sync
fi
fi
[[ -s $dn/gocryptfs.conf ]] || Abort "$dn is not a gocryptfs directory"
# Allow mounting over non-empty mountpoint with -nonempty
while ! "$gcfs" $q -nonempty -passfile "$pass" -- "$dn" "$mount" 2>"$log"
do
shred -fu -- "$pass"
sync
Password "Decryption failed, try a different password!\n\n$cue"
done
hexdump -C "$pass" >~/hexkey
[[ $run ]] && DISPLAY=:0.0 $run &
# Tar up the encrypted directory for backup
tar cfC "$dnbt" "$dir" "$name" ||
Info "Return code $? on creating backup archive '$dnbt'"
! diff "$dnt" "$dnbt" >/dev/null && cp "$dnbt" "$dnt" && sync
shred -fu -- "$dnbt"
shred -fu -- "$pass"
sync
[[ $sleep ]] && sleep $sleep && Unmount &
sync
exit 0