diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index c012b667..15f54d0b 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1023,6 +1023,18 @@ RabbitMQ queue type for the topic. Default: classic +#### reporting +##### max_freebusy_occurrence + +When returning a free-busy report, a list of busy time occurrences are +generated based on a given time frame. Large time frames could +generate a lot of occurrences based on the time frame supplied. This +setting limits the lookup to prevent potential denial of service +attacks on large time frames. If the limit is reached, an HTTP error +is thrown instead of returning the results. + +Default: 10000 + ## Supported Clients Radicale has been tested with: diff --git a/config b/config index a9fe9da7..829ad7db 100644 --- a/config +++ b/config @@ -172,3 +172,9 @@ #rabbitmq_endpoint = #rabbitmq_topic = #rabbitmq_queue_type = classic + +[reporting] + +# When returning a free-busy report, limit the number of returned +# occurences per event to prevent DOS attacks. +#max_freebusy_occurrence = 10000 \ No newline at end of file diff --git a/radicale/app/report.py b/radicale/app/report.py index 7ea34d21..9d57b389 100644 --- a/radicale/app/report.py +++ b/radicale/app/report.py @@ -117,10 +117,18 @@ def free_busy_report(base_prefix: str, path: str, xml_request: Optional[ET.Eleme # TODO: coalesce overlapping periods + if max_occurrence > 0: + n_occurrences = max_occurrence+1 + else: + n_occurrences = 0 occurrences = radicale_filter.time_range_fill(item.vobject_item, time_range_element, "VEVENT", - n=max_occurrence) + n=n_occurrences) + if len(occurrences) >= max_occurrence: + raise ValueError("FREEBUSY occurrences limit of {} hit" + .format(max_occurrence)) + for occurrence in occurrences: vfb = cal.add('vfreebusy') vfb.add('dtstamp').value = item.vobject_item.vevent.dtstamp.value diff --git a/radicale/tests/test_base.py b/radicale/tests/test_base.py index ae3669d0..fc708ebc 100644 --- a/radicale/tests/test_base.py +++ b/radicale/tests/test_base.py @@ -1392,6 +1392,14 @@ permissions: RrWw""") types[fbtype_val] += 1 assert types == {'BUSY': 2, 'FREE': 1} + # Test max_freebusy_occurrence limit + self.configure({"reporting": {"max_freebusy_occurrence": 1}}) + code, responses = self.report(calendar_path, """\ + + + +""", 400, is_xml=False) + def _report_sync_token( self, calendar_path: str, sync_token: Optional[str] = None ) -> Tuple[str, RESPONSES]: