예제 #1
0
    def get_file(self, headers: OrderedDict, data: Optional[str],
                 client_cert: SSLCertT) -> HttpResponse:
        # Ordinary file on filesystem
        username = CertHelpers.get_field(client_cert, "emailAddress")
        url_path = headers[":path"].split("?")[0]
        url_path_safe = "".join(
            filter(lambda c: c.isalpha() or c in "/-_.",
                   url_path)).replace("..", "").strip("/")
        file_path = os.path.join(config.CFG.http["DOC_ROOT"], url_path_safe)

        if os.path.isdir(file_path):
            file_path = os.path.join(file_path,
                                     config.CFG.http["DOC_DEFAULT_NAME"])

        ctype = mimetypes.guess_type(
            file_path)[0] or "application/octet-stream"

        try:
            fd = open(file_path, 'rb')
            response = fd.read()
            fd.close()
        except FileNotFoundError:
            warn("[{}] Cannot open requested file \"{}\"".format(
                username, file_path))
            http_resp = HttpResponse.empty(HttpStatus.NotFound)
        else:
            info("[{}] Serving ordinary file {} of type \"{}\"".format(
                username, file_path, ctype))
            http_resp = HttpResponse(HttpStatus.Ok, response, ctype)

        return http_resp
예제 #2
0
    def zone_unset(self, input_args: JsonNodeT, username: str) -> JsonNodeT:
        try:
            usr_op_journal = self.op_journal[username]
        except KeyError:
            warn("zone_set: Op transaction not started")
            return

        usr_op_journal.append(KnotOp(KnotZoneCmd.UNSET, input_args))
예제 #3
0
    def zone_commit_transaction(self, input_args: JsonNodeT, username: str) -> JsonNodeT:
        try:
            usr_op_journal = self.op_journal[username]
        except KeyError:
            warn("zone_commit_transaction: Nothing to commit")
            return

        # TODO
        del self.op_journal[username]
예제 #4
0
    def run_conf_edit_handler(self, ii: InstanceRoute, ch: DataChange):
        try:
            sch_pth_list = list(filter(lambda n: isinstance(n, MemberName),
                                       ii))

            if ch.change_type == ChangeType.CREATE:
                # Get target member name
                input_member_name_fq = tuple(ch.input_data.keys())[0]
                input_member_name_ns, input_member_name = input_member_name_fq.split(
                    ":", maxsplit=1)
                # Append it to ii
                sch_pth_list.append(MemberName(input_member_name, None))

            sch_pth = DataHelpers.ii2str(sch_pth_list)
            sn = self.get_schema_node(sch_pth)

            if sn is None:
                return

            h = CONF_DATA_HANDLES.get_handler(str(id(sn)))
            if h is not None:
                info("handler for actual data node triggered")
                if isinstance(h, ConfDataObjectHandler):
                    if ch.change_type == ChangeType.CREATE:
                        h.create(ii, ch)
                    elif ch.change_type == ChangeType.REPLACE:
                        h.replace(ii, ch)
                    elif ch.change_type == ChangeType.DELETE:
                        h.delete(ii, ch)
                if isinstance(h, ConfDataListHandler):
                    if ch.change_type == ChangeType.CREATE:
                        h.create_item(ii, ch)
                    elif ch.change_type == ChangeType.REPLACE:
                        h.replace_item(ii, ch)
                    elif ch.change_type == ChangeType.DELETE:
                        h.delete_item(ii, ch)
            else:
                sn = sn.parent
                while sn is not None:
                    h = CONF_DATA_HANDLES.get_handler(str(id(sn)))
                    if h is not None and isinstance(h, ConfDataObjectHandler):
                        info(
                            "handler for superior data node triggered, replace"
                        )
                        # print(h.schema_path)
                        # print(h.__class__.__name__)
                        h.replace(ii, ch)
                    if h is not None and isinstance(h, ConfDataListHandler):
                        info(
                            "handler for superior data node triggered, replace_item"
                        )
                        h.replace_item(ii, ch)
                    sn = sn.parent
        except NonexistentInstance:
            warn("Cannnot notify {}, parent container removed".format(ii))
예제 #5
0
    def zone_commit_transaction(self, input_args: JsonNodeT,
                                username: str) -> JsonNodeT:
        try:
            usr_op_journal = self.op_journal[username]
        except KeyError:
            warn("zone_commit_transaction: Nothing to commit")
            return

        # Connect to Knot socket and start zone transaction
        so.KNOT.knot_connect()
        so.KNOT.begin_zone()

        for knot_op in usr_op_journal:
            input_args = knot_op.op_input
            domain = input_args["dns-zone-rpcs:zone"]
            if knot_op.cmd == KnotZoneCmd.SET:
                rr_type = input_args["dns-zone-rpcs:type"][0]
                if rr_type == "SOA":
                    rrdata = input_args["dns-zone-rpcs:SOA"]
                    rr = SOARecord()
                    rr.ttl = input_args["dns-zone-rpcs:ttl"]
                    rr.mname = rrdata["mname"]
                    rr.rname = rrdata["rname"]
                    rr.serial = rrdata["serial"]
                    rr.refresh = rrdata["refresh"]
                    rr.retry = rrdata["retry"]
                    rr.expire = rrdata["expire"]
                    rr.minimum = rrdata["minimum"]
                    so.KNOT.zone_add_record(domain, rr)
                elif rr_type == "A":
                    rrdata = input_args["dns-zone-rpcs:A"]
                    rr = ARecord(input_args["dns-zone-rpcs:owner"],
                                 input_args["dns-zone-rpcs:ttl"])
                    rr.address = rrdata["address"]
                    so.KNOT.zone_add_record(domain, rr)
                elif rr_type == "AAAA":
                    rrdata = input_args["dns-zone-rpcs:AAAA"]
                    rr = AAAARecord(input_args["dns-zone-rpcs:owner"],
                                    input_args["dns-zone-rpcs:ttl"])
                    rr.address = rrdata["address"]
                    so.KNOT.zone_add_record(domain, rr)
                elif rr_type == "MX":
                    rrdata = input_args["dns-zone-rpcs:MX"]
                    rr = MXRecord(input_args["dns-zone-rpcs:owner"],
                                  input_args["dns-zone-rpcs:ttl"])
                    rr.preference = rrdata["preference"]
                    rr.exchange = rrdata["exchange"]
                    so.KNOT.zone_add_record(domain, rr)
                elif rr_type == "CNAME":
                    rrdata = input_args["dns-zone-rpcs:CNAME"]
                    rr = CNAMERecord(input_args["dns-zone-rpcs:owner"],
                                     input_args["dns-zone-rpcs:ttl"])
                    rr.cname = rrdata["cname"]
                    so.KNOT.zone_add_record(domain, rr)

            elif knot_op.cmd == KnotZoneCmd.UNSET:
                input_args = knot_op.op_input
                domain = input_args["dns-zone-rpcs:zone"]
                owner = input_args.get("dns-zone-rpcs:owner")

                try:
                    rr_type = input_args["dns-zone-rpcs:type"][0]
                    rrdata = input_args["dns-zone-rpcs:" + rr_type]
                except KeyError:
                    rr_type = None
                    rrdata = None

                if rrdata:
                    if rr_type == "A":
                        rr = ARecord(input_args["dns-zone-rpcs:owner"], 0)
                        rr.address = rrdata["address"]
                    elif rr_type == "AAAA":
                        rr = AAAARecord(input_args["dns-zone-rpcs:owner"], 0)
                        rr.address = rrdata["address"]
                    elif rr_type == "MX":
                        rr = MXRecord(input_args["dns-zone-rpcs:owner"], 0)
                        rr.preference = rrdata["preference"]
                        rr.exchange = rrdata["exchange"]
                    elif rr_type == "CNAME":
                        rr = CNAMERecord(input_args["dns-zone-rpcs:owner"], 0)
                        rr.cname = rrdata["cname"]
                    else:
                        rr = None

                    selector = rr.rrdata_format() if rr is not None else None
                else:
                    selector = None

                so.KNOT.zone_del_record(domain, owner, rr_type, selector)

        so.KNOT.commit()
        so.KNOT.knot_disconnect()
        del self.op_journal[username]
예제 #6
0
 def zone_abort_transaction(self, input_args: JsonNodeT,
                            username: str) -> JsonNodeT:
     try:
         del self.op_journal[username]
     except KeyError:
         warn("zone_abort_transaction: Nothing to abort")
