def make_identity_pem_string(given_key, given_cert, given_ca_cert):
    '''Given objects for client key, client cert, and CA cert,
        extract their contents in PEM format and combine them
        into a single text string.  Return that string or None.'''
    common.logging_info("Generating client identity PEM.")
    try:
        key_pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, given_key)
    except crypto.Error:
        common.logging_error("Could not get PEM contents of private key.")
        return None
    try:
        cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, given_cert)
    except crypto.Error:
        common.logging_error(
            "Could not get PEM contents of client certificate.")
        return None
    try:
        ca_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, given_ca_cert)
    except crypto.Error:
        common.logging_error("Could not get PEM contents of CA certificate.")
        return None
    combined_pem = '%(key_pem)s\n%(cert_pem)s\n%(ca_pem)s' % {
        'key_pem': key_pem,
        'cert_pem': cert_pem,
        'ca_pem': ca_pem
    }
    return combined_pem
def read_cert_cn(given_cert):
    '''Given any cert object and passphrase, return its subject CN or None.'''
    common.logging_info("Getting CN for given certificate.")
    try:
        return str(given_cert.get_subject().CN)
    except crypto.Error:
        common.logging_error("Could not get the certificate's common name.")
        return None
示例#3
0
def join_group_manifest(given_computer_manifest_name,
                        given_group_manifest_name):
    '''Modifes a computer manifest's included_manifests key to include the name of a group manifest,
        effectively making the computer a member of the group and its parent groups.
        Both manifests must be present and valid plists.  Returns true if successful, false otherwise.'''
    # Filesystem paths for manifests:
    given_computer_manifest_name = given_computer_manifest_name.upper(
    )  # if not already
    computer_manifest_path = os.path.join(munki_repo.COMPUTER_MANIFESTS_PATH,
                                          given_computer_manifest_name)
    group_manifest_path = os.path.join(munki_repo.GROUP_MANIFESTS_PATH,
                                       given_group_manifest_name)

    # Catch missing manifests:
    if not os.path.exists(computer_manifest_path):
        common.logging_error("Computer manifest not found at %s." %
                             computer_manifest_path)
        return False
    if not os.path.exists(group_manifest_path):
        common.logging_error("Group manifest not found at %s." %
                             group_manifest_path)
        return False

    # Load manifests:
    try:
        computer_manifest_dict = plistlib.readPlist(computer_manifest_path)
    except xml.parsers.expat.ExpatError:
        common.logging_error("Computer manifest %s is invalid." %
                             given_computer_manifest_name)
        return False
    try:
        group_manifest_dict = plistlib.readPlist(group_manifest_path)
    except xml.parsers.expat.ExpatError:
        common.logging_error("Group manifest %s is invalid." %
                             given_group_manifest_name)
        return False

    # Modify computer manifest and save it:
    common.logging_info(
        "Adding %(computer)s to %(group)s." % {
            'computer': given_computer_manifest_name,
            'group': given_group_manifest_name
        })
    # Make sure these stay empty; data should come from included (group) manifest only:
    computer_manifest_dict['managed_installs'] = []
    computer_manifest_dict['managed_uninstalls'] = []
    computer_manifest_dict['catalogs'] = munki_repo.CATALOG_ARRAY
    # Add to group:
    computer_manifest_dict['included_manifests'] = []
    computer_manifest_dict['included_manifests'].append(
        'groups/%s' % given_group_manifest_name)
    try:
        plistlib.writePlist(computer_manifest_dict, computer_manifest_path)
        return True
    except TypeError:
        common.logging_error("Failed to write manifest for %s." %
                             given_computer_manifest_name)
        return False
def generate_private_key():
    '''Generates a private key (RSA).
        Returns the key or None if something went wrong.'''
    common.logging_info("Generating a private key.")
    try:
        key = crypto.PKey()
        return key.generate_key(crypto.TYPE_RSA,
                                config_client_pki.PRIVATE_KEY_BITS)
    except crypto.Error:
        return None
def sign_with_ca(given_csr, given_ca_cert):
    '''Signs a CSR with the specified CA's private key.
        Returns the signed certificate or None.'''
    common.logging_info("Signing certificate signing request.")
    # Check CSR:
    if not given_csr:
        common.logging_error("Invalid CSR.")
        return None
    # Check CA certificate:
    if not given_ca_cert:
        common.logging_error("Cannot sign CSR - CA certificate error.")
        return None
    # Check for missing CA key file:
    if not os.path.exists(config_ca.CA_PRIVATE_KEY_FILE_PATH):
        common.logging_error("No CA private key file found at %s." %
                             config_ca.CA_PRIVATE_KEY_FILE_PATH)
        return None
    # Read and load CA private key:
    try:
        file_object = open(config_ca.CA_PRIVATE_KEY_FILE_PATH, 'r')
        file_contents = file_object.read()
        file_object.close()
    except IOError:
        return None
    try:
        ca_key = crypto.load_privatekey(crypto.FILETYPE_PEM, file_contents,
                                        config_ca.CA_PRIVATE_KEY_PASSPHRASE)
    except crypto.Error:
        common.logging_error("Could not read CA private key from %s." %
                             config_ca.CA_PRIVATE_KEY_FILE_PATH)
        return None
    # Set issuer:
    try:
        given_csr.set_issuer(given_ca_cert.get_subject())
    except crypto.Error:
        common.logging_error("Could not set issuer for CSR to CA subject.")
        ca_key = None  # safety!
        return None
    # Sign the CSR with the CA's private key:
    try:
        given_csr.sign(ca_key, config_client_pki.CSR_SIGNING_HASH_ALGORITHM)
    except crypto.Error:
        common.logging_error("Could not sign CSR!")
        ca_key = None  # safety!
        return None
    # Wipe CA private key and these variables before exiting this method:
    ca_key = None
    file_contents = None
    file_object = None
    # Return signed cert:
    return given_csr
示例#6
0
def write_metadata_to_manifest(given_computer_manifest_name,
                               given_metadata_key, given_metadata_value):
    '''Modifes the given computer manifest's _metadata dict, adding (overwriting) the given key and value.
        Given manifest must be present and a valid plist.  Returns true if successful, false otherwise.'''
    # Filesystem paths for the manifest:
    given_computer_manifest_name = given_computer_manifest_name.upper(
    )  # if not already
    computer_manifest_path = os.path.join(munki_repo.COMPUTER_MANIFESTS_PATH,
                                          given_computer_manifest_name)

    # Catch missing manifest:
    if not os.path.exists(computer_manifest_path):
        common.logging_error("Computer manifest not found at %s." %
                             computer_manifest_path)
        return False

    # Load manifest:
    try:
        computer_manifest_dict = plistlib.readPlist(computer_manifest_path)
    except xml.parsers.expat.ExpatError:
        common.logging_error("Computer manifest %s is invalid." %
                             given_computer_manifest_name)
        return False

    # Load manifest metadata or start with blank:
    try:
        manifest_metadata_dict = computer_manifest_dict['_metadata']
    except KeyError:
        manifest_metadata_dict = {}

    # Modify metadata dict:
    common.logging_info("Adding %(key)s to %(manifest)s." % {
        'key': given_metadata_key,
        'manifest': given_computer_manifest_name
    })
    manifest_metadata_dict[given_metadata_key] = given_metadata_value
    computer_manifest_dict['_metadata'] = manifest_metadata_dict

    # Save manifest:
    try:
        plistlib.writePlist(computer_manifest_dict, computer_manifest_path)
        return True
    except TypeError:
        common.logging_error("Failed to write manifest for %s." %
                             given_computer_manifest_name)
        return False
