# {{ ansible_managed }}
# vim: foldmarker=[[[,]]]:foldmethod=marker
{% macro get_yaml_list_for_elem(list_or_elem) %}{# [[[ #}
{{ ([ list_or_elem ]
    if (list_or_elem is string or list_or_elem in [True, False])
    else (list_or_elem|list)) | to_nice_yaml }}
{% endmacro %}
{% macro get_realm_yaml_list(domains, fallback_realm) %}{# [[[ #}
{% set custom_realm_list = [] %}
{% if domains and (ansible_local|d() and ansible_local.pki|d() and ansible_local.pki.known_realms|d()) %}
{%   for domain in (get_yaml_list_for_elem(domains) | from_yaml) %}
{%     if domain in ansible_local.pki.known_realms %}
{%       set _ = custom_realm_list.append(domain) %}
{%     elif (domain.split('.')[1:] | join('.')) in ansible_local.pki.known_realms %}
{%       set _ = custom_realm_list.append(domain.split('.')[1:] | join('.')) %}
{%     endif %}
{%   endfor %}
{% endif %}
{% if custom_realm_list|length == 0 %}
{%   set _ = custom_realm_list.append(fallback_realm) %}
{% endif %}
{{ custom_realm_list | to_nice_yaml }}
{% endmacro %}
{% macro get_apache_version() %}{# [[[ #}
{{ ansible_local.apache.version
   if (ansible_local|d() and ansible_local.apache|d() and
       ansible_local.apache.version|d())
   else "2.4.0" -}}
{% endmacro %}
{% macro get_apache_min_version() %}{# [[[ #}
{{ ansible_local.apache.min_version
   if (ansible_local|d() and ansible_local.apache|d() and
       ansible_local.apache.min_version|d())
   else "2.4.0" -}}
{% endmacro %}
{% macro get_openssl_version() %}{# [[[ #}
{{ ansible_local.pki.openssl_version
   if (ansible_local|d() and ansible_local.pki|d() and
       ansible_local.pki.openssl_version|d())
   else "1.0.2" }}
{% endmacro %}
{% macro get_gnutls_version() %}{# [[[ #}
{{ ansible_local.pki.gnutls_version
   if (ansible_local|d() and ansible_local.pki|d() and
       ansible_local.pki.gnutls_version|d())
   else "0.0.0" }}
{% endmacro %}
{% macro debops_indent(content, width=4, indentfirst=False) %}{# [[[ #}
{# Fixed version of the `indent` filter which does not insert trailing spaces on empty lines.
## Note that you can not use this macro like a filter but have to use it like a regular macro.
## Example: {{ indent(some_content, 4) }}
##
## Python re.sub seems to default to re.MULTILINE in Ansible.
#}
{{ content | indent(width, indentfirst) | regex_replace("[ \\t\\r\\f\\v]+(\\n|$)", "\\1") -}}
{% endmacro %}
{% macro get_min_version_wrapped(content, version, compare_operator) %}{# [[[ #}
{# Examples:

{{ get_min_version_wrapped('# 2.3.0 directive', "2.3", ">=") }}
{{ get_min_version_wrapped('# 2.4.0 directive', "2.4", ">=") }}
{{ get_min_version_wrapped('# 2.4.1 directive', "2.4.1", ">=") }}
{{ get_min_version_wrapped('# 2.5.0 directive', "2.5", ">=") }}

#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_compare_operator_includes_equal = True if (compare_operator in ['>=', '<=', '==']) else False %}
{% if (version is version_compare(get_apache_min_version(), compare_operator))
    or (version is version_compare(get_apache_min_version(), ">")) %}
{%   if apache__tpl_compare_operator_includes_equal and (version is version_compare(get_apache_min_version(), "==")) %}
{{     content -}}
{%   else %}
<IfVersion {{ compare_operator }} {{ version }}>
    {{ debops_indent(content, 4) }}
</IfVersion>
{%   endif %}
{% endif %}
{% endmacro %}
{% macro get_version_wrapped(content, version, compare_operator, fallback_content=False) %}{# [[[ #}
{# This macro has not been extensivly tested yet. If it contains bugs please try to debug it if you need it.

Examples:

{{ get_version_wrapped('# 2.3.0 directive', "2.3", ">=", '# Prior to 2.3 directive') }}
{{ get_version_wrapped('# 2.4.0 directive', "2.4", ">=", '# Prior to 2.4 directive') }}
{{ get_version_wrapped('# 2.4.1 directive', "2.4.1", ">=", '# Prior to 2.4.1 directive') }}
{{ get_version_wrapped('# 2.5.0 directive', "2.5", ">=", '# Prior to 2.5 directive') }}

#}
{% set version = '==' if (version == '=') else version %}
{% set apache__tpl_xor_compare_operator_map = {} %}
{% set apache__tpl_xor_compare_operator_list = [
    ('==', '!='),
    ('>=', '<'),
    ('<=', '>'),
] %}
{% for compare_operator, xor_compare_operator in apache__tpl_xor_compare_operator_list %}
{%   set _ = apache__tpl_xor_compare_operator_map.update({ compare_operator: xor_compare_operator }) %}
{%   set _ = apache__tpl_xor_compare_operator_map.update({ xor_compare_operator: compare_operator }) %}
{% endfor %}
{% if apache__config_use_if_version|bool %}
{{   get_min_version_wrapped(content, version, compare_operator) -}}
{%   if fallback_content != False %}
{{     get_min_version_wrapped(fallback_content, version, apache__tpl_xor_compare_operator_map[compare_operator]) -}}
{%   endif %}
{% else %}
{%   if get_apache_version() is version_compare(version, compare_operator) %}
{{     content -}}
{%   elif fallback_content != False %}
{{     fallback_content -}}
{%   endif %}
{% endif %}
{% endmacro %}
{% macro get_server_name_aliases(name_item) %}{# [[[ #}
{% set apache__tpl_names = get_yaml_list_for_elem(name_item) | from_yaml %}
{% if not apache__tpl_names %}
{% set apache__tpl_names = [ apache__server_name ] %}
{% endif %}
ServerName {{ apache__tpl_names[0] | quote }}
{% if apache__tpl_names|length > 1 %}
ServerAlias {{ apache__tpl_names[1:] | map("quote") | join(" ") }}
{% endif %}
{% endmacro %}
{% macro get_listen_sockets(item_listen_list, server_port_listen_list) %}{# [[[ #}
{% set apache__tpl_listen_sockets = [] %}
{% if item_listen_list|length == 0 %}
{%   set item_listen_list = server_port_listen_list %}
{% endif %}
{% for item_listen in item_listen_list %}
{%   if ':' not in item_listen|string %}
{# {%     if item_listen in server_port_listen_list %}
## Apperently, this is not even needed for Apache to (re)start without issues. #}
{%     set _ = apache__tpl_listen_sockets.append("*:" + item_listen|string) %}
{%   else %}
{%     set _ = apache__tpl_listen_sockets.append(item_listen|string) %}
{%   endif %}
{% endfor %}
{{ apache__tpl_listen_sockets | join(" ") -}}
{% endmacro %}
{% macro get_header_comments(item) %}{# [[[ #}
{% if item.by_role|d() %}

# Generated by Ansible role: {{ item.by_role }}
{% endif %}
{% endmacro %}
{% macro get_server_directives(item) %}{# [[[ #}
{% set sanitized_name = (get_yaml_list_for_elem(item.filename|d(item.name)) | from_yaml)[0] | replace(".", "_") %}
{% if item.name|d([]) %}
{{ get_server_name_aliases(item.name|d([])) }}
{% endif %}
ServerAdmin {{ item.server_admin|d(apache__server_admin) | quote }}
CustomLog ${APACHE_LOG_DIR}/{{ sanitized_name }}_access.log {{ apache__access_log_format }}
ErrorLog ${APACHE_LOG_DIR}/{{ sanitized_name }}_error.log
{% endmacro %}
{% macro get_server_status_directives(item, enabled) %}{# [[[ #}
{% if enabled|bool %}
<Location {{ item.status_location|d(apache__status_location) | quote }}>
    SetHandler server-status
{%   if item.status_allow_localhost|d(apache__status_allow_localhost) | bool %}
    Require local
{%   endif %}
{%   if item.status_directives|d(apache__status_directives) %}
    {{ item.status_directives|d(apache__status_directives) }}
{%   elif not (item.status_allow_localhost|d(apache__status_allow_localhost) | bool) %}
    Require all denied
{%   endif %}
</Location>
{% endif %}
{% endmacro %}
{% macro get_vhost_content_directives(item, mode='https', https_enabled=True) %}{# [[[ #}
{% set apache__tpl_use_redirect_module = 'alias' %}
{% set apache__tpl_status_enabled = item.status_enabled|d(apache__status_for_vhost_enabled) | bool %}
{{ get_server_status_directives(item, apache__tpl_status_enabled) -}}
{% if apache__tpl_status_enabled|bool and (
        (mode == 'http' and (item.redirect_http|d() or item.redirect_to_https|d(apache__redirect_to_https) | bool)) or
        (mode == 'https' and item.redirect_https|d())
    ) %}
<IfModule mod_rewrite>
RewriteEngine On
RewriteRule "^{{ item.status_location|d(apache__status_location) }}" "-" [L]
</IfModule>
{%   set apache__tpl_use_redirect_module = 'rewrite' %}
{% endif %}
{% if mode == 'http' and item.redirect_http|d() %}
{{ get_redirect(item.redirect_http_code|d(307), "/", item.redirect_http, apache__tpl_use_redirect_module) }}
{% elif mode == 'http' and item.redirect_to_https|d(apache__redirect_to_https) | bool %}
{{ get_redirect(item.redirect_to_https_with_code|d("301"), "/", "https://" + (get_yaml_list_for_elem(item.name) | from_yaml)[0], apache__tpl_use_redirect_module) }}
{% elif mode == 'https' and item.redirect_https|d() %}
{{ get_redirect(item.redirect_https_code|d(307), "/", item.redirect_https, apache__tpl_use_redirect_module) }}
{% else %}
{{ get_content_directives(item) }}
{% endif %}
{% endmacro %}
{% macro get_document_root_directives(item) %}{# [[[ #}
Options {{ get_yaml_list_for_elem(item.options|d(apache__vhost_options))|from_yaml | join(" ") }}
AllowOverride {{ get_yaml_list_for_elem(item.allow_override|d(apache__vhost_allow_override))|from_yaml | join(" ") }}

{{ get_version_wrapped('Require all granted', "2.4", ">=",
'Order allow,deny
Allow from all') -}}
{% if item.root_directives|d() %}

{{ item.root_directives }}
{% endif %}
{% endmacro %}
{% macro get_content_directives(item) %}{# [[[ #}
{% if item.include|d() %}
{% for include_file in get_yaml_list_for_elem(item.include)|from_yaml %}
Include {{ include_file | quote }}
{% endfor %}
{% endif %}
{% if item.include_optional|d() %}
{% for include_optional_file in get_yaml_list_for_elem(item.include_optional)|from_yaml %}
IncludeOptional {{ include_optional_file | quote }}
{% endfor %}
{% endif %}
{% if item.root|d(item.document_root|d()) %}
DocumentRoot {{ item.root|d(item.document_root) | quote }}

{% if item.index|d() %}
DirectoryIndex {{ get_yaml_list_for_elem(item.index)|from_yaml | join(" ") }}
{% endif %}
{% if item.alias|d() %}
{{ get_alias(item.alias, item.root|d(item.document_root)) }}
{%- endif %}
<Directory {{ item.root|d(item.document_root) | quote }}>
    {{ debops_indent(get_document_root_directives(item), 4) }}
</Directory>
{% endif %}
{% if item.raw_content|d() %}

{{ item.raw_content }}
{% endif %}
{% endmacro %}
{% macro get_default_tls_directives(item) %}{# [[[ #}
{# Included in server context to provide sane defaults (there might be vhosts
# not controled by this template) and in virtual host context to ensure those
# settings are appliend and to allow per-vhost changes.
# Refer to Applied-Crypto-Hardening_bettercrypto/src/configuration/Webservers/Apache/default-ssl for details.
#}
<FilesMatch "\.(cgi|shtml|phtml|php)$">
    SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
    SSLOptions +StdEnvVars
</Directory>

BrowserMatch "MSIE [2-6]" \
    nokeepalive ssl-unclean-shutdown \
    downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown

# TLS cipher suites set: {{ item.tls_cipher_suite_set_name|d(apache__tls_cipher_suite_set_name) | quote }}
SSLCipherSuite       {{ apache__tls_cipher_suite_sets[item.tls_cipher_suite_set_name|d(apache__tls_cipher_suite_set_name)] | quote }}
SSLProtocol          {{ item.tls_protocols|d(apache__tls_protocols) | join(" ") }}
SSLHonorCipherOrder  {{ item.tls_honor_cipher_order|d(apache__tls_honor_cipher_order) | quote }}
SSLCompression       {{ item.tls_compression|d(apache__tls_compression) | quote }}

{#
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_Apache2.html
# https://github.com/sovereign/sovereign/issues/373
#}
{% if get_apache_version() is version_compare("2.4.8", ">=") and get_openssl_version() is version_compare("1.0.2", ">=") %}
# SSLOpenSSLConfCmd DHParameters {{ item.tls_dhparam_file|d(apache__tls_dhparam_file) | quote }}
{% endif %}
{% endmacro %}
{% macro get_https_directives(item) %}{# [[[ #}
SSLEngine on

{{ get_default_tls_directives(item) }}
{% set apache__tpl_pki_realm = item.pki_realm | d((get_realm_yaml_list(item.name, apache__pki_realm) | from_yaml)[0]) %}
{% set apache__tpl_pki_realm_path = apache__pki_realm_path + "/" + apache__tpl_pki_realm %}
SSLCertificateFile      {{ item.tls_crt | d(apache__tpl_pki_realm_path + "/" + (item.pki_crt|d(apache__pki_crt_filename))) | quote }}
SSLCertificateKeyFile   {{ item.tls_key | d(apache__tpl_pki_realm_path + "/" + (item.pki_key|d(apache__pki_key_filename))) | quote }}
{% if item.ocsp_stapling_enabled|d(apache__ocsp_stapling_enabled)|bool and get_openssl_version() is version_compare("0.9.8h", ">=") %}
SSLUseStapling on
{% endif %}

{% if item.hsts_enabled|d(apache__hsts_enabled) | bool %}
Header always set Strict-Transport-Security "max-age={{ item.hsts_max_age|d(apache__hsts_max_age) }}{{ "; includeSubDomains" if item.hsts_subdomains|d(apache__hsts_subdomains) | bool else "" }}{{ "; preload" if ((item.hsts_preload|d(apache__hsts_preload)) | bool) else "" }}"
{% endif %}
{% endmacro %}
{% macro get_http_security_headers(item) %}{# [[[ #}
{% set apache__tpl_directive_options = get_yaml_list_for_elem(item.http_sec_headers_directive_options|d(apache__http_sec_headers_directive_options)) | from_yaml | map("quote") | join(" ") %}
{% if item.csp_enabled|d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy "{{ item.csp|d("default-src https: ;") + (" " + item.csp_append|d(apache__http_csp_append) if (item.csp_append|d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.csp_report_enabled|d(False) | bool %}
Header {{ apache__tpl_directive_options }} Content-Security-Policy-Report-Only "{{ item.csp_report|d(item.csp|d("default-src https: ;")) + (" " + item.csp_append|d(apache__http_csp_append) if (item.csp_append|d(apache__http_csp_append)) else "") }}"
{% endif %}
{% if item.http_frame_options|d(apache__http_frame_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Frame-Options "{{ item.http_frame_options|d(apache__http_frame_options) }}"
{% endif %}
{% if item.http_xss_protection|d(apache__http_xss_protection) != omit %}
Header {{ apache__tpl_directive_options }} X-XSS-Protection "{{ item.http_xss_protection|d(apache__http_xss_protection) }}"
{% endif %}
{% if item.http_referrer_policy|d(apache__http_referrer_policy) != omit %}
Header {{ apache__tpl_directive_options }} Referrer-Policy "{{ item.http_referrer_policy|d(apache__http_referrer_policy) }}"
{% endif %}
{% if item.http_content_type_options|d(apache__http_content_type_options) != omit %}
Header {{ apache__tpl_directive_options }} X-Content-Type-Options "{{ item.http_content_type_options|d(apache__http_content_type_options) }}"
{% endif %}
{% endmacro %}
{% macro get_common_headers(item) %}{# [[[ #}
{% if item.http_clacks_overhead|d(apache__http_clacks_overhead|d(True)) | bool %}
{#
# Respect the will of the DebOps Creator.
# Ref: https://github.com/debops/ansible-nginx/commit/d6cd455c68a7584b2592053fd98d3e539054e09a
#}
Header always set X-Clacks-Overhead "GNU Terry Pratchett"
{% endif %}
{% endmacro %}
{% macro get_redirect(code, from, to, module) %}{# [[[ #}
{# Prefer the alias module but support the use of the rewrite module in case
other rewrite rules are used in the same context because the rewrite rules are
handled before all the directives from the alias module.
#}
{% set to = to + ("/" if (to[-1] != "/") else "") %}
{% if module == 'alias' %}
Redirect {{ code|string }} "{{ from }}" "{{ to }}"
{% elif module == 'rewrite' %}
<IfModule mod_rewrite>
RewriteRule "^{{ from }}?(.*)" "{{ to }}$1" [L,R={{ code }},NE]
</IfModule>
{% endif %}
{% endmacro %}
{% macro get_alias(url_path, fs_directory) %}{# [[[ #}
Alias {{ url_path | quote }} {{ (fs_directory + ("/" if (fs_directory[-1] != "/") else "")) | quote }}
{% endmacro %}

{#
# It seems that Apache does not allow to overwrite directives in the same context (server config):
# Examples: LogLevel
# It is probably not good practice to change that in server context anyway.
#}

MaxConnectionsPerChild {{ apache__mpm_max_connections_per_child | quote }}

ExtendedStatus {{ apache__status_extended_enabled|bool | ternary("On", "Off") }}


{% for module_name, value in apache__combined_modules|dictsort %}
{%   if value is mapping and 'config' in value %}

<IfModule {{ module_name }}_module>
    {{ value.config | indent(4) }}
</IfModule>
{%   endif %}
{% endfor %}

ServerTokens {{ apache__server_tokens }}
ServerSignature {{ apache__server_signature }}
TraceEnable {{ apache__trace_enabled }}
LogLevel {{ apache__log_level }}

{{ get_common_headers(item) | indent(4) }}

{# Don’t use apache__combined_modules because of heavy use of template macros. #}
<IfModule ssl_module>
{{ get_default_tls_directives(item.value|d({})) }}

{% if get_openssl_version() is version_compare("0.9.8h", ">=") %}
SSLStaplingCache {{ apache__ocsp_stapling_cache | quote }}
SSLStaplingResponseMaxAge {{ apache__ocsp_stapling_response_max_age|string }}
{%   if apache__ocsp_stapling_force_url %}
SSLStaplingForceURL {{ apache__ocsp_stapling_force_url | quote }}
{%   endif %}
{% endif %}
</IfModule>
