def command_response(body, message):
    """
    Process a command received from an actuator
    :param body: command body
    :param message: complete message (headers, meta, ...)
    :return: None
    """
    log.info(msg=f'Message response received: {body}')
    headers = getattr(message, "headers", {})
    actuator = None

    if headers.get('error', False):
        correlation_ID = headers['source'].get('correlationID', '')
        opts = {
            '_coap_id' if isHex(correlation_ID) else 'command_id':
            correlation_ID
        }

        command = get_or_none(SentHistory, **opts)
        log.error(msg=f'Message Failure: cmd - {command.command_id}, {body}')

        response = {'error': body}

    else:
        act_host, act_port = headers.get('socket', '').split(':')[0:2]
        correlation_ID = headers.get('correlationID', '')
        opts = {
            '_coap_id' if isHex(correlation_ID) else 'command_id':
            correlation_ID
        }

        command = get_or_none(SentHistory, **opts)
        profile = headers.get('profile', '')

        encode = headers.get('encode', 'json')
        response = decode_msg(body, encode)

        actuator = get_or_none(
            model=Actuator,
            profile__iexact=profile,
            device__transport__host__iexact=act_host,
            device__transport__port=safe_cast(act_port, int),
            device__transport__protocol=get_or_none(Protocol,
                                                    name__iexact=headers.get(
                                                        'transport', '')))

        if hasattr(actuator, '__iter__'):
            log.warn(
                msg=
                f'Multiple actuators match for command response - {command.command_id}'
            )
            actuator = random.choice(actuator)

    try:
        cmd_rsp = ResponseHistory(command=command,
                                  actuator=actuator,
                                  response=response)
        cmd_rsp.save()
    except Exception as e:
        log.error(msg=f'Message response failed to save: {e}')
Beispiel #2
0
def check_command_id(sender, instance=None, **kwargs):
    """
    Validate the command id given is a UUID
    :param sender: sender instance - SentHistory
    :param instance: SENDER instance
    :param kwargs: key/value args
    :return: None
    """
    if instance.command_id is None:
        log.info(
            msg=f"Command submitted without command id, command id generated")
        instance.command_id = uuid.uuid4()
        instance.command.update({"id": str(instance.command_id)})
    else:
        try:
            val = uuid.UUID(str(instance.command_id), version=4)
        except ValueError:
            log.info(msg=f"Invalid command id received: {instance.command_id}")
            raise ValueError("Invalid command id")

        tmp = get_or_none(sender, command_id=val)
        if val is None and tmp is not None:
            log.info(
                msg=f"Duplicate command id received: {instance.command_id}")
            raise ValueError("command id has been used")
def action_send(usr=None, cmd={}, actuator="", channel={}):
    """
    Process a command prior to sending it to the specified actuator(s)/profile
    :param usr: user sending command
    :param cmd: OpenC2 command
    :param actuator: actuator/profile receiving command
    :param channel: serialization & protocol to send the command
    :return: response dict
    """
    err = validate_usr(usr)
    if err:
        return err

    err = validate_cmd(usr, cmd)
    if err:
        return err

    actuators = None
    err = validate_actuator(usr, actuator)
    if err:
        if isinstance(err, (Actuator, list)):
            actuators = err
        else:
            return err

    protocol, serialization = validate_channel(actuators, channel)

    # Store command in db
    cmd_id = cmd.get("id", uuid.uuid4())
    if get_or_none(SentHistory, command_id=cmd_id):
        return dict(command_id=["This ID is used by another command."]), 400
    else:
        com = SentHistory(command_id=cmd_id, user=usr, command=cmd)
        try:
            com.save()
        except ValueError as e:
            return dict(detail="command error", response=str(e)), 400

    orc_ip = global_preferences.get("orchestrator__host", "127.0.0.1")
    orc_id = global_preferences.get("orchestrator__id", "")
    # Process Actuators that should receive command
    processed_acts = set()

    # Process Protocols
    protocols = [protocol] if protocol else Protocol.objects.all()
    for proto in protocols:
        proto_acts = [
            a for a in actuators
            if a.device.transport.filter(protocol__name=proto.name).exists()
        ]
        proto_acts = list(
            filter(lambda a: a.id not in processed_acts, proto_acts))
        processed_acts.update({act.id for act in proto_acts})

        if len(proto_acts) >= 1:
            if proto.name.lower() == "coap" and com.coap_id is b"":
                corr_id = com.gen_coap_id()
                com.save()
            else:
                corr_id = str(com.command_id)

            header = dict(source=dict(
                orchestratorID=orc_id,
                transport=dict(type=proto.name,
                               socket=f"{orc_ip}:{proto.port}"),
                correlationID=corr_id,
                date=f"{com.received_on:%a, %d %b %Y %X %Z}"),
                          destination=[])

            for act in proto_acts:
                com.actuators.add(act)
                trans = act.device.transport.filter(
                    protocol__name=proto.name).first()
                encoding = (serialization if serialization else
                            trans.serialization.first()).name.lower()

                dev = list(
                    filter(
                        lambda d: d["deviceID"] == str(act.device.device_id),
                        header["destination"]))
                profile = str(act.profile).lower()

                if len(dev) == 1:
                    idx = header["destination"].index(dev[0])
                    header["destination"][idx]["profile"].append(profile)

                else:
                    dest = dict(deviceID=str(act.device.device_id),
                                socket=f"{trans.host}:{trans.port}",
                                profile=[profile],
                                encoding=encoding)
                    if trans.protocol.pub_sub:
                        print("PUB SUB")
                    header["destination"].append(dest)

            # Send command to transport
            log.info(
                usr=usr,
                msg=
                f"Send command {com.command_id}/{com.coap_id.hex()} to buffer")
            settings.MESSAGE_QUEUE.send(msg=json.dumps(cmd),
                                        headers=header,
                                        routing_key=proto.name.lower().replace(
                                            " ", "_"))

    wait = safe_cast(global_preferences.get("command__wait", 1), int, 1)
    rsp = None
    for _ in range(wait):
        rsp = get_or_none(ResponseHistory, command=com)
        if rsp:
            break
        else:
            time.sleep(1)

    rsp = [r.response for r in rsp] if hasattr(rsp, "__iter__") else (
        [rsp.response] if hasattr(rsp, "response") else None)

    return dict(detail=f"command {'received' if rsp is None else 'processed'}",
                response=rsp if rsp else "pending",
                command_id=com.command_id,
                command=com.command,
                wait=wait), 200