def make_p12_with_ca_cert(given_ca_cert, given_passphrase):
    '''Given CA cert object and passphrase, create a PKCS container
        and return it as data.  Return None if something went wrong.'''
    common.logging_info("Generating PKCS12 store for given CA certificate.")
    p = crypto.PKCS12()
    try:
        p.set_ca_certificates([given_ca_cert])
    except crypto.Error:
        common.logging_error(
            "Could create a p12 object with given CA certificate.")
        return None
    try:
        p12_ca_data = p.export(passphrase=given_passphrase)
    except crypto.Error:
        common.logging_error("Could not export data from p12 object.")
        return None
    return p12_ca_data
def generate_csr(given_key, given_cn):
    '''Generates a certificate signing request.
        Returns that request or None.'''
    common.logging_info("Generating a certificate signing request for %s." %
                        given_cn)
    # Create X509 object:  Technically this is not a CSR (X509Req) object
    # because we're signing it locally.  Or maybe that's not the reason.
    # Regardless, X509Req objects don't become X509 objects, and only X509
    # objects can added to a PKCS12 container.  We need to do that later.
    # Hence we're using an X509 object.
    try:
        csr = crypto.X509()
        # Set CSR attributes:
        csr.get_subject().C = config_client_pki.CSR_COUNTRY
        csr.get_subject().ST = config_client_pki.CSR_STATE
        csr.get_subject().L = config_client_pki.CSR_LOCALE
        csr.get_subject().O = config_client_pki.CSR_ORG
        csr.get_subject().OU = config_client_pki.CSR_OU
        csr.get_subject().CN = given_cn.upper()
        # Apple Keychain CDSA requires certificate serial attribute:
        # Keychain Access will show an error message, but the profiles
        # tool and Profiles prefs pane will just fail silently.
        csr.set_serial_number(0)
    except crypto.Error:
        return None
    # Set dates:
    try:
        csr.gmtime_adj_notBefore(0)
        csr.gmtime_adj_notAfter(config_client_pki.CERT_LIFE_YEARS * 365 * 24 *
                                3600)
    except crypto.Error:
        return None
    # Associate with keypair:
    try:
        csr.set_pubkey(given_key)
    except crypto.Error:
        return None
    # Return:
    return csr
def read_ca_cert():
    '''Loads CA certificate from the filesystem and returns it as an object.
        Returns None if something went wrong.'''
    common.logging_info("Loading CA certificate.")
    # Check for missing CA certificate:
    if not os.path.exists(config_ca.CA_CERT_FILE_PATH):
        common.logging_error("No CA certificate file found at %s." %
                             config_ca.CA_CERT_FILE_PATH)
        return None
    # Read and load CA certificate:
    try:
        file_object = open(config_ca.CA_CERT_FILE_PATH, 'r')
        file_contents = file_object.read()
        file_object.close()
    except IOError:
        return None
    # Return CA cert:
    try:
        return crypto.load_certificate(crypto.FILETYPE_PEM, file_contents)
    except crypto.Error:
        common.logging_error("Could not read CA certificate from %s." %
                             config_ca.CA_CERT_FILE_PATH)
        return None
