BluecatAddressManager

Use the BlueCat Address Manager integration to enrich IP addresses and manage response policies.

Network Security · Bluecat Address Manager

Details

IDBluecatAddressManager
ProviderBlueCat Networks
CategoryNetwork Security
From Version5.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This integration supports Bluecat Address Manager version 9.5.

Use the BlueCat integration to enrich IP addresses and manage response policies.

Configure BluecatAddressManager on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for BluecatAddressManager.
  3. Click Add instance to create and configure a new integration instance.
    • Name: a textual name for the integration instance.
    • Incident type
    • Server URL (e.g., https://192.168.0.1)
    • Username
    • Trust any certificate (insecure)
    • Use system proxy
    • Configuration Name
  4. Click Test to validate the URLs, token, and connection.

Commands

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.

  1. Enrich an IP address: bluecat-am-query-ip
  2. Get all response policies: bluecat-am-get-response-policies
  3. Search response policies by domain: bluecat-am-search-response-policies-by-domain
  4. Add a domain to a response policy: bluecat-am-response-policy-add-domain
  5. Remove a domain from a response policy: bluecat-am-response-policy-remove-domain
  6. Get an IPv4 block containing an IPv4 address: bluecat-am-get-range-by-ip

1. Enrich an IP address


Enriches an IP address with data about IP networks and blocks to which it belongs, linked IPs, MAC addresses, and so on.

Base Command

bluecat-am-query-ip

Input
Argument Name Description Required
ip The IP to get data for. Required

 

Context Output
Path Type Description
BlueCat.AddressManager.IP.ID Number The address manager ID of the IP address.
BlueCat.AddressManager.IP.Name String Name of the IP address.
BlueCat.AddressManager.IP.MacAddress String Corresponding MAC address.
BlueCat.AddressManager.IP.Parents.ID String ID of the parent IP address.
BlueCat.AddressManager.IP.Parents.Type String Type of the parent IP address.
BlueCat.AddressManager.IP.Parents.Name String Name of the parent IP address.
BlueCat.AddressManager.IP.Parents.CIDR String Classless Inter-Domain Routing.
BlueCat.AddressManager.IP.Type String Type of IP address.
IP.Address String Address of IP.
BlueCat.AddressManager.IP.Parents.Prefix String Prefix of the IP address.

 

Command Example
bluecat-am-query-ip ip="10.0.0.10"
Context Example
{
    "IP": {
        "Address": "10.0.0.10"
    },
    "BlueCat.AddressManager.IP": {
        "Name": null,
        "LocationInherited": "true",
        "State": "STATIC",
        "Parents": [
            {
                "InheritPingBeforeAssign": "true",
                "InheritDNSRestrictions": "true",
                "LocationInherited": "true",
                "PingBeforeAssign": "disable",
                "AllowDuplicateHost": "disable",
                "ID": 100913,
                "InheritAllowDuplicateHost": "true",
                "InheritDefaultView": "true",
                "CIDR": "10.0.0.0/24",
                "DefaultView": "100907",
                "Type": "IP4Network",
                "Gateway": "10.0.0.1",
                "InheritDefaultDomains": "true",
                "Name": "East Office"
            },
            {
                "InheritPingBeforeAssign": "true",
                "InheritDNSRestrictions": "true",
                "LocationInherited": "true",
                "PingBeforeAssign": "disable",
                "AllowDuplicateHost": "disable",
                "InheritAllowDuplicateHost": "true",
                "InheritDefaultView": "true",
                "CIDR": "10.0.0.0/21",
                "DefaultView": "100907",
                "Type": "IP4Block",
                "ID": 100912,
                "InheritDefaultDomains": "true",
                "Name": "Tampa"
            },
            {
                "InheritPingBeforeAssign": "true",
                "InheritDNSRestrictions": "true",
                "LocationInherited": "true",
                "PingBeforeAssign": "disable",
                "AllowDuplicateHost": "disable",
                "InheritAllowDuplicateHost": "true",
                "InheritDefaultView": "true",
                "CIDR": "10.0.0.0/19",
                "DefaultView": "100907",
                "Type": "IP4Block",
                "ID": 100911,
                "InheritDefaultDomains": "true",
                "Name": "Florida"
            },
            {
                "InheritPingBeforeAssign": "true",
                "InheritDNSRestrictions": "true",
                "LocationInherited": "true",
                "PingBeforeAssign": "disable",
                "AllowDuplicateHost": "disable",
                "InheritAllowDuplicateHost": "true",
                "InheritDefaultView": "true",
                "CIDR": "10.0.0.0/16",
                "DefaultView": "100907",
                "Type": "IP4Block",
                "ID": 100910,
                "InheritDefaultDomains": "true",
                "Name": "North America"
            },
            {
                "InheritPingBeforeAssign": "false",
                "InheritDNSRestrictions": "true",
                "LocationInherited": "true",
                "PingBeforeAssign": "disable",
                "AllowDuplicateHost": "disable",
                "InheritAllowDuplicateHost": "false",
                "InheritDefaultView": "false",
                "CIDR": "10.0.0.0/8",
                "DefaultView": "100907",
                "Type": "IP4Block",
                "ID": 100909,
                "InheritDefaultDomains": "true",
                "Name": "global"
            }
        ],
        "Address": "10.0.0.10",
        "Type": "IPv4",
        "ID": 100923
    }
}
Human Readable Output

10.0.0.10 IP Result:

ID Name Type Address State Location Inherited
100923   IPv4 10.0.0.10 STATIC true

 

Parents Details:

ID Type Name CIDR Allow Duplicate Host Inherit Allow Duplicate Host Ping Before Assign Inherit Ping Before Assign Location Inherited Inherit Default Domains Default View Inherit Default View Inherit DNS Restrictions
100909 IP4Block global 10.0.0.0/8 disable false disable false true true 100907 false true
100910 IP4Block North America 10.0.0.0/16 disable true disable true true true 100907 true true
100911 IP4Block Florida 10.0.0.0/19 disable true disable true true true 100907 true true
100912 IP4Block Tampa 10.0.0.0/21 disable true disable true true true 100907 true true
100913 IP4Network East Office 10.0.0.0/24 disable true disable true true true 100907 true true

 

2. Get all response policies


Returns all response policies.

Base Command

bluecat-am-get-response-policies

Input
Argument Name Description Required
start Start index from which to get the response policies. Default is 0. Optional
count Maximum number of response policies to return. Optional

 

Context Output
Path Type Description
BlueCat.AddressManager.ResponsePolicies.ID Number ID of the response policy.
BlueCat.AddressManager.ResponsePolicies.Name String Name of the response policy.
BlueCat.AddressManager.ResponsePolicies.Ttl Unknown Time to live (TTL) of the response policy.
BlueCat.AddressManager.ResponsePolicies.Type String Type of the response policy (BLACKLIST, BLACKHOLE, WHITELIST, or REDIRECT).
BlueCat.AddressManager.ResponsePolicies.RedirectTarget String Target of redirect, in case of REDIRECT policy type.

 

Command Example
bluecat-am-get-response-policies start="0" count="30"
Context Example
{
    "BlueCat.AddressManager.ResponsePolicies": [
        {
            "RedirectTarget": "foo.com",
            "ResponsePolicyType": "REDIRECT",
            "Ttl": "30",
            "Type": "ResponsePolicy",
            "ID": 100930,
            "Name": "Malware Trap"
        }
    ]
}
Human Readable Output

Response Policies:

Malware Trap

ID Name Type Ttl ResponsePolicyType RedirectTarget
100930 Malware Trap ResponsePolicy 30 REDIRECT foo.com

 

3. Search response policies by domain


Searches all response policies in which the given domain is included.

Base Command

bluecat-am-search-response-policies-by-domain

Input
Argument Name Description Required
domain Domain name by which to search. Required

 

Context Output
Path Type Description
BlueCat.AddressManager.ResponsePolicies.ID Number ID of the response policy.
BlueCat.AddressManager.ResponsePolicies.Name String Name of the response policy.
BlueCat.AddressManager.ResponsePolicies.Ttl Unknown Time to live (TTL) of the response policy
BlueCat.AddressManager.ResponsePolicies.Type String Type of the responce policy ( BLACKLIST, BLACKHOLE, WHITELIST, or REDIRECT).
BlueCat.AddressManager.ResponsePolicies.RedirectTarget String Target of redirect, in case of REDIRECT policy type.

 

Command Example
bluecat-am-search-response-policies-by-domain domain="demisto.com"
Human Readable Output

Could not find any response policy

4. Add a domain to a response policy


Adds a domain to the given response policy.

Base Command

bluecat-am-response-policy-add-domain

Input
Argument Name Description Required
policy_id ID of the response policy to edit. Required
domain Domain to add to the response policy. Required

 

Context Output

There is no context output for this command.

Command Example
bluecat-am-response-policy-add-domain policy_id="100930" domain="demisto.com"
Human Readable Output

Successfully added demisto.com to response policy 100930

5. Remove a domain from a response policy


Removes a domain from the given response policy.

Base Command

bluecat-am-response-policy-remove-domain

Input
Argument Name Description Required
policy_id ID of the response policy to edit. Required
domain Domain to remove from the response policy. Required

 

Context Output

There is no context output for this command.

Command Example
bluecat-am-response-policy-remove-domain policy_id="100930" domain="demisto.com"
Human Readable Output

Successfully removed demisto.com from response policy 100930

6. Get an IPv4 block containing an IPv4 address


Gets an IPv4 block, which contains a specified IPv4 address.

Base Command

bluecat-am-get-range-by-ip

Input
Argument Name Description Required
ip The IP address for which to get the range. Required

 

Context Output
Path Type Description
BlueCat.AddressManager.Range.ID String The address manager ID of the Range.
BlueCat.AddressManager.Range.Name String Name of the Range.
BlueCat.AddressManager.Range.Type String Type of the Range.
BlueCat.AddressManager.Range.Parents.ID String ID of the parent Range.
BlueCat.AddressManager.Range.Parents.Type String Type of the parent Range.
BlueCat.AddressManager.Range.Parents.Name String Name of the parent Range.
BlueCat.AddressManager.Range.Parents.CIDR String Classless Inter-Domain Routing.

 

Command Example
  !bluecat-am-get-range-by-ip ip=10.0.0.11
Human Readable Output

Screen Shot 2019-08-08 at 15 44 51

Configuration parameters

  • url — Server URL (e.g., https://192.168.0.1) (required)
  • credentials — Username (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • conf_name — Configuration Name

Commands (6)

  • bluecat-am-get-range-by-ip

    Returns the IPv4 block that contains the specified IPv4 address.

  • bluecat-am-get-response-policies

    Returns all response policies.

  • bluecat-am-query-ip

    Enriches an IP address with data, including IP networks and blocks to which it belongs, linked IPs, MAC addresses, and more.

  • bluecat-am-response-policy-add-domain

    Adds a domain to the given response policy.

  • bluecat-am-response-policy-remove-domain

    Removes a domain from the given response policy.

  • bluecat-am-search-response-policies-by-domain

    Searches all response policies in which the given domain is included.

import ipaddress

import requests
import urllib3
from CommonServerPython import *

# Disable insecure warnings
urllib3.disable_warnings()
TOKEN_LIFE_TIME_MINUTES = 5


class Client:
    def __init__(self, server_url: str, username: str, password: str, use_ssl: bool, user_conf: str):
        self._base_url = server_url
        self._use_ssl = use_ssl
        self._username = username
        self._password = password
        self.user_conf = user_conf
        self._headers = {"Content-Type": "application/json", "Accept": "application/json"}
        self.conf = self.get_configuration()

    def get_token_request(self):
        url_args = {"username": self._username, "password": self._password}
        start_idx = 16
        end_delim = " <-"
        url = f"{self._base_url}/login"
        res = requests.request("GET", url, verify=self._use_ssl, params=url_args)
        if res.status_code != 200:
            raise Exception("Error: Failed to create a new token, please check your credentials")
        res_json = res.json()
        end_idx = res_json.index(end_delim)
        return res_json[start_idx:end_idx]

    def get_token(self, new_token=False):
        """
        Retrieves the token from the server if it's expired and updates the global HEADERS to include it

        :param new_token: If set to True will generate a new token regardless of time passed

        :rtype: ``str``
        :return: Token
        """
        now = datetime.now()
        ctx = demisto.getIntegrationContext()
        if ctx and not new_token:
            passed_minutes = get_passed_minutes(now, datetime.fromtimestamp(ctx.get("time")))
            if passed_minutes >= TOKEN_LIFE_TIME_MINUTES:
                # token expired
                auth_token = self.get_token_request()
                demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000})
            else:
                # token hasn't expired
                auth_token = ctx.get("auth_token")
        else:
            # generating new token
            auth_token = self.get_token_request()
            demisto.setIntegrationContext({"auth_token": auth_token, "time": date_to_timestamp(now) / 1000})
        return auth_token

    def http_request(self, method, url_suffix, params=None, data=None, safe=False):
        """
        A wrapper for requests lib to send our requests and handle requests and responses better.

        :type method: ``str``
        :param method: HTTP method for the request.

        :type url_suffix: ``str``
        :param url_suffix: The suffix of the URL (endpoint)

        :type params: ``dict``
        :param params: The URL params to be passed.

        :type data: ``str``
        :param data: The body data of the request.

        :type headers: ``dict``
        :param headers: Request headers

        :type safe: ``bool``
        :param safe: If set to true will return None in case of http error

        :return: Returns the http request response json
        :rtype: ``dict``
        """
        self._headers["Authorization"] = self.get_token()
        url = f"{self._base_url}{url_suffix}"
        try:
            res = requests.request(method, url, verify=self._use_ssl, params=params, data=data, headers=self._headers)
            # Try to create a new token
            if res.status_code == 401:
                self._headers["Authorization"] = self.get_token(new_token=True)
                res = requests.request(method, url, verify=self._use_ssl, params=params, data=data, headers=self._headers)
        except requests.exceptions.RequestException:
            raise Exception("Error in connection to the server. Please make sure you entered the URL correctly.")
        # Handle error responses gracefully
        if res.status_code not in {200, 201, 202}:
            result_msg = None
            try:
                result_msg = res.json()
            finally:
                reason = result_msg if result_msg else res.reason
                err_msg = f"Error in API call. code:{res.status_code}; reason: {reason}"
                if safe:
                    return None
                raise Exception(err_msg)
        return res.json()

    def get_configuration(self):
        """
        Gets the chosen configuration to run queries on

        :return: User configuration id, or the first configuration id if no user configuration provided
        """
        user_conf = self.user_conf
        params = {
            "type": "Configuration",
            "start": 0,
            "count": 100,
            "parentId": 0,
        }
        confs = self.http_request("GET", "/getEntities", params)
        if not confs:
            raise Exception("No configurations could be fetched from the system")
        if user_conf:
            for conf in confs:
                if conf.get("name") == user_conf:
                    return conf.get("id")

        return confs[0].get("id")


def get_passed_minutes(start_time, end_time):
    """
    Returns the time passed in minutes
    :param start_time: Start time in datetime
    :param end_time: End time in datetime
    :return: The passed minutes in int
    """
    time_delta = start_time - end_time
    return time_delta.seconds / 60


def properties_to_camelized_dict(properties):
    properties = properties.split("|")
    properties_dict = {}
    for _property in properties:
        if _property:
            key_val_pair = _property.split("=")
            # camelize the key
            key = key_val_pair[0][0].upper() + key_val_pair[0][1:]
            properties_dict[key] = key_val_pair[1]
    return properties_dict


""" COMMANDS + REQUESTS FUNCTIONS """


def test_module(client: Client):
    """
    Performs basic get request to get item samples
    """
    client.get_token(new_token=True)
    demisto.results("ok")


def query_ip_command(client: Client):
    ip = demisto.getArg("ip")
    try:
        if isinstance(ipaddress.ip_address(ip), ipaddress.IPv6Address):
            ip_type = "IPv6"
            base_ip_raw_res = query_ipv6(client, ip)
        else:
            ip_type = "IPv4"
            base_ip_raw_res = query_ipv4(client, ip)

        # entity with id 0 is root, and CONF is root of parent
        if base_ip_raw_res.get("id") in (None, 0, client.conf):
            return_outputs(f"IP: {ip} was not found.", {}, base_ip_raw_res)
        else:
            base_ip_parents = get_entity_parents(client, base_ip_raw_res.get("id"))
            ip_object = {
                "ID": base_ip_raw_res.get("id"),
                "Name": base_ip_raw_res.get("name"),
                "Parents": base_ip_parents,
                "Type": ip_type,
            }
            ip_object.update(properties_to_camelized_dict(base_ip_raw_res.get("properties")))
            ec = {"BlueCat.AddressManager.IP(obj.ID === val.ID)": ip_object, "IP(val.Address === obj.Address)": {"Address": ip}}
            hr = create_human_readable_ip(ip_object, ip)
            return_outputs(hr, ec, base_ip_raw_res)

    except ipaddress.AddressValueError:
        raise Exception(f"Invalid IP: {ip}")


def query_ipv4(client: Client, ip: str):
    params = {"containerId": client.conf, "address": ip}
    return client.http_request("GET", "/getIP4Address", params=params)


def query_ipv6(client: Client, ip: str):
    params = {"containerId": client.conf, "address": ip}
    return client.http_request("GET", "/getIP6Address", params=params)


def get_entity_parents(client: Client, base_id):
    base_ip_parents = []
    entity_parent = get_entity_parent(client, entity_id=base_id)
    # entity with id 0 is root, and CONF is root of parent
    while entity_parent.get("id") not in (None, 0, client.conf):
        parent_obj = {
            "ID": entity_parent.get("id"),
            "Type": entity_parent.get("type"),
            "Name": entity_parent.get("name"),
        }
        parent_obj.update(properties_to_camelized_dict(entity_parent.get("properties")))
        base_ip_parents.append(parent_obj)
        entity_parent = get_entity_parent(client, entity_id=entity_parent.get("id"))

    return base_ip_parents


def get_entity_parent(client: Client, entity_id):
    params = {"entityId": entity_id}
    return client.http_request("GET", "/getParent", params=params)


def create_human_readable_ip(ip_object: dict, ip_value: str):
    ip_object_cpy = dict(ip_object)
    reversed_parents = list(reversed(ip_object_cpy["Parents"]))
    ip_object_cpy.pop("Parents")
    hr = tblToMd(f"{ip_value} IP Result:", ip_object_cpy, headerTransform=pascalToSpace)
    hr += tblToMd("Parents Details:", reversed_parents, headerTransform=pascalToSpace)
    return hr


def get_range_by_ip_command(client: Client):
    ip = demisto.getArg("ip")
    try:
        if isinstance(ipaddress.ip_address(ip), ipaddress.IPv6Address | ipaddress.IPv4Address):
            range_raw_res = get_range_by_ip(client, ip)

            if range_raw_res.get("id") in (None, 0, client.conf):
                return_outputs(f"IP range was not found for {ip}.", {}, range_raw_res)
            else:
                base_ip_parents = get_entity_parents(client, range_raw_res.get("id"))

                range_object = {
                    "ID": range_raw_res.get("id"),
                    "Name": range_raw_res.get("name"),
                    "Parents": base_ip_parents,
                    "Type": range_raw_res.get("type"),
                }

                range_object.update(properties_to_camelized_dict(range_raw_res.get("properties")))
                ec = {"BlueCat.AddressManager.Range(obj.ID === val.ID)": range_object}
                hr = create_human_readable_range(range_object, ip)
                return_outputs(hr, ec, range_raw_res)

    except ipaddress.AddressValueError:
        raise Exception(f"Invalid IP: {ip}")


def get_range_by_ip(client: Client, ip: str):
    params = {"containerId": client.conf, "type": "", "address": ip}
    return client.http_request("GET", "/getIPRangedByIP", params=params)


def create_human_readable_range(range_object: dict, ip_value: str):
    range_object_cpy = dict(range_object)
    reversed_parents = list(reversed(range_object_cpy["Parents"]))
    range_object_cpy.pop("Parents")
    hr = tblToMd(f"{ip_value} Range Result:", range_object_cpy, headerTransform=pascalToSpace)
    hr += tblToMd("Parents Details:", reversed_parents, headerTransform=pascalToSpace)
    return hr


def get_response_policies_command(client: Client):
    start = demisto.getArg("start")
    count = demisto.getArg("count")
    raw_response_policies = get_response_policies(client, start, count)
    response_policies, hr = create_response_policies_result(raw_response_policies)
    return_outputs(hr, response_policies, raw_response_policies)


def get_response_policies(client: Client, start: str, count: str):
    params = {"parentId": client.conf, "type": "ResponsePolicy", "start": start, "count": count}
    return client.http_request("GET", "/getEntities", params=params)


def create_response_policies_result(raw_response_policies: dict):
    response_policies = []
    if raw_response_policies:
        hr = "## Response Policies:\n"
        for response_policy in raw_response_policies:
            response_policy_obj = {
                "ID": response_policy.get("id"),
                "Name": response_policy.get("name"),
                "Type": response_policy.get("type"),
            }
            response_policy_obj.update(properties_to_camelized_dict(response_policy.get("properties")))
            hr += tblToMd(response_policy_obj["Name"], response_policy_obj)
            response_policies.append(response_policy_obj)
        return {"BlueCat.AddressManager.ResponsePolicies(val.ID === obj.ID)": response_policies}, hr
    return {}, "Could not find any response policy"


def add_domain_response_policy_command(client: Client):
    policy_id = demisto.getArg("policy_id")
    domain = demisto.getArg("domain")
    raw_response = add_domain_response_policy(client, policy_id, domain)
    error_msg = (
        f"Failed to add {domain} to response policy {policy_id}, possibly the domain already exists in the response policy."
    )
    if raw_response:
        return_outputs(f"Successfully added {domain} to response policy {policy_id}", {}, raw_response)
    else:
        return_outputs(error_msg, {}, raw_response)


def add_domain_response_policy(client: Client, policy_id: str, domain: str):
    params = {"policyId": policy_id, "itemName": domain}
    return client.http_request("POST", "/addResponsePolicyItem", params=params)


def remove_domain_response_policy_command(client: Client):
    policy_id = demisto.getArg("policy_id")
    domain = demisto.getArg("domain")
    raw_response = remove_domain_response_policy(client, policy_id, domain)
    error_msg = (
        f"Failed to remove {domain} from response policy {policy_id}, "
        f"possibly the domain doesn't exist in the response policy."
    )
    if raw_response:
        return_outputs(f"Successfully removed {domain} from response policy {policy_id}", {}, raw_response)
    else:
        return_outputs(error_msg, {}, raw_response)


def remove_domain_response_policy(client: Client, policy_id: str, domain: str):
    params = {"policyId": policy_id, "itemName": domain}
    return client.http_request("DELETE", "/deleteResponsePolicyItem", params=params)


def search_response_policy_by_domain_command(client: Client):
    domain = demisto.getArg("domain")
    raw_response_policies = search_response_policy_by_domain(client, domain)
    response_policies, hr = create_response_policies_result(raw_response_policies)
    return_outputs(hr, response_policies, raw_response_policies)


def search_response_policy_by_domain(client: Client, domain: str):
    params = {"configurationId": client.conf, "itemName": domain}
    return client.http_request("GET", "/findResponsePoliciesWithItem", params=params)


def main():
    params = demisto.params()
    url = params.get("url")
    username = params.get("credentials").get("identifier")
    password = params.get("credentials").get("password")
    user_conf = params.get("conf_name")

    # Remove trailing slash to prevent wrong URL path to service
    server = url[:-1] if (url and url.endswith("/")) else url
    base_url = f"{server}/Services/REST/v1"
    use_ssl = not params.get("insecure", False)

    handle_proxy()
    command = demisto.command()
    LOG(f"Command being called is {command}")

    try:
        client = Client(server_url=base_url, username=username, password=password, use_ssl=use_ssl, user_conf=user_conf)

        if command == "test-module":
            test_module(client)
        elif command == "bluecat-am-query-ip":
            query_ip_command(client)
        elif command == "bluecat-am-get-range-by-ip":
            get_range_by_ip_command(client)
        elif command == "bluecat-am-get-response-policies":
            get_response_policies_command(client)
        elif command == "bluecat-am-search-response-policies-by-domain":
            search_response_policy_by_domain_command(client)
        elif command == "bluecat-am-response-policy-add-domain":
            add_domain_response_policy_command(client)
        elif command == "bluecat-am-response-policy-remove-domain":
            remove_domain_response_policy_command(client)
        else:
            raise NotImplementedError(f"{command} is not an existing Bluecat Address Mannager command")

    except Exception as e:
        return_error(str(e))


if __name__ in ["__main__", "builtin", "builtins"]:
    main()