def main (): '''Parses the options supplied on the command line and writes to a log. The first item is assumed to be the name of the log to write to in the path. Ther est of the arguments are joined as the message for the log entry. ''' parser = argparse.ArgumentParser(prog='Management Logger', description="Used to output information to a log. The first word is the name of the log, and the rest is the message.") parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.2') parser.add_argument('-p', '--path', help="A different directory to put the log file in.", default='') parser.add_argument('-l', '--level', type=int, default=20, help="Specify the logging level. Lower numbers mean that the logger will record more events (higher numbers are more restrictive).") parser.add_argument('name', help="The name of the log file.") parser.add_argument('message', nargs='+', help="The stuff to put into the log entry.") args = parser.parse_args() logger = loggers.file_logger(name=args.name, level=-5, path=args.path) message = ' '.join(args.message) logger.log(args.level, message)
def setup_logger(log, log_dest): try: from management_tools import loggers except ImportError as e: print "You need the 'Management Tools' module to be installed first." print "https://github.com/univ-of-utah-marriott-library-apple/management_tools" raise e if not log: logger = loggers.stream_logger(1) else: if log_dest: logger = loggers.file_logger(options["name"], path=log_dest) else: logger = loggers.file_logger(options["name"]) return logger
def __init__(self, name, log, log_dest=''): try: from management_tools import loggers except ImportError as e: print( "You need the 'Management Tools' module to be installed first.") print( "https://github.com/univ-of-utah-marriott-library-apple/" + "management_tools") raise e if not log: self.logger = loggers.stream_logger(1) else: if log_dest: self.logger = loggers.file_logger(name, path=log_dest) else: self.logger = loggers.file_logger(name)
def setup_logger(): '''Creates the logger to be used throughout. If it was not specified not to create a log, the log will be created in either the default location (as per management_tools) or a specified location. Otherwise, the logger will just be console output. ''' global logger if options['log']: # A logger! if not options['log_dest']: logger = loggers.file_logger(options['name']) else: logger = loggers.file_logger(options['name'], path=options['log_dest']) else: # A dummy logger. It won't record anything to file. logger = loggers.stream_logger(1)
def setup_logger (): '''Creates the logger to be used throughout. If it was not specified not to create a log, the log will be created in either the default location (as per helpful_tools) or a specified location. Otherwise, the logger will just be console output. ''' global logger if options['log']: # Write the logs to files. if not options['log_dest']: # If no destination is explicitly given, use the defaults. logger = loggers.file_logger(options['name']) else: # Otherwise, use the specified value. logger = loggers.file_logger(options['name'], path=options['log_dest']) else: # A dummy logger. It won't record anything to file. logger = loggers.stream_logger(1)
def logger (log, log_dest='', name=''): '''Creates and returns the logger to be used throughout. If it was not specified not to create a log, the log will be created in either the default location (as per management_tools) or a specified location. Otherwise, the logger will just be console output. ''' if not name: name = 'unnamed' if log: # A logger! if not log_dest: return loggers.file_logger(name) else: return loggers.file_logger(name, path=log_dest) else: # A dummy logger. It won't record anything to file. return loggers.stream_logger(1)
def logger(log, log_dest='', name=''): '''Creates and returns the logger to be used throughout. If it was not specified not to create a log, the log will be created in either the default location (as per management_tools) or a specified location. Otherwise, the logger will just be console output. ''' if not name: name = 'unnamed' if log: # A logger! if not log_dest: return loggers.file_logger(name) else: return loggers.file_logger(name, path=log_dest) else: # A dummy logger. It won't record anything to file. return loggers.stream_logger(1)
def setup_logger(): '''Creates the logger to be used throughout. If it was not specified not to create a log, the log will be created in either the default location (as per helpful_tools) or a specified location. Otherwise, the logger will just be console output. ''' global logger if options['log']: # Write the logs to files. if not options['log_dest']: # If no destination is explicitly given, use the defaults. logger = loggers.file_logger(options['name']) else: # Otherwise, use the specified value. logger = loggers.file_logger(options['name'], path=options['log_dest']) else: # A dummy logger. It won't record anything to file. logger = loggers.stream_logger(1)
def main(): '''Parses the options supplied on the command line and writes to a log. The first item is assumed to be the name of the log to write to in the path. Ther est of the arguments are joined as the message for the log entry. ''' parser = argparse.ArgumentParser( prog='Management Logger', description= "Used to output information to a log. The first word is the name of the log, and the rest is the message." ) parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.2') parser.add_argument('-p', '--path', help="A different directory to put the log file in.", default='') parser.add_argument( '-l', '--level', type=int, default=20, help= "Specify the logging level. Lower numbers mean that the logger will record more events (higher numbers are more restrictive)." ) parser.add_argument('name', help="The name of the log file.") parser.add_argument('message', nargs='+', help="The stuff to put into the log entry.") args = parser.parse_args() logger = loggers.file_logger(name=args.name, level=-5, path=args.path) message = ' '.join(args.message) logger.log(args.level, message)
def main(): """ Master Control Function. """ # # require root to run. if os.geteuid(): print "Must be root to run script." exit(2) # # parse option definitions parser = argparse.ArgumentParser( description='Manages the firmware password on Apple Computers.') # # required, mutually exclusive commands prime_group = parser.add_argument_group('Required management settings', 'Choosing one of these options is required \ to run FWPM. They tell FWPM how you \ want to manage the firmware password.') subprime = prime_group.add_mutually_exclusive_group(required=True) subprime.add_argument('-r', '--remove', action="store_true", default=False, help='Remove the firmware password') subprime.add_argument('-m', '--management', default=None, help='Set a custom nvram management string') subprime.add_argument('-#', '--hash', action="store_true", default=False, help='Set nvram string to hash of keyfile') subprime.add_argument('-n', '--nostring', action="store_true", default=False, help='Do not set an nvram management string') keyfile_group = parser.add_argument_group('Keyfile options', 'The keyfile is \ required to use FWPM. These options \ allow you to set the location and \ format of the keyfile.') keyfile_group.add_argument('-k', '--keyfile', help='Set the path to your keyfile', required=True) keyfile_group.add_argument('-o', '--obfuscated', action="store_true", default=False, help='Tell FWPM your keylist is an obfuscated plist.') slack_group = parser.add_argument_group('Slack integration', 'FWPM allows you to send informational \ and error messages to your Slack team. \ Additionally you can select different \ methods of identifiying clients.') slack_group.add_argument('-s', '--slack', action="store_true", default=False, help='Send important messages to Slack.') slack_group.add_argument('-i', '--identifier', default=None, choices=['IP', 'hostname', 'MAC', 'computername', 'serial'], required=False, help='Set slack identifier.') parser.add_argument('-b', '--reboot', action="store_true", default=False, help='Reboots the computer after the script completes successfully.') parser.add_argument('-t', '--testmode', action="store_true", default=False, help='Test mode. Verbose logging, will not delete keyfile.') parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.1.5') args = parser.parse_args() if args.testmode: print args # # Open log file logger = loggers.file_logger(name='FWPW_Manager2') logger.info("Running Firmware Password Manager 2") # # set up slack channel(s) slack_info_url = 'your FWPM slack info URL' slack_info_channel = '#your FWPM slack info channel' info_bot = IWS(slack_info_url, bot_name="FWPM informational message", channel=slack_info_channel) slack_error_url = 'your FWPM slack error URL' slack_error_channel = '#your FWPM slack error channel' error_bot = IWS(slack_error_url, bot_name="FWPM error message", channel=slack_error_channel) local_identifier = None if args.slack: full_ioreg = subprocess.check_output(['ioreg', '-l']) serial_number_raw = re.findall(r'\"IOPlatformSerialNumber\" = \"(.*)\"', full_ioreg) serial_number = serial_number_raw[0] if args.testmode: print "Serial number: %r" % serial_number if args.identifier == 'IP' or args.identifier == 'MAC' or args.identifier == 'hostname': processed_device_list = [] # Get ordered list of network devices base_network_list = subprocess.check_output(["/usr/sbin/networksetup", "-listnetworkserviceorder"]) network_device_list = re.findall(r'\) (.*)\n\(.*Device: (.*)\)', base_network_list) for device in network_device_list: device_name = device[0] port_name = device[1] try: device_info_raw = subprocess.check_output(["/sbin/ifconfig", port_name]) mac_address = re.findall(r'ether (.*) \n', device_info_raw) if args.testmode: print "%r" % mac_address ether_address = re.findall(r'inet (.*) netmask', device_info_raw) if args.testmode: print "%r" % ether_address processed_device_list.append([device_name, port_name, ether_address[0], mac_address[0]]) except: pass if len(processed_device_list) > 0: logger.info("1 or more active IP addresses. Choosing primary.") if args.testmode: print processed_device_list if args.identifier == 'IP': local_identifier = processed_device_list[0][2] + " (" + processed_device_list[0][0] + ":" + processed_device_list[0][1] + ")" if args.identifier == 'MAC': local_identifier = processed_device_list[0][3] + " (" + processed_device_list[0][0] + ":" + processed_device_list[0][1] + ")" if args.identifier == 'hostname': try: local_identifier = socket.getfqdn() except: logger.error("error discovering hostname info.") local_identifier = serial_number elif len(processed_device_list) == 0: logger.error("error discovering IP info.") local_identifier = serial_number elif args.identifier == 'computername': try: cname_identifier_raw = subprocess.check_output(['/usr/sbin/scutil', '--get', 'ComputerName']) local_identifier = cname_identifier_raw.split('\n')[0] logger.info("Computername: " + local_identifier) except: logger.info("error discovering computername.") local_identifier = serial_number elif args.identifier == 'serial': local_identifier = serial_number logger.info("Serial number: " + local_identifier) else: logger.info("bad or no identifier flag, defaulting to serial number.") local_identifier = serial_number if args.testmode: print "Local identifier: %r" % local_identifier # # keyfile checks path_to_keyfile_exists = os.path.exists(args.keyfile) if not path_to_keyfile_exists: logger.critical(args.keyfile + " does not exist. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry_sign:\n" + "Keyfile does not exist.") exit(2) # # generate hash from incoming keyfile logger.info("Checking incoming hash.") if args.management: fwpw_managed_string = args.management elif args.hash: incoming_hash = subprocess.check_output(["/usr/bin/openssl", "dgst", "-sha256", args.keyfile]) incoming_hash = incoming_hash.rstrip('\r\n') fwpw_managed_string = incoming_hash.split(" ")[1] # prepend '2:' to denote hash created with v2 of script, will force a password change from v1 fwpw_managed_string = '2:' + fwpw_managed_string else: fwpw_managed_string = None if args.testmode: print "Incoming hash: %s" % fwpw_managed_string # # compare incoming hash with current nvram hash existing_keyfile_hash = None logger.info("Checking existing hash.") if not args.remove: try: existing_keyfile_hash = subprocess.check_output(["/usr/sbin/nvram", "fwpw-hash"]) existing_keyfile_hash = existing_keyfile_hash.rstrip('\r\n') existing_keyfile_hash = existing_keyfile_hash.split("\t")[1] if args.testmode: print "Existing hash: %s" % existing_keyfile_hash except Exception as this_exception: logger.warning("nvram failed with " + str(this_exception) + " (fwpw probably not set).") if existing_keyfile_hash == fwpw_managed_string: if args.hash: logger.info("Hashes match. Exiting.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :white_check_mark:\n" + "Hashes match. No Change.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.reboot: logger.info("Reboot not required, canceling.") # return a value? exit(0) else: logger.info("Management strings match. Continuing.") # # firmwarepasswd tool checks new_fw_tool_path = '/usr/sbin/firmwarepasswd' new_fw_tool_exists = os.path.exists(new_fw_tool_path) if not new_fw_tool_exists: logger.critical("No Firmware password tool available. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "No Firmware password tool available.") exit(1) # # checking for existing fw password logger.info("Checking for existing firmware password") existing_fw_pw = subprocess.check_output([new_fw_tool_path, "-check"]) logger.info("New tools says " + existing_fw_pw) if 'No' in existing_fw_pw: if args.remove: logger.critical("Asked to delete, no password set. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Asked to Delete, no password set.") try: modify_nvram = subprocess.call(["/usr/sbin/nvram", "-d", "fwpw-hash"]) logger.info("nvram entry pruned.") except: logger.warning("nvram reported error attempting to remove hash. Hash may not have existed.") exit(1) else: logger.info("No firmware password set.") existing_password = False elif 'Yes' in existing_fw_pw: logger.info("Existing firmware password set.") existing_password = True else: logger.critical("Firmwarepasswd bad response at -check. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Firmwarepasswd bad response at -check.") exit(1) logger.info("Reading password file") if args.obfuscated: # # unobfuscate plist logger.info("Reading plist") passwords = [] try: input_plist = plistlib.readPlist(args.keyfile) except: logger.critical("Error reading plist. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Error reading plist.") exit(1) content_raw = input_plist["data"] content_raw = base64.b64decode(content_raw) content_raw = content_raw.split(",") content_raw = [x for x in content_raw if x] for item in content_raw: label, pword = item.split(':') pword = base64.b64decode(pword) try: commented = label.split('#')[1] commented = base64.b64decode(commented) is_commented = True except: is_commented = False if is_commented: output_string = "#" + commented + ":"+pword else: output_string = label + ":"+pword passwords.append(output_string) else: # # read keyfile logger.info("Reading plain text") try: with open(args.keyfile, "r") as keyfile: passwords = keyfile.read().splitlines() except: logger.critical("Error reading keyfile. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Error reading keyfile.") exit(1) logger.info("Closed password file") new_password = None other_password_list = [] # # parse data from keyfile and build list of passwords for entry in passwords: try: key, value = entry.split(":", 1) except Exception as this_exception: logger.critical("Malformed keyfile, key:value format required. " + this_exception + ". Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) if args.testmode: logger.info(key + ":" + value) if key.lower() == 'new': if new_password is not None: logger.critical("Malformed keyfile, multiple new keys. Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) else: new_password = value other_password_list.append(value) else: other_password_list.append(value) logger.info("Sanity checking password file contents") if new_password is None and not args.remove: logger.critical("Malformed keyfile, no \'new\' key. Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) exit_normal = False known_current_password = False # # if a password is set, attempt to discover it using keyfile if existing_password: logger.info("Verifying current FW password") new_fw_tool_cmd = [new_fw_tool_path, '-verify'] logger.info(' '.join(new_fw_tool_cmd)) for keyfile_index in reversed(xrange(len(other_password_list))): child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'Correct', 'Incorrect']) if result == 0: # # correct password, exit loop current_password = other_password_list[keyfile_index] known_current_password = True break else: # # wrong password, keep going continue # # We've discovered the currently set firmware password if known_current_password: # # Deleting firmware password if args.remove: logger.info("Deleting FW password") new_fw_tool_cmd = [new_fw_tool_path, '-delete'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'removed', 'incorrect']) if result == 0: # # password accepted, log result and exit logger.info("Finished. Password should be removed. Restart required. [" + (keyfile_index + 1) + "]") exit_normal = True else: logger.critical("Asked to delete, current password not accepted. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Asked to delete, current password not accepted.") exit(1) # # Current and new password are identical elif current_password == new_password: logger.info("Match, no change required. Exiting.") exit_normal = True # # Change current firmware password to new password else: logger.info("Updating FW password") new_fw_tool_cmd = [new_fw_tool_path, '-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter password:'******'Enter new password:'******'Re-enter new password:'******'-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter new password:'******'Re-enter new password:'******'t exist. logger.info("Assuming nvram entry doesn't exist.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :closed_lock_with_key:\n" + "FWPW updated.") if args.management or args.hash: try: modify_nvram = subprocess.call(["/usr/sbin/nvram", "fwpw-hash="+fwpw_managed_string]) logger.info("nvram modified.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :closed_lock_with_key:\n" + "FWPW updated.") except: logger.warning("nvram modification failed. nvram reported error.") exit(1) if args.reboot: logger.warning("Normal completion. Rebooting.") os.system('reboot') else: exit(0) # # Errors detected during run. else: logger.critical("An error occured. Failed to modify firmware password.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "An error occured. Failed to modify firmware password.") exit(1)
def main(): # # require root to run. if os.geteuid(): print "Must be root to script." exit(2) # # parse option definitions parser = argparse.ArgumentParser(description='Manages the firmware password on Apple Computers.') parser.add_argument('-r', '--remove', action="store_true", default=False, help='Remove firmware password') parser.add_argument('-k', '--keyfile', help='Set path to keyfile', required=True) parser.add_argument('-t', '--testmode', action="store_true", default=False, help='Test mode. Verbose logging, will not delete keyfile.') parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.0.0') parser.add_argument('-m', '--management', default=None, help='Set nvram management string') parser.add_argument('-#', '--hash', action="store_true", default=False, help='Set nvram string to hash of keyfile') parser.add_argument('-n', '--nostring', action="store_true", default=False, help='Do not set nvram management string') parser.add_argument('-s', '--slack', action="store_true", default=False, help='Send important messages to Slack.') args = parser.parse_args() if args.testmode: print args # # Open log file logger = loggers.file_logger(name='FWPW_Manager2') logger.info("Running Firmware Password Manager 2") # # test flags for conflicts if args.management is not None and (args.hash or args.nostring or args.remove): conflicting_flags = True elif args.hash and (args.nostring or args.remove): conflicting_flags = True elif args.nostring and args.remove: conflicting_flags = True else: conflicting_flags = False if conflicting_flags: logger.info("Remove, Hash, Management and \'No string\' flags are mutually exclusive. Select one. Exiting.") secure_delete_keyfile(logger, args) exit(2) else: logger.info("Flags okay.") # # check for no flags set. if args.management is None and not args.hash and not args.nostring and not args.remove: logger.critical("A specific flag is required: Remove, Hash, Management and \'No string\'. Select one. Exiting.") secure_delete_keyfile(logger, args) exit(2) # # get local Local identifier local_identifier = socket.gethostbyname(socket.gethostname()) if args.testmode: print "Local identifier: %r" % local_identifier # # if we get localhost or no return, use serial number instead. if (local_identifier == '127.0.0.1') or (local_identifier == ''): # logger.info ('Reporting $s as IP, hrm....' % local_identifier) if args.slack: full_ioreg = subprocess.check_output(['ioreg', '-l']) pattern = '(IOPlatformSerialNumber.*)' serial_data_raw = re.findall(pattern, full_ioreg) serial_data_raw = serial_data_raw[0] serial_number = serial_data_raw.split("= ")[1] serial_number = re.findall('(\w*)', serial_number)[1] if args.testmode: print "Serial number :%r" % serial_number logger.info("Serial number :%r" % serial_number) local_identifier = serial_number # # keyfile checks path_to_keyfile_exists = os.path.exists(args.keyfile) if not path_to_keyfile_exists: logger.critical("%s does not exist. Exiting." % args.keyfile) if args.slack: send_slack_message("Keyfile does not exist.", 'critical', local_identifier) exit(2) # # generate hash from incoming keyfile logger.info("Checking incoming hash.") if args.management: fwpw_managed_string = args.management elif args.hash: incoming_hash = subprocess.check_output(["/usr/bin/openssl", "dgst", "-sha256", args.keyfile]) incoming_hash = incoming_hash.rstrip('\r\n') fwpw_managed_string = incoming_hash.split(" ")[1] # prepend '2:' to denote hash created with v2 of script, will force a password change from v1 fwpw_managed_string = '2:' + fwpw_managed_string else: fwpw_managed_string = None if args.testmode: print "Incoming hash: %s" % fwpw_managed_string # # compare incoming hash with current nvram hash existing_keyfile_hash = None logger.info("Checking existing hash.") if not args.remove: try: existing_keyfile_hash = subprocess.check_output(["/usr/sbin/nvram", "fwpw-hash"]) existing_keyfile_hash = existing_keyfile_hash.rstrip('\r\n') existing_keyfile_hash = existing_keyfile_hash.split("\t")[1] if args.testmode: print "Existing hash: %s" % existing_keyfile_hash except Exception as this_Exception: logger.warning("nvram failed on %r (value probably doesn't exist)." % this_Exception) matching_passwords = False if existing_keyfile_hash == fwpw_managed_string: matching_passwords = True if args.slack: send_slack_message("Hashes match. No Change.", 'success', local_identifier) if args.hash: logger.info("Hashes match. Exiting.") elif args.management: logger.info("FWPW managed. Exiting.") secure_delete_keyfile(logger, args) # return a value? exit(0) # # firmwarepasswd tool checks new_fw_tool_path = '/usr/sbin/firmwarepasswd' new_fw_tool_exists = os.path.exists(new_fw_tool_path) if not new_fw_tool_exists: logger.critical("No Firmware password tool available. Exiting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("No Firmware password tool available.", 'critical', local_identifier) exit(1) # # checking for existing fw password logger.info("Checking for existing firmware password") existing_fw_pw = subprocess.check_output([new_fw_tool_path, "-check"]) logger.info("New tools says %r " % existing_fw_pw) if 'No' in existing_fw_pw: if args.remove: logger.critical("Asked to delete, no password set. Exiting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Asked to Delete, no password set.", 'critical', local_identifier) exit(1) else: logger.info("No firmware password set.") existing_password = False elif 'Yes' in existing_fw_pw: logger.info("Existing firmware password set.") existing_password = True else: logger.critical("Firmwarepasswd bad response at -check. Exiting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Firmwarepasswd bad response at -check.", 'critical', local_identifier) exit(1) # # read keyfile logger.info("Reading password file") with open(args.keyfile) as f: passwords = f.read().splitlines() f.close() logger.info("Closed password file") new_password = None other_password_list = [] # # parse data from keyfile and build list of passwords for entry in passwords: try: key, value = entry.split(":", 1) except Exception as this_Exception: logger.critical("Malformed keyfile, key:value format required. %r. Quitting." % this_Exception) secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Malformed keyfile.", 'critical', local_identifier) exit(1) if args.testmode: logger.info('%s:%s' % (key, value)) if key.lower() == 'new': if new_password is not None: logger.critical("Malformed keyfile, multiple new keys. Quitting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Malformed keyfile.", 'critical', local_identifier) exit(1) else: new_password = value other_password_list.append(value) else: other_password_list.append(value) logger.info("Sanity checking password file contents") if new_password is None and not args.remove: logger.critical("Malformed keyfile, no \'new\' key. Quitting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Malformed keyfile.", 'critical', local_identifier) exit(1) exit_Normal = False known_current_password = False # # if a password is set, attempt to discover it using keyfile if existing_password: logger.info("Verifying current FW password") new_fw_tool_cmd = [new_fw_tool_path, '-verify'] logger.info(' '.join(new_fw_tool_cmd)) for index in reversed(xrange(len(other_password_list))): child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'Correct', 'Incorrect']) if result == 0: # # correct password, exit loop current_password = other_password_list[index] known_current_password = True break else: # # wrong password, keep going continue # # We've discovered the currently set firmware password if known_current_password: # # Deleting firmware password if args.remove: logger.info("Deleting FW password") new_fw_tool_cmd = [new_fw_tool_path, '-delete'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'removed', 'incorrect']) if result == 0: # # password accepted, log result and exit logger.info("Finished. Password should be removed. Restart required. [%i]" % (index + 1)) exit_Normal = True else: logger.critical("Asked to delete, current password not accepted. Exiting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Asked to delete, current password not accepted.", 'critical', local_identifier) exit(1) # # Current and new password are identical elif current_password == new_password: logger.info("Match, no change required. Exiting.") exit_Normal = True # # Change current firmware password to new password else: logger.info("Updating FW password") new_fw_tool_cmd = [new_fw_tool_path, '-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter password:'******'critical', local_identifier) exit(1) child.sendline(current_password) result = child.expect('Enter new password:'******'critical', local_identifier) exit(1) child.sendline(new_password) result = child.expect('Re-enter new password:'******'critical', local_identifier) exit(1) child.sendline(new_password) child.expect(pexpect.EOF) child.close() logger.info("Updated FW Password.") exit_Normal = True # # Unable to match current password with contents of keyfile else: logger.critical("Current FW password not in keyfile. Quitting.") secure_delete_keyfile(logger, args) if args.slack: send_slack_message("Current FW password not in keyfile.", 'critical', local_identifier) exit(1) # # No current firmware password, setting it else: new_fw_tool_cmd = [new_fw_tool_path, '-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter new password:'******'critical', local_identifier) exit(1) child.sendline(new_password) result = child.expect('Re-enter new password:'******'critical', local_identifier) exit(1) child.sendline(new_password) child.expect(pexpect.EOF) child.close() logger.info("Added FW Password.") exit_Normal = True # # Delete keyfile securely. secure_delete_keyfile(logger, args) # # closing reports logger.info("Closing out.") # # No errors detected during run. if exit_Normal: if args.slack: if args.remove: send_slack_message("FWPW removed.", 'remove', local_identifier) else: send_slack_message("FWPW updated.", 'success', local_identifier) if not args.remove and not args.nostring and not matching_passwords: try: modify_nvram = subprocess.call(["/usr/sbin/nvram", "fwpw-hash="+fwpw_managed_string]) logger.info("nvram modified.") except: logger.warning("nvram reported error.") elif args.remove or args.nostring: try: modify_nvram = subprocess.call(["/usr/sbin/nvram", "-d", "fwpw-hash"]) logger.info("nvram pruned.") except: logger.warning("nvram reported error.") exit(0) # # Errors detected during run. else: logger.critical("An error occured. Failed to modify firmware password.") if args.slack: send_slack_message("An error occured. Failed to modify firmware password.", 'critical', local_identifier) exit(1)
def main(): """ Master Control Function. """ # # require root to run. if os.geteuid(): print "Must be root to run script." exit(2) # # parse option definitions parser = argparse.ArgumentParser( description='Manages the firmware password on Apple Computers.') # # required, mutually exclusive commands prime_group = parser.add_argument_group( 'Required management settings', 'Choosing one of these options is required \ to run FWPM. They tell FWPM how you \ want to manage the firmware password.' ) subprime = prime_group.add_mutually_exclusive_group(required=True) subprime.add_argument('-r', '--remove', action="store_true", default=False, help='Remove the firmware password') subprime.add_argument('-m', '--management', default=None, help='Set a custom nvram management string') subprime.add_argument('-#', '--hash', action="store_true", default=False, help='Set nvram string to hash of keyfile') subprime.add_argument('-n', '--nostring', action="store_true", default=False, help='Do not set an nvram management string') keyfile_group = parser.add_argument_group( 'Keyfile options', 'The keyfile is \ required to use FWPM. These options \ allow you to set the location and \ format of the keyfile.') keyfile_group.add_argument('-k', '--keyfile', help='Set the path to your keyfile', required=True) keyfile_group.add_argument( '-o', '--obfuscated', action="store_true", default=False, help='Tell FWPM your keylist is an obfuscated plist.') slack_group = parser.add_argument_group( 'Slack integration', 'FWPM allows you to send informational \ and error messages to your Slack team. \ Additionally you can select different \ methods of identifiying clients.') slack_group.add_argument('-s', '--slack', action="store_true", default=False, help='Send important messages to Slack.') slack_group.add_argument( '-i', '--identifier', default=None, choices=['IP', 'hostname', 'MAC', 'computername', 'serial'], required=False, help='Set slack identifier.') parser.add_argument( '-b', '--reboot', action="store_true", default=False, help='Reboots the computer after the script completes successfully.') parser.add_argument( '-t', '--testmode', action="store_true", default=False, help='Test mode. Verbose logging, will not delete keyfile.') parser.add_argument('-v', '--version', action='version', version='%(prog)s 2.1.4') args = parser.parse_args() if args.testmode: print args # # Open log file logger = loggers.file_logger(name='FWPW_Manager2') logger.info("Running Firmware Password Manager 2") # # set up slack channel(s) slack_info_url = 'your FWPM slack info URL' slack_info_channel = '#your FWPM slack info channel' info_bot = IWS(slack_info_url, bot_name="FWPM informational message", channel=slack_info_channel) slack_error_url = 'your FWPM slack error URL' slack_error_channel = '#your FWPM slack error channel' error_bot = IWS(slack_error_url, bot_name="FWPM error message", channel=slack_error_channel) local_identifier = None if args.slack: full_ioreg = subprocess.check_output(['ioreg', '-l']) serial_number_raw = re.findall( r'\"IOPlatformSerialNumber\" = \"(.*)\"', full_ioreg) serial_number = serial_number_raw[0] if args.testmode: print "Serial number: %r" % serial_number if args.identifier == 'IP' or args.identifier == 'MAC' or args.identifier == 'hostname': processed_device_list = [] # Get ordered list of network devices base_network_list = subprocess.check_output( ["/usr/sbin/networksetup", "-listnetworkserviceorder"]) network_device_list = re.findall(r'\) (.*)\n\(.*Device: (.*)\)', base_network_list) for device in network_device_list: device_name = device[0] port_name = device[1] try: device_info_raw = subprocess.check_output( ["/sbin/ifconfig", port_name]) mac_address = re.findall(r'ether (.*) \n', device_info_raw) if args.testmode: print "%r" % mac_address ether_address = re.findall(r'inet (.*) netmask', device_info_raw) if args.testmode: print "%r" % ether_address processed_device_list.append([ device_name, port_name, ether_address[0], mac_address[0] ]) except: pass if len(processed_device_list) > 0: logger.info("1 or more active IP addresses. Choosing primary.") if args.testmode: print processed_device_list if args.identifier == 'IP': local_identifier = processed_device_list[0][ 2] + " (" + processed_device_list[0][ 0] + ":" + processed_device_list[0][1] + ")" if args.identifier == 'MAC': local_identifier = processed_device_list[0][ 3] + " (" + processed_device_list[0][ 0] + ":" + processed_device_list[0][1] + ")" if args.identifier == 'hostname': try: local_identifier = socket.getfqdn() except: logger.error("error discovering hostname info.") local_identifier = serial_number elif len(processed_device_list) == 0: logger.error("error discovering IP info.") local_identifier = serial_number elif args.identifier == 'computername': try: cname_identifier_raw = subprocess.check_output( ['/usr/sbin/scutil', '--get', 'ComputerName']) local_identifier = cname_identifier_raw.split('\n')[0] logger.info("Computername: " + local_identifier) except: logger.info("error discovering computername.") local_identifier = serial_number elif args.identifier == 'serial': local_identifier = serial_number logger.info("Serial number: " + local_identifier) else: logger.info( "bad or no identifier flag, defaulting to serial number.") local_identifier = serial_number if args.testmode: print "Local identifier: %r" % local_identifier # # keyfile checks path_to_keyfile_exists = os.path.exists(args.keyfile) if not path_to_keyfile_exists: logger.critical(args.keyfile + " does not exist. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry_sign:\n" + "Keyfile does not exist.") exit(2) # # generate hash from incoming keyfile logger.info("Checking incoming hash.") if args.management: fwpw_managed_string = args.management elif args.hash: incoming_hash = subprocess.check_output( ["/usr/bin/openssl", "dgst", "-sha256", args.keyfile]) incoming_hash = incoming_hash.rstrip('\r\n') fwpw_managed_string = incoming_hash.split(" ")[1] # prepend '2:' to denote hash created with v2 of script, will force a password change from v1 fwpw_managed_string = '2:' + fwpw_managed_string else: fwpw_managed_string = None if args.testmode: print "Incoming hash: %s" % fwpw_managed_string # # compare incoming hash with current nvram hash existing_keyfile_hash = None logger.info("Checking existing hash.") if not args.remove: try: existing_keyfile_hash = subprocess.check_output( ["/usr/sbin/nvram", "fwpw-hash"]) existing_keyfile_hash = existing_keyfile_hash.rstrip('\r\n') existing_keyfile_hash = existing_keyfile_hash.split("\t")[1] if args.testmode: print "Existing hash: %s" % existing_keyfile_hash except Exception as this_exception: logger.warning("nvram failed on " + this_exception + " (value probably doesn't exist).") if existing_keyfile_hash == fwpw_managed_string: if args.hash: logger.info("Hashes match. Exiting.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :white_check_mark:\n" + "Hashes match. No Change.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.reboot: logger.info("Reboot not required, canceling.") # return a value? exit(0) else: logger.info("Management strings match. Continuing.") # # firmwarepasswd tool checks new_fw_tool_path = '/usr/sbin/firmwarepasswd' new_fw_tool_exists = os.path.exists(new_fw_tool_path) if not new_fw_tool_exists: logger.critical("No Firmware password tool available. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "No Firmware password tool available.") exit(1) # # checking for existing fw password logger.info("Checking for existing firmware password") existing_fw_pw = subprocess.check_output([new_fw_tool_path, "-check"]) logger.info("New tools says " + existing_fw_pw) if 'No' in existing_fw_pw: if args.remove: logger.critical("Asked to delete, no password set. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Asked to Delete, no password set.") try: modify_nvram = subprocess.call( ["/usr/sbin/nvram", "-d", "fwpw-hash"]) logger.info("nvram entry pruned.") except: logger.warning( "nvram reported error attempting to remove hash. Hash may not have existed." ) exit(1) else: logger.info("No firmware password set.") existing_password = False elif 'Yes' in existing_fw_pw: logger.info("Existing firmware password set.") existing_password = True else: logger.critical("Firmwarepasswd bad response at -check. Exiting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Firmwarepasswd bad response at -check.") exit(1) logger.info("Reading password file") if args.obfuscated: # # unobfuscate plist logger.info("Reading plist") passwords = [] try: input_plist = plistlib.readPlist(args.keyfile) except: logger.critical("Error reading plist. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Error reading plist.") exit(1) content_raw = input_plist["data"] content_raw = base64.b64decode(content_raw) content_raw = content_raw.split(",") content_raw = [x for x in content_raw if x] for item in content_raw: label, pword = item.split(':') pword = base64.b64decode(pword) try: commented = label.split('#')[1] commented = base64.b64decode(commented) is_commented = True except: is_commented = False if is_commented: output_string = "#" + commented + ":" + pword else: output_string = label + ":" + pword passwords.append(output_string) else: # # read keyfile logger.info("Reading plain text") try: with open(args.keyfile, "r") as keyfile: passwords = keyfile.read().splitlines() except: logger.critical("Error reading keyfile. Exiting.") if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Error reading keyfile.") exit(1) logger.info("Closed password file") new_password = None other_password_list = [] # # parse data from keyfile and build list of passwords for entry in passwords: try: key, value = entry.split(":", 1) except Exception as this_exception: logger.critical("Malformed keyfile, key:value format required. " + this_exception + ". Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) if args.testmode: logger.info(key + ":" + value) if key.lower() == 'new': if new_password is not None: logger.critical( "Malformed keyfile, multiple new keys. Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) else: new_password = value other_password_list.append(value) else: other_password_list.append(value) logger.info("Sanity checking password file contents") if new_password is None and not args.remove: logger.critical("Malformed keyfile, no \'new\' key. Quitting.") secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message("_*" + local_identifier + "*_ :no_entry:\n" + "Malformed keyfile.") exit(1) exit_normal = False known_current_password = False # # if a password is set, attempt to discover it using keyfile if existing_password: logger.info("Verifying current FW password") new_fw_tool_cmd = [new_fw_tool_path, '-verify'] logger.info(' '.join(new_fw_tool_cmd)) for keyfile_index in reversed(xrange(len(other_password_list))): child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'Correct', 'Incorrect']) if result == 0: # # correct password, exit loop current_password = other_password_list[keyfile_index] known_current_password = True break else: # # wrong password, keep going continue # # We've discovered the currently set firmware password if known_current_password: # # Deleting firmware password if args.remove: logger.info("Deleting FW password") new_fw_tool_cmd = [new_fw_tool_path, '-delete'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) child.expect('Enter password:'******'removed', 'incorrect']) if result == 0: # # password accepted, log result and exit logger.info( "Finished. Password should be removed. Restart required. [" + (keyfile_index + 1) + "]") exit_normal = True else: logger.critical( "Asked to delete, current password not accepted. Exiting." ) secure_delete_keyfile(logger, args, error_bot, local_identifier) if args.slack: error_bot.send_message( "_*" + local_identifier + "*_ :no_entry:\n" + "Asked to delete, current password not accepted.") exit(1) # # Current and new password are identical elif current_password == new_password: logger.info("Match, no change required. Exiting.") exit_normal = True # # Change current firmware password to new password else: logger.info("Updating FW password") new_fw_tool_cmd = [new_fw_tool_path, '-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter password:'******'Enter new password:'******'Re-enter new password:'******'-setpasswd'] logger.info(' '.join(new_fw_tool_cmd)) child = pexpect.spawn(' '.join(new_fw_tool_cmd)) result = child.expect('Enter new password:'******'Re-enter new password:'******'t exist. logger.info("Assuming nvram entry doesn't exist.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :closed_lock_with_key:\n" + "FWPW updated.") if args.management or args.hash: try: modify_nvram = subprocess.call( ["/usr/sbin/nvram", "fwpw-hash=" + fwpw_managed_string]) logger.info("nvram modified.") if args.slack: info_bot.send_message("_*" + local_identifier + "*_ :closed_lock_with_key:\n" + "FWPW updated.") except: logger.warning( "nvram modification failed. nvram reported error.") exit(1) if args.reboot: logger.warning("Normal completion. Rebooting.") os.system('reboot') else: exit(0) # # Errors detected during run. else: logger.critical( "An error occured. Failed to modify firmware password.") if args.slack: error_bot.send_message( "_*" + local_identifier + "*_ :no_entry:\n" + "An error occured. Failed to modify firmware password.") exit(1)
def setup_logger(): '''Creates the logger to be used throughout.''' global logger logger = loggers.file_logger(options['name'])