289 lines
7.8 KiB
Bash
Executable File
289 lines
7.8 KiB
Bash
Executable File
#!/bin/bash
|
|
_help() {
|
|
echo '
|
|
2FA code viewer
|
|
|
|
Usage:
|
|
- Create a (possible encrypted) folder for your service keys
|
|
- When the service presents you the QRCode, you typically can
|
|
view the actual code as text. It looks something like:
|
|
I5XXIY3IMEWCA3LBMRSSA6LPOUQGY33PNMQQU===
|
|
- Store this piece of text in a file: "Service name".key
|
|
- Run 2fa-view command in the folder
|
|
|
|
Viewer:
|
|
- Codes and service names are printed on the screen
|
|
- The upper timer shows the 30 second interval when
|
|
the code changes
|
|
- The lower timer shows time left before the program exits
|
|
|
|
Options:
|
|
--light to disable animations
|
|
--color X to prefer a color for the skull. Choose color from:
|
|
k r g b c m y w K R G B C M Y W
|
|
'
|
|
|
|
|
|
exit
|
|
}
|
|
|
|
for (( i=1; i<=$#; i++ )); do
|
|
value=${!i}
|
|
j=$(( i + 1 ))
|
|
[[ "$value" = "--help" ]] && _help
|
|
[[ "$value" = "--light" ]] && { LIGHT=1; continue; }
|
|
[[ "$value" = "--color" ]] && {
|
|
TFA_COLOR_TITLE="${!j}"
|
|
((i++))
|
|
continue;
|
|
}
|
|
[[ "${value}" = "-"* ]] && {
|
|
[[ "$value" =~ -.*h ]] && { _help; }
|
|
continue
|
|
}
|
|
done
|
|
|
|
which oathtool &> /dev/null || {
|
|
echo Missing oathtool
|
|
exit 1
|
|
}
|
|
|
|
timeadjust=0
|
|
update_frequency=30
|
|
exit_timeout=120
|
|
if [ "$1" = "light" ]; then LIGHT=1; fi
|
|
shopt -s nullglob
|
|
. qolop
|
|
|
|
picture=" ..;;;::;:;::;;:;,.
|
|
.:::::;;;:;:::::::;;;;::;:;:.
|
|
.::::;;::;::;:;::::::::::;:;::;::.
|
|
,::::;:::::::;;;::;:;;::::;::::::::.
|
|
';:::::::::::::::;;;;;;;:::;;::;;;:..
|
|
';;::::',',',;:::;:;;;,'.;::::;;:;...
|
|
.'. . .,:,.. ..
|
|
' @ .::;:;. @ ..
|
|
.:' .;: .::. .::.
|
|
..,;;;:;:;;:' ,;,'';;,..,,'
|
|
. .;;. .. .,::, .
|
|
';:;;;::;;:::..:::
|
|
,:::...;:. . .,,
|
|
..
|
|
"
|
|
picture_colors="kkKKwwbm$TFA_COLOR_TITLE$TFA_COLOR_TITLE$TFA_COLOR_TITLE$TFA_COLOR_TITLE$TFA_COLOR_TITLE$TFA_COLOR_TITLE"
|
|
picture_color_codes=()
|
|
for x in $( seq 0 $(( ${#picture_colors} - 1 )) ); do
|
|
picture_color_codes+=( $( _qCol ${picture_colors:$x:1} ) )
|
|
done
|
|
picture_eye=$( _qCol R )
|
|
|
|
_skull() {
|
|
if [ "$LIGHT" = 1 ]; then return; fi
|
|
_qPos abs 4 1
|
|
for x in $( seq 1 ${#picture} ); do
|
|
chr="${picture:$x:1}"
|
|
if [ "$chr" = " " ]; then _qPos right 1; continue; fi
|
|
color="${picture_color_codes[$(( $RANDOM % ${#picture_colors} ))]}"
|
|
if [ "$chr" = "@" ]; then color="$picture_eye"; chr="o"; fi
|
|
printf "%s%s" \
|
|
"$color" \
|
|
"$chr"
|
|
done
|
|
|
|
_qCol z
|
|
}
|
|
|
|
flame=( )
|
|
for x in $( seq 1 $(( 44 * 8 )) ); do
|
|
flame+=( 0 )
|
|
done
|
|
flame_mask_array=( \
|
|
"11111000000000000000000000000000000000001111" \
|
|
"11000000000000000000000000000000000000000001" \
|
|
"00000000000000000000000000000000000000000000" \
|
|
"00000000000000000000000000000000000000000000" \
|
|
"10000000000001110110111111001110000000000001" \
|
|
"11100000010111111111011110111101100000000111" \
|
|
"11100000011111111111111111111111110000000111" \
|
|
"11110001111111111111111111111111110100011111" \
|
|
)
|
|
flame_mask=( )
|
|
for y in {0..7}; do
|
|
for x in {0..43}; do
|
|
flame_mask+=( ${flame_mask_array[$y]:$x:1} )
|
|
done
|
|
done
|
|
flame_chars=" .:##@"
|
|
flame_colors="zzzmryYY"
|
|
flame_color_codes=()
|
|
for x in $( seq 0 $(( ${#flame_colors} - 1 )) ); do
|
|
flame_color_codes+=( $( _qCol ${flame_colors:$x:1} ) )
|
|
done
|
|
flame_next=$( _qPos right 1 )
|
|
flame_corner=$( _qPos abs 1 1 )
|
|
|
|
_flames() {
|
|
if [ "$LIGHT" = 1 ]; then return; fi
|
|
width=43
|
|
height=7
|
|
seq_x=$( seq 0 $width )
|
|
for x in $seq_x; do
|
|
pos=$(( ( $height ) * ( $width + 1 ) + $x ))
|
|
flame[$pos]=$(( $RANDOM % 256 ))
|
|
done
|
|
|
|
for y in $( seq 0 $(( $height - 1 )) ); do
|
|
for x in $seq_x; do
|
|
if [ $x -eq $width ]; then continue; fi
|
|
if [ $x -eq 0 ]; then continue; fi
|
|
pos=$(( $y * ( $width + 1 ) + $x ))
|
|
pos_under=$(( ( $y + 1 ) * ( $width + 1 ) + $x ))
|
|
new_value=$(( ( ( \
|
|
${flame[$(( $pos_under ))]} + \
|
|
${flame[$(( $pos_under + 1 ))]} + \
|
|
${flame[$(( $pos_under - 1 ))]} \
|
|
) * 17 ) / 48 ))
|
|
|
|
if [ $new_value -gt 255 ]; then
|
|
new_value=255
|
|
fi
|
|
flame[$pos]=$new_value
|
|
done
|
|
done
|
|
flame_buffer="${flame_corner}${flame_next}${flame_next}${flame_next}"
|
|
for x in $( seq 0 $(( ( $width + 1 ) * ( $height + 1 ) - 1 )) ); do
|
|
if [ $(( $x % ( $width + 1 ) )) -eq $width ]; then flame_buffer+="\n${flame_next}${flame_next}${flame_next}"; fi
|
|
if [ ${flame_mask[$x]} -eq 1 ]; then flame_buffer+="${flame_next}"; continue; fi
|
|
print_value=$(( flame[$x] / 32 ))
|
|
flame_buffer+="${flame_color_codes[${print_value}]}${flame_chars:${print_value}:1}"
|
|
done
|
|
printf "$flame_buffer"
|
|
_qPos abs 1 1
|
|
}
|
|
|
|
_stars() {
|
|
if [ "$LIGHT" = 1 ]; then return; fi
|
|
wall_chars=" ,.'\`"
|
|
wall_colors="kkkKKKzzzr"
|
|
wall_color_codes=()
|
|
for x in $( seq 0 $(( ${#wall_colors} - 1 )) ); do
|
|
wall_color_codes+=( $( _qCol ${wall_colors:$x:1} ) )
|
|
done
|
|
|
|
lines=$(( 25 + $KEY_FILES ))
|
|
_qPos abs 1 1
|
|
seq_x=$( seq 1 50 )
|
|
for y in $( seq 1 $lines ); do
|
|
for x in $seq_x; do
|
|
chr="${wall_chars:$(( $RANDOM % ${#wall_chars} )):1}"
|
|
if [ "$chr" = " " ]; then _qPos right 1; continue; fi
|
|
color="${wall_color_codes[$(( $RANDOM % ${#wall_colors} ))]}"
|
|
printf "%s%s" \
|
|
"$color" \
|
|
"$chr"
|
|
done
|
|
printf "\n"
|
|
done
|
|
_qCol z
|
|
}
|
|
|
|
_exit() {
|
|
clear
|
|
_skull
|
|
_qPos down 4
|
|
exit
|
|
}
|
|
_get_code() {
|
|
oathtool -b --totp $( cat "$1" | tr -dc '[:print:]' )
|
|
}
|
|
_print_codes() {
|
|
#~ printf 'L%.0s' $( seq -1 $update_frequency )
|
|
if [ "$LIGHT" = 1 ]; then
|
|
_qPos abs 3 1;
|
|
else
|
|
_qPos abs 20 1
|
|
fi
|
|
even=0
|
|
for k in *key; do
|
|
if [ $even -eq 0 ]; then
|
|
_qCol z Y
|
|
else
|
|
_qCol z G
|
|
fi
|
|
name=${k%.key}
|
|
name=${name//_/ }
|
|
_qPos right 6
|
|
printf "[ %s ]%s %s \n" \
|
|
$( _get_code "$k" ) \
|
|
$( _qCol s ) \
|
|
"$name"
|
|
even=$(( 1 - $even ))
|
|
done
|
|
_qCol z
|
|
}
|
|
|
|
_show_clock() {
|
|
for foo in {1..1}; do
|
|
i=$(( ( $( date +%s ) - $timeadjust ) % $update_frequency ))
|
|
l=$(( $update_frequency - $i ))
|
|
if [ "$LIGHT" = 1 ]; then
|
|
_qPos abs $(( $KEY_FILES + 5 )) 1
|
|
else
|
|
_qPos abs $(( $KEY_FILES + 22 )) 1
|
|
fi
|
|
_qCol CLREND z g;_qPos right 5; date
|
|
# seconds to code update
|
|
_qCol z g
|
|
printf "\r"; _qPos right 4; printf "[";
|
|
printf ' %.0s' $( seq 1 $update_frequency )
|
|
printf ']\r'; _qPos right 5
|
|
if [ $l -gt 8 ]; then _qCol z G;
|
|
elif [ $l -gt 3 ]; then _qCol z Y;
|
|
else _qCol z R;
|
|
fi
|
|
printf '=%.0s' $( seq 1 $l )
|
|
_qCol z c
|
|
# seconds to this client shutdown
|
|
left=$(( $update_frequency * ( $exit_timeout - $SECONDS ) / $exit_timeout ))
|
|
printf "\n\r"; _qPos right 4; printf "[";
|
|
printf ' %.0s' $( seq 1 $update_frequency )
|
|
printf ']\r'; _qPos right 5
|
|
if [ $left -gt 8 ]; then _qCol z c;
|
|
elif [ $left -gt 3 ]; then _qCol z y;
|
|
else _qCol z r;
|
|
fi
|
|
printf '#%.0s' $( seq 1 $left )
|
|
_qCol z; printf "\n"
|
|
_qCol CLREND
|
|
# die if key pressed, or client open for exit_timeout length
|
|
wait_for_second=$SECONDS
|
|
for i in {1..10}; do
|
|
read -t 0.1 foo
|
|
[[ "$?" -eq 0 ]] && _exit
|
|
[[ $SECONDS -gt $exit_timeout ]] && _exit
|
|
_flames
|
|
if [ $SECONDS -gt $wait_for_second ]; then break; fi
|
|
done
|
|
done
|
|
}
|
|
if [ -f "$1" ]; then _get_code "$1"; _get_code "$1" | xclip; exit; fi
|
|
for key in *key; do ((KEY_FILES++)); done
|
|
clear
|
|
_print_codes
|
|
_skull
|
|
_flames; _flames; _flames; _flames
|
|
|
|
while :;do
|
|
set -e
|
|
new_codes=$( _print_codes )
|
|
set +e
|
|
[[ ! "$new_codes" = "$codes" ]] && {
|
|
# print codes only if they change, this keeps selection in shell intact
|
|
_stars
|
|
codes=$new_codes
|
|
echo -e "$codes"
|
|
echo ''
|
|
}
|
|
_show_clock
|
|
done
|