#!/usr/bin/python3 """A fallback-reboot client in Python.""" import os import sys import socket import getpass import binascii import subprocess sys.path.insert(0, os.path.expanduser('~/lib')) sys.path.insert(0, '/usr/local/lib') import bufsock as bufsock_mod # noqa: disable=E402 import python2x3 # noqa: disable=E402 def usage(): """Output a usage message.""" sys.stderr.write(sys.argv[0]+' hostname [portnumber]\n') sys.stderr.write('portnumber defaults to 3002\n') sys.exit(0) class Fake_it: """Pretend we have a native RIPEMD-160 hash using the openssl executable.""" def __init__(self): """Initialize.""" dirs = os.environ['PATH'].split(':') found = False for possible in dirs: try: file_ = open(possible+'/openssl', 'r') except (OSError, IOError): pass else: file_.close() found = True openssl_path = possible+'/openssl' break if found: sys.stderr.write('Good! Found the openssl binary.\n') else: sys.stderr.write('Please install openssl (the executable, not the library) and retry.\n') sys.exit(1) command = '{} rmd160'.format(openssl_path) process = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (self.stdin, self.stdouterr) = (bufsock_mod.bufsock(process.stdin), bufsock_mod.bufsock(process.stdout)) def update(self, text): """Add text.""" self.stdin.write(text) self.stdin.flush() def digest(self): """Return the digest.""" self.stdin.close() stripped_line = self.stdouterr.readline().strip() bad_string = b'(stdin)= ' if stripped_line.startswith(bad_string): stripped_line = stripped_line[len(bad_string):] return binascii.a2b_hex(stripped_line) def main(): # pylint: disable=too-many-statements,too-many-branches """Connect, authenticate and (implicitly) initiate.""" digest_obj = Fake_it() if sys.argv[1:]: try: hostname = sys.argv[1] except IndexError: usage() if sys.argv[2:]: try: port = int(sys.argv[2]) except ValueError: usage() else: port = 3002 else: sys.stderr.write('{}: Must specify a hostname\n'.format(sys.argv[0])) sys.exit(1) if sys.argv[3:]: usage() sock = socket.create_connection((hostname, port)) bufsock = bufsock_mod.bufsock(sock) version = bufsock.readto('\n') if b'RIPEMD-160 version' in version: sys.stderr.write('Connected to RIPEMD-160 (cryptographic, supported) version of fallback-reboot.\n') else: print('Garbled input 1, exiting...') sys.exit(1) hashline = bufsock.readto('\n') if b'Challenge is ' in hashline: fields = hashline.split() challenge_hex = fields[2] print('received challenge {}'.format(challenge_hex)) else: print('Garbled input 2, exiting...') sys.exit(1) enter = bufsock.readto('\n') if b'Enter' not in enter: print('Garbled input 3, exiting...') sys.exit(1) if os.isatty(0): # If we're on a tty, then prompt, and don't echo passwd_hex = python2x3.string_to_binary(getpass.getpass("Enter password: ")) else: # if we're not on a tty, then don't prompt, and let the # nontty sort out whether to echo :) But it probably won't, # and these passwords are too long for shoulder-surfing # anyway :) passwd_hex = sys.stdin.readline().strip() digest_obj.update(passwd_hex) digest_obj.update(challenge_hex) result = python2x3.string_to_binary(binascii.b2a_hex(digest_obj.digest())) bufsock.send(result + b'\n') bufsock.flush() response = bufsock.readto('\n') print(response) main()