Beispiel #4
0
def action_send(usr: User, cmd: dict, actuator: str, channel: dict):
    """
    Process a command prior to sending it to the specified actuator(s)/profile
    :param usr: user sending command
    :param cmd: OpenC2 command
    :param actuator: actuator/profile receiving command
    :param channel: serialization & protocol to send the command
    :return: response Tuple(dict, int)
    """
    val = Validator(usr, cmd, actuator, channel)
    acts, protocol, serialization = val.validate()
    (actuators, fmt) = acts

    # Store command in db
    if "id" in cmd:
        cmd_id = cmd.get("id", uuid.uuid4())
        try:
            cmd_id = uuid.UUID(cmd_id, version=4)
        except ValueError:
            cmd_id = uuid.uuid4()
            cmd["id"] = str(cmd_id)
    else:
        cmd_id = uuid.uuid4()

    if get_or_none(SentHistory, command_id=cmd_id):
        return dict(
            command_id=[
                "This ID is used by another command."
            ]
        ), 400

    com = SentHistory(command_id=cmd_id, user=usr, command=cmd)
    try:
        com.save()
    except ValueError as e:
        return dict(
            detail="command error",
            response=str(e)
        ), 400

    # Process Actuators that should receive command
    processed_acts = set()

    # Process Protocols
    for proto in [protocol] if protocol else Protocol.objects.all():
        proto_acts = [a for a in actuators if a.device.transport.filter(protocol__name=proto.name).exists()]
        proto_acts = list(filter(lambda a: a.id not in processed_acts, proto_acts))
        processed_acts.update({act.id for act in proto_acts})

        if len(proto_acts) >= 1:
            if proto.name.lower() == "coap" and com.coap_id == b"":
                com.gen_coap_id()
                com.save()

            # Send command to transport
            log.info(usr=usr, msg=f"Send command {com.command_id}/{com.coap_id.hex()} to buffer")
            settings.MESSAGE_QUEUE.send(
                msg=json.dumps(cmd),
                headers=get_headers(proto, com, proto_acts, serialization, fmt),
                routing_key=proto.name.lower().replace(" ", "_")
            )

    wait = safe_cast(global_preferences.get("command__wait", 1), int, 1)
    rsp = None
    for _ in range(wait):
        rsp = get_or_none(ResponseHistory, command=com)
        if rsp:
            break
        time.sleep(1)

    rsp = [r.response for r in rsp] if hasattr(rsp, "__iter__") else ([rsp.response] if hasattr(rsp, "response") else None)

    return dict(
        detail=f"command {'received' if rsp is None else 'processed'}",
        response=rsp if rsp else "pending",
        command_id=com.command_id,
        command=com.command,
        wait=wait
    ), 200