We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Certdump: Make acme.json as a variable - enhancement
## Environment & Version

### Environment

- [x] docker-compose
- [ ] kubernetes
- [ ] docker swarm

### Version

- Version: `1.9`

## Description
I am using Traefik as reverse proxy and Traefik can handle multiple domain names for one host. For this reason i setup Traefik to store for each domain the certificates in different files, e. g. `acme-<domain>.json`
Certdumps script has a hardcoded point to acme.json in a specific folder. I mounted the volume of Traefik which contains the acme files.
Without editing the script in the running Certdump container i can't get the updated certificate that is done by Traefik

## Replication Steps
Store cert in a separate file.
traefik.yml:
```
certificatesResolvers:
  letsencrypt-<domain>:
    acme:
      email: certificate@example.com
      storage: /data/acme-<domain>.json
      tlsChallenge: {}
```
Using the cert resolver for Mailu.
Mailu docker-compose:
```
traefik.http.routers.mailu.tls.certresolver=letsencrypt-<domain>
```
Mount the acme.json folder to certdump
Mailu docker-compose:
```
certdumper:
    image: ${DOCKER_REGISTRY}/${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}traefik-certdumper:${MAILU_VERSION:-1.9}
    container_name: mailu_certdump-1.9
    restart: always
    environment:
      - DOMAIN=mail.<fqdn>
      - TRAEFIK_VERSION=${TRAEFIK_VERSION}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      # Folder, which contains the acme.json
      - "traefik_data:/traefik:ro"
      # Folder, where cert.pem and key.pem will be written
      - "certdumper-data-1.9:/output"
```
## Observed behaviour
In the Traefik folder is still an old acme.json file with expired certificates. Certdump always takes acme.json until I change it manually in `run.sh` script. Of course this works only until the container will be deployed again.
## Expected behaviour
Certdump should not take hardcoded acme.json
## Logs
Not necessary.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `core/admin/mailu/internal/views/auth.py`
Content:
```
1 from mailu import models, utils
2 from mailu.internal import internal, nginx
3 from flask import current_app as app
4 
5 import flask
6 import flask_login
7 import base64
8 import sqlalchemy.exc
9 import urllib
10 
11 @internal.route("/auth/email")
12 def nginx_authentication():
13     """ Main authentication endpoint for Nginx email server
14     """
15     client_ip = flask.request.headers["Client-Ip"]
16     headers = flask.request.headers
17     is_port_25 = headers["Auth-Port"] == '25'
18     if is_port_25 and headers['Auth-Method'] != 'none':
19         response = flask.Response()
20         response.headers['Auth-Status'] = 'AUTH not supported'
21         response.headers['Auth-Error-Code'] = '502 5.5.1'
22         utils.limiter.rate_limit_ip(client_ip)
23         return response
24     is_from_webmail = headers['Auth-Port'] in ['10143', '10025']
25     is_app_token = utils.is_app_token(headers.get('Auth-Pass',''))
26     if not is_from_webmail and not is_port_25 and not is_app_token and utils.limiter.should_rate_limit_ip(client_ip):
27         status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')
28         response = flask.Response()
29         response.headers['Auth-Status'] = status
30         response.headers['Auth-Error-Code'] = code
31         if int(flask.request.headers['Auth-Login-Attempt']) < 10:
32             response.headers['Auth-Wait'] = '3'
33         return response
34     raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''
35     headers = nginx.handle_authentication(flask.request.headers)
36     response = flask.Response()
37     for key, value in headers.items():
38         response.headers[key] = str(value)
39     is_valid_user = False
40     username = response.headers.get('Auth-User', None)
41     if response.headers.get("Auth-User-Exists") == "True":
42         if not is_app_token and utils.limiter.should_rate_limit_user(username, client_ip):
43             # FIXME could be done before handle_authentication()
44             status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')
45             response = flask.Response()
46             response.headers['Auth-Status'] = status
47             response.headers['Auth-Error-Code'] = code
48             if int(flask.request.headers['Auth-Login-Attempt']) < 10:
49                 response.headers['Auth-Wait'] = '3'
50             return response
51         is_valid_user = True
52     if headers.get("Auth-Status") == "OK":
53         # successful email delivery isn't enough to warrant an exemption
54         if not is_port_25:
55             utils.limiter.exempt_ip_from_ratelimits(client_ip)
56     elif is_valid_user:
57         password = None
58         try:
59             password = raw_password.encode("iso8859-1").decode("utf8")
60         except:
61             app.logger.warn(f'Received undecodable password for {username} from nginx: {raw_password!r}')
62             utils.limiter.rate_limit_user(username, client_ip, password=None)
63         else:
64             utils.limiter.rate_limit_user(username, client_ip, password=password)
65     elif not is_from_webmail:
66         utils.limiter.rate_limit_ip(client_ip, username)
67     return response
68 
69 @internal.route("/auth/admin")
70 def admin_authentication():
71     """ Fails if the user is not an authenticated admin.
72     """
73     if (not flask_login.current_user.is_anonymous
74         and flask_login.current_user.global_admin
75         and flask_login.current_user.enabled):
76         return ""
77     return flask.abort(403)
78 
79 @internal.route("/auth/user")
80 def user_authentication():
81     """ Fails if the user is not authenticated.
82     """
83     if (not flask_login.current_user.is_anonymous
84         and flask_login.current_user.enabled):
85         response = flask.Response()
86         email = flask_login.current_user.get_id()
87         response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, email, "")
88         response.headers["X-User-Token"] = utils.gen_temp_token(email, flask.session)
89         return response
90     return flask.abort(403)
91 
92 
93 @internal.route("/auth/basic")
94 def basic_authentication():
95     """ Tries to authenticate using the Authorization header.
96     """
97     client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)
98     if utils.limiter.should_rate_limit_ip(client_ip):
99         response = flask.Response(status=401)
100         response.headers["WWW-Authenticate"] = 'Basic realm="Authentication rate limit from one source exceeded"'
101         response.headers['Retry-After'] = '60'
102         return response
103     authorization = flask.request.headers.get("Authorization")
104     if authorization and authorization.startswith("Basic "):
105         encoded = authorization.replace("Basic ", "")
106         user_email, password = base64.b64decode(encoded).split(b":", 1)
107         user_email = user_email.decode("utf8")
108         if utils.limiter.should_rate_limit_user(user_email, client_ip):
109             response = flask.Response(status=401)
110             response.headers["WWW-Authenticate"] = 'Basic realm="Authentication rate limit for this username exceeded"'
111             response.headers['Retry-After'] = '60'
112             return response
113         try:
114             user = models.User.query.get(user_email) if '@' in user_email else None
115         except sqlalchemy.exc.StatementError as exc:
116             exc = str(exc).split('\n', 1)[0]
117             app.logger.warn(f'Invalid user {user_email!r}: {exc}')
118         else:
119             if user is not None and nginx.check_credentials(user, password.decode('utf-8'), client_ip, "web", flask.request.headers.get('X-Real-Port', None), user_email):
120                 response = flask.Response()
121                 response.headers["X-User"] = models.IdnaEmail.process_bind_param(flask_login, user.email, "")
122                 utils.limiter.exempt_ip_from_ratelimits(client_ip)
123                 return response
124             # We failed check_credentials
125             utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip, user_email)
126     response = flask.Response(status=401)
127     response.headers["WWW-Authenticate"] = 'Basic realm="Login Required"'
128     return response
129
```

