From 55add9716a79b9b8070cde20388a5d125f63a224 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 2 Nov 2026 04:58:53 +0000 Subject: [PATCH] dev2 webmail: calendar, tasks and contacts (CalDAV/CardDAV) - webmail/Dockerfile -> profullstack/webmail:1.7.5-cal1 (built on dev2): stock Roundcube 1.7.5 + kolab/calendar + kolab/tasklist 3.6.1 + roundcube/carddav 5.1.5. pear/http_request2 2.8.0 aliased as 2.5.1 (libkolab pins ~2.5.0, CVE-2025-43717); SKIP_DB_INIT at build; libkolab sqlite schema's duplicate ix_contact_type renamed. - anyhost: startup hook picks calendar/tasklist drivers per session: Forward Email -> caldav.forwardemail.net, any other server -> Roundcube database calendars. - carddav preset: Forward Email address book auto-added for FE sessions only. Co-Authored-By: Claude Opus 5.5 --- dev2/sites.d/webmail.profullstack.com.json | 6 +++--- dev2/webmail/Dockerfile | 24 ++++++++++++++++++++++ dev2/webmail/anyhost/anyhost.php | 22 ++++++++++++++++++++ dev2/webmail/carddav.config.inc.php | 20 ++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 dev2/webmail/Dockerfile create mode 100644 dev2/webmail/carddav.config.inc.php diff --git a/dev2/sites.d/webmail.profullstack.com.json b/dev2/sites.d/webmail.profullstack.com.json index 1ce592d..24a37d2 100644 --- a/dev2/sites.d/webmail.profullstack.com.json +++ b/dev2/sites.d/webmail.profullstack.com.json @@ -3,7 +3,7 @@ "service": "webmail.profullstack.com", "repo": null, "branch": null, - "image": "roundcube/roundcubemail:1.7.5-apache", + "image": "profullstack/webmail:1.7.5-cal1", "start_command": "apache2-foreground", "port_var": "80", "domains": [ @@ -48,9 +48,9 @@ "ROUNDCUBEMAIL_DEFAULT_PORT": "993", "ROUNDCUBEMAIL_SMTP_SERVER": "ssl://smtp.forwardemail.net", "ROUNDCUBEMAIL_SMTP_PORT": "465", - "ROUNDCUBEMAIL_PLUGINS": "anyhost,archive,zipdownload", + "ROUNDCUBEMAIL_PLUGINS": "anyhost,archive,zipdownload,libcalendaring,libkolab,calendar,tasklist,carddav", "ROUNDCUBEMAIL_SKIN": "elastic", "ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE": "25M" }, - "notes": "Not a Railway site: born on dev2 2026-11-02. Primary host webmail.profullstack.com (A -> dev2); webmail.pit.world kept as an alias. Cert by HTTP-01 (both names resolve to dev2). Roundcube webmail, stock image, no repo. Login form has a Server field: users sign in to ANY IMAP server (blank = Forward Email, imap.forwardemail.net). The anyhost plugin (cli-tools dev2/webmail/anyhost, copied to volumes/plugin-anyhost) turns a bare hostname into ssl://host:993, derives SMTP (imap.X -> smtp.X, else the same host; 465 then 587) and refuses private/loopback addresses so the form cannot probe dev2. volumes/config/zz-profullstack.inc.php holds des_key (minted on the box, never committed) and the imap_host override. mail.profullstack.com is NOT this site: it is the bbs box's Mailu MTA (PTR, SPF, cert)." + "notes": "Not a Railway site: born on dev2 2026-11-02. Primary host webmail.profullstack.com (A -> dev2); webmail.pit.world kept as an alias. Cert by HTTP-01 (both names resolve to dev2). Roundcube webmail, stock image, no repo. Login form has a Server field: users sign in to ANY IMAP server (blank = Forward Email, imap.forwardemail.net). The anyhost plugin (cli-tools dev2/webmail/anyhost, copied to volumes/plugin-anyhost) turns a bare hostname into ssl://host:993, derives SMTP (imap.X -> smtp.X, else the same host; 465 then 587) and refuses private/loopback addresses so the form cannot probe dev2. volumes/config/zz-profullstack.inc.php holds des_key (minted on the box, never committed) and the imap_host override. mail.profullstack.com is NOT this site: it is the bbs box's Mailu MTA (PTR, SPF, cert). Since 2026-11-02 the image is profullstack/webmail:, built ON dev2 from cli-tools dev2/webmail/Dockerfile (docker build -t profullstack/webmail: dev2/webmail; not on any registry, deploy pull fails harmlessly): stock 1.7.5 + Kolab calendar/tasklist 3.6.1 + RCMCardDAV 5.1.5. FE sessions sync caldav.forwardemail.net / carddav.forwardemail.net; other servers get database calendars. Plugin schemas loaded once into volumes/db with bin/updatedb.sh." } diff --git a/dev2/webmail/Dockerfile b/dev2/webmail/Dockerfile new file mode 100644 index 0000000..ac41a6a --- /dev/null +++ b/dev2/webmail/Dockerfile @@ -0,0 +1,24 @@ +# webmail.profullstack.com: stock Roundcube + calendar/tasks (Kolab, CalDAV) + contacts (RCMCardDAV). +# Built on dev2 as profullstack/webmail: (see README.md); the entrypoint copies +# /usr/src/roundcubemail into /var/www/html on every fresh container. +FROM roundcube/roundcubemail:1.7.5-apache + +# libkolab pins pear/http_request2 ~2.5.0, which is under CVE-2025-43717 (XSS, fixed +# 2.7.0); composer blocks it. Install the fixed 2.8.0 aliased as 2.5.1 instead of +# ignoring the advisory. + +# roundcube/plugin-installer runs each plugin's SQL on install; there is no database at +# build time (SKIP_DB_INIT). The live schema is created on the box (README.md). +RUN cd /usr/src/roundcubemail \ + && SKIP_DB_INIT=1 SKIP_DB_UPDATE=1 COMPOSER_ALLOW_SUPERUSER=1 composer require --no-interaction --no-progress --update-no-dev --prefer-dist \ + "pear/http_request2:2.8.0 as 2.5.1" \ + kolab/calendar:3.6.1 kolab/tasklist:3.6.1 roundcube/carddav:5.1.5 \ + && ls plugins + +# libkolab 3.6.1 sqlite.initial.sql creates ix_contact_type twice (kolab_cache_contact and +# kolab_cache_dav_contact); SQLite index names are per database, so init fails. +RUN sed -i 's/^CREATE INDEX ix_contact_type ON kolab_cache_dav_contact/CREATE INDEX ix_dav_contact_type ON kolab_cache_dav_contact/' \ + /usr/src/roundcubemail/plugins/libkolab/SQL/sqlite.initial.sql \ + && grep -c ix_dav_contact_type /usr/src/roundcubemail/plugins/libkolab/SQL/sqlite.initial.sql + +COPY --chmod=644 carddav.config.inc.php /usr/src/roundcubemail/plugins/carddav/config.inc.php diff --git a/dev2/webmail/anyhost/anyhost.php b/dev2/webmail/anyhost/anyhost.php index eeff6dc..6de2fa7 100644 --- a/dev2/webmail/anyhost/anyhost.php +++ b/dev2/webmail/anyhost/anyhost.php @@ -25,6 +25,28 @@ public function init() $this->add_hook('authenticate', [$this, 'authenticate']); $this->add_hook('smtp_connect', [$this, 'smtp_connect']); $this->add_hook('template_object_loginform', [$this, 'loginform']); + // calendar/tasklist load their own config.inc.php in init() (after this plugin) and + // pick their driver in startup; our startup hook is registered first, so it wins. + $this->add_hook('startup', function ($args) { $this->pick_dav_drivers(); return $args; }); + } + + /** + * Calendar + tasks backend for this session (anyhost loads before calendar/tasklist, + * so this runs first): Forward Email users sync with caldav.forwardemail.net; every + * other server gets Roundcube's own database calendar (Mailu has no CalDAV). + */ + private function pick_dav_drivers() + { + $config = rcmail::get_instance()->config; + if ((string) ($_SESSION['storage_host'] ?? '') === 'imap.forwardemail.net') { + $config->set('calendar_driver', 'caldav'); + $config->set('calendar_caldav_server', 'https://caldav.forwardemail.net'); + $config->set('tasklist_driver', 'caldav'); + $config->set('tasklist_caldav_server', 'https://caldav.forwardemail.net'); + } else { + $config->set('calendar_driver', 'database'); + $config->set('tasklist_driver', 'database'); + } } public function loginform($args) diff --git a/dev2/webmail/carddav.config.inc.php b/dev2/webmail/carddav.config.inc.php new file mode 100644 index 0000000..6dbb4fb --- /dev/null +++ b/dev2/webmail/carddav.config.inc.php @@ -0,0 +1,20 @@ + CardDAV. +$prefs['_GLOBAL']['pwstore_scheme'] = 'des_key'; +$prefs['_GLOBAL']['loglevel'] = \Psr\Log\LogLevel::WARNING; + +if ((string) ($_SESSION['storage_host'] ?? '') === 'imap.forwardemail.net') { + $prefs['forwardemail'] = [ + 'accountname' => 'Forward Email', + 'discovery_url' => 'https://carddav.forwardemail.net/dav/', + 'username' => '%u', + 'password' => '%p', + 'fixed' => ['discovery_url', 'username', 'password'], + 'refresh_time' => '00:15:00', + 'active' => true, + 'readonly' => false, + ]; +}