Files
q-tools/shell/qaskpass
2025-03-10 22:21:30 +02:00

181 lines
5.7 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import hashlib
import os
import random
import re
import sys
import termios
import time
import tty
__version__ = "20250310.a"
class getch:
def get(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class bc:
m = "\033[35m"
b = "\033[34m"
g = "\033[32m"
y = "\033[33m"
r = "\033[31m"
c = "\033[36m"
k = "\033[30m\033[40m"
M = "\033[95m"
B = "\033[94m"
G = "\033[92m"
Y = "\033[93m"
R = "\033[91m"
C = "\033[96m"
bold = "\033[1m"
z = "\033[0m"
def disable(self):
for x in "rgbcmyzkRGBCMY":
setattr(self, x, "")
setattr(self, "bold", "")
class QAsk:
def __init__(self):
self.opts = self.get_opts()
self.c = bc()
if self.opts.no_color:
self.c.disable()
self.user_input = ""
self.eq = "▁▂▃▄▅▆▇█▇▆▅▄▃▂"
self.limits = ((self.c.r, 2), (self.c.y, 4), (self.c.c, 8), (self.c.g, 14))
self.ch = getch()
def get_opts(self):
parser = argparse.ArgumentParser(
description="Colorful password dialog",
epilog="Dialog printed to stderr, user input echoed to stdout, i.e. input=$( qaskpass )",
)
parser.add_argument("--title", "-t", action="store", default=None, help="Title for dialog")
parser.add_argument(
"-w", action="store", type=int, default=5, help="Width of display area. 0 to disable display."
)
parser.add_argument("--no-color", action="store_true", default=False, help="Disable colors")
parser.add_argument("--measure", action="store_true", default=False, help="Also measure password goodness")
parser.add_argument(
"--expect-sha256",
action="store",
default=None,
help="Show green dots when string matches the sha256 sum. Note: strip newlines before calculating!. Exitcode = 10 if checksum does not match.",
)
parser.add_argument("--version", action="version", version="%(prog)s {version}".format(version=__version__))
args = parser.parse_args()
return args
def pwscore(self):
if not self.opts.measure:
return random.randint(0, 18)
score = len(set(self.user_input)) / 5
simple = False
if re.search("[A-Z]", self.user_input):
score *= 2
simple = True
if re.search("[a-z]", self.user_input):
score *= 1.5
simple = True
if re.search("[0-9]", self.user_input):
score *= 1.5
simple = True
if not simple:
score *= 2
return score
def animchar(self, pos):
colorpos = len(self.user_input) % self.opts.w
i = len(self.user_input) - pos
if i < 0 and self.opts.measure:
return self.c.r + ""[random.randint(0, 2)]
if pos == colorpos:
clr = self.c.C
else:
clr = self.c.r
score = self.pwscore()
for limit in self.limits:
if score > limit[1] - 1 + 2 * random.random():
clr = limit[0]
else:
break
if self.opts.measure:
rheight = random.randint(-1, 1)
else:
rheight = random.randint(-4, 4)
if len(self.user_input) == 0:
rheight = 0
return clr + self.eq[int(rheight + (i / self.opts.w)) % 14]
def pquit(self, s="", e=0):
print(self.c.z, file=sys.stderr)
print(s, end="", file=sys.stdout if e == 0 else sys.stderr)
sys.exit(e)
def ask(self):
# 3= ctrl-c, 13=enter
# 127 = backspace
dot = ""
display = f"{dot*4} {dot*4}"
if self.opts.title:
print(f"{self.c.Y}{self.opts.title}{self.c.z}", file=sys.stderr)
enter_exitcode = 0
while True:
try:
if self.opts.w > 0:
dot_color = self.c.m
dot = ""
if self.opts.expect_sha256:
enter_exitcode = 10
if hashlib.sha256(self.user_input.encode("utf-8")).hexdigest() == self.opts.expect_sha256:
dot_color = self.c.G
dot = ""
enter_exitcode = 0
display = dot_color + f"{dot*4} "
for i in range(self.opts.w):
display += self.animchar(i)
display += dot_color + f" {dot*4}" + self.c.z
print("\r" + self.c.z + display + "\r" + self.c.k, file=sys.stderr, end="")
sys.stderr.flush()
key = self.ch.get()
if ord(key) == 3: # ctrl-c
self.pquit(e=1)
if ord(key) == 13: # enter
self.pquit(self.user_input, e=enter_exitcode)
if ord(key) == 27: # esc (also starts control characters
key = self.ch.get()
if ord(key) == 27:
self.pquit(e=1)
continue
if ord(key) == 127: # backspace
self.user_input = self.user_input[0:-1]
else:
self.user_input += key
# ~ print(f'-{key}-',file=sys.stderr)
# ~ print(f'-{ord(key)}-',file=sys.stderr)
except Exception as e:
self.pquit(s=str(e), e=1)
if __name__ == "__main__":
q = QAsk()
q.ask()