示例#10
0
def make_computer_manifest(given_serial):
    '''Checks for the presence and validity of an existing manifest for this client in the repository.
        Creates a new manifest if an existing one is not found or is invalid.'''
    # Filesystem path for this computer's manifest:
    computer_manifest_name = given_serial.upper()  # if not already
    computer_manifest_path = os.path.join(munki_repo.COMPUTER_MANIFESTS_PATH,
                                          computer_manifest_name)
    # Control variable: should a new manifest be created?
    # Assume yes, unless an existing manifest is found and is valid.
    should_create_new_client_manifest = True

    # Catch missing computer manifests directory:
    if not os.path.exists(munki_repo.COMPUTER_MANIFESTS_PATH):
        common.logging_error("Computers manifests directory not found at %s." %
                             munki_repo.COMPUTER_MANIFESTS_PATH)
        raise

    # Check existing manifest for this client:
    if os.path.exists(computer_manifest_path):
        common.logging_info(
            "Manifest for %s already in repository. Checking it." %
            computer_manifest_name)
        try:
            computer_manifest_dict = plistlib.readPlist(computer_manifest_path)
            # Manifest already exists; do not overwrite if it's a valid dict!
            if computer_manifest_dict:
                should_create_new_client_manifest = False
                common.logging_info("Manifest for %s should be left alone." %
                                    computer_manifest_name)
        except xml.parsers.expat.ExpatError:
            common.logging_error("Manifest for %s is invalid. Will recreate." %
                                 computer_manifest_name)

    # Build a new client manifest if required:
    if should_create_new_client_manifest:
        common.logging_info("Creating new manifest for %s." %
                            computer_manifest_name)
        computer_manifest_dict = {}
        computer_manifest_dict['managed_installs'] = []
        computer_manifest_dict['managed_uninstalls'] = []
        computer_manifest_dict['catalogs'] = munki_repo.CATALOG_ARRAY
        computer_manifest_dict['included_manifests'] = [
            'groups/%s' % munki_repo.DEFAULT_GROUP
        ]
        try:
            plistlib.writePlist(computer_manifest_dict, computer_manifest_path)
        except TypeError:
            common.logging_error("Failed to write manifest for %s." %
                                 computer_manifest_name)
示例#11
0
def list_group_manifests():
    '''Scans the munki repository for manifests that we designate as computer groups.
        Returns an array of dictionaries, each dict representing a group.'''
    # Catch missing manifests path:
    if not os.path.exists(munki_repo.GROUP_MANIFESTS_PATH):
        common.logging_error("Missing group manifests directory.")
        return False, []  # empty list
    path_glob = '%s/*' % str(munki_repo.GROUP_MANIFESTS_PATH)
    common.logging_info("Listing group manifests: %s" % path_glob)
    try:
        search_results_path_array = glob.glob(path_glob)
    except:
        common.logging_error(
            "Glob error while listing group manifests directory.")
        return False, []  # empty list
    # Go through the returned group manifests, validating them and reading metadata:
    groups_array = []
    for manifest_path in search_results_path_array:
        try:
            manifest_dict = plistlib.readPlist(manifest_path)
            have_valid_manifest = True
        except xml.parsers.expat.ExpatError:
            # Skip manifests that cannot be parsed as plists:
            common.logging_error("Skipping invalid group manifest: %s" %
                                 manifest_path)
            have_valid_manifest = False
            pass
        if have_valid_manifest:
            # Build dictionary representing this group:
            group_details_dict = {}
            # At least the name is present:
            group_details_dict['name'] = os.path.basename(manifest_path)
            # Assign defaults unless we can override them with metadata:
            group_details_dict['display_name'] = group_details_dict['name']
            group_details_dict['description'] = group_details_dict['name']
            group_details_dict[
                'computer_name_prefix'] = munki_repo.DEFAULT_COMPUTER_NAME_PREFIX
            # Check for metadata in the manifest:
            try:
                manifest_metadata_dict = manifest_dict['_metadata']
                have_manifest_metadata = True
            except KeyError:
                have_manifest_metadata = False
                pass
            # Attempt to read various metadata keys:
            if have_manifest_metadata:
                try:
                    group_details_dict[
                        'display_name'] = manifest_metadata_dict[
                            'display_name']
                except KeyError:
                    pass
                try:
                    group_details_dict['description'] = manifest_metadata_dict[
                        'description']
                except KeyError:
                    pass
                try:
                    group_details_dict[
                        'computer_name_prefix'] = manifest_metadata_dict[
                            'computer_name_prefix']
                except KeyError:
                    pass
            # Append group details dict to ths groups array:
            groups_array.append(group_details_dict)
    # Catch empty groups_array:
    if len(groups_array) == 0:
        return False, []  # empty list
    # Return group list:
    return True, groups_array
def make_tar_file(given_serial, given_pki_contents,
                  given_mobileconfig_contents):
    '''Creates a tarfile with the configuration profile and client identity.
        Returns base64-encoded data representing the tarfile contents or
        None if something went wrong.'''
    common.logging_info("Generating tar file response for %s." % given_serial)

    tar_file_name = "mes-%s.tar" % given_serial.upper()
    tar_file_path = os.path.join(config_app.TEMP_DIR, tar_file_name)

    # Remove existing archive:
    if os.path.exists(tar_file_path):
        os.remove(tar_file_path)
        common.logging_info("Removed existing temp file: %s." % tar_file_path)

    # Create file objects:
    member_files_array = []
    # Config profile:
    member_file_meta_dict = {}
    member_file_meta_dict['file_name'] = "client-enrollment.mobileconfig"
    member_file_meta_dict['file_size'] = len(given_mobileconfig_contents)
    member_file_meta_dict['file_object'] = StringIO(
        given_mobileconfig_contents)
    member_files_array.append(member_file_meta_dict)

    # Identity:
    member_file_meta_dict = {}
    member_file_meta_dict['file_name'] = "client-identity.pem"
    member_file_meta_dict['file_size'] = len(given_pki_contents)
    member_file_meta_dict['file_object'] = StringIO(given_pki_contents)
    member_files_array.append(member_file_meta_dict)

    # Create new archive:
    try:
        tar_file = tarfile.open(tar_file_path, 'w')
    except tarfile.TarError:
        common.logging_error("Failed to create archive file at %s." %
                             tar_file_path)
        return None

    # Add files to tar:
    for m in member_files_array:
        try:
            file_info = tarfile.TarInfo(m['file_name'])
            file_info.size = m['file_size']
            if file_info.size == 0:
                common.logging_error("%s appears to be empty." %
                                     m['file_name'])
            tar_file.addfile(file_info, m['file_object'])
        except:
            common.logging_error("Failed to add %s to archive." %
                                 m['file_name'])

    tar_file.close()

    # Read archive:
    try:
        tar_file = open(tar_file_path, 'r')
        attachment_contents = base64.b64encode(tar_file.read())
        tar_file.close()
    except tarfile.TarError:
        common.logging_error("Failed to read newly created archive at %s." %
                             tar_file_path)
        return None

    # Remove archive:
    if os.path.exists(tar_file_path):
        os.remove(tar_file_path)
        common.logging_info("Removed archive: %s." % tar_file_path)

    # Return:
    return attachment_contents
def make_mobileconfig(given_serial, given_ca_cert_p12_data, given_ca_cert_cn):
    '''Generates a configuration profile with a P12 payload containing the given CA P12 data
        and a MCX payload with the ManagedInstalls.plist for the munki_client.
        Returns the XML contents of the config file or None.'''
    common.logging_info("Generating configuration profile for %s." %
                        given_serial)

    # New profile main dict:
    mobileconfig_top_dict = {}
    mobileconfig_top_dict['PayloadVersion'] = int(1)
    mobileconfig_top_dict['PayloadType'] = "Configuration"
    mobileconfig_top_dict['PayloadScope'] = "System"
    mobileconfig_top_dict[
        'PayloadIdentifier'] = "%s.config.profile.munki-enrollment" % config_site.ORGANIZATION_ID_PREFIX
    mobileconfig_top_dict['PayloadUUID'] = str(uuid.uuid4())
    mobileconfig_top_dict[
        'PayloadOrganization'] = config_munki_client.CONFIG_PROFILE_ORG
    mobileconfig_top_dict[
        'PayloadDisplayName'] = config_munki_client.CONFIG_PROFILE_DISPLAY_NAME
    mobileconfig_top_dict[
        'PayloadDescription'] = config_munki_client.CONFIG_PROFILE_DESCRIPTION
    mobileconfig_top_dict['PayloadContent'] = [
    ]  # This is an array of other dicts.

    # P12 CA Payload:
    payload_p12_dict = {}
    payload_p12_dict['PayloadVersion'] = int(1)
    payload_p12_dict['PayloadType'] = "com.apple.security.pkcs12"
    payload_p12_dict['PayloadUUID'] = str(uuid.uuid4())
    payload_p12_dict[
        'PayloadIdentifier'] = "%s.config.payload.com.apple.security.pkcs12.ca-cert" % config_site.ORGANIZATION_ID_PREFIX
    payload_p12_dict[
        'PayloadDisplayName'] = config_munki_client.P12_CA_DISPLAY_NAME
    payload_p12_dict['PayloadDescription'] = "CA: %s" % given_ca_cert_cn
    payload_p12_dict['PayloadContent'] = plistlib.Data(given_ca_cert_p12_data)
    payload_p12_dict['Password'] = given_serial
    mobileconfig_top_dict['PayloadContent'].append(payload_p12_dict)
    p12_server_ca = None

    # Munki client prefs start with template file read by our configuration:
    mcx_preference_settings = config_munki_client.MUNKI_CLIENT_PREFS_DICT
    # Test for mandatory keys:
    for key in config_munki_client.MUNKI_CLIENT_MANDATORY_KEYS:
        try:
            value = mcx_preference_settings[key]
        except KeyError:
            return None
    # Add key overrides:
    mcx_preference_settings['UseClientCertificate'] = True
    mcx_preference_settings['UseClientCertificateCNAsClientIdentifier'] = False
    mcx_preference_settings[
        'ClientIdentifier'] = 'computers/%s' % given_serial.upper()
    # Random password for the Munki keychain:
    mcx_preference_settings['KeychainPassword'] = str(uuid.uuid4()).replace(
        '-', '')
    # Set the keychain name so that it's clear it can be removed as long as the
    # client's identity PEM file is present.
    mcx_preference_settings['KeychainName'] = "munki-temp.keychain"

    # Munki config profile payload:
    payload_mcx_munki_client_dict = {}
    payload_mcx_munki_client_dict['PayloadVersion'] = int(1)
    payload_mcx_munki_client_dict[
        'PayloadType'] = "com.apple.ManagedClient.preferences"
    payload_mcx_munki_client_dict['PayloadUUID'] = str(uuid.uuid4())
    payload_mcx_munki_client_dict[
        'PayloadIdentifier'] = "%s.config.payload.com.apple.ManagedClient.preferences.munki" % config_site.ORGANIZATION_ID_PREFIX
    payload_mcx_munki_client_dict[
        'PayloadDisplayName'] = config_munki_client.MUNKI_MCX_PAYLOAD_DISPLAY_NAME
    payload_mcx_munki_client_dict[
        'PayloadDescription'] = "Server: %s" % mcx_preference_settings[
            'SoftwareRepoURL'].replace('https://', '').replace('/', '')
    payload_mcx_munki_client_dict['PayloadContent'] = {}
    payload_mcx_munki_client_dict['PayloadContent'][
        config_munki_client.MUNKI_MCX_DEFAULTS_DOMAIN] = {}
    payload_mcx_munki_client_dict['PayloadContent'][
        config_munki_client.MUNKI_MCX_DEFAULTS_DOMAIN]['Forced'] = [{}]
    payload_mcx_munki_client_dict['PayloadContent'][
        config_munki_client.MUNKI_MCX_DEFAULTS_DOMAIN]['Forced'][0][
            'mcx_preference_settings'] = mcx_preference_settings
    mobileconfig_top_dict['PayloadContent'].append(
        payload_mcx_munki_client_dict)

    # Return XML of the mobileconfig:
    try:
        return plistlib.writePlistToString(mobileconfig_top_dict)
    except xml.parsers.expat.ExpatError:
        return None
    except TypeError:
        return None