예제 #7
0
    def data_received(self, data: bytes):
        events = self.conn.receive_data(data)

        for event in events:
            if isinstance(event, RequestReceived):
                # Store request headers
                headers = OrderedDict(event.headers)
                request_data = RequestData(headers, BytesIO())
                self.stream_data[event.stream_id] = request_data
            elif isinstance(event, DataReceived):
                # Store incoming data
                try:
                    stream_data = self.stream_data[event.stream_id]
                except KeyError:
                    self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.PROTOCOL_ERROR)
                else:
                    # Check if incoming data are not excessively large
                    if (stream_data.data.tell() + len(event.data)) < (config.CFG.http["UPLOAD_SIZE_LIMIT"] * 1048576):
                        stream_data.data.write(event.data)
                        self.conn.acknowledge_received_data(len(event.data), event.stream_id)
                    else:
                        stream_data.data_overflow = True
                        self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.ENHANCE_YOUR_CALM)
            elif isinstance(event, StreamEnded):
                # Process request
                try:
                    request_data = self.stream_data.pop(event.stream_id)
                except KeyError:
                    self.send_response(
                        HttpResponse.error(HttpStatus.BadRequest, RestconfErrType.Transport, ERRTAG_MALFORMED),
                        event.stream_id
                    )
                else:
                    if request_data.data_overflow:
                        self.send_response(
                            HttpResponse.error(HttpStatus.ReqTooLarge, RestconfErrType.Transport, ERRTAG_REQLARGE),
                            event.stream_id
                        )
                    else:
                        headers = request_data.headers
                        http_method = headers[":method"]

                        if http_method in ("GET", "DELETE", "OPTIONS", "HEAD"):
                            self.run_request_handler(headers, event.stream_id, None)
                        elif http_method in ("PUT", "POST"):
                            body = request_data.data.getvalue().decode("utf-8")
                            self.run_request_handler(headers, event.stream_id, body)
                        else:
                            warn("Unknown http method \"{}\"".format(headers[":method"]))
                            self.send_response(
                                HttpResponse.error(
                                    HttpStatus.MethodNotAllowed,
                                    RestconfErrType.Transport,
                                    ERRTAG_OPNOTSUPPORTED
                                ),
                                event.stream_id
                            )
            elif isinstance(event, RemoteSettingsChanged):
                changed_settings = {}
                for s in event.changed_settings.items():
                    changed_settings[s[0]] = s[1].new_value
                self.conn.update_settings(changed_settings)
            elif isinstance(event, WindowUpdated):
                try:
                    debug_srv("str {} nw={}".format(event.stream_id, self.conn.local_flow_control_window(event.stream_id)))
                    self.send_response_continue(event.stream_id)
                except (ProtocolError, KeyError) as e:
                    debug_srv("wupexception strid={}: {}".format(event.stream_id, str(e)))
            elif isinstance(event, ConnectionTerminated):
                self.transport.close()

            dts = self.conn.data_to_send()
            if dts:
                self.transport.write(dts)
