From 920c0c064e008f2876883b64055c42b961616e56 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 2 Feb 2017 10:09:10 +0100 Subject: [PATCH 01/20] Use static IPs for Rspamd and SOGo to not emerg Nginx, start Rspamd after Nginx, add health check for SQL --- docker-compose.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 921ecaf0..34b917f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,9 @@ version: '2.1' services: pdns-mailcow: image: andryyy/mailcow-dockerized:pdns + depends_on: + mysql-mailcow: + condition: service_healthy volumes: - ./data/conf/pdns/:/etc/powerdns/ restart: always @@ -14,9 +17,11 @@ services: mysql-mailcow: image: mariadb:10.1 - depends_on: - - pdns-mailcow - command: mysqld + healthcheck: + test: ["CMD", "mysqladmin", "ping", "--host", "localhost", "--silent"] + interval: 10s + timeout: 30s + retries: 5 volumes: - mysql-vol-1:/var/lib/mysql/ - ./data/conf/mysql/:/etc/mysql/conf.d/:ro @@ -52,7 +57,7 @@ services: rspamd-mailcow: image: andryyy/mailcow-dockerized:rspamd depends_on: - - pdns-mailcow + - nginx-mailcow volumes: - ./data/conf/rspamd/override.d/:/etc/rspamd/override.d:ro - ./data/conf/rspamd/local.d/:/etc/rspamd/local.d:ro @@ -65,6 +70,7 @@ services: dns_search: mailcow-network networks: mailcow-network: + ipv4_address: 172.22.1.253 aliases: - rspamd @@ -95,7 +101,6 @@ services: image: andryyy/mailcow-dockerized:sogo depends_on: - pdns-mailcow - - mysql-mailcow environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -110,6 +115,7 @@ services: restart: always networks: mailcow-network: + ipv4_address: 172.22.1.252 aliases: - sogo @@ -197,10 +203,8 @@ services: nginx-mailcow: depends_on: - - mysql-mailcow - sogo-mailcow - php-fpm-mailcow - - rspamd-mailcow image: nginx:mainline command: /bin/bash -c "envsubst < /etc/nginx/conf.d/listen.template > /etc/nginx/conf.d/listen.active && nginx -g 'daemon off;'" environment: From 7c3a8a5819d128acb59bb08b1b79da373d7f2503 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 2 Feb 2017 10:09:44 +0100 Subject: [PATCH 02/20] Use IPs to not emerg Nginx when host does not exist --- data/conf/nginx/site.conf | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index e543b84a..8a74c8c0 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -38,7 +38,7 @@ server { rewrite ^(/save.+)$ /rspamd$1 last; location /rspamd/ { - proxy_pass http://rspamd:11334/; + proxy_pass http://172.22.1.253:11334/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; @@ -60,7 +60,7 @@ server { } location ^~ /Microsoft-Server-ActiveSync { - proxy_pass http://sogo:20000/SOGo/Microsoft-Server-ActiveSync; + proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync; proxy_connect_timeout 1000; proxy_next_upstream timeout error; proxy_send_timeout 1000; @@ -82,7 +82,7 @@ server { } location ^~ /SOGo { - proxy_pass http://sogo:20000; + proxy_pass http://172.22.1.252:20000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; @@ -104,7 +104,7 @@ server { } location /SOGo.woa/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; + proxy_pass http://172.22.1.252:9192/WebServerResources/; proxy_set_header Host $host; proxy_cache sogo; proxy_cache_valid 200 1d; @@ -114,7 +114,7 @@ server { } location /SOGo/WebServerResources/ { - proxy_pass http://sogo:9192/WebServerResources/; + proxy_pass http://172.22.1.252:9192/WebServerResources/; proxy_set_header Host $host; proxy_cache sogo; proxy_cache_valid 200 1d; @@ -124,7 +124,7 @@ server { } location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ { - proxy_pass http://sogo:9192/$1.SOGo/Resources/$2; + proxy_pass http://172.22.1.252:9192/$1.SOGo/Resources/$2; proxy_set_header Host $host; proxy_cache sogo; proxy_cache_valid 200 1d; From 29684425e2e37b0ce718ca44779b5cccb020aeb2 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 2 Feb 2017 10:11:01 +0100 Subject: [PATCH 03/20] Add Spanish language, gracias a NTHINGs! --- data/web/inc/header.inc.php | 1 + data/web/inc/prerequisites.inc.php | 8 ++++++++ data/web/index.php | 1 + 3 files changed, 10 insertions(+) diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 7dcb8638..f051bf25 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -45,6 +45,7 @@ diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 52f9a0f1..42704767 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -59,6 +59,10 @@ if (isset($_COOKIE['language'])) { $_SESSION['mailcow_locale'] = 'en'; setcookie('language', 'en'); break; + case "es": + $_SESSION['mailcow_locale'] = 'es'; + setcookie('language', 'es'); + break; case "nl": $_SESSION['mailcow_locale'] = 'nl'; setcookie('language', 'nl'); @@ -79,6 +83,10 @@ if (isset($_GET['lang'])) { $_SESSION['mailcow_locale'] = 'en'; setcookie('language', 'en'); break; + case "es": + $_SESSION['mailcow_locale'] = 'es'; + setcookie('language', 'es'); + break; case "nl": $_SESSION['mailcow_locale'] = 'nl'; setcookie('language', 'nl'); diff --git a/data/web/index.php b/data/web/index.php index e6659149..31bafda6 100644 --- a/data/web/index.php +++ b/data/web/index.php @@ -48,6 +48,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; From bd57cd8d0a69e6ecfb92d035734010336dd37a3a Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 2 Feb 2017 21:02:11 +0100 Subject: [PATCH 04/20] Remove sender_acl from view to prevent race conditions, use SOGo delegations instead --- data/Dockerfiles/sogo/reconf-domains.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/data/Dockerfiles/sogo/reconf-domains.sh b/data/Dockerfiles/sogo/reconf-domains.sh index f4cd26aa..7cc1b1b4 100755 --- a/data/Dockerfiles/sogo/reconf-domains.sh +++ b/data/Dockerfiles/sogo/reconf-domains.sh @@ -8,10 +8,9 @@ while mysqladmin ping --host mysql --silent; do mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF -CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, senderacl, home, kind, multiple_bookings) AS +CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), IFNULL(gs.send_as, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username -LEFT OUTER JOIN grouped_sender_acl gs ON gs.username = mailbox.username LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username WHERE mailbox.active = '1'; EOF @@ -50,11 +49,10 @@ EOF # Generate multi-domain setup while read line do - DOMAIN_SANE=$(echo ${line} | tr '-' 'b' | tr '.' 'p' | tr -cd '[[:alnum:]]') echo " ${line} SOGoMailDomain - ${DOMAIN_SANE} + ${line SOGoUserSources @@ -62,7 +60,6 @@ while read line aliases ad_aliases - senderacl KindFieldName kind From 0a828e8f236570304f3fae2b61ff5980c6c11e1c Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 2 Feb 2017 21:12:30 +0100 Subject: [PATCH 05/20] Fix SOGo Dockerfile --- data/Dockerfiles/sogo/reconf-domains.sh | 6 +++--- data/web/inc/init.sql | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/sogo/reconf-domains.sh b/data/Dockerfiles/sogo/reconf-domains.sh index 7cc1b1b4..7ec4e173 100755 --- a/data/Dockerfiles/sogo/reconf-domains.sh +++ b/data/Dockerfiles/sogo/reconf-domains.sh @@ -9,7 +9,7 @@ mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS so mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS -SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), IFNULL(gs.send_as, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox +SELECT mailbox.username, mailbox.domain, mailbox.username, mailbox.password, mailbox.name, mailbox.username, IFNULL(ga.aliases, ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username = mailbox.username LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.username WHERE mailbox.active = '1'; @@ -52,7 +52,7 @@ while read line echo " ${line} SOGoMailDomain - ${line + ${line} SOGoUserSources @@ -95,4 +95,4 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist sleep 99999 -done; +done diff --git a/data/web/inc/init.sql b/data/web/inc/init.sql index 6dc7fa4b..84e19f74 100644 --- a/data/web/inc/init.sql +++ b/data/web/inc/init.sql @@ -276,6 +276,6 @@ CREATE TABLE IF NOT EXISTS sogo_user_profile ( PRIMARY KEY (c_uid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; -INSERT INTO `admin` (username, password, superadmin, created, modified, active) SELECT 'admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1 FROM `admin` WHERE NOT EXISTS (SELECT * FROM `admin`); +INSERT INTO `admin` (username, password, superadmin, created, modified, active) SELECT 'admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1 WHERE NOT EXISTS (SELECT * FROM `admin`); DELETE FROM `domain_admins`; INSERT INTO `domain_admins` (username, domain, created, active) SELECT `username`, 'ALL', NOW(), 1 FROM `admin` WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`); From 3795ae0b60061e3b5466a0542b6d1e179d7200d1 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 3 Feb 2017 08:32:25 +0100 Subject: [PATCH 06/20] Fix mailbox edit domain --- data/web/inc/functions.inc.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index a4467caa..1750e023 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -3433,7 +3433,6 @@ function mailbox_edit_domain($postarray) { `active` = :active, `quota` = :quota, `maxquota` = :maxquota, - `modified` = :modified, `mailboxes` = :mailboxes, `aliases` = :aliases, `description` = :description @@ -3444,7 +3443,6 @@ function mailbox_edit_domain($postarray) { ':active' => $active, ':quota' => $quota, ':maxquota' => $maxquota, - ':modified' => date('Y-m-d H:i:s'), ':mailboxes' => $mailboxes, ':aliases' => $aliases, ':modified' => date('Y-m-d H:i:s'), From 01d2d095c8de881b43a252e98b8fa027d474ffd4 Mon Sep 17 00:00:00 2001 From: andryyy Date: Fri, 3 Feb 2017 08:46:19 +0100 Subject: [PATCH 07/20] Fix mailbox edit domain --- data/web/inc/functions.inc.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 1750e023..d08aacc2 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -3393,8 +3393,6 @@ function mailbox_edit_domain($postarray) { } if ($MailboxData['maxquota'] > $maxquota) { - echo $MailboxData['maxquota']; - die(); $_SESSION['return'] = array( 'type' => 'danger', 'msg' => sprintf($lang['danger']['max_quota_in_use'], $MailboxData['maxquota']) From 563d26bb20c479f94f1b95bd5d219bc84899849e Mon Sep 17 00:00:00 2001 From: andryyy Date: Sun, 5 Feb 2017 20:49:25 +0100 Subject: [PATCH 08/20] Fix relay all rcpt display in edit form --- data/web/edit.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/edit.php b/data/web/edit.php index a9334833..6a287172 100644 --- a/data/web/edit.php +++ b/data/web/edit.php @@ -181,7 +181,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm

- +

From 0ac333bfbf998d21bead341e6fae34c24dac84dc Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 8 Feb 2017 17:21:20 +0100 Subject: [PATCH 09/20] Add Roundcube reqs --- data/Dockerfiles/php-fpm/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/data/Dockerfiles/php-fpm/Dockerfile b/data/Dockerfiles/php-fpm/Dockerfile index 0dc1061f..ad4b105d 100644 --- a/data/Dockerfiles/php-fpm/Dockerfile +++ b/data/Dockerfiles/php-fpm/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update \ RUN docker-php-ext-configure intl RUN docker-php-ext-install intl pdo pdo_mysql xmlrpc +RUN pear install channel://pear.php.net/Net_IDNA2-0.1.1 Auth_SASL Net_IMAP NET_SMTP Net_IDNA2 Mail_mime COPY ./docker-entrypoint.sh / From 8883960d5ae35dc0c241beb7941dbc1aaf4f0837 Mon Sep 17 00:00:00 2001 From: andryyy Date: Wed, 8 Feb 2017 19:11:25 +0100 Subject: [PATCH 10/20] Add mime types and full path to fcgi params --- data/conf/nginx/site.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index 8a74c8c0..467b56e4 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -1,6 +1,7 @@ proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; server { include /etc/nginx/conf.d/listen.active; + include /etc/nginx/mime.types; charset utf-8; override_charset on; ssl on; @@ -27,7 +28,7 @@ server { fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass phpfpm:9000; fastcgi_index index.php; - include fastcgi_params; + include /etc/nginx/fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param PHP_VALUE "max_execution_time = 1200 From c67704dcd5d9e22b04e1fde124a6033ee4f85844 Mon Sep 17 00:00:00 2001 From: andryyy Date: Thu, 9 Feb 2017 22:22:12 +0100 Subject: [PATCH 11/20] Fix missing mailbox on sender acl --- data/web/inc/functions.inc.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index d08aacc2..91c1a96b 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -4816,7 +4816,8 @@ function mailbox_get_sender_acl_handles($mailbox) { ':logged_in_as' => $mailbox, ':goto' => $mailbox )); - while ($row = array_shift($rows)) { + $rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC); + while ($row = array_shift($rows_mbox)) { if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) { $data['sender_acl_addresses']['selectable'][] = $row['address']; } From 460bfa5f1642b174e3d8fbcdfa3a161bb1054c6b Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 11 Feb 2017 17:14:40 +0100 Subject: [PATCH 12/20] Fix autodiscover name issue --- data/web/autodiscover.php | 196 +++++++++++++++++++++----------------- 1 file changed, 106 insertions(+), 90 deletions(-) diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index a503b80c..5f9025e8 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,6 +1,4 @@ 'yes', 'autodiscoverType' => 'activesync', @@ -15,22 +13,43 @@ $config = array( 'ssl' => 'on' ), 'activesync' => array( - 'url' => 'https://' . $mailcow_hostname . '/Microsoft-Server-ActiveSync' + 'url' => 'https://'.$mailcow_hostname.'/Microsoft-Server-ActiveSync' ) ); -// If useEASforOutlook == no, the autodiscoverType option will be replaced to imap. + +/* ---------- DO NOT MODIFY ANYTHING BEYOND THIS LINE. IGNORE AT YOUR OWN RISK. ---------- */ + if ($config['useEASforOutlook'] == 'no') { if (strpos($_SERVER['HTTP_USER_AGENT'], 'Outlook')) { $config['autodiscoverType'] = 'imap'; } } -// Workaround for short open tags -echo ''; -?> - - PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, +]; +$pdo = new PDO($dsn, $database_user, $database_pass, $opt); +$login_user = strtolower(trim($_SERVER['PHP_AUTH_USER'])); +$as = check_login($login_user, $_SERVER['PHP_AUTH_PW']); + +if (!isset($_SERVER['PHP_AUTH_USER']) OR $as !== "user") { + header('WWW-Authenticate: Basic realm=""'); + header('HTTP/1.0 401 Unauthorized'); + exit; +} else { + if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { + if ($as === "user") { + header("Content-Type: application/xml"); + echo ''; + + $data = trim(file_get_contents("php://input")); + if(!$data) { list($usec, $sec) = explode(' ', microtime()); echo ''; echo ''; @@ -38,84 +57,81 @@ if(!$data) { echo ''; echo ''; exit(0); -} + } + $discover = new SimpleXMLElement($data); + $email = $discover->Request->EMailAddress; -$discover = new SimpleXMLElement($data); -$email = $discover->Request->EMailAddress; - -if ($config['autodiscoverType'] == 'imap') { -?> - - - email - settings - - IMAP - - - off - - off - - on - - - SMTP - - - off - - off - - on - on - off - - - - PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - ]; - $pdo = new PDO($dsn, $database_user, $database_pass, $opt); - $username = trim($email); - try { - $stmt = $pdo->prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); - $stmt->execute(array(':username' => $username)); - $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); - } - catch(PDOException $e) { - die("Failed to determine name from SQL"); - } - if (!empty($MailboxData['name'])) { - $displayname = utf8_encode($MailboxData['name']); - } - else { - $displayname = $email; - } -?> - - en:en - - - - - - - - MobileSync - - - - - - - + if ($config['autodiscoverType'] == 'imap') { + ?> + + + email + settings + + IMAP + + + off + + off + + on + + + SMTP + + + off + + off + + on + on + off + + + + prepare("SELECT `name` FROM `mailbox` WHERE `username`= :username"); + $stmt->execute(array(':username' => $username)); + $MailboxData = $stmt->fetch(PDO::FETCH_ASSOC); + } + catch(PDOException $e) { + die("Failed to determine name from SQL"); + } + if (!empty($MailboxData['name'])) { + $displayname = utf8_encode($MailboxData['name']); + } + else { + $displayname = $email; + } + ?> + + en:en + + + + + + + + MobileSync + + + + + + + + From 3415fcd4e3a363c21a608bf58eb6c8fe17f50dda Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 11 Feb 2017 20:53:25 +0100 Subject: [PATCH 13/20] Revert selection of alias domains in sender acl --- data/web/inc/functions.inc.php | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 91c1a96b..d7993388 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -4787,22 +4787,6 @@ function mailbox_get_sender_acl_handles($mailbox) { while ($row_domain = array_shift($rows_domain)) { if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) { $data['sender_acl_domains']['selectable'][] = $row_domain['domain']; - $stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` - WHERE `target_domain` = :target_domain - AND `alias_domain` NOT IN ( - SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl` - WHERE `logged_in_as` = :logged_in_as - AND `send_as` LIKE '@%')"); - $stmt->execute(array( - ':target_domain' => $row_domain['domain'], - ':logged_in_as' => $mailbox, - )); - $rows_ad = $stmt->fetchAll(PDO::FETCH_ASSOC); - while ($row_ad = array_shift($rows_ad)) { - if (is_valid_domain_name($row_ad['alias_domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_ad['alias_domain'])) { - $data['sender_acl_domains']['selectable'][] = $row_ad['alias_domain']; - } - } } } From c73cc42a9579e690302d23c841d77b09e1ffb452 Mon Sep 17 00:00:00 2001 From: andryyy Date: Sat, 11 Feb 2017 20:54:14 +0100 Subject: [PATCH 14/20] Handle alias domains the same way as their parents in sender_acl, thanks to @tehXor --- data/conf/postfix/sql/mysql_virtual_sender_acl.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf index 3707a2b2..52fc7d41 100644 --- a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf +++ b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf @@ -2,4 +2,4 @@ user = mailcow password = mysafepasswd hosts = mysql dbname = mailcow -query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u', '@', alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1' +query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN(SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u','@',alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1 \ No newline at end of file From a6c6e34fe9ed68cdd08e2804bab0b106029439b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Sun, 12 Feb 2017 19:28:52 +0100 Subject: [PATCH 15/20] Update mysql_virtual_sender_acl.cf --- data/conf/postfix/sql/mysql_virtual_sender_acl.cf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf index 52fc7d41..02107e6e 100644 --- a/data/conf/postfix/sql/mysql_virtual_sender_acl.cf +++ b/data/conf/postfix/sql/mysql_virtual_sender_acl.cf @@ -2,4 +2,4 @@ user = mailcow password = mysafepasswd hosts = mysql dbname = mailcow -query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN(SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u','@',alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1 \ No newline at end of file +query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND domain IN(SELECT domain FROM domain WHERE domain='%d' AND active='1') UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' AND alias.address = CONCAT('%u','@',alias_domain.target_domain) AND alias.active ='1' AND alias_domain.active='1' From 7668f82566ba773b63838dd7ae25f04d3bcefb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Peters?= Date: Mon, 13 Feb 2017 07:52:12 +0100 Subject: [PATCH 16/20] Update autodiscover.php --- data/web/autodiscover.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/data/web/autodiscover.php b/data/web/autodiscover.php index 5f9025e8..745d9da0 100644 --- a/data/web/autodiscover.php +++ b/data/web/autodiscover.php @@ -1,4 +1,5 @@ 'yes', 'autodiscoverType' => 'activesync', @@ -25,7 +26,9 @@ if ($config['useEASforOutlook'] == 'no') { } } require_once 'inc/vars.inc.php'; -include_once 'inc/vars.local.inc.php'; +if(file_exists('inc/vars.local.inc.php')) { + include_once 'inc/vars.local.inc.php'; +} require_once 'inc/functions.inc.php'; $dsn = "$database_type:host=$database_host;dbname=$database_name"; From 622a8872e776a169ae593ebd76761a3a4881867f Mon Sep 17 00:00:00 2001 From: andryyy Date: Mon, 13 Feb 2017 13:42:54 +0100 Subject: [PATCH 17/20] Multiple TFA keys --- data/web/admin.php | 18 +++- data/web/edit.php | 2 +- data/web/inc/footer.inc.php | 1 + data/web/inc/functions.inc.php | 144 ++++++++++++++++++++++------- data/web/inc/prerequisites.inc.php | 6 +- data/web/inc/tfa_modals.php | 15 +++ data/web/inc/triggers.inc.php | 3 + data/web/inc/vars.inc.php | 4 - data/web/lang/lang.de.php | 4 + data/web/lang/lang.en.php | 4 + data/web/user.php | 19 +++- 11 files changed, 173 insertions(+), 47 deletions(-) diff --git a/data/web/admin.php b/data/web/admin.php index 4397a279..f0c11f60 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -4,6 +4,7 @@ require_once("inc/prerequisites.inc.php"); if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") { require_once("inc/header.inc.php"); $_SESSION['return_to'] = $_SERVER['REQUEST_URI']; +$tfa_data = get_tfa(); ?>

@@ -43,12 +44,23 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
:
-

+

+
+ +
+ +
🔑 []
+
+ +
+
-
:
-
+
:
+
+
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php index d250fc0b..67afe50c 100644 --- a/data/web/inc/footer.inc.php +++ b/data/web/inc/footer.inc.php @@ -74,6 +74,7 @@ $(document).ready(function() { // Set TFA modals + $('#selectTFA').change(function () { if ($(this).val() == "yubi_otp") { $('#YubiOTPModal').modal('show'); diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index d7993388..913f8924 100644 --- a/data/web/inc/functions.inc.php +++ b/data/web/inc/functions.inc.php @@ -63,6 +63,7 @@ function hasMailboxObjectAccess($username, $role, $object) { return false; } function init_db_schema() { + // This will be much better in future releases... global $pdo; try { $stmt = $pdo->prepare("SELECT NULL FROM `admin`, `imapsync`, `tfa`"); @@ -101,7 +102,7 @@ function init_db_schema() { $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'kind'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); if ($num_results == 0) { - $pdo->query("ALTER TABLE `mailbox` ADD `kind` varchar(100) NOT NULL DEFAULT ''"); + $pdo->query("ALTER TABLE `mailbox` ADD `kind` VARCHAR(100) NOT NULL DEFAULT ''"); } $stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE 'multiple_bookings'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -113,6 +114,11 @@ function init_db_schema() { if ($num_results == 0) { $pdo->query("ALTER TABLE `mailbox` ADD `wants_tagged_subject` tinyint(1) NOT NULL DEFAULT '0'"); } + $stmt = $pdo->query("SHOW COLUMNS FROM `tfa` LIKE 'key_id'"); + $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); + if ($num_results == 0) { + $pdo->query("ALTER TABLE `tfa` ADD `key_id` VARCHAR(255) DEFAULT 'unidentified'"); + } } function verify_ssha256($hash, $password) { // Remove tag if any @@ -198,6 +204,8 @@ function check_login($user, $pass) { } else { unset($_SESSION['ldelay']); + $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user"); + $stmt->execute(array(':user' => $user)); return "domainadmin"; } } @@ -1806,6 +1814,10 @@ function set_tfa($postarray) { switch ($postarray["tfa_method"]) { case "yubi_otp": + (!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"]; + $yubico_id = $postarray['yubico_id']; + $yubico_key = $postarray['yubico_key']; + $yubi = new Auth_Yubico($yubico_id, $yubico_key); if (!$yubi) { $_SESSION['return'] = array( 'type' => 'danger', @@ -1824,16 +1836,21 @@ function set_tfa($postarray) { if (PEAR::isError($yauth)) { $_SESSION['return'] = array( 'type' => 'danger', - 'msg' => 'Yubico Authentication error: ' . $yauth->getMessage() + 'msg' => 'Yubico API: ' . $yauth->getMessage() ); return false; } try { - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username"); - $stmt->execute(array(':username' => $username)); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `active`) VALUES - (:username, 'yubi_otp', 1)"); - $stmt->execute(array(':username' => $username)); + // We could also do a modhex translation here + $yubico_modhex_id = substr($postarray["otp_token"], 0, 12); + $stmt = $pdo->prepare("DELETE FROM `tfa` + WHERE `username` = :username + AND (`authmech` != 'yubi_otp') + OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES + (:key_id, :username, 'yubi_otp', '1', :secret)"); + $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id)); } catch (PDOException $e) { $_SESSION['return'] = array( @@ -1850,9 +1867,12 @@ function set_tfa($postarray) { case "u2f": try { + (!isset($postarray["key_id"])) ? $key_id = 'unidentified' : $key_id = $postarray["key_id"]; $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token'])); - $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) VALUES (?, 'u2f', ?, ?, ?, ?)"); - $stmt->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'"); + $stmt->execute(array(':username' => $username)); + $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')"); + $stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); $_SESSION['return'] = array( 'type' => 'success', 'msg' => sprintf($lang['success']['object_modified'], $username) @@ -1887,6 +1907,55 @@ function set_tfa($postarray) { break; } } +function unset_tfa_key($postarray) { + // Can only unset own keys + // Needs at least one key left + global $pdo; + global $lang; + $id = intval($postarray['id']); + if ($_SESSION['mailcow_cc_role'] != "domainadmin" && + $_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $username = $_SESSION['mailcow_cc_username']; + try { + if (!is_numeric($id)) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['access_denied']) + ); + return false; + } + $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); + $stmt->execute(array(':username' => $username)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + if ($row['keys'] == "1") { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => sprintf($lang['danger']['last_key']) + ); + return false; + } + $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id"); + $stmt->execute(array(':username' => $username, ':id' => $id)); + $_SESSION['return'] = array( + 'type' => 'success', + 'msg' => sprintf($lang['success']['object_modified'], $username) + ); + } + catch (PDOException $e) { + $_SESSION['return'] = array( + 'type' => 'danger', + 'msg' => 'MySQL: '.$e + ); + return false; + } +} function get_tfa($username = null) { global $pdo; if (isset($_SESSION['mailcow_cc_username'])) { @@ -1896,8 +1965,8 @@ function get_tfa($username = null) { return false; } - $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username"); + $stmt = $pdo->prepare("SELECT * FROM `tfa` + WHERE `username` = :username AND `active` = '1'"); $stmt->execute(array(':username' => $username)); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1905,11 +1974,27 @@ function get_tfa($username = null) { case "yubi_otp": $data['name'] = "yubi_otp"; $data['pretty'] = "Yubico OTP"; + $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } return $data; break; case "u2f": $data['name'] = "u2f"; $data['pretty'] = "Fido U2F"; + $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username"); + $stmt->execute(array( + ':username' => $username, + )); + $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); + while($row = array_shift($rows)) { + $data['additional'][] = $row; + } return $data; break; case "hotp": @@ -1935,7 +2020,7 @@ function verify_tfa_login($username, $token) { global $yubi; $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa` - WHERE `username` = :username"); + WHERE `username` = :username AND `active` = '1'"); $stmt->execute(array(':username' => $username)); $row = $stmt->fetch(PDO::FETCH_ASSOC); @@ -1944,6 +2029,16 @@ function verify_tfa_login($username, $token) { if (!ctype_alnum($token) || strlen($token) != 44) { return false; } + $yubico_modhex_id = substr($token, 0, 12); + $stmt = $pdo->prepare("SELECT `secret` FROM `tfa` + WHERE `username` = :username + AND `authmech` = 'yubi_otp' + AND `active`='1' + AND `secret` LIKE :modhex"); + $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id)); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + $yubico_auth = explode(':', $row['secret']); + $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]); $yauth = $yubi->verify($token); if (PEAR::isError($yauth)) { $_SESSION['return'] = array( @@ -2089,8 +2184,8 @@ function edit_domain_admin($postarray) { ':modified' => date('Y-m-d H:i:s'), ':active' => $active )); - if (isset($postarray['delete_tfa'])) { - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + if (isset($postarray['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); $stmt->execute(array(':username' => $username_now)); } else { @@ -2115,8 +2210,8 @@ function edit_domain_admin($postarray) { ':modified' => date('Y-m-d H:i:s'), ':active' => $active )); - if (isset($postarray['delete_tfa'])) { - $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username"); + if (isset($postarray['disable_tfa'])) { + $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username"); $stmt->execute(array(':username' => $username)); } else { @@ -4818,23 +4913,8 @@ function mailbox_get_sender_acl_handles($mailbox) { } function get_u2f_registrations($username) { global $pdo; - $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `username` = ?"); + $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'"); $sel->execute(array($username)); return $sel->fetchAll(PDO::FETCH_OBJ); } -function add_u2f_registration($username, $reg) { - global $pdo; - global $lang; - $ins = $pdo->prepare("INSERT INTO `tfa` (`username`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`) VALUES (?, 'u2f', ?, ?, ?, ?)"); - $ins->execute(array($username, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter)); - $_SESSION['return'] = array( - 'type' => 'success', - 'msg' => sprintf($lang['success']['object_modified'], $username) - ); -} -function edit_u2f_registration($reg) { - global $pdo; - $upd = $pdo->prepare("update tfa set counter = ? where id = ?"); - $upd->execute(array($reg->counter, $reg->id)); -} ?> diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php index 42704767..0a9ba2e7 100644 --- a/data/web/inc/prerequisites.inc.php +++ b/data/web/inc/prerequisites.inc.php @@ -22,10 +22,8 @@ if (file_exists('./inc/vars.local.inc.php')) { } // Yubi OTP API -if (!empty($YUBI_API['ID']) && !empty($YUBI_API['KEY'])) { - require_once 'inc/lib/Yubico.php'; - $yubi = new Auth_Yubico($YUBI_API['ID'], $YUBI_API['KEY']); -} +require_once 'inc/lib/Yubico.php'; + // U2F API require_once 'inc/lib/U2F.php'; $scheme = isset($_SERVER['HTTPS']) ? "https://" : "http://"; diff --git a/data/web/inc/tfa_modals.php b/data/web/inc/tfa_modals.php index 39c841f5..d6956724 100644 --- a/data/web/inc/tfa_modals.php +++ b/data/web/inc/tfa_modals.php @@ -4,6 +4,18 @@