From 5f263d88822324d98350fc660b3ca0b077bd1501 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Mon, 12 Nov 2012 09:02:54 -0500 Subject: flush input buffers before accepting stdin --- src/lib/Bcfg2/Client/Frame.py | 5 +++ src/lib/Bcfg2/Client/Tools/Action.py | 7 ++++ src/lib/Bcfg2/Client/Tools/__init__.py | 6 ++++ src/lib/Bcfg2/Server/Admin/Init.py | 59 ++++++++++++++++++++-------------- src/lib/Bcfg2/Server/Admin/Pull.py | 8 ++++- src/sbin/bcfg2-crypt | 19 +++++++---- 6 files changed, 72 insertions(+), 32 deletions(-) diff --git a/src/lib/Bcfg2/Client/Frame.py b/src/lib/Bcfg2/Client/Frame.py index 64460ea66..53180ab68 100644 --- a/src/lib/Bcfg2/Client/Frame.py +++ b/src/lib/Bcfg2/Client/Frame.py @@ -1,8 +1,10 @@ """ Frame is the Client Framework that verifies and installs entries, and generates statistics. """ +import os import sys import time +import select import fnmatch import logging import Bcfg2.Client.Tools @@ -160,6 +162,9 @@ class Frame(object): iprompt = entry.get('qtext') else: iprompt = prompt % (entry.tag, entry.get('name')) + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) try: ans = input(iprompt.encode(sys.stdout.encoding, 'replace')) if ans in ['y', 'Y']: diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py index 7726da94c..b1a897c81 100644 --- a/src/lib/Bcfg2/Client/Tools/Action.py +++ b/src/lib/Bcfg2/Client/Tools/Action.py @@ -1,5 +1,8 @@ """Action driver""" +import os +import sys +import select import Bcfg2.Client.Tools from Bcfg2.Client.Frame import matches_white_list, passes_black_list from Bcfg2.Compat import input # pylint: disable=W0622 @@ -33,6 +36,10 @@ class Action(Bcfg2.Client.Tools.Tool): if self.setup['interactive']: prompt = ('Run Action %s, %s: (y/N): ' % (entry.get('name'), entry.get('command'))) + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], + 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) ans = input(prompt) if ans not in ['y', 'Y']: return False diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py index 4022692be..927b25ba8 100644 --- a/src/lib/Bcfg2/Client/Tools/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/__init__.py @@ -1,7 +1,9 @@ """This contains all Bcfg2 Tool modules""" import os +import sys import stat +import select from subprocess import Popen, PIPE import Bcfg2.Client.XML from Bcfg2.Compat import input, walk_packages # pylint: disable=W0622 @@ -373,6 +375,10 @@ class SvcTool(Tool): if self.setup['interactive']: prompt = ('Restart service %s?: (y/N): ' % entry.get('name')) + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], + 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) ans = input(prompt) if ans not in ['y', 'Y']: continue diff --git a/src/lib/Bcfg2/Server/Admin/Init.py b/src/lib/Bcfg2/Server/Admin/Init.py index 869dc1aca..14065980d 100644 --- a/src/lib/Bcfg2/Server/Admin/Init.py +++ b/src/lib/Bcfg2/Server/Admin/Init.py @@ -1,11 +1,13 @@ """ Interactively initialize a new repository. """ -import getpass + import os +import sys +import stat +import select import random import socket -import stat import string -import sys +import getpass import subprocess import Bcfg2.Server.Admin @@ -85,6 +87,14 @@ OS_LIST = [('Red Hat/Fedora/RHEL/RHAS/Centos', 'redhat'), ('Arch', 'arch')] +def safe_input(prompt): + """ input() that flushes the input buffer before accepting input """ + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) + return input(prompt) + + def gen_password(length): """Generates a random alphanumeric password with length characters.""" chars = string.letters + string.digits @@ -116,8 +126,8 @@ def create_conf(confpath, confdata): """ create the config file """ # Don't overwrite existing bcfg2.conf file if os.path.exists(confpath): - result = input("\nWarning: %s already exists. " - "Overwrite? [y/N]: " % confpath) + result = safe_input("\nWarning: %s already exists. " + "Overwrite? [y/N]: " % confpath) if result not in ['Y', 'y']: print("Leaving %s unchanged" % confpath) return @@ -186,8 +196,8 @@ class Init(Bcfg2.Server.Admin.Mode): def _prompt_hostname(self): """Ask for the server hostname.""" - data = input("What is the server's hostname [%s]: " % - socket.getfqdn()) + data = safe_input("What is the server's hostname [%s]: " % + socket.getfqdn()) if data != '': self.data['shostname'] = data else: @@ -195,21 +205,21 @@ class Init(Bcfg2.Server.Admin.Mode): def _prompt_config(self): """Ask for the configuration file path.""" - newconfig = input("Store Bcfg2 configuration in [%s]: " % - self.configfile) + newconfig = safe_input("Store Bcfg2 configuration in [%s]: " % + self.configfile) if newconfig != '': self.data['configfile'] = os.path.abspath(newconfig) def _prompt_repopath(self): """Ask for the repository path.""" while True: - newrepo = input("Location of Bcfg2 repository [%s]: " % + newrepo = safe_input("Location of Bcfg2 repository [%s]: " % self.data['repopath']) if newrepo != '': self.data['repopath'] = os.path.abspath(newrepo) if os.path.isdir(self.data['repopath']): - response = input("Directory %s exists. Overwrite? [y/N]:" % - self.data['repopath']) + response = safe_input("Directory %s exists. Overwrite? [y/N]:" + % self.data['repopath']) if response.lower().strip() == 'y': break else: @@ -225,8 +235,8 @@ class Init(Bcfg2.Server.Admin.Mode): def _prompt_server(self): """Ask for the server name.""" - newserver = input("Input the server location [%s]: " % - self.data['server_uri']) + newserver = safe_input("Input the server location [%s]: " % + self.data['server_uri']) if newserver != '': self.data['server_uri'] = newserver @@ -238,7 +248,7 @@ class Init(Bcfg2.Server.Admin.Mode): prompt += ': ' while True: try: - osidx = int(input(prompt)) + osidx = int(safe_input(prompt)) self.data['os_sel'] = OS_LIST[osidx - 1][1] break except ValueError: @@ -248,27 +258,28 @@ class Init(Bcfg2.Server.Admin.Mode): """Ask for the key details (country, state, and location).""" print("The following questions affect SSL certificate generation.") print("If no data is provided, the default values are used.") - newcountry = input("Country name (2 letter code) for certificate: ") + newcountry = safe_input("Country name (2 letter code) for " + "certificate: ") if newcountry != '': if len(newcountry) == 2: self.data['country'] = newcountry else: while len(newcountry) != 2: - newcountry = input("2 letter country code (eg. US): ") + newcountry = safe_input("2 letter country code (eg. US): ") if len(newcountry) == 2: self.data['country'] = newcountry break else: self.data['country'] = 'US' - newstate = input("State or Province Name (full name) for " - "certificate: ") + newstate = safe_input("State or Province Name (full name) for " + "certificate: ") if newstate != '': self.data['state'] = newstate else: self.data['state'] = 'Illinois' - newlocation = input("Locality Name (eg, city) for certificate: ") + newlocation = safe_input("Locality Name (eg, city) for certificate: ") if newlocation != '': self.data['location'] = newlocation else: @@ -277,12 +288,12 @@ class Init(Bcfg2.Server.Admin.Mode): def _prompt_keypath(self): """ Ask for the key pair location. Try to use sensible defaults depending on the OS """ - keypath = input("Path where Bcfg2 server private key will be created " - "[%s]: " % self.data['keypath']) + keypath = safe_input("Path where Bcfg2 server private key will be " + "created [%s]: " % self.data['keypath']) if keypath: self.data['keypath'] = keypath - certpath = input("Path where Bcfg2 server cert will be created" - "[%s]: " % self.data['certpath']) + certpath = safe_input("Path where Bcfg2 server cert will be created" + "[%s]: " % self.data['certpath']) if certpath: self.data['certpath'] = certpath diff --git a/src/lib/Bcfg2/Server/Admin/Pull.py b/src/lib/Bcfg2/Server/Admin/Pull.py index e41652205..130e85b67 100644 --- a/src/lib/Bcfg2/Server/Admin/Pull.py +++ b/src/lib/Bcfg2/Server/Admin/Pull.py @@ -1,8 +1,10 @@ """ Retrieves entries from clients and integrates the information into the repository """ -import getopt +import os import sys +import getopt +import select import Bcfg2.Server.Admin from Bcfg2.Compat import input # pylint: disable=W0622 @@ -99,6 +101,10 @@ class Pull(Bcfg2.Server.Admin.MetadataCore): else: print(" => host entry: %s" % (choice.hostname)) + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], + 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) ans = input("Use this entry? [yN]: ") in ['y', 'Y'] if ans: return choice diff --git a/src/sbin/bcfg2-crypt b/src/sbin/bcfg2-crypt index 0693b430c..961a8dc58 100755 --- a/src/sbin/bcfg2-crypt +++ b/src/sbin/bcfg2-crypt @@ -4,6 +4,7 @@ import os import sys import copy +import select import logging import lxml.etree import Bcfg2.Logger @@ -31,7 +32,7 @@ class Encryptor(object): self.passphrase = None self.pname = None self.logger = logging.getLogger(self.__class__.__name__) - + def get_encrypted_filename(self, plaintext_filename): """ get the name of the file encrypted data should be written to """ return plaintext_filename @@ -67,7 +68,7 @@ class Encryptor(object): if self.setup['passphrase']: self.pname = self.setup['passphrase'] - + if self.pname: if self.setup.cfp.has_option("encryption", self.pname): self.passphrase = self.setup.cfp.get("encryption", @@ -182,7 +183,7 @@ class Encryptor(object): self.logger.error("Error getting encrypted data from %s: %s" % (fname, err)) return False - + try: return self.unchunk(plaintext, crypted) except EncryptionChunkingError: @@ -317,10 +318,14 @@ class PropertiesEncryptor(Encryptor): print(lxml.etree.tostring( elt, xml_declaration=False).decode("UTF-8").strip()) + # flush input buffer + while len(select.select([sys.stdin.fileno()], [], [], + 0.0)[0]) > 0: + os.read(sys.stdin.fileno(), 4096) ans = input("Encrypt this element? [y/N] ") if not ans.lower().startswith("y"): elements.remove(element) - + # this is not a good use of a generator, but we need to # generate the full list of elements in order to ensure that # some exist before we know what to return @@ -386,11 +391,11 @@ def main(): # pylint: disable=R0912,R0915 elif setup['interactive']: logger.error("Cannot decrypt interactively") setup['interactive'] = False - + if setup['cfg']: if setup['properties']: logger.error("You cannot specify both --cfg and --properties") - raise SystemExit(1) + raise SystemExit(1) if setup['xpath']: logger.error("Specifying --xpath with --cfg is nonsensical, " "ignoring --xpath") @@ -411,7 +416,7 @@ def main(): # pylint: disable=R0912,R0915 if not os.path.exists(fname): logger.error("%s does not exist, skipping" % fname) continue - + # figure out if we need to encrypt this as a Properties file # or as a Cfg file props = False -- cgit v1.2.3-1-g7c22