blob: 3d0628a67fda7c1a2fa8bd8fe8b0538c91adb817 (
plain)
1 #!/usr/bin/env bash
2 # GPGSecure is a shell script that manages GPG encrypted archives
3 # Copyright (C) 2018 Aaron Ball <nullspoon@oper.io>
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
19 export KEY=${KEY:-} # GPG key to encrypt the container with
20 export DIR # Directory path to present the gpg archive to
21 export TMP # Temp directory in memory to decrypt to
22
23 trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP
24
25
26 shutdown() {
27 gpgtar -e --recipient "${KEY}" -o "${DIR}.tar.gpg" .
28 cd - 2>/dev/null 1>/dev/null
29
30 # Shred all files in memory
31 find "${TMP}" -type f -exec shred -n 100 -f -u "{}" \;
32 # Delete the link
33 rm "${DIR}"
34 # Delete the temp dir from memory
35 rm -rf "${TMP}"
36 sync
37 exit
38 }
39
40
41 writeback() {
42 trap shutdown SIGINT SIGTERM SIGKILL SIGQUIT SIGHUP
43 local tmp # Temp file in memory to write re-encrypted container to. This is
44 # used for faster writebacks to storage for bigger containers.
45 # This also protects data, bigger containers take longer to write
46 # to storage, leaving a bigger window for data corruption.
47 local sleep=20
48 local perms # Permissions of the dest encrypted file. Used for setting perms
49 # on temp archive to avoid overwriting permissions on move
50
51 # Read dest archive permissions, if it exists, else set to 700
52 if [ -f "${DIR}.tar.gpg" ]; then
53 perms=$(stat -c %a "${DIR}.tar.gpg")
54 else
55 perms=700
56 fi
57
58 cd "${TMP}"
59
60 # Sync back to disk every ${sleep} seconds
61 while [ 0 ]; do
62 # Create temp archive for writing back so we don't risk corrupting the
63 # actual destination archive in case of crash. Protect with 700 perms.
64 tmp="$(mktemp /tmp/XXXXXXXXXXXX)"
65 chmod 700 "${tmp}"
66
67 # Write encrypted archive to temp file
68 gpgtar -e --recipient "${KEY}" -o "${tmp}" .
69 if [ $? -gt 0 ]; then
70 printf 'WARNING: Something went wrong syncing back to encrypted storage\n'
71 printf 'Your data is likely in danger.\n'
72 printf 'If you see this message more than once, take a manual backup\n'
73 fi
74
75 # Update perms of temp file to match destination archive so we don't
76 # overwrite those of the destination archive on move
77 # TODO: This is a split second of permissions danger. We should find a way
78 # to remediate this. Set perms on dest archive *after* move instead?
79 chmod "${perms}" "${tmp}"
80 mv "${tmp}" "${DIR}.tar.gpg"
81 if [ $? -gt 0 ]; then
82 printf 'WARNING: Something went wrong syncing back to encrypted storage\n'
83 printf 'Your data is likely in danger.\n'
84 printf 'If you see this message more than once, take a manual backup\n'
85 fi
86
87 sleep ${sleep} &
88 wait $!
89 done
90 }
91
92
93 open() {
94 local archive="${1}"
95
96 # Convert DIR to absolute path to avoid cd issues
97 local dirname="$(cd $(dirname ${archive}) && pwd)"
98 local basename="$(basename ${archive})"
99 local dir="${dirname}/${basename}"
100 # Create a temp dir in memory to extract to for safety
101 export TMP=$(mktemp -d /tmp/dec-XXXXXXXXXXXXXX)
102 # Link!
103 ln -s "${TMP}" "${dir}"
104
105 if [ ! -f "${dir}.tar.gpg" ]; then
106 # Tell the user if that encrypted archive does not exist.
107 printf 'Encrypted archive does not exist. Creating.\n'
108 else
109 # Extract the encrypted tarchive if it exists
110 gpgtar --decrypt --directory "${TMP}" "${dir}.tar.gpg"
111 fi
112
113 writeback &
114 echo $! > "${dirname}/.${basename}.pid"
115 }
116
117
118 status() {
119 local archive=${1}
120
121 local dirname="$(dirname ${archive})"
122 local basename="$(basename ${archive})"
123 local pidfile="${dirname}/.${basename}.pid"
124
125 # If no pidfile, assume closed
126 if [ ! -f "${pidfile}" ]; then
127 printf '%s is closed\n' "${archive}"
128 return 0
129 fi
130
131 local pid="$(cat ${pidfile})"
132
133 ps "${pid}" 2>/dev/null 1>/dev/null
134 if [ $? -eq 0 ]; then
135 printf '%s is open\n' "${archive}"
136 elif [ $? -gt 0 ]; then
137 printf '%s is closed but a stale pidfile was found. Removing\n' "${archive}"
138 rm -f "${pidfile}"
139 else
140 printf '%s is closed\n' "${archive}"
141 fi
142 }
143
144
145 close() {
146 local archive=${1}
147
148 local dirname="$(dirname ${archive})"
149 local basename="$(basename ${archive})"
150 local pidfile="${dirname}/.${basename}.pid"
151 local pid="$(cat ${pidfile})"
152
153 ps "${pid}" 2>/dev/null 1>/dev/null
154 if [ $? -gt 0 ]; then
155 printf "Stale pidfile detected but share is not open. Removing\n"
156 rm -f "${pidfile}"
157 return 1
158 else
159 # Send SIGTERM (15) to tell the process to exit cleanly
160 kill -15 "${pid}"
161 [ $? -eq 0 ] && rm "${pidfile}" && return 0
162
163 printf 'Error closing archive "%s"\n' "${archive}"
164 return 1
165 fi
166 }
167
168
169 pathtoabs() {
170 local path="${1}"
171
172 if [ -f "${path}" ]; then
173 cd $(dirname ${path})
174 printf "%s/%s\n" "$(pwd)" "$(basename ${path})"
175 return 0
176 elif [ -d "${path}" ]; then
177 cd ${path} && pwd
178 return 0
179 elif [ ! -e "${path}" ]; then
180 printf -- "%s/%s\n" "$(pwd)" "${path}"
181 return 0
182 fi
183 return 1
184 }
185
186 main() {
187 local action="${1}"
188 local archive="${2}"
189
190 # Input validation
191 if [ -z "${action:-}" ]; then
192 printf 'Action (open, close, or status) required\n'
193 return 1
194 fi
195 if [ -z "${archive:-}" ]; then
196 printf 'Archive to decrypt required\n'
197 return 1
198 fi
199
200 if [ -z "${KEY}" ]; then
201 printf 'KEY variable unset. Cannot re-encrypt. Exiting.\n'
202 return 1
203 fi
204
205 gpg --list-keys ${KEY} 2>/dev/null 1>/dev/null
206 if [ $? -gt 0 ]; then
207 printf 'Unknown key "%s". Cannot proceed.\n' "${KEY}"
208 return 1
209 fi
210
211 local dirname="$(dirname ${archive})"
212 local basename="$(basename ${archive})"
213 export DIR="$(pathtoabs ${dirname}/${basename})"
214
215 if [ "${action}" = 'open' ]; then
216 # Check if already open
217 if [ -f "${dirname}/.${basename}.pid" ]; then
218 printf 'ERROR: Archive "%s" is already open\n' "${archive}"
219 return 1
220 else
221 printf 'Opening!\n'
222 open "${archive}"
223 return $?
224 fi
225 elif [ "${action}" = 'close' ]; then
226 # Check if already closed
227 if [ ! -f "${dirname}/.${basename}.pid" ]; then
228 printf 'ERROR: Archive "%s" is not open\n' "${archive}"
229 exit 1
230 else
231 printf 'Closing!\n'
232 close "${archive}"
233 return $?
234 fi
235 elif [ "${action}" = 'status' ]; then
236 status "${archive}"
237 return $?
238 fi
239
240 # If we make it here, something went wrong.
241 printf 'ERROR: Unknown action "%s"\n' "${action}"
242 return 1
243 }
244
245 main ${@}
|