예제 #8
0
    def get_node_rpc(self, rpc: RpcInfo, staging=False) -> InstanceNode:
        ii = self.parse_ii(rpc.path, rpc.path_format)

        if staging:
            try:
                root = self.get_data_root_staging(rpc.username)
            except StagingDataException:
                # root = self._data
                info("Starting transaction for user \"{}\"".format(
                    rpc.username))
                self.make_user_journal(rpc.username, None)
                root = self.get_data_root_staging(rpc.username)
        else:
            root = self._data

        yl_data_request = False
        if (len(ii) > 0) and (isinstance(ii[0], MemberName)):
            # Not getting root
            ns_first = ii[0].namespace
            if (ns_first == "ietf-netconf-acm") and (
                    rpc.username not in CONFIG_NACM["ALLOWED_USERS"]):
                raise NacmForbiddenError(rpc.username +
                                         " not allowed to access NACM data")
            elif ns_first == "ietf-yang-library":
                root = self._yang_lib_data
                yl_data_request = True
        else:
            # Root node requested
            # Remove NACM data if user is not NACM privieged
            if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]:
                try:
                    root = root.delete_item("ietf-netconf-acm:nacm")
                except NonexistentInstance:
                    pass

            # Append YANG library data
            for member_name, member_val in self._yang_lib_data.value.items():
                root = root.put_member(member_name, member_val).top()

        # Resolve schema node of the desired data node
        sch_pth_list = filter(lambda isel: isinstance(isel, MemberName), ii)
        sch_pth = DataHelpers.ii2str(sch_pth_list)
        sn = self.get_schema_node(sch_pth)

        state_roots = sn.state_roots()

        # Check if URL points to state data or node that contains state data
        if state_roots and not yl_data_request:
            debug_data("State roots: {}".format(state_roots))
            n = None

            for state_root_sch_pth in state_roots:
                state_root_sn = self._dm.get_data_node(state_root_sch_pth)

                # Check if the desired node is child of the state root
                sni = sn
                is_child = False
                while sni:
                    if sni is state_root_sn:
                        is_child = True
                        break
                    sni = sni.parent

                if is_child:
                    # Direct request for the state data
                    sdh = STATE_DATA_HANDLES.get_handler(state_root_sch_pth)
                    if sdh is not None:
                        if isinstance(sdh, StateDataContainerHandler):
                            state_handler_val = sdh.generate_node(
                                ii, rpc.username, staging)
                            state_root_n = sdh.schema_node.orphan_instance(
                                state_handler_val)
                        elif isinstance(sdh, StateDataListHandler):
                            if (sn is sdh.schema_node) and isinstance(
                                    ii[-1], MemberName):
                                state_handler_val = sdh.generate_list(
                                    ii, rpc.username, staging)
                                state_root_n = sdh.schema_node.orphan_instance(
                                    state_handler_val)
                            else:
                                state_handler_val = sdh.generate_item(
                                    ii, rpc.username, staging)
                                state_root_n = sdh.schema_node.orphan_entry(
                                    state_handler_val)

                        # Select desired subnode from handler-generated content
                        ii_prefix, ii_rel = sdh.schema_node.split_instance_route(
                            ii)
                        n = state_root_n.goto(ii_rel)

                        # There should be only one state root, no need to continue
                        if len(state_roots) != 1:
                            warn(
                                "URI points to directly to state data, but more state roots found"
                            )
                        break
                    else:
                        raise NoHandlerForStateDataError(rpc.path)
                else:
                    # Request for config data containing state data
                    n = root.goto(ii)

                    def _fill_state_roots(node: InstanceNode) -> InstanceNode:
                        if isinstance(node.value, ObjectValue):
                            if node.schema_node is state_root_sn.parent:
                                ii_gen = DataHelpers.node_get_ii(node)
                                sdh = STATE_DATA_HANDLES.get_handler(
                                    state_root_sch_pth)
                                if sdh is not None:
                                    try:
                                        if isinstance(
                                                sdh,
                                                StateDataContainerHandler):
                                            state_handler_val = sdh.generate_node(
                                                ii_gen, rpc.username, staging)
                                        elif isinstance(
                                                sdh, StateDataListHandler):
                                            state_handler_val = sdh.generate_list(
                                                ii_gen, rpc.username, staging)
                                    except Exception as e:
                                        error(
                                            "Error occured in state data generator (sn: {})"
                                            .format(state_root_sch_pth))
                                        error(epretty(e))
                                        error(
                                            "This state node will be omitted.")
                                    else:
                                        if state_root_sn.ns == state_root_sn.parent.ns:
                                            nm_name = state_root_sn.qual_name[
                                                0]
                                        else:
                                            nm_name = state_root_sn.qual_name[
                                                1] + ":" + state_root_sn.qual_name[
                                                    0]

                                        # print("nm={}".format(nm_name))
                                        node = node.put_member(
                                            nm_name,
                                            state_handler_val,
                                            raw=True).up()
                            else:
                                for key in node:
                                    member = node[key]
                                    node = _fill_state_roots(member).up()
                        elif isinstance(node.value, ArrayValue):
                            i = 0
                            arr_len = len(node.value)
                            while i < arr_len:
                                node = _fill_state_roots(node[i]).up()
                                i += 1

                        return node

                    n = _fill_state_roots(n)
                    root = n.top()
        else:
            # No state data in requested node
            n = root.goto(ii)

        # Process "with-defaults" query parameter
        try:
            with_defs = rpc.qs["with-defaults"][0]
        except (IndexError, KeyError):
            with_defs = None

        if with_defs == "report-all":
            n = n.add_defaults()

        # Evaluate NACM if required
        if self.nacm and not rpc.skip_nacm_check:
            nrpc = self.nacm.get_user_rules(rpc.username)
            if nrpc.check_data_node_permission(
                    root, ii, Permission.NACM_ACCESS_READ) == Action.DENY:
                raise NacmForbiddenError()
            else:
                # Prune nodes that should not be accessible to user
                n = nrpc.prune_data_tree(n, root, ii,
                                         Permission.NACM_ACCESS_READ)

        # Process "depth" query parameter
        try:
            max_depth_str = rpc.qs["depth"][0]
            if max_depth_str == "unbounded":
                max_depth = None
            else:
                max_depth = int(max_depth_str) - 1
                if (max_depth < 0) or (max_depth > 65535):
                    raise ValueError()
        except (IndexError, KeyError):
            max_depth = None
        except ValueError:
            raise ValueError("Invalid value of query param \"depth\"")

        if max_depth is not None:

            def _tree_limit_depth(node: InstanceNode,
                                  depth: int) -> InstanceNode:
                if isinstance(node.value, ObjectValue):
                    if depth > max_depth:
                        node.value = ObjectValue({})
                    else:
                        for child_key in sorted(node.value.keys()):
                            m = node[child_key]
                            node = _tree_limit_depth(m, depth + 1).up()
                elif isinstance(node.value, ArrayValue):
                    depth -= 1
                    for i in range(len(node.value)):
                        e = node[i]
                        node = _tree_limit_depth(e, depth + 1).up()

                return node

            n = _tree_limit_depth(n, 1)

        # Return result
        return n