Path: `core/nginx/letsencrypt.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 import os
4 import time
5 import subprocess
6 
7 hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))
8 
9 command = [
10     "certbot",
11     "-n", "--agree-tos", # non-interactive
12     "-d", hostnames, "--expand", "--allow-subset-of-names",
13     "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
14     "certonly", "--standalone",
15     "--cert-name", "mailu",
16     "--preferred-challenges", "http", "--http-01-port", "8008",
17     "--keep-until-expiring",
18     "--allow-subset-of-names",
19     "--renew-with-new-domains",
20     "--config-dir", "/certs/letsencrypt",
21     "--post-hook", "/config.py"
22 ]
23 command2 = [
24     "certbot",
25     "-n", "--agree-tos", # non-interactive
26     "-d", hostnames, "--expand", "--allow-subset-of-names",
27     "-m", "{}@{}".format(os.environ["POSTMASTER"], os.environ["DOMAIN"]),
28     "certonly", "--standalone",
29     "--cert-name", "mailu-ecdsa",
30     "--preferred-challenges", "http", "--http-01-port", "8008",
31     "--keep-until-expiring",
32     "--allow-subset-of-names",
33     "--key-type", "ecdsa",
34     "--renew-with-new-domains",
35     "--config-dir", "/certs/letsencrypt",
36     "--post-hook", "/config.py"
37 ]
38 
39 # Wait for nginx to start
40 time.sleep(5)
41 
42 # Run certbot every day
43 while True:
44     subprocess.call(command)
45     subprocess.call(command2)
46     time.sleep(86400)
47
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py
--- a/core/admin/mailu/internal/views/auth.py
+++ b/core/admin/mailu/internal/views/auth.py
@@ -28,8 +28,6 @@
         response = flask.Response()
         response.headers['Auth-Status'] = status
         response.headers['Auth-Error-Code'] = code
-        if int(flask.request.headers['Auth-Login-Attempt']) < 10:
-            response.headers['Auth-Wait'] = '3'
         return response
     raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''
     headers = nginx.handle_authentication(flask.request.headers)
@@ -45,8 +43,6 @@
             response = flask.Response()
             response.headers['Auth-Status'] = status
             response.headers['Auth-Error-Code'] = code
-            if int(flask.request.headers['Auth-Login-Attempt']) < 10:
-                response.headers['Auth-Wait'] = '3'
             return response
         is_valid_user = True
     if headers.get("Auth-Status") == "OK":
diff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py
--- a/core/nginx/letsencrypt.py
+++ b/core/nginx/letsencrypt.py
@@ -1,9 +1,15 @@
 #!/usr/bin/env python3
 
+import logging as log
 import os
-import time
+import requests
+import sys
 import subprocess
+import time
+from threading import Thread
+from http.server import HTTPServer, SimpleHTTPRequestHandler
 
+log.basicConfig(stream=sys.stderr, level="WARNING")
 hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))
 
 command = [
@@ -39,8 +45,34 @@
 # Wait for nginx to start
 time.sleep(5)
 
+class MyRequestHandler(SimpleHTTPRequestHandler):
+    def do_GET(self):
+        if self.path == '/testing':
+            self.send_response(204)
+        else:
+            self.send_response(404)
+        self.send_header('Content-Type', 'text/plain')
+        self.end_headers()
+
+def serve_one_request():
+    with HTTPServer(("0.0.0.0", 8008), MyRequestHandler) as server:
+        server.handle_request()
+
 # Run certbot every day
 while True:
+    while True:
+        hostname = os.environ['HOSTNAMES'].split(' ')[0]
+        target = f'http://{hostname}/.well-known/acme-challenge/testing'
+        thread = Thread(target=serve_one_request)
+        thread.start()
+        r = requests.get(target)
+        if r.status_code != 204:
+            log.error(f"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.")
+            time.sleep(5)
+        else:
+            break
+        thread.join()
+
     subprocess.call(command)
     subprocess.call(command2)
     time.sleep(86400)

{"golden_diff": "diff --git a/core/admin/mailu/internal/views/auth.py b/core/admin/mailu/internal/views/auth.py\n--- a/core/admin/mailu/internal/views/auth.py\n+++ b/core/admin/mailu/internal/views/auth.py\n@@ -28,8 +28,6 @@\n         response = flask.Response()\n         response.headers['Auth-Status'] = status\n         response.headers['Auth-Error-Code'] = code\n-        if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n-            response.headers['Auth-Wait'] = '3'\n         return response\n     raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''\n     headers = nginx.handle_authentication(flask.request.headers)\n@@ -45,8 +43,6 @@\n             response = flask.Response()\n             response.headers['Auth-Status'] = status\n             response.headers['Auth-Error-Code'] = code\n-            if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n-                response.headers['Auth-Wait'] = '3'\n             return response\n         is_valid_user = True\n     if headers.get(\"Auth-Status\") == \"OK\":\ndiff --git a/core/nginx/letsencrypt.py b/core/nginx/letsencrypt.py\n--- a/core/nginx/letsencrypt.py\n+++ b/core/nginx/letsencrypt.py\n@@ -1,9 +1,15 @@\n #!/usr/bin/env python3\n \n+import logging as log\n import os\n-import time\n+import requests\n+import sys\n import subprocess\n+import time\n+from threading import Thread\n+from http.server import HTTPServer, SimpleHTTPRequestHandler\n \n+log.basicConfig(stream=sys.stderr, level=\"WARNING\")\n hostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))\n \n command = [\n@@ -39,8 +45,34 @@\n # Wait for nginx to start\n time.sleep(5)\n \n+class MyRequestHandler(SimpleHTTPRequestHandler):\n+    def do_GET(self):\n+        if self.path == '/testing':\n+            self.send_response(204)\n+        else:\n+            self.send_response(404)\n+        self.send_header('Content-Type', 'text/plain')\n+        self.end_headers()\n+\n+def serve_one_request():\n+    with HTTPServer((\"0.0.0.0\", 8008), MyRequestHandler) as server:\n+        server.handle_request()\n+\n # Run certbot every day\n while True:\n+    while True:\n+        hostname = os.environ['HOSTNAMES'].split(' ')[0]\n+        target = f'http://{hostname}/.well-known/acme-challenge/testing'\n+        thread = Thread(target=serve_one_request)\n+        thread.start()\n+        r = requests.get(target)\n+        if r.status_code != 204:\n+            log.error(f\"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.\")\n+            time.sleep(5)\n+        else:\n+            break\n+        thread.join()\n+\n     subprocess.call(command)\n     subprocess.call(command2)\n     time.sleep(86400)\n", "issue": "Certdump: Make acme.json as a variable - enhancement\n## Environment & Version\r\n\r\n### Environment\r\n\r\n- [x] docker-compose\r\n- [ ] kubernetes\r\n- [ ] docker swarm\r\n\r\n### Version\r\n\r\n- Version: `1.9`\r\n\r\n## Description\r\nI am using Traefik as reverse proxy and Traefik can handle multiple domain names for one host. For this reason i setup Traefik to store for each domain the certificates in different files, e. g. `acme-<domain>.json`\r\nCertdumps script has a hardcoded point to acme.json in a specific folder. I mounted the volume of Traefik which contains the acme files.\r\nWithout editing the script in the running Certdump container i can't get the updated certificate that is done by Traefik\r\n\r\n## Replication Steps\r\nStore cert in a separate file.\r\ntraefik.yml:\r\n```\r\ncertificatesResolvers:\r\n  letsencrypt-<domain>:\r\n    acme:\r\n      email: certificate@example.com\r\n      storage: /data/acme-<domain>.json\r\n      tlsChallenge: {}\r\n```\r\nUsing the cert resolver for Mailu.\r\nMailu docker-compose:\r\n```\r\ntraefik.http.routers.mailu.tls.certresolver=letsencrypt-<domain>\r\n```\r\nMount the acme.json folder to certdump\r\nMailu docker-compose:\r\n```\r\ncertdumper:\r\n    image: ${DOCKER_REGISTRY}/${DOCKER_ORG:-mailu}/${DOCKER_PREFIX:-}traefik-certdumper:${MAILU_VERSION:-1.9}\r\n    container_name: mailu_certdump-1.9\r\n    restart: always\r\n    environment:\r\n      - DOMAIN=mail.<fqdn>\r\n      - TRAEFIK_VERSION=${TRAEFIK_VERSION}\r\n    volumes:\r\n      - /etc/localtime:/etc/localtime:ro\r\n      # Folder, which contains the acme.json\r\n      - \"traefik_data:/traefik:ro\"\r\n      # Folder, where cert.pem and key.pem will be written\r\n      - \"certdumper-data-1.9:/output\"\r\n```\r\n## Observed behaviour\r\nIn the Traefik folder is still an old acme.json file with expired certificates. Certdump always takes acme.json until I change it manually in `run.sh` script. Of course this works only until the container will be deployed again.\r\n## Expected behaviour\r\nCertdump should not take hardcoded acme.json\r\n## Logs\r\nNot necessary.\r\n\n", "before_files": [{"content": "from mailu import models, utils\nfrom mailu.internal import internal, nginx\nfrom flask import current_app as app\n\nimport flask\nimport flask_login\nimport base64\nimport sqlalchemy.exc\nimport urllib\n\n@internal.route(\"/auth/email\")\ndef nginx_authentication():\n    \"\"\" Main authentication endpoint for Nginx email server\n    \"\"\"\n    client_ip = flask.request.headers[\"Client-Ip\"]\n    headers = flask.request.headers\n    is_port_25 = headers[\"Auth-Port\"] == '25'\n    if is_port_25 and headers['Auth-Method'] != 'none':\n        response = flask.Response()\n        response.headers['Auth-Status'] = 'AUTH not supported'\n        response.headers['Auth-Error-Code'] = '502 5.5.1'\n        utils.limiter.rate_limit_ip(client_ip)\n        return response\n    is_from_webmail = headers['Auth-Port'] in ['10143', '10025']\n    is_app_token = utils.is_app_token(headers.get('Auth-Pass',''))\n    if not is_from_webmail and not is_port_25 and not is_app_token and utils.limiter.should_rate_limit_ip(client_ip):\n        status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n        response = flask.Response()\n        response.headers['Auth-Status'] = status\n        response.headers['Auth-Error-Code'] = code\n        if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n            response.headers['Auth-Wait'] = '3'\n        return response\n    raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''\n    headers = nginx.handle_authentication(flask.request.headers)\n    response = flask.Response()\n    for key, value in headers.items():\n        response.headers[key] = str(value)\n    is_valid_user = False\n    username = response.headers.get('Auth-User', None)\n    if response.headers.get(\"Auth-User-Exists\") == \"True\":\n        if not is_app_token and utils.limiter.should_rate_limit_user(username, client_ip):\n            # FIXME could be done before handle_authentication()\n            status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n            response = flask.Response()\n            response.headers['Auth-Status'] = status\n            response.headers['Auth-Error-Code'] = code\n            if int(flask.request.headers['Auth-Login-Attempt']) < 10:\n                response.headers['Auth-Wait'] = '3'\n            return response\n        is_valid_user = True\n    if headers.get(\"Auth-Status\") == \"OK\":\n        # successful email delivery isn't enough to warrant an exemption\n        if not is_port_25:\n            utils.limiter.exempt_ip_from_ratelimits(client_ip)\n    elif is_valid_user:\n        password = None\n        try:\n            password = raw_password.encode(\"iso8859-1\").decode(\"utf8\")\n        except:\n            app.logger.warn(f'Received undecodable password for {username} from nginx: {raw_password!r}')\n            utils.limiter.rate_limit_user(username, client_ip, password=None)\n        else:\n            utils.limiter.rate_limit_user(username, client_ip, password=password)\n    elif not is_from_webmail:\n        utils.limiter.rate_limit_ip(client_ip, username)\n    return response\n\n@internal.route(\"/auth/admin\")\ndef admin_authentication():\n    \"\"\" Fails if the user is not an authenticated admin.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.global_admin\n        and flask_login.current_user.enabled):\n        return \"\"\n    return flask.abort(403)\n\n@internal.route(\"/auth/user\")\ndef user_authentication():\n    \"\"\" Fails if the user is not authenticated.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.enabled):\n        response = flask.Response()\n        email = flask_login.current_user.get_id()\n        response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, email, \"\")\n        response.headers[\"X-User-Token\"] = utils.gen_temp_token(email, flask.session)\n        return response\n    return flask.abort(403)\n\n\n@internal.route(\"/auth/basic\")\ndef basic_authentication():\n    \"\"\" Tries to authenticate using the Authorization header.\n    \"\"\"\n    client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        response = flask.Response(status=401)\n        response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit from one source exceeded\"'\n        response.headers['Retry-After'] = '60'\n        return response\n    authorization = flask.request.headers.get(\"Authorization\")\n    if authorization and authorization.startswith(\"Basic \"):\n        encoded = authorization.replace(\"Basic \", \"\")\n        user_email, password = base64.b64decode(encoded).split(b\":\", 1)\n        user_email = user_email.decode(\"utf8\")\n        if utils.limiter.should_rate_limit_user(user_email, client_ip):\n            response = flask.Response(status=401)\n            response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit for this username exceeded\"'\n            response.headers['Retry-After'] = '60'\n            return response\n        try:\n            user = models.User.query.get(user_email) if '@' in user_email else None\n        except sqlalchemy.exc.StatementError as exc:\n            exc = str(exc).split('\\n', 1)[0]\n            app.logger.warn(f'Invalid user {user_email!r}: {exc}')\n        else:\n            if user is not None and nginx.check_credentials(user, password.decode('utf-8'), client_ip, \"web\", flask.request.headers.get('X-Real-Port', None), user_email):\n                response = flask.Response()\n                response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, user.email, \"\")\n                utils.limiter.exempt_ip_from_ratelimits(client_ip)\n                return response\n            # We failed check_credentials\n            utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip, user_email)\n    response = flask.Response(status=401)\n    response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Login Required\"'\n    return response\n", "path": "core/admin/mailu/internal/views/auth.py"}, {"content": "#!/usr/bin/env python3\n\nimport os\nimport time\nimport subprocess\n\nhostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))\n\ncommand = [\n    \"certbot\",\n    \"-n\", \"--agree-tos\", # non-interactive\n    \"-d\", hostnames, \"--expand\", \"--allow-subset-of-names\",\n    \"-m\", \"{}@{}\".format(os.environ[\"POSTMASTER\"], os.environ[\"DOMAIN\"]),\n    \"certonly\", \"--standalone\",\n    \"--cert-name\", \"mailu\",\n    \"--preferred-challenges\", \"http\", \"--http-01-port\", \"8008\",\n    \"--keep-until-expiring\",\n    \"--allow-subset-of-names\",\n    \"--renew-with-new-domains\",\n    \"--config-dir\", \"/certs/letsencrypt\",\n    \"--post-hook\", \"/config.py\"\n]\ncommand2 = [\n    \"certbot\",\n    \"-n\", \"--agree-tos\", # non-interactive\n    \"-d\", hostnames, \"--expand\", \"--allow-subset-of-names\",\n    \"-m\", \"{}@{}\".format(os.environ[\"POSTMASTER\"], os.environ[\"DOMAIN\"]),\n    \"certonly\", \"--standalone\",\n    \"--cert-name\", \"mailu-ecdsa\",\n    \"--preferred-challenges\", \"http\", \"--http-01-port\", \"8008\",\n    \"--keep-until-expiring\",\n    \"--allow-subset-of-names\",\n    \"--key-type\", \"ecdsa\",\n    \"--renew-with-new-domains\",\n    \"--config-dir\", \"/certs/letsencrypt\",\n    \"--post-hook\", \"/config.py\"\n]\n\n# Wait for nginx to start\ntime.sleep(5)\n\n# Run certbot every day\nwhile True:\n    subprocess.call(command)\n    subprocess.call(command2)\n    time.sleep(86400)\n", "path": "core/nginx/letsencrypt.py"}], "after_files": [{"content": "from mailu import models, utils\nfrom mailu.internal import internal, nginx\nfrom flask import current_app as app\n\nimport flask\nimport flask_login\nimport base64\nimport sqlalchemy.exc\nimport urllib\n\n@internal.route(\"/auth/email\")\ndef nginx_authentication():\n    \"\"\" Main authentication endpoint for Nginx email server\n    \"\"\"\n    client_ip = flask.request.headers[\"Client-Ip\"]\n    headers = flask.request.headers\n    is_port_25 = headers[\"Auth-Port\"] == '25'\n    if is_port_25 and headers['Auth-Method'] != 'none':\n        response = flask.Response()\n        response.headers['Auth-Status'] = 'AUTH not supported'\n        response.headers['Auth-Error-Code'] = '502 5.5.1'\n        utils.limiter.rate_limit_ip(client_ip)\n        return response\n    is_from_webmail = headers['Auth-Port'] in ['10143', '10025']\n    is_app_token = utils.is_app_token(headers.get('Auth-Pass',''))\n    if not is_from_webmail and not is_port_25 and not is_app_token and utils.limiter.should_rate_limit_ip(client_ip):\n        status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n        response = flask.Response()\n        response.headers['Auth-Status'] = status\n        response.headers['Auth-Error-Code'] = code\n        return response\n    raw_password = urllib.parse.unquote(headers['Auth-Pass']) if 'Auth-Pass' in headers else ''\n    headers = nginx.handle_authentication(flask.request.headers)\n    response = flask.Response()\n    for key, value in headers.items():\n        response.headers[key] = str(value)\n    is_valid_user = False\n    username = response.headers.get('Auth-User', None)\n    if response.headers.get(\"Auth-User-Exists\") == \"True\":\n        if not is_app_token and utils.limiter.should_rate_limit_user(username, client_ip):\n            # FIXME could be done before handle_authentication()\n            status, code = nginx.get_status(flask.request.headers['Auth-Protocol'], 'ratelimit')\n            response = flask.Response()\n            response.headers['Auth-Status'] = status\n            response.headers['Auth-Error-Code'] = code\n            return response\n        is_valid_user = True\n    if headers.get(\"Auth-Status\") == \"OK\":\n        # successful email delivery isn't enough to warrant an exemption\n        if not is_port_25:\n            utils.limiter.exempt_ip_from_ratelimits(client_ip)\n    elif is_valid_user:\n        password = None\n        try:\n            password = raw_password.encode(\"iso8859-1\").decode(\"utf8\")\n        except:\n            app.logger.warn(f'Received undecodable password for {username} from nginx: {raw_password!r}')\n            utils.limiter.rate_limit_user(username, client_ip, password=None)\n        else:\n            utils.limiter.rate_limit_user(username, client_ip, password=password)\n    elif not is_from_webmail:\n        utils.limiter.rate_limit_ip(client_ip, username)\n    return response\n\n@internal.route(\"/auth/admin\")\ndef admin_authentication():\n    \"\"\" Fails if the user is not an authenticated admin.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.global_admin\n        and flask_login.current_user.enabled):\n        return \"\"\n    return flask.abort(403)\n\n@internal.route(\"/auth/user\")\ndef user_authentication():\n    \"\"\" Fails if the user is not authenticated.\n    \"\"\"\n    if (not flask_login.current_user.is_anonymous\n        and flask_login.current_user.enabled):\n        response = flask.Response()\n        email = flask_login.current_user.get_id()\n        response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, email, \"\")\n        response.headers[\"X-User-Token\"] = utils.gen_temp_token(email, flask.session)\n        return response\n    return flask.abort(403)\n\n\n@internal.route(\"/auth/basic\")\ndef basic_authentication():\n    \"\"\" Tries to authenticate using the Authorization header.\n    \"\"\"\n    client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr)\n    if utils.limiter.should_rate_limit_ip(client_ip):\n        response = flask.Response(status=401)\n        response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit from one source exceeded\"'\n        response.headers['Retry-After'] = '60'\n        return response\n    authorization = flask.request.headers.get(\"Authorization\")\n    if authorization and authorization.startswith(\"Basic \"):\n        encoded = authorization.replace(\"Basic \", \"\")\n        user_email, password = base64.b64decode(encoded).split(b\":\", 1)\n        user_email = user_email.decode(\"utf8\")\n        if utils.limiter.should_rate_limit_user(user_email, client_ip):\n            response = flask.Response(status=401)\n            response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Authentication rate limit for this username exceeded\"'\n            response.headers['Retry-After'] = '60'\n            return response\n        try:\n            user = models.User.query.get(user_email) if '@' in user_email else None\n        except sqlalchemy.exc.StatementError as exc:\n            exc = str(exc).split('\\n', 1)[0]\n            app.logger.warn(f'Invalid user {user_email!r}: {exc}')\n        else:\n            if user is not None and nginx.check_credentials(user, password.decode('utf-8'), client_ip, \"web\", flask.request.headers.get('X-Real-Port', None), user_email):\n                response = flask.Response()\n                response.headers[\"X-User\"] = models.IdnaEmail.process_bind_param(flask_login, user.email, \"\")\n                utils.limiter.exempt_ip_from_ratelimits(client_ip)\n                return response\n            # We failed check_credentials\n            utils.limiter.rate_limit_user(user_email, client_ip) if user else utils.limiter.rate_limit_ip(client_ip, user_email)\n    response = flask.Response(status=401)\n    response.headers[\"WWW-Authenticate\"] = 'Basic realm=\"Login Required\"'\n    return response\n", "path": "core/admin/mailu/internal/views/auth.py"}, {"content": "#!/usr/bin/env python3\n\nimport logging as log\nimport os\nimport requests\nimport sys\nimport subprocess\nimport time\nfrom threading import Thread\nfrom http.server import HTTPServer, SimpleHTTPRequestHandler\n\nlog.basicConfig(stream=sys.stderr, level=\"WARNING\")\nhostnames = ','.join(set(host.strip() for host in os.environ['HOSTNAMES'].split(',')))\n\ncommand = [\n    \"certbot\",\n    \"-n\", \"--agree-tos\", # non-interactive\n    \"-d\", hostnames, \"--expand\", \"--allow-subset-of-names\",\n    \"-m\", \"{}@{}\".format(os.environ[\"POSTMASTER\"], os.environ[\"DOMAIN\"]),\n    \"certonly\", \"--standalone\",\n    \"--cert-name\", \"mailu\",\n    \"--preferred-challenges\", \"http\", \"--http-01-port\", \"8008\",\n    \"--keep-until-expiring\",\n    \"--allow-subset-of-names\",\n    \"--renew-with-new-domains\",\n    \"--config-dir\", \"/certs/letsencrypt\",\n    \"--post-hook\", \"/config.py\"\n]\ncommand2 = [\n    \"certbot\",\n    \"-n\", \"--agree-tos\", # non-interactive\n    \"-d\", hostnames, \"--expand\", \"--allow-subset-of-names\",\n    \"-m\", \"{}@{}\".format(os.environ[\"POSTMASTER\"], os.environ[\"DOMAIN\"]),\n    \"certonly\", \"--standalone\",\n    \"--cert-name\", \"mailu-ecdsa\",\n    \"--preferred-challenges\", \"http\", \"--http-01-port\", \"8008\",\n    \"--keep-until-expiring\",\n    \"--allow-subset-of-names\",\n    \"--key-type\", \"ecdsa\",\n    \"--renew-with-new-domains\",\n    \"--config-dir\", \"/certs/letsencrypt\",\n    \"--post-hook\", \"/config.py\"\n]\n\n# Wait for nginx to start\ntime.sleep(5)\n\nclass MyRequestHandler(SimpleHTTPRequestHandler):\n    def do_GET(self):\n        if self.path == '/testing':\n            self.send_response(204)\n        else:\n            self.send_response(404)\n        self.send_header('Content-Type', 'text/plain')\n        self.end_headers()\n\ndef serve_one_request():\n    with HTTPServer((\"0.0.0.0\", 8008), MyRequestHandler) as server:\n        server.handle_request()\n\n# Run certbot every day\nwhile True:\n    while True:\n        hostname = os.environ['HOSTNAMES'].split(' ')[0]\n        target = f'http://{hostname}/.well-known/acme-challenge/testing'\n        thread = Thread(target=serve_one_request)\n        thread.start()\n        r = requests.get(target)\n        if r.status_code != 204:\n            log.error(f\"Can't reach {target}!, please ensure it's fixed or change the TLS_FLAVOR.\")\n            time.sleep(5)\n        else:\n            break\n        thread.join()\n\n    subprocess.call(command)\n    subprocess.call(command2)\n    time.sleep(86400)\n", "path": "core/nginx/letsencrypt.py"}]}