public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
[Web] move /process/login to internal endpoint
dca5f1ba
.gitignore | 1 +
data/Dockerfiles/dovecot/docker-entrypoint.sh | 10 +++--
data/conf/dovecot/auth/mailcowauth.php | 54 +++++++++++++++++++++++++++
data/conf/nginx/mailcow_auth.conf | 23 ++++++++++++
data/web/inc/functions.auth.inc.php | 27 +++++---------
data/web/inc/functions.inc.php | 19 ++++++++++
data/web/inc/prerequisites.inc.php | 17 +--------
data/web/json_api.php | 20 ----------
docker-compose.yml | 8 ++++
generate_config.sh | 2 +-
10 files changed, 123 insertions(+), 58 deletions(-)
create mode 100644 data/conf/dovecot/auth/mailcowauth.php
create mode 100644 data/conf/nginx/mailcow_auth.conf
Diff
diff --git a/.gitignore b/.gitignore
index e25639a7..e1e8e888 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,3 +69,4 @@ rebuild-images.sh
refresh_images.sh
update_diffs/
create_cold_standby.sh
+!data/conf/nginx/mailcow_auth.conf
diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh
index 9755398e..ca06108d 100755
--- a/data/Dockerfiles/dovecot/docker-entrypoint.sh
+++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh
@@ -152,13 +152,14 @@ function auth_password_verify(request, password)
-- check against mailbox passwds
local b, c = https.request {
method = "POST",
- url = "https://nginx/api/v1/process/login",
+ url = "https://nginx:9082",
source = ltn12.source.string(req_json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#req_json)
},
- sink = ltn12.sink.table(res)
+ sink = ltn12.sink.table(res),
+ insecure = true
}
local api_response = json.decode(table.concat(res))
if api_response.role == 'user' then
@@ -182,13 +183,14 @@ function auth_password_verify(request, password)
local b, c = https.request {
method = "POST",
- url = "https://nginx/api/v1/process/login",
+ url = "https://nginx:9082",
source = ltn12.source.string(req_json),
headers = {
["content-type"] = "application/json",
["content-length"] = tostring(#req_json)
},
- sink = ltn12.sink.table(res)
+ sink = ltn12.sink.table(res),
+ insecure = true
}
local api_response = json.decode(table.concat(res))
if api_response.role == 'user' then
diff --git a/data/conf/dovecot/auth/mailcowauth.php b/data/conf/dovecot/auth/mailcowauth.php
new file mode 100644
index 00000000..57dffdb5
--- /dev/null
+++ b/data/conf/dovecot/auth/mailcowauth.php
@@ -0,0 +1,54 @@
+<?php
+header('Content-Type: application/json');
+
+$post = trim(file_get_contents('php://input'));
+if ($post) {
+ $post = json_decode($post, true);
+}
+
+$return = array("success" => false, "role" => false);
+if(!isset($post['username']) || !isset($post['password'])){
+ echo json_encode($return);
+ exit();
+}
+
+require_once('../../../web/inc/vars.inc.php');
+if (file_exists('../../../web/inc/vars.local.inc.php')) {
+ include_once('../../../web/inc/vars.local.inc.php');
+}
+require_once '../../../web/inc/lib/vendor/autoload.php';
+
+// Do not show errors, we log to using error_log
+ini_set('error_reporting', 0);
+// Init database
+//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
+$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
+$opt = [
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ PDO::ATTR_EMULATE_PREPARES => false,
+];
+try {
+ $pdo = new PDO($dsn, $database_user, $database_pass, $opt);
+}
+catch (PDOException $e) {
+ error_log("MAILCOWAUTH: " . $e . PHP_EOL);
+ http_response_code(501);
+ exit;
+}
+
+// Load core functions first
+require_once 'functions.inc.php';
+require_once 'functions.auth.inc.php';
+require_once 'sessions.inc.php';
+
+// Init Keycloak Provider
+$iam_provider = identity_provider('init');
+
+$result = check_login($post['username'], $post['password'], $post['protocol'], true);
+if ($result) {
+ $return = array("success" => true, "role" => $result);
+}
+
+echo json_encode($return);
+exit();
diff --git a/data/conf/nginx/mailcow_auth.conf b/data/conf/nginx/mailcow_auth.conf
new file mode 100644
index 00000000..3e4a8c73
--- /dev/null
+++ b/data/conf/nginx/mailcow_auth.conf
@@ -0,0 +1,23 @@
+server {
+ listen 9082 ssl http2;
+
+ ssl_certificate /etc/ssl/mail/cert.pem;
+ ssl_certificate_key /etc/ssl/mail/key.pem;
+
+ index mailcowauth.php;
+ server_name _;
+ error_log /var/log/nginx/error.log;
+ access_log /var/log/nginx/access.log;
+ root /mailcowauth;
+ client_max_body_size 10M;
+
+ location ~ \.php$ {
+ client_max_body_size 10M;
+ try_files $uri =404;
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+ fastcgi_pass phpfpm:9001;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param PATH_INFO $fastcgi_path_info;
+ }
+}
diff --git a/data/web/inc/functions.auth.inc.php b/data/web/inc/functions.auth.inc.php
index 83c9ba8b..d68a7b6e 100644
--- a/data/web/inc/functions.auth.inc.php
+++ b/data/web/inc/functions.auth.inc.php
@@ -9,7 +9,7 @@ function unset_auth_session(){
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_methods']);
}
-function check_login($user, $pass, $app_passwd_data = false) {
+function check_login($user, $pass, $app_passwd_data = false, $is_internal = false) {
global $pdo;
global $redis;
@@ -35,12 +35,6 @@ function check_login($user, $pass, $app_passwd_data = false) {
}
// Validate mailbox user
- // skip log & ldelay if requests comes from dovecot
- $is_dovecot = false;
- $request_ip = $_SERVER['REMOTE_ADDR'];
- if ($request_ip == getenv('IPV4_NETWORK').'.250'){
- $is_dovecot = true;
- }
// check authsource
$stmt = $pdo->prepare("SELECT authsource FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
@@ -54,9 +48,9 @@ function check_login($user, $pass, $app_passwd_data = false) {
// mbox does not exist, call keycloak login and create mbox if possible
$identity_provider_settings = identity_provider('get');
if ($identity_provider_settings['login_flow'] == 'ropc'){
- $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot, true);
+ $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_internal, true);
} else {
- $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot, true);
+ $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_internal, true);
}
if ($result){
return $result;
@@ -64,7 +58,7 @@ function check_login($user, $pass, $app_passwd_data = false) {
} else if ($row['authsource'] == 'keycloak'){
if ($app_passwd_data){
// first check if password is app_password
- $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_dovecot);
+ $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal);
if ($result){
return $result;
}
@@ -72,9 +66,9 @@ function check_login($user, $pass, $app_passwd_data = false) {
$identity_provider_settings = identity_provider('get');
if ($identity_provider_settings['login_flow'] == 'ropc'){
- $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot);
+ $result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_internal);
} else {
- $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot);
+ $result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_internal);
}
if ($result){
return $result;
@@ -82,21 +76,20 @@ function check_login($user, $pass, $app_passwd_data = false) {
} else {
if ($app_passwd_data){
// first check if password is app_password
- $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_dovecot);
+ $result = mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal);
if ($result){
return $result;
}
}
- $result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_dovecot);
+ $result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_internal);
if ($result){
return $result;
}
}
- // skip log and only return false
- // netfilter uses dovecot error log for banning
- if ($is_dovecot){
+ // skip log and only return false if it's an internal request
+ if ($is_internal){
return false;
}
if (!isset($_SESSION['ldelay'])) {
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 9ee4d0d0..b88133e5 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -2214,6 +2214,25 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
return true;
break;
+ case "init":
+ $identity_provider_settings = identity_provider('get');
+ $provider = null;
+ if ($identity_provider_settings['server_url'] && $identity_provider_settings['realm'] && $identity_provider_settings['client_id'] &&
+ $identity_provider_settings['client_secret'] && $identity_provider_settings['redirect_url'] && $identity_provider_settings['version']){
+ $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([
+ 'authServerUrl' => $identity_provider_settings['server_url'],
+ 'realm' => $identity_provider_settings['realm'],
+ 'clientId' => $identity_provider_settings['client_id'],
+ 'clientSecret' => $identity_provider_settings['client_secret'],
+ 'redirectUri' => $identity_provider_settings['redirect_url'],
+ 'version' => $identity_provider_settings['version'],
+ // 'encryptionAlgorithm' => 'RS256', // optional
+ // 'encryptionKeyPath' => '../key.pem' // optional
+ // 'encryptionKey' => 'contents_of_key_or_certificate' // optional
+ ]);
+ }
+ return $provider;
+ break;
}
}
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index d0ed3109..90cff4b2 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -179,22 +179,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
// Init Keycloak Provider
-$identity_provider_settings = identity_provider('get');
-$keycloak_provider = null;
-if ($identity_provider_settings['server_url'] && $identity_provider_settings['realm'] && $identity_provider_settings['client_id'] &&
- $identity_provider_settings['client_secret'] && $identity_provider_settings['redirect_url'] && $identity_provider_settings['version']){
- $keycloak_provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([
- 'authServerUrl' => $identity_provider_settings['server_url'],
- 'realm' => $identity_provider_settings['realm'],
- 'clientId' => $identity_provider_settings['client_id'],
- 'clientSecret' => $identity_provider_settings['client_secret'],
- 'redirectUri' => $identity_provider_settings['redirect_url'],
- 'version' => $identity_provider_settings['version'],
- // 'encryptionAlgorithm' => 'RS256', // optional
- // 'encryptionKeyPath' => '../key.pem' // optional
- // 'encryptionKey' => 'contents_of_key_or_certificate' // optional
- ]);
-}
+$keycloak_provider = identity_provider('init');
// IMAP lib
// use Ddeboer\Imap\Server;
diff --git a/data/web/json_api.php b/data/web/json_api.php
index 9564af88..69b236d7 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -401,26 +401,6 @@ if (isset($_GET['query'])) {
);
echo json_encode($return);
break;
- case "login":
- header('Content-Type: application/json');
- $post = trim(file_get_contents('php://input'));
- if ($post) {
- $post = json_decode($post, true);
- }
-
- $return = array("success" => false, "role" => false);
- if(!isset($post['username']) || !isset($post['password'])){
- echo json_encode($return);
- return;
- }
- $result = check_login($post['username'], $post['password'], $post['protocol']);
- if ($result) {
- $return = array("success" => true, "role" => $result);
- }
-
- echo json_encode($return);
- return;
- break;
}
break;
case "get":
diff --git a/docker-compose.yml b/docker-compose.yml
index df545c15..fffa193c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -120,6 +120,10 @@ services:
- ./data/web:/web:z
- ./data/conf/rspamd/dynmaps:/dynmaps:ro,z
- ./data/conf/rspamd/custom/:/rspamd_custom_maps:z
+ - ./data/conf/dovecot/auth/mailcowauth.php:/mailcowauth/mailcowauth.php:z
+ - ./data/web/inc/functions.inc.php:/mailcowauth/functions.inc.php:z
+ - ./data/web/inc/functions.auth.inc.php:/mailcowauth/functions.auth.inc.php:z
+ - ./data/web/inc/sessions.inc.php:/mailcowauth/sessions.inc.php:z
- rspamd-vol-1:/var/lib/rspamd
- mysql-socket-vol-1:/var/run/mysqld/
- ./data/conf/sogo/:/etc/sogo/:z
@@ -389,6 +393,10 @@ services:
- ./data/assets/ssl/:/etc/ssl/mail/:ro,z
- ./data/conf/nginx/:/etc/nginx/conf.d/:z
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro,z
+ - ./data/conf/dovecot/auth/mailcowauth.php:/mailcowauth/mailcowauth.php:z
+ - ./data/web/inc/functions.inc.php:/mailcowauth/functions.inc.php:z
+ - ./data/web/inc/functions.auth.inc.php:/mailcowauth/functions.auth.inc.php:z
+ - ./data/web/inc/sessions.inc.php:/mailcowauth/sessions.inc.php:z
- sogo-web-vol-1:/usr/lib/GNUstep/SOGo/
ports:
- "${HTTPS_BIND:-}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
diff --git a/generate_config.sh b/generate_config.sh
index 05d9ee2f..6fdc2553 100755
--- a/generate_config.sh
+++ b/generate_config.sh
@@ -258,7 +258,7 @@ DBROOT=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2> /dev/null | head -c 28)
# Might be important: This will also change the binding within the container.
# If you use a proxy within Docker, point it to the ports you set below.
# Do _not_ use IP:PORT in HTTP(S)_BIND or HTTP(S)_PORT
-# IMPORTANT: Do not use port 8081, 9081 or 65510!
+# IMPORTANT: Do not use port 8081, 9081, 9082 or 65510!
# Example: HTTP_BIND=1.2.3.4
# For IPv4 leave it as it is: HTTP_BIND= & HTTPS_PORT=
# For IPv6 see https://docs.mailcow.email/post_installation/firststeps-ip_bindings/