From b3058348f1a6874d29122baf4acd54a2ccf67715 Mon Sep 17 00:00:00 2001 From: brent s Date: Tue, 7 Apr 2020 21:30:36 -0400 Subject: [PATCH] finished clipboard and QR functionality. --- vaultpass/QR.py | 5 ++ vaultpass/__init__.py | 1 + vaultpass/args.py | 7 +-- vaultpass/clipboard.py | 126 +++++++++++++++++++++++++++++++++++++++++ vaultpass/constants.py | 2 + 5 files changed, 137 insertions(+), 4 deletions(-) diff --git a/vaultpass/QR.py b/vaultpass/QR.py index 122e6ef..82d7219 100644 --- a/vaultpass/QR.py +++ b/vaultpass/QR.py @@ -1,4 +1,5 @@ import io +import json import os import subprocess import logging @@ -21,6 +22,10 @@ except ImportError: def genQr(data, image = False): + if isinstance(data, dict): + data = json.dumps(dict, indent = 4) + if not isinstance(data, str): + data = str(data) _logger.debug('Generating QR code') qr = qrcode.QRCode(error_correction = qrcode.constants.ERROR_CORRECT_H) qr.add_data(data) diff --git a/vaultpass/__init__.py b/vaultpass/__init__.py index d1f3e5a..c0a94fd 100644 --- a/vaultpass/__init__.py +++ b/vaultpass/__init__.py @@ -270,6 +270,7 @@ class VaultPass(object): print(data.read()) data.seek(0, 0) # TODO: clip, etc. + clipboard.pasteClipboard(printme = printme) return(data) def initVault(self, *args, **kwargs): diff --git a/vaultpass/args.py b/vaultpass/args.py index 0bf7958..e96f1a2 100644 --- a/vaultpass/args.py +++ b/vaultpass/args.py @@ -413,12 +413,11 @@ def parseArgs(): show.add_argument('-c', '--clip', nargs = '?', type = int, - default = constants.SHOW_CLIP_LINENUM, + default = None, metavar = 'LINE_NUMBER', dest = 'clip', - help = ('If specified, copy line number LINE_NUMBER (Default: {0}) from the secret to the ' - 'clipboard instead of printing it. ' - 'Use 0 for LINE_NUMBER for the entire secret').format(constants.SHOW_CLIP_LINENUM)) + help = ('If specified, do not print the secret but instead copy it to the clipboard. ' + 'LINE_NUMBER has no effect and is kept for compatibility reasons')) show.add_argument('-q', '--qrcode', dest = 'qr', nargs = '?', diff --git a/vaultpass/clipboard.py b/vaultpass/clipboard.py index 3771612..1067fbd 100644 --- a/vaultpass/clipboard.py +++ b/vaultpass/clipboard.py @@ -1,2 +1,128 @@ +import base64 +import json +import logging import os +import pwd +import re +# We COULD use pyperclip or pygtk or whatever for this, but we have enough external deps already. import subprocess +import sys +import time +import warnings +_logger = logging.getLogger() +## +import psutil +## +from . import constants + + +def getProc(display, clipboard): + real_uid = pwd.getpwnam(os.getlogin()).pw_uid + procs = [] # Normally I'd do this in a list comprehension but switching to a loop for readability. + for p in psutil.process_iter(attrs = ['name', 'cmdline', 'environ', 'uids']): + if p.name() != 'xclip': + continue + if p.uids().effective == real_uid: + p_display = p.environ().get('DISPLAY') + cbrd = None + for idx, arg in enumerate(p.cmdline()): + if arg.startswith('-se'): + cbrd = p.cmdline()[(idx + 1)] + break + if p_display == display and cbrd == clipboard: + return(p) + return(None) + + +def pasteClipboard(data, + seconds = constants.CLIP_TIMEOUT, + clipboard = constants.CLIPBOARD, + printme = False, + *args, **kwargs): + if clipboard not in constants.ALLOWED_CLIPBOARDS: + _logger.error('Invalid clipboard name') + _logger.debug(('The clipboard "{0}" is invalid. ' + 'Must be one of: {1}.').format(', '.join((clipboard, constants.ALLOWED_CLIPBOARDS)))) + raise ValueError('Invalid clipboard') + if isinstance(data, dict): + data = json.dumps(dict, indent = 4) + if not isinstance(data, str): + data = str(data) + _logger.debug('Copying to clipboard {0} for {1} seconds'.format(clipboard, seconds)) + termname = os.environ.get('TERM', 'linux') + if termname == 'linux': + # We don't have X, so we have no usable xclip. + _logger.warning('Disabling clipboard copying because we don\'t have X') + return(None) + display = os.environ.get('DISPLAY') + if not display: + # We don't have X, so we have no usable xclip. + _logger.warning('Disabling clipboard copying because we don\'t have X') + return(None) + exists = getProc(display) + current = None + if exists: + cmd = subprocess.run(['xclip', + '-out', + '-display', display, + '-selection', clipboard]) + current = cmd.stdout + exists.kill() + cmd = subprocess.run(['xclip', + '-display', display, + '-selection', clipboard], + input = data.encode('utf-8'), + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + if cmd.returncode != 0: + _logger.error('Could not write to clipboard') + _logger.debug('Could not write to clipboard "{0}" on display {1}.'.format(clipboard, display)) + for x in ('stdout', 'stderr'): + i = getattr(cmd, x) + if i: + i = i.decode('utf-8').strip() + if i != '': + _logger.debug('{0}: {1}'.format(x.upper(), i)) + raise RuntimeError('Could not write to clipboard') + if printme: + print('Copied to clipboard "{0}".'.format(clipboard)) + if seconds is not None: + if printme: + print('Active for {0} seconds...'.format(seconds)) + for s in range(seconds, 0, -1): + sys.stdout.write('{0} seconds remaining...'.format(s)) + sys.stdout.flush() + time.sleep(1) + sys.stdout.write('\r') + print('\033[2KClipboard cleared.') + else: + for s in range(seconds, 0, -1): + time.sleep(1) + if current: + cmd = subprocess.run(['xclip', + '-display', display, + '-selection', clipboard], + input = current, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + if cmd.returncode != 0: + _logger.warning('Could not restore clipboard') + _logger.debug('Could not restore clipboard "{0}" on display {1}.'.format(clipboard, display)) + for x in ('stdout', 'stderr'): + i = getattr(cmd, x) + if i: + i = i.decode('utf-8').strip() + if i != '': + _logger.debug('{0}: {1}'.format(x.upper(), i)) + # We absolutely should warn about this. + warnings.warn('Could not restore clipboard; secret remains in clipboard!') + else: + proc = getProc(display, clipboard) + if not proc: + _logger.warning('Could not restore clipboard') + _logger.debug('Could not restore clipboard "{0}" on display {1}.'.format(clipboard, display)) + # We absolutely should warn about this. + warnings.warn('Could not restore clipboard; secret remains in clipboard!') + else: + proc.kill() + return(None) diff --git a/vaultpass/constants.py b/vaultpass/constants.py index 8628e5e..7196ba9 100644 --- a/vaultpass/constants.py +++ b/vaultpass/constants.py @@ -18,6 +18,8 @@ SHOW_CLIP_LINENUM = 1 CLIP_TIMEOUT = 45 SELECTED_PASS_CHARS = ALL_PASS_CHARS SELECTED_PASS_NOSYMBOL_CHARS = ALPHANUM_PASS_CHARS +# xclip(1) +ALLOWED_CLIPBOARDS = ('primary', 'secondary', 'clipboard') CLIPBOARD = 'clipboard' GENERATED_LENGTH = 25 # I personally would prefer 32, but Pass compatibility... EDITOR = 'vi' # vi is on ...every? single distro and UNIX/UNIX-like, to my knowledge.