diff --git a/shell/qaskpass b/shell/qaskpass index 2dd4443..d8c4553 100755 --- a/shell/qaskpass +++ b/shell/qaskpass @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import argparse -import curses import hashlib import os import random @@ -26,31 +25,6 @@ class getch: return ch -def get_opts(): - 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( - "--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 termsize(): - rows, columns = os.popen("stty size", "r").read().split() - return (int(rows), int(columns)) - - class bc: m = "\033[35m" b = "\033[34m" @@ -74,97 +48,133 @@ class bc: setattr(self, "bold", "") -def pwscore(s): - score = len(s) / 8 - simple = False - if re.search("[A-Z]", s): - score *= 2 - simple = True - if re.search("[a-z]", s): - score *= 1.5 - simple = True - if re.search("[0-9]", s): - score *= 1.5 - simple = True - if not simple: - score *= 2 - return score +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 )", + ) -def animchar(i, pos, colorpos, width, c, user_input): - if i < 0: - return " " - if pos == colorpos: - clr = c.C - else: - clr = c.r - score = pwscore(user_input) - for limit in ((c.r, 2), (c.y, 4), (c.c, 8), (c.g, 14)): - if score > limit[1] - 1 + 2 * random.random(): - clr = limit[0] - else: - break + 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 - return clr + "▁▂▃▄▅▆▇█▇▆▅▄▃▂"[int(random.randint(-1, 1) + (i / width)) % 14] + 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(s="", e=0, c=bc()): - print(c.z, file=sys.stderr) - print(s, end="", file=sys.stdout if e == 0 else sys.stderr) - sys.exit(e) + 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__": - - opts = get_opts() - # 3= ctrl-c, 13=enter - # 127 = backspace - ch = getch() - user_input = "" - - dot = "•" - display = f"{dot*4} {dot*4}" - c = bc() - if opts.no_color: - c.disable() - if opts.title: - print(f"{c.Y}{opts.title}{c.z}", file=sys.stderr) - enter_exitcode = 0 - while True: - try: - if opts.w > 0: - dot_color = c.m - dot = "•" - if opts.expect_sha256: - enter_exitcode = 10 - if hashlib.sha256(user_input.encode("utf-8")).hexdigest() == opts.expect_sha256: - dot_color = c.G - dot = "♥" - enter_exitcode = 0 - colorpos = len(user_input) % opts.w - display = dot_color + f"{dot*4} " - for i in range(opts.w): - display += animchar(len(user_input) - i, i, colorpos, opts.w, c, user_input) - display += dot_color + f" {dot*4}" + c.z - print("\r" + c.z + display + "\r" + c.k, file=sys.stderr, end="") - sys.stderr.flush() - key = ch.get() - - if ord(key) == 3: # ctrl-c - pquit(e=1) - if ord(key) == 13: # enter - pquit(user_input, e=enter_exitcode) - if ord(key) == 27: # esc (also starts control characters - key = ch.get() - if ord(key) == 27: - pquit(e=1) - continue - if ord(key) == 127: # backspace - user_input = user_input[0:-1] - else: - user_input += key - # ~ print(f'-{key}-',file=sys.stderr) - # ~ print(f'-{ord(key)}-',file=sys.stderr) - - except Exception as e: - pquit(s=str(e), e=1) + q = QAsk() + q.ask()