mirror of
https://github.com/gaschz/qubes-pass.git
synced 2025-06-07 01:38:31 +02:00
Support clipboard and QR code functionality in pass get / generate / get-or-generate.
This commit is contained in:
parent
f4fc15d42e
commit
cae35f2bfc
212
bin/qvm-pass
212
bin/qvm-pass
@ -8,6 +8,11 @@ import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
from pipes import quote
|
||||
except ImportError:
|
||||
from shlex import quote
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
|
||||
@ -18,11 +23,11 @@ usage = "\n".join(
|
||||
"",
|
||||
" qvm-pass [-d <passvm>] [subcommand] [arguments...]",
|
||||
"",
|
||||
"subcommands:",
|
||||
"subcommands (try qvm-pass subcommand --help for more):",
|
||||
"",
|
||||
" [ls|list|show]",
|
||||
" Retrieves the list of keys from the pass store.",
|
||||
" [show] <key>",
|
||||
" [show] [-c] <key>",
|
||||
" Retrieves a key from the pass store.",
|
||||
" generate [-n] [-f] <key> [pass-length]",
|
||||
" Retrieves a key from the pass store; creates the key",
|
||||
@ -52,6 +57,87 @@ usage = "\n".join(
|
||||
)
|
||||
|
||||
|
||||
# The following code was lifted from the pass program to support
|
||||
# identical functionality.
|
||||
shell_functions = """
|
||||
X_SELECTION="${PASSWORD_STORE_X_SELECTION:-clipboard}"
|
||||
CLIP_TIME="${PASSWORD_STORE_CLIP_TIME:-45}"
|
||||
|
||||
clip() {
|
||||
if [[ -n $WAYLAND_DISPLAY ]]; then
|
||||
local copy_cmd=( wl-copy )
|
||||
local paste_cmd=( wl-paste -n )
|
||||
if [[ $X_SELECTION == primary ]]; then
|
||||
copy_cmd+=( --primary )
|
||||
paste_cmd+=( --primary )
|
||||
fi
|
||||
local display_name="$WAYLAND_DISPLAY"
|
||||
elif [[ -n $DISPLAY ]]; then
|
||||
local copy_cmd=( xclip -selection "$X_SELECTION" )
|
||||
local paste_cmd=( xclip -o -selection "$X_SELECTION" )
|
||||
local display_name="$DISPLAY"
|
||||
else
|
||||
die "Error: No X11 or Wayland display detected"
|
||||
fi
|
||||
local sleep_argv0="password store sleep on display $display_name"
|
||||
|
||||
# This base64 business is because bash cannot store binary data in a shell
|
||||
# variable. Specifically, it cannot store nulls nor (non-trivally) store
|
||||
# trailing new lines.
|
||||
pkill -f "^$sleep_argv0" 2>/dev/null && sleep 0.5
|
||||
local before="$("${paste_cmd[@]}" 2>/dev/null | $BASE64)"
|
||||
echo -n "$1" | "${copy_cmd[@]}" || die "Error: Could not copy data to the clipboard"
|
||||
(
|
||||
( exec -a "$sleep_argv0" bash <<<"trap 'kill %1' TERM; sleep '$CLIP_TIME' & wait" )
|
||||
local now="$("${paste_cmd[@]}" | $BASE64)"
|
||||
[[ $now != $(echo -n "$1" | $BASE64) ]] && before="$now"
|
||||
|
||||
# It might be nice to programatically check to see if klipper exists,
|
||||
# as well as checking for other common clipboard managers. But for now,
|
||||
# this works fine -- if qdbus isn't there or if klipper isn't running,
|
||||
# this essentially becomes a no-op.
|
||||
#
|
||||
# Clipboard managers frequently write their history out in plaintext,
|
||||
# so we axe it here:
|
||||
qdbus org.kde.klipper /klipper org.kde.klipper.klipper.clearClipboardHistory &>/dev/null
|
||||
|
||||
echo "$before" | $BASE64 -d | "${copy_cmd[@]}"
|
||||
) >/dev/null 2>&1 & disown
|
||||
echo "Copied $2 to clipboard. Will clear in $CLIP_TIME seconds."
|
||||
}
|
||||
|
||||
qrcode() {
|
||||
if [[ -n $DISPLAY || -n $WAYLAND_DISPLAY ]]; then
|
||||
if type feh >/dev/null 2>&1; then
|
||||
echo -n "$1" | qrencode --size 10 -o - | feh -x --title "pass: $2" -g +200+200 -
|
||||
return
|
||||
elif type gm >/dev/null 2>&1; then
|
||||
echo -n "$1" | qrencode --size 10 -o - | gm display -title "pass: $2" -geometry +200+200 -
|
||||
return
|
||||
elif type display >/dev/null 2>&1; then
|
||||
echo -n "$1" | qrencode --size 10 -o - | display -title "pass: $2" -geometry +200+200 -
|
||||
return
|
||||
fi
|
||||
fi
|
||||
echo -n "$1" | qrencode -t utf8
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def pass_frontend_shell(cmd):
|
||||
global shell_functions
|
||||
quoted_cmd = shell_functions + "\n\n" + " ".join(quote(x) for x in cmd)
|
||||
return subprocess.call(["bash", "-c", quoted_cmd])
|
||||
|
||||
|
||||
def clip(data, path):
|
||||
return pass_frontend_shell(["clip", data, path])
|
||||
|
||||
|
||||
def qrcode(data, path):
|
||||
return pass_frontend_shell(["qrcode", data, path])
|
||||
|
||||
|
||||
parser_for_discrimination = argparse.ArgumentParser(description="(nobody sees this)")
|
||||
parser_for_discrimination.add_argument(
|
||||
"-d",
|
||||
@ -141,6 +227,22 @@ for p in ["get-or-generate", "generate"]:
|
||||
help="no symbols in generated password",
|
||||
default=False,
|
||||
)
|
||||
for p in ["show", "get-or-generate", "generate"]:
|
||||
_parsers[p].add_argument(
|
||||
"-c",
|
||||
"--clip",
|
||||
action="store_true",
|
||||
help="copy password to clipboard instead of displaying onscreen",
|
||||
default=False,
|
||||
)
|
||||
_parsers[p].add_argument(
|
||||
"-q",
|
||||
"--qrcode",
|
||||
action="store_true",
|
||||
help="display password as QR code",
|
||||
default=False,
|
||||
)
|
||||
|
||||
for p in ["mv", "cp", "rm", "insert", "generate"]:
|
||||
_parsers[p].add_argument(
|
||||
"-f",
|
||||
@ -226,11 +328,14 @@ def pass_manage(*args, **kwargs):
|
||||
|
||||
|
||||
arguments = sys.argv[1:]
|
||||
global_opts, args = parser_for_discrimination.parse_known_args(arguments)
|
||||
if len(global_opts.arguments) == 0:
|
||||
arguments = ["ls"] + arguments
|
||||
elif len(global_opts.arguments) == 1 and global_opts.arguments[0] not in subcommands:
|
||||
arguments = ["show"] + arguments
|
||||
if not "--help" in arguments and not "-h" in arguments and not "-?" in arguments:
|
||||
global_opts, args = parser_for_discrimination.parse_known_args(arguments)
|
||||
if len(global_opts.arguments) == 0:
|
||||
arguments = ["ls"] + arguments
|
||||
elif (
|
||||
len(global_opts.arguments) == 1 and global_opts.arguments[0] not in subcommands
|
||||
):
|
||||
arguments = ["show"] + arguments
|
||||
opts = parser_for_subcommands.parse_args(arguments)
|
||||
|
||||
|
||||
@ -253,7 +358,17 @@ if opts.subcommand == "ls" or (opts.subcommand == "show" and opts.key is None):
|
||||
sys.exit(pass_read("list"))
|
||||
elif opts.subcommand == "show":
|
||||
# User requested a password, or show with an argument.
|
||||
sys.exit(pass_read("get", opts.key))
|
||||
if opts.clip or opts.qrcode:
|
||||
ret, stdout = pass_read("get", opts.key, return_stdout=True)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
stdout = stdout.decode("utf-8")
|
||||
if opts.clip:
|
||||
sys.exit(clip(stdout, opts.key))
|
||||
elif opts.qrcode:
|
||||
sys.exit(qrcode(stdout, opts.key))
|
||||
else:
|
||||
sys.exit(pass_read("get", opts.key))
|
||||
elif opts.subcommand in ("mv", "cp"):
|
||||
if not opts.force and sys.stdin.isatty():
|
||||
with open(os.devnull, "w") as null:
|
||||
@ -278,57 +393,58 @@ elif opts.subcommand == "rm":
|
||||
else:
|
||||
sys.exit(1)
|
||||
sys.exit(pass_manage(opts.subcommand, opts.key))
|
||||
elif opts.subcommand == "get-or-generate":
|
||||
with open(os.devnull, "w") as null:
|
||||
ret, stdout = pass_read("get", opts.key, return_stdout=True, stderr=null)
|
||||
if ret == 8:
|
||||
# Not there.
|
||||
with open(os.devnull, "w") as null:
|
||||
ret = pass_manage(
|
||||
"generate",
|
||||
opts.key,
|
||||
str(int(opts.no_symbols)),
|
||||
str(int(opts.pass_length)),
|
||||
stdout=null,
|
||||
)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
sys.exit(pass_read("get", opts.key))
|
||||
elif ret == 0:
|
||||
# There.
|
||||
sys.stdout.buffer.write(stdout)
|
||||
sys.exit(ret)
|
||||
else:
|
||||
# Woops.
|
||||
sys.exit(ret)
|
||||
elif opts.subcommand == "generate":
|
||||
doit = lambda: sys.exit(
|
||||
pass_manage(
|
||||
opts.subcommand,
|
||||
elif opts.subcommand in ("get-or-generate", "generate"):
|
||||
|
||||
def doit():
|
||||
kwargs = {"return_stdout": True} if (opts.clip or opts.qrcode) else {}
|
||||
ret = pass_manage(
|
||||
"generate",
|
||||
opts.key,
|
||||
str(int(opts.no_symbols)),
|
||||
str(int(opts.pass_length)),
|
||||
**kwargs,
|
||||
)
|
||||
)
|
||||
if not kwargs:
|
||||
sys.exit(ret)
|
||||
if opts.clip or opts.qrcode:
|
||||
ret, stdout = pass_read("get", opts.key, **kwargs)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
if opts.clip:
|
||||
sys.exit(clip(stdout.decode("utf-8"), opts.key))
|
||||
elif opts.qrcode:
|
||||
sys.exit(qrcode(stdout.decode("utf-8"), opts.key))
|
||||
sys.exit(ret)
|
||||
|
||||
with open(os.devnull, "w") as null:
|
||||
ret = pass_read("get", opts.key, stdout=null, stderr=null)
|
||||
ret, stdout = pass_read("get", opts.key, return_stdout=True, stderr=null)
|
||||
|
||||
if ret == 8:
|
||||
# Not there.
|
||||
doit()
|
||||
elif ret == 0:
|
||||
# There:
|
||||
if not opts.force and sys.stdin.isatty():
|
||||
sys.stderr.write(
|
||||
"An entry already exists for %s. Overwrite it? [y/N] " % (opts.key,)
|
||||
)
|
||||
ans = sys.stdin.readline().strip()
|
||||
if ans and ans[0] in "yY":
|
||||
doit()
|
||||
if opts.subcommand == "get-or-generate":
|
||||
if opts.clip:
|
||||
sys.exit(clip(stdout.decode("utf-8"), opts.key))
|
||||
elif opts.qrcode:
|
||||
sys.exit(qrcode(stdout.decode("utf-8"), opts.key))
|
||||
else:
|
||||
sys.exit(1)
|
||||
else:
|
||||
doit()
|
||||
sys.stdout.buffer.write(stdout)
|
||||
sys.exit(ret)
|
||||
else: # generate
|
||||
if not opts.force and sys.stdin.isatty():
|
||||
sys.stderr.write(
|
||||
"An entry already exists for %s. Overwrite it? [y/N] " % (opts.key,)
|
||||
)
|
||||
ans = sys.stdin.readline().strip()
|
||||
if ans and ans[0] in "yY":
|
||||
doit()
|
||||
else:
|
||||
sys.exit(1)
|
||||
else:
|
||||
doit()
|
||||
else:
|
||||
# Woops.
|
||||
sys.exit(ret)
|
||||
elif opts.subcommand == "insert":
|
||||
if not opts.force and sys.stdin.isatty():
|
||||
|
Loading…
x
Reference in New Issue
Block a user