def process_request():
    '''Method called when a request is sent to this web app.'''
    # Defaults:
    # Default response type is plist unless set otherwise:
    response_is_tar_file = False
    response_attachment = None
    response_dict = {}
    response = None
    # Get HTTP POST variables:
    # All interactions must have a command POST variables.
    try:
        command = request.form['command']
    except NameError:
        common.logging_error("No command sent in POST.")
        return None
    except KeyError:
        common.logging_error("No command sent in POST.")
        return None
    # Toss unrecognized commands:
    if command not in config_server_app.TRANSACTIONS:
        return None

    # Handle authentication if required:
    if command not in config_server_app.ANON_TRANSACTIONS:
        # Grab additional POST variables:
        try:
            message = request.form['message']
        except KeyError:
            common.logging_error("Message not sent in POST.")
            return None
        try:
            signature = request.form['signature']
        except KeyError:
            common.logging_error("Signature not sent in POST.")
            return None
        try:
            cert_pem_str = request.form['certificate']
        except KeyError:
            common.logging_error("Certificate not sent in POST.")
            return None
        # Load certificate:
        client_cert = security.pem_to_cert(cert_pem_str)
        if not client_cert:
            common.logging_error("Certificate data is invalid!")
        # Determine computer's manifest name by reading the CN of its certificate.
        computer_manifest_name = security.read_cert_cn(client_cert).upper()
        if not computer_manifest_name:
            return None
        # Authentication: Verify signed message using certificate's public key.
        if not security.verify_signed_message(message, signature, client_cert):
            common.logging_error(
                "Authentication error: failed to verify the signed message.")
            return None

    # Handle command:
    if command == 'request-enrollment':
        client_serial = request.form['message']
        common.logging_info("Processing enrollment request for %s..." %
                            client_serial)
        response_attachment = enrollment.do_enrollment(client_serial)
        if not response_attachment:
            common.logging_error("Invalid tar file returned by do_enrollment!")
            return None
        response_is_tar_file = True
    elif command == 'transaction-a':
        common.logging_info("Processing transaction A...")
        response_dict[
            'computer_manifest'] = manifests.get_computer_manifest_details(
                computer_manifest_name)
        ignored, response_dict[
            'group_manifests_array'] = manifests.list_group_manifests()
    elif command == 'transaction-b':
        common.logging_info("Processing transaction B...")
        response_dict['joined_group'] = False
        response_dict['recorded_name'] = False
        try:
            message_dict = plistlib.readPlistFromString(message)
        except xml.parsers.expat.ExpatError:
            message_dict = {}
        try:
            group_manifest_name = message_dict['group_manifest_name']
            response_dict['joined_group'] = manifests.join_group_manifest(
                computer_manifest_name, group_manifest_name)
        except KeyError:
            pass
        try:
            desired_computer_name = message_dict['desired_computer_name']
            response_dict[
                'recorded_name'] = manifests.write_metadata_to_manifest(
                    computer_manifest_name, 'computer_name',
                    desired_computer_name)
        except KeyError:
            pass
        common.logging_info("Completed transaction B.")
    elif command == 'join-manifest':  # DEPRECATED!
        common.logging_info("Processing request to join a group manifest...")
        group_manifest_name = message
        response_dict['result'] = manifests.join_group_manifest(
            computer_manifest_name, group_manifest_name)
        common.logging_info("Completed request to join a group manifest.")
    elif command == 'set-name':  # DEPRECATED!
        common.logging_info("Processing request to store computer name...")
        desired_computer_name = message
        response_dict['result'] = manifests.write_metadata_to_manifest(
            computer_manifest_name, 'computer_name', desired_computer_name)
        common.logging_info("Completed request to store computer name.")

    # Process responses:
    if response_is_tar_file and response_attachment:
        common.logging_info("Processing response: tar file.")
        response = make_response(response_attachment)
        response.headers['Content-Disposition'] = "attachment"
    elif response_dict:
        common.logging_info("Processing response: plist.")
        try:
            response_plist_str = plistlib.writePlistToString(response_dict)
        except xml.parsers.expat.ExpatError:
            response_plist_str = ''
        except TypeError:
            response_plist_str = ''
        if response_plist_str:
            response = make_response(response_plist_str)
    # Return response:
    if not response:
        return None
    return response