Repository: Mailu/Mailu

Issue:
Upgrade Roundcube to 1.5
## Before you open your issue
- [x] Check if no issue or pull-request for this already exists.
- [x] Check [documentation](https://mailu.io/master/) and [FAQ](https://mailu.io/master/faq.html). (Tip, use the search function on the documentation page)
- [x] You understand `Mailu` is made by volunteers in their **free time** — be conscise, civil and accept that delays can occur.
- [x] The title of the issue should be short and simple. It should contain specific terms related to the actual issue. Be specific while writing the title.

## Environment & Versions
### Environment
 - [x] docker-compose
 - [x] kubernetes
 - [x] docker swarm

### Versions
Since this is not a bug report, I’m not sure if you need my current version.

## Description
Currently, Mailu still uses version 1.4 of the Roundcube webmail client, which has recently received an update. Highlights of the 1.5 release are summarized in this [blogpost from roundcube.net](https://roundcube.net/news/2021/10/18/roundcube-1.5.0-released), though (for me) the most interesting are:
- OAuth2 support
- Elastic now comes with a dark mode

From a quick glance at the Dockerfile, it didn’t seem like there are any huge requirements, but I’ll give it a try later and see what happens. I’d be happy to contribute this change if it works, but I don’t have a full picture of the behind the scenes of the mailu stack yet, so I may need help if I walk into any traps.

Thank you all for your work with mailu! It’s an awesome project and really makes hosting a mailserver extremly easy, at least on a small scale.

#### Changlog
[2021-11-01 08:41 +00]: add thank you note


Discussion:
I did not know roundcube 1.5 was released. I see it was released only 2 weeks ago. We could have a look at including this with the next Mailu release. 
It should be a matter of updating the release tar.g.z url in the Dockerfile.
At the same time the carddav plugin should also be updated to the newest version:
https://github.com/mstilkerich/rcmcarddav/releases/download/v4.2.2/carddav-v4.2.2.tar.gz

Resolving patch:
diff --git a/webmails/roundcube/start.py b/webmails/roundcube/start.py
--- a/webmails/roundcube/start.py
+++ b/webmails/roundcube/start.py
@@ -1,61 +1,105 @@
 #!/usr/bin/python3
 
 import os
-import logging as log
+import logging
 import sys
 from socrate import conf
 import subprocess
+import hmac
 
-log.basicConfig(stream=sys.stderr, level=os.environ.get("LOG_LEVEL", "WARNING"))
+env = os.environ
 
-os.environ["MAX_FILESIZE"] = str(int(int(os.environ.get("MESSAGE_SIZE_LIMIT")) * 0.66 / 1048576))
+logging.basicConfig(stream=sys.stderr, level=env.get("LOG_LEVEL", "WARNING"))
 
-db_flavor = os.environ.get("ROUNDCUBE_DB_FLAVOR", "sqlite")
+# jinja context
+context = {}
+context.update(env)
+
+context["MAX_FILESIZE"] = str(int(int(env.get("MESSAGE_SIZE_LIMIT", "50000000")) * 0.66 / 1048576))
+
+db_flavor = env.get("ROUNDCUBE_DB_FLAVOR", "sqlite")
 if db_flavor == "sqlite":
-    os.environ["DB_DSNW"] = "sqlite:////data/roundcube.db"
+    context["DB_DSNW"] = "sqlite:////data/roundcube.db"
 elif db_flavor == "mysql":
-    os.environ["DB_DSNW"] = "mysql://%s:%s@%s/%s" % (
-        os.environ.get("ROUNDCUBE_DB_USER", "roundcube"),
-        os.environ.get("ROUNDCUBE_DB_PW"),
-        os.environ.get("ROUNDCUBE_DB_HOST", "database"),
-        os.environ.get("ROUNDCUBE_DB_NAME", "roundcube")
+    context["DB_DSNW"] = "mysql://%s:%s@%s/%s" % (
+        env.get("ROUNDCUBE_DB_USER", "roundcube"),
+        env.get("ROUNDCUBE_DB_PW"),
+        env.get("ROUNDCUBE_DB_HOST", "database"),
+        env.get("ROUNDCUBE_DB_NAME", "roundcube")
     )
 elif db_flavor == "postgresql":
-    os.environ["DB_DSNW"] = "pgsql://%s:%s@%s/%s" % (
-        os.environ.get("ROUNDCUBE_DB_USER", "roundcube"),
-        os.environ.get("ROUNDCUBE_DB_PW"),
-        os.environ.get("ROUNDCUBE_DB_HOST", "database"),
-        os.environ.get("ROUNDCUBE_DB_NAME", "roundcube")
+    context["DB_DSNW"] = "pgsql://%s:%s@%s/%s" % (
+        env.get("ROUNDCUBE_DB_USER", "roundcube"),
+        env.get("ROUNDCUBE_DB_PW"),
+        env.get("ROUNDCUBE_DB_HOST", "database"),
+        env.get("ROUNDCUBE_DB_NAME", "roundcube")
     )
 else:
-    print("Unknown ROUNDCUBE_DB_FLAVOR: %s", db_flavor)
+    print(f"Unknown ROUNDCUBE_DB_FLAVOR: {db_flavor}", file=sys.stderr)
     exit(1)
 
-conf.jinja("/php.ini", os.environ, "/usr/local/etc/php/conf.d/roundcube.ini")
+# derive roundcube secret key
+secret_key = env.get("SECRET_KEY")
+if not secret_key:
+    try:
+        secret_key = open(env.get("SECRET_KEY_FILE"), "r").read().strip()
+    except Exception as exc:
+        print(f"Can't read SECRET_KEY from file: {exc}", file=sys.stderr)
+        exit(2)
+
+context['SECRET_KEY'] = hmac.new(bytearray(secret_key, 'utf-8'), bytearray('ROUNDCUBE_KEY', 'utf-8'), 'sha256').hexdigest()
+
+# roundcube plugins
+# (using "dict" because it is ordered and "set" is not)
+plugins = dict((p, None) for p in env.get("ROUNDCUBE_PLUGINS", "").replace(" ", "").split(",") if p and os.path.isdir(os.path.join("/var/www/plugins", p)))
+if plugins:
+    plugins["mailu"] = None
+else:
+    plugins = dict((k, None) for k in ["archive", "zipdownload", "markasjunk", "managesieve", "enigma", "carddav", "mailu"])
+
+context["PLUGINS"] = ",".join(f"'{p}'" for p in plugins)
 
-# Create dirs, setup permissions
+# add overrides
+context["INCLUDES"] = sorted(inc for inc in os.listdir("/overrides") if inc.endswith(".inc")) if os.path.isdir("/overrides") else []
+
+# create config files
+conf.jinja("/php.ini", context, "/usr/local/etc/php/conf.d/roundcube.ini")
+conf.jinja("/config.inc.php", context, "/var/www/html/config/config.inc.php")
+
+# create dirs
 os.system("mkdir -p /data/gpg")
 
+print("Initializing database")
 try:
-    print("Initializing database")
     result = subprocess.check_output(["/var/www/html/bin/initdb.sh", "--dir", "/var/www/html/SQL"],
                                      stderr=subprocess.STDOUT)
     print(result.decode())
-except subprocess.CalledProcessError as e:
-    if "already exists" in e.stdout.decode():
+except subprocess.CalledProcessError as exc:
+    err = exc.stdout.decode()
+    if "already exists" in err:
         print("Already initialzed")
     else:
-        print(e.stdout.decode())
-        quit(1)
+        print(err)
+        exit(3)
 
+print("Upgrading database")
 try:
-    print("Upgrading database")
     subprocess.check_call(["/var/www/html/bin/update.sh", "--version=?", "-y"], stderr=subprocess.STDOUT)
-except subprocess.CalledProcessError as e:
-    quit(1)
+except subprocess.CalledProcessError as exc:
+    exit(4)
+else:
+    print("Cleaning database")
+    try:
+        subprocess.check_call(["/var/www/html/bin/cleandb.sh"], stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as exc:
+        exit(5)
 
-# Setup database permissions
+# setup permissions
 os.system("chown -R www-data:www-data /data")
 
-# Run apache
-os.execv("/usr/local/bin/apache2-foreground", ["apache2-foreground"])
+# clean env
+[env.pop(key, None) for key in env.keys() if key == "SECRET_KEY" or key.startswith("ROUNDCUBE_")]
+
+# run apache
+os.execve("/usr/local/bin/apache2-foreground", ["apache2-foreground"], env)
+


Test patch: