-
Notifications
You must be signed in to change notification settings - Fork 0
/
ncrpt
executable file
·344 lines (315 loc) · 11.1 KB
/
ncrpt
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
#!/bin/bash
# encrypts all files in the specified directory 'indir'
# writes key files into directory 'keydir', extensions indicating compression methods
# writes encrypted binary files with the same names into directory 'outdir'
PROGN=${0##*/}
COMPRESSOR=lzma # the default compression program
EXT=lz # and the extension that goes with it
NOCOMPRESS=( 'jpg' 'jpeg' 'mp4' 'zip' '7z' 'lz' 'zst' 'gz' 'tgz' 'bz2' )
# function to compress and encrypt one file
# takes three arguments: INDIR KEYDIR OUTDIR
# KEYDIR OUTDIR must be writeable
# creates and cleans up two temporary files in OUTDIR
function processfile {
local INFILE="$1"
local KEYFILE="$2"
local OUTFILE="$3"
local EX="${1##*.}" # this file's last extension
local CH='u' # default uncompressed marker
local EX=${EX,,} # convert to lower case, to detect both cases
# if EX is on excluded list, return
[ -n "$EXCLUDE" ] && {
for EXT in "$EXCLUDE"; do
[ "$EX" = "$EXT" ] && return
done
}
# if EX is on NOCOMPRESS list, then skip compression
for KEEPEXT in "${NOCOMPRESS[@]}"; do
[ "$EX" = "$KEEPEXT" ] && {
xorfork < "$INFILE" "$OUTFILE" > "$KEYFILE.u"
return
}
done
if [ $HEXTEST ] && hexcheck < "$INFILE" > "$OUTFILE".tmp 2>/dev/null; then
local CH='h' # hexadecimal compression enabled and detected
local INFILE="$OUTFILE.tmp"
else if [ $B64TEST ] && base64 -d -w 0 "$INFILE" > "$OUTFILE".tmp 2>/dev/null; then
local CH='b' # base64 compression enabled and detected
local INFILE="$OUTFILE".tmp
fi
fi
# regardless whether hexcheck or base64 tests succeeded or not, apply general compression test
$COMPRESSOR $FLAGS "$INFILE" > "$OUTFILE".cmp
local CMPS=$( stat -c%s "$OUTFILE".cmp ) # the size after compression
local ORGS=$( stat -c%s "$INFILE" ) # and before compression
# use compressed version only if it is actually smaller
[ "$CMPS" -lt "$ORGS" ] && { # successful general compression
local INFILE="$OUTFILE".cmp # make the final compressed file to be the infile
if [ "$COMPRESSOR" = 'lzma' ]; then
case "$CH" in
u ) local CH='i';;
h ) local CH='j';;
b ) local CH='k';;
* ) printf "$PROGN processfile: unrecognised ch\n"
exit 2;;
esac
else # compressor program is 'zstd'
case "$CH" in
u ) local CH='l';;
h ) local CH='m';;
b ) local CH='n';;
* ) printf "$PROGN processfile: unrecognised ch\n"
exit 2;;
esac
fi
} # else no general compression gain, keep the original INFILE and codes: u,h,b
# generate key of the same size as infile,
# encrypt with it and write to OUTFILE.$CH
xorfork < "$INFILE" "$OUTFILE" > "$KEYFILE"."$CH"
# clean up
if [ -e "$OUTFILE".tmp ]; then rm "$OUTFILE".tmp; fi
if [ -e "$OUTFILE".cmp ]; then rm "$OUTFILE".cmp; fi
} # end of processfile
# function to process a whole directory
# takes three arguments: INDIR KEYDIR OUTDIR
function processdir {
# do not process when INDIR is on IGNORE list
BN=`basename $1`
for D in "$IGNORE"; do
[ "$BN" = "$D" ] && return
done
# create the ouput directories
mkdir "$2" "$3" || {
printf "$PROGN quitting, failed to create output directories '$2' '$3'!\n" >&2
exit 2
}
cd "$1" || { # must be quoted or fails on windozy dirs with spaces!
printf "$PROGN processdir: failed to enter directory $1!\n" >&2
exit 2
}
# iterate through the listing
for ITEM in *; do
[[ "$ITEM" == '*' ]] && break; # no more
if [ -d "$ITEM" ]; then # ITEM is directory
[ "$RECURSE" ] || continue
processdir "$ITEM" ../"$2/$ITEM" ../"$3/$ITEM" & # push ITEM onto paths
else # process only a genuine file
[ -f "$ITEM" ] && processfile "$ITEM" ../"$2/$ITEM" ../"$3/$ITEM" &
fi
done
cd ..
wait
} # end of processdir
# function to update a whole directory
# takes three arguments: INDIR KEYDIR OUTDIR
function updatedir {
BN=`basename $1`
# do not process when INDIR is on IGNORE list
for D in "$IGNORE"; do
[ "$BN" = "$D" ] && return
done
[ -d "$2" ] || {
printf "$PROGN updatedir: key dir $2 is invalid\n" >&2
exit 2
}
[ -d "$3" ] || {
printf "$PROGN updatedir: output dir $3 is invalid\n" >&2
exit 2
}
cd "$1" || { # must be quoted or fails on windozy dirs with spaces!
printf "$PROGN updatedir: failed to enter directory $1!\n" >&2
exit 2
}
# iterate through the indir listing
for ITEM in *; do
[[ "$ITEM" == '*' ]] && break; # no more
local KD=../"$2/$ITEM" # corresponding $keydir path
local OD=../"$3/$ITEM" # corresponding $outdir path
if [ -d "$ITEM" ]; then # directory
[ "$RECURSE" ] || continue; # continue if not recursing
[ -d "$OD" ] || { # matching dir does not exist
printf " A: $ITEM\n" # so add it
processdir "$ITEM" "$KD" "$OD" & # push ITEM onto paths
continue
}
# test the dirs modification times
# note that -nt only works for additions/deletions to dirs
[ $(stat -c%Y "$ITEM") -gt $(stat -c%Y "$OD") ] && {
printf " U: $ITEM\n" # input dir is newer, so update
updatedir "$ITEM" "$KD" "$OD" &
continue
}
else
[ -f "$ITEM" ] || continue; # ignore if not genuine file
[ -e "$OD" ] || { # matching $OD file does not exist
printf " a: $ITEM\n" # so add it
processfile "$ITEM" "$KD" "$OD"
continue
}
[ "$ITEM" -nt "$OD" ] && { # here the input file is newer
printf " u: $ID\n" # so update it
processfile "$ITEM" "$KD" "$OD"
}
fi
done
cd ..
wait
} # end of updatedir
# function to clean up a whole directory
# takes three arguments: INDIR KEYDIR OUTDIR
function cleandir {
BN=`basename $3`
for D in "$IGNORE"; do
[ "$D" = "$BN" ] && { # outdir is on the ignore list
# so remove it and its corresponding keydir
printf " D: $KD $OD\n"
rm -rf "$KD" "$OD" &
return
}
done
[ -d "$1" ] || {
printf "$PROGN updatedir: input dir $1 is invalid\n" >&2
exit 2
}
[ -d "$2" ] || {
printf "$PROGN updatedir: key dir $2 is invalid\n" >&2
exit 2
}
cd "$3" || { # must be quoted or fails on windozy dirs with spaces!
printf "$PROGN cleandir: failed to enter outdir $3!\n" >&2
exit 2
}
# iterate through the outdir listing
for OD in *; do
[[ "$OD" == '*' ]] && break; # no more
local ID=../"$1/$OD" # corresponding $indir path
local KD=../"$2/$OD" # corresponding $keydir path
if [ -d "$OD" ]; then # directory
[ "$RECURSE" ] || continue
[ -d "$ID" ] || { # corresponding indir does not exist
# so remove the corresponding keydir and this outdir
printf " D: $KD $OD\n"
rm -rf "$KD" "$OD" &
continue
}
cleandir "$ID" "$KD" "$OD" & # descend
else
[ -f "$OD" ] || continue; # only process genuine files now
[ -e "$ID" ] || { # here $ID file does not exist
printf " d: $OD $KD.*\n" # so delete it from outdir and keydir
rm "$OD" "$KD".*
continue
}
local EX="${OD##*.}" # this file's last extension
local EX=${EX,,} # convert to lower case, to detect both cases
# if EX is on excluded list, delete
[ -n "$EXCLUDE" ] && {
for EXT in "$EXCLUDE"; do
[ "$EXT" = "$EX" ] && {
printf " d: $OD $KD.*\n" # so delete it from outdir and keydir
rm "$OD" "$KD".*
break
}
done
}
fi
done
cd ..
wait
} # end of cleandir
function usage { printf "Usage: $PROGN -[b][c][h][q][r][u][v][x][z] indir keydir outdir\n" >&2; }
function helpmsg {
usage
printf "\t%s\n" \
'-b | --b64 : test for base64 encoded files' \
'-c | --cleanup : the archive (do deletions)' \
'-e | --exclude : (globally) listed file extensions' \
'-h | --help : print this text' \
'-i | --ignore : (globally) listed directories' \
'-q | --quiet : turn off the final summary' \
'-r | --recurse : into subdirectories' \
'-u | --update : the archive with changed content' \
'-v | --verbose : detailed reporting' \
'-x | --hex : test for hexadecimal files' \
'-z | --zstd : use zstd compression instead of lzma' >&2
exit 1
}
# Main starts here ###########################################################
# get options, including long options, extracted as arg to the last option '-'
while getopts bce:hi:qruvxz-: OPT; do
if [ "$OPT" = "-" ]; then # long option: reset OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument
OPTARG="${OPTARG#=}" # remove any assignments `=` from long options
fi
case "$OPT" in
b | b64 ) B64TEST=0;;
c | clean ) CLEAN=0;;
e | exclude ) EXCLUDE=$OPTARG;;
h | help ) helpmsg;;
i | ignore ) IGNORE=$OPTARG;;
q | quiet ) QUIET=0;;
r | recurse ) RECURSE=0;;
u | update ) UPDATE=0;;
v | verbose ) VERBOSE=0;;
x | hex ) HEXTEST=0;;
z | zstd ) COMPRESSOR=zstd; EXT=zst;; #alternative compressor and its extension
??* ) printf "$PROGN quitting, invalid long option: $OPT\n" >&2; usage; exit 2;;
*) exit 2;; # invalid short option (error will be reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
[ $RECURSE ] && {
printf "Recursing "
if [ -n "$IGNORE" ]; then
printf "and ignoring directories: $IGNORE\n"
else
printf "fully\n"
fi
}
[ -n "$EXCLUDE" ] && printf "Excluding extensions: $EXCLUDE\n"
# Validate the program arguments
[ "$#" -ne 3 ] && {
printf "$PROGN expected three arguments, "$#" given!\n" >&2
exit 2
}
# Validate the input directory
[ -d "$1" ] || {
printf "$PROGN quitting, input directory $1 not found\n" >&2
exit 2
}
# Validate the compressor. It has been set either to lzma or zstd
[ -z $( which "$COMPRESSOR" ) ] && {
printf "$PROGN quitting, compressor utility $COMPRESSOR not found\n" >&2
exit 2
}
FLAGS='-c -z -q' # the default lags to the compressor
if [ $VERBOSE ]; then
if [ "$COMPRESSOR" = 'lzma' ]; then FLAGS='-c -z -v'; else FLAGS='-c'; fi
fi
if [ $UPDATE ]; then
[ $CLEAN ] && {
printf "$PROGN: cleaning up archive dir '$3', using %d cores\n" $( grep -c ^processor /proc/cpuinfo )
cleandir "$1" "$2" "$3"
}
printf "$PROGN: updating archive dir '$3', using %d cores\n" $( grep -c ^processor /proc/cpuinfo )
updatedir "$1" "$2" "$3"
else
printf "$PROGN: encrypting dir '$1', using %d cores\n" $( grep -c ^processor /proc/cpuinfo )
[ -e "$2" ] && {
printf "$PROGN: keydir $2 already exists!\n" >&2
exit 2
}
[ -e "$3" ] && {
printf "$PROGN: outdir $3 already exists!\n" >&2
exit 2
}
processdir "$1" "$2" "$3"
fi
# optional final report
if ! [ $QUIET ]; then
SIZES=( $( du -bs "$3" "$1" ) ) # array of size,dir,size,dir
printf "$PROGN: ${SIZES[3]} ${SIZES[2]} (100%%) => ${SIZES[1]} ${SIZES[0]} (%d%%)\n" \
$(( 100*${SIZES[0]}/${SIZES[2]} )) # calculate percentage
printf "$PROGN: keys are in dir '$2'\n"
fi