[Web] organize auth functions+api auth w/ dovecot
This commit is contained in:
parent
926824b056
commit
638a81c58c
|
@ -78,6 +78,7 @@ RUN groupadd -g 5000 vmail \
|
||||||
libwww-perl \
|
libwww-perl \
|
||||||
lua-sql-mysql \
|
lua-sql-mysql \
|
||||||
lua-socket \
|
lua-socket \
|
||||||
|
lua-json \
|
||||||
mariadb-client \
|
mariadb-client \
|
||||||
procps \
|
procps \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
|
|
@ -129,114 +129,86 @@ iterate_query = SELECT username FROM mailbox WHERE active = '1' OR active = '2';
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > /etc/dovecot/lua/passwd-verify.lua
|
cat <<EOF > /etc/dovecot/lua/passwd-verify.lua
|
||||||
function auth_password_verify(req, pass)
|
function auth_password_verify(request, password)
|
||||||
|
if request.domain == nil then
|
||||||
if req.domain == nil then
|
|
||||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, "No such user"
|
||||||
end
|
end
|
||||||
|
|
||||||
if cur == nil then
|
json = require "json"
|
||||||
script_init()
|
ltn12 = require "ltn12"
|
||||||
end
|
http = require "socket.http"
|
||||||
|
http.TIMEOUT = 5
|
||||||
if req.user == nil then
|
mysql = require "luasql.mysql"
|
||||||
req.user = ''
|
env = mysql.mysql()
|
||||||
end
|
con = env:connect("__DBNAME__","__DBUSER__","__DBPASS__","localhost")
|
||||||
|
|
||||||
respbody = {}
|
|
||||||
|
|
||||||
|
local req = {
|
||||||
|
username = request.user,
|
||||||
|
password = password
|
||||||
|
}
|
||||||
|
local req_json = json.encode(req)
|
||||||
|
local res = {}
|
||||||
|
|
||||||
-- check against mailbox passwds
|
-- check against mailbox passwds
|
||||||
local cur,errorString = con:execute(string.format([[SELECT password FROM mailbox
|
local b, c = http.request {
|
||||||
WHERE username = '%s'
|
method = "POST",
|
||||||
AND active = '1'
|
url = "https://nginx/api/v1/process/login",
|
||||||
AND domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')
|
source = ltn12.source.string(req_json),
|
||||||
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(mailbox.attributes, '$.force_pw_update')), 0) != '1'
|
headers = {
|
||||||
AND IFNULL(JSON_UNQUOTE(JSON_VALUE(attributes, '$.%s_access')), 1) = '1']], con:escape(req.user), con:escape(req.domain), con:escape(req.service)))
|
["content-type"] = "application/json",
|
||||||
local row = cur:fetch ({}, "a")
|
["content-length"] = tostring(#req_json)
|
||||||
while row do
|
},
|
||||||
if req.password_verify(req, row.password, pass) == 1 then
|
sink = ltn12.sink.table(res)
|
||||||
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
}
|
||||||
VALUES ("%s", 0, "%s", "%s")]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip)))
|
local api_response = json.decode(table.concat(res))
|
||||||
cur:close()
|
if api_response.role == 'user' then
|
||||||
con:close()
|
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
VALUES ("%s", 0, "%s", "%s")]], con:escape(request.service), con:escape(request.user), con:escape(request.real_rip)))
|
||||||
end
|
con:close()
|
||||||
row = cur:fetch (row, "a")
|
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. password
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- check against app passwds for imap and smtp
|
-- check against app passwds for imap and smtp
|
||||||
-- app passwords are only available for imap, smtp, sieve and pop3 when using sasl
|
-- app passwords are only available for imap, smtp, sieve and pop3 when using sasl
|
||||||
if req.service == "smtp" or req.service == "imap" or req.service == "sieve" or req.service == "pop3" then
|
if request.service == "smtp" or request.service == "imap" or request.service == "sieve" or request.service == "pop3" then
|
||||||
local cur,errorString = con:execute(string.format([[SELECT app_passwd.id, %s_access AS has_prot_access, app_passwd.password FROM app_passwd
|
req.protocol = {}
|
||||||
INNER JOIN mailbox ON mailbox.username = app_passwd.mailbox
|
req.protocol[request.service] = true
|
||||||
WHERE mailbox = '%s'
|
req_json = json.encode(req)
|
||||||
AND app_passwd.active = '1'
|
|
||||||
AND mailbox.active = '1'
|
req.protocol.ignore_hasaccess = false
|
||||||
AND app_passwd.domain IN (SELECT domain FROM domain WHERE domain='%s' AND active='1')]], con:escape(req.service), con:escape(req.user), con:escape(req.domain)))
|
if tostring(req.real_rip) == "__IPV4_SOGO__" then
|
||||||
local row = cur:fetch ({}, "a")
|
req.protocol.ignore_hasaccess = true
|
||||||
while row do
|
end
|
||||||
if req.password_verify(req, row.password, pass) == 1 then
|
|
||||||
-- if password is valid and protocol access is 1 OR real_rip matches SOGo, proceed
|
local b, c = http.request {
|
||||||
if tostring(req.real_rip) == "__IPV4_SOGO__" then
|
method = "POST",
|
||||||
cur:close()
|
url = "https://nginx/api/v1/process/login",
|
||||||
con:close()
|
source = ltn12.source.string(req_json),
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
headers = {
|
||||||
elseif row.has_prot_access == "1" then
|
["content-type"] = "application/json",
|
||||||
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
["content-length"] = tostring(#req_json)
|
||||||
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
|
},
|
||||||
cur:close()
|
sink = ltn12.sink.table(res)
|
||||||
con:close()
|
}
|
||||||
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. pass
|
local api_response = json.decode(table.concat(res))
|
||||||
end
|
if api_response.role == 'user' then
|
||||||
|
if req.protocol.ignore_hasaccess == false then
|
||||||
|
con:execute(string.format([[REPLACE INTO sasl_log (service, app_password, username, real_rip)
|
||||||
|
VALUES ("%s", %d, "%s", "%s")]], con:escape(req.service), row.id, con:escape(req.user), con:escape(req.real_rip)))
|
||||||
end
|
end
|
||||||
row = cur:fetch (row, "a")
|
con:close()
|
||||||
|
return dovecot.auth.PASSDB_RESULT_OK, "password=" .. password
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
cur:close()
|
|
||||||
con:close()
|
con:close()
|
||||||
|
|
||||||
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
return dovecot.auth.PASSDB_RESULT_PASSWORD_MISMATCH, "Failed to authenticate"
|
||||||
|
|
||||||
-- PoC
|
|
||||||
-- local reqbody = string.format([[{
|
|
||||||
-- "success":0,
|
|
||||||
-- "service":"%s",
|
|
||||||
-- "app_password":false,
|
|
||||||
-- "username":"%s",
|
|
||||||
-- "real_rip":"%s"
|
|
||||||
-- }]], con:escape(req.service), con:escape(req.user), con:escape(req.real_rip))
|
|
||||||
-- http.request {
|
|
||||||
-- method = "POST",
|
|
||||||
-- url = "http://nginx:8081/sasl_log.php",
|
|
||||||
-- source = ltn12.source.string(reqbody),
|
|
||||||
-- headers = {
|
|
||||||
-- ["content-type"] = "application/json",
|
|
||||||
-- ["content-length"] = tostring(#reqbody)
|
|
||||||
-- },
|
|
||||||
-- sink = ltn12.sink.table(respbody)
|
|
||||||
-- }
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function auth_passdb_lookup(req)
|
function auth_passdb_lookup(req)
|
||||||
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
|
return dovecot.auth.PASSDB_RESULT_USER_UNKNOWN, ""
|
||||||
end
|
end
|
||||||
|
|
||||||
function script_init()
|
|
||||||
mysql = require "luasql.mysql"
|
|
||||||
http = require "socket.http"
|
|
||||||
http.TIMEOUT = 5
|
|
||||||
ltn12 = require "ltn12"
|
|
||||||
env = mysql.mysql()
|
|
||||||
con = env:connect("__DBNAME__","__DBUSER__","__DBPASS__","localhost")
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function script_deinit()
|
|
||||||
con:close()
|
|
||||||
env:close()
|
|
||||||
end
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Replace patterns in app-passdb.lua
|
# Replace patterns in app-passdb.lua
|
||||||
|
|
|
@ -0,0 +1,278 @@
|
||||||
|
<?php
|
||||||
|
function check_login($user, $pass, $app_passwd_data = false) {
|
||||||
|
global $pdo;
|
||||||
|
global $redis;
|
||||||
|
|
||||||
|
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => 'malformed_username'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate admin
|
||||||
|
$result = mailcow_admin_login($user, $pass);
|
||||||
|
if ($result){
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate domain admin
|
||||||
|
$result = mailcow_domainadmin_login($user, $pass);
|
||||||
|
if ($result){
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate mailbox user
|
||||||
|
// skip log & ldelay if requests comes from dovecot
|
||||||
|
$is_dovecot = false;
|
||||||
|
$request_ip = ($_SERVER['HTTP_X_REAL_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
|
||||||
|
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||||
|
AND `mailbox`.`active`='1'
|
||||||
|
AND `domain`.`active`='1'
|
||||||
|
AND `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($row['authsource'] == 'keycloak'){
|
||||||
|
$result = keycloak_mbox_login($user, $pass, $is_dovecot);
|
||||||
|
if ($result){
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$result = mailcow_mbox_login($user, $pass, $app_passwd_data, $is_dovecot);
|
||||||
|
if ($result){
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip log and only return false
|
||||||
|
// netfilter uses dovecot error log for banning
|
||||||
|
if ($is_dovecot){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isset($_SESSION['ldelay'])) {
|
||||||
|
$_SESSION['ldelay'] = "0";
|
||||||
|
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
|
}
|
||||||
|
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
||||||
|
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
||||||
|
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
|
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
||||||
|
}
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'danger',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => 'login_failed'
|
||||||
|
);
|
||||||
|
|
||||||
|
sleep($_SESSION['ldelay']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mailcow_mbox_login($user, $pass, $app_passwd_data = false, $is_internal = false){
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM `mailbox`
|
||||||
|
INNER JOIN domain on mailbox.domain = domain.domain
|
||||||
|
WHERE `kind` NOT REGEXP 'location|thing|group'
|
||||||
|
AND `mailbox`.`active`='1'
|
||||||
|
AND `domain`.`active`='1'
|
||||||
|
AND (`mailbox`.`authsource`='mailcow' OR `mailbox`.`authsource` IS NULL)
|
||||||
|
AND `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// check if password is app password
|
||||||
|
$is_app_passwd = false;
|
||||||
|
if ($app_passwd_data['eas']){
|
||||||
|
$is_app_passwd = 'eas';
|
||||||
|
} else if ($app_passwd_data['dav']){
|
||||||
|
$is_app_passwd = 'dav';
|
||||||
|
} else if ($app_passwd_data['smtp']){
|
||||||
|
$is_app_passwd = 'smtp';
|
||||||
|
} else if ($app_passwd_data['imap']){
|
||||||
|
$is_app_passwd = 'imap';
|
||||||
|
} else if ($app_passwd_data['sieve']){
|
||||||
|
$is_app_passwd = 'sieve';
|
||||||
|
} else if ($app_passwd_data['pop3']){
|
||||||
|
$is_app_passwd = 'pop3';
|
||||||
|
}
|
||||||
|
if ($is_app_passwd){
|
||||||
|
// fetch app password data
|
||||||
|
$app_passwd_query = "SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
|
||||||
|
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
|
||||||
|
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
|
||||||
|
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
|
||||||
|
AND `mailbox`.`active` = '1'
|
||||||
|
AND `domain`.`active` = '1'
|
||||||
|
AND `app_passwd`.`active` = '1'
|
||||||
|
AND `app_passwd`.`mailbox` = :user";
|
||||||
|
// check if app password has protocol access
|
||||||
|
// skip if $app_passwd_data['ignore_hasaccess'] is true and the call is not external
|
||||||
|
if (!$app_passwd_data['ignore_hasaccess'] || !$is_internal){
|
||||||
|
$app_passwd_query = $app_passwd_query . " AND `app_passwd`.`" . $is_app_passwd . "_access` = '1'";
|
||||||
|
}
|
||||||
|
// fetch password data
|
||||||
|
$stmt = $pdo->prepare($app_passwd_query);
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
// verify password
|
||||||
|
if (verify_hash($row['password'], $pass) !== false) {
|
||||||
|
if (!$is_app_passwd){
|
||||||
|
// password is not a app password
|
||||||
|
// check for tfa authenticators
|
||||||
|
$authenticators = get_tfa($user);
|
||||||
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 && !$is_internal) {
|
||||||
|
// authenticators found, init TFA flow
|
||||||
|
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||||
|
$_SESSION['pending_mailcow_cc_role'] = "user";
|
||||||
|
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => array('logged_in_as', $user)
|
||||||
|
);
|
||||||
|
return "pending";
|
||||||
|
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
|
||||||
|
// no authenticators found, login successfull
|
||||||
|
if (!$is_internal){
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
// skip log
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => array('logged_in_as', $user)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
} elseif ($is_app_passwd) {
|
||||||
|
// password is a app password
|
||||||
|
if ($is_internal){
|
||||||
|
// skip log
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
|
||||||
|
$service = strtoupper($is_app_passwd);
|
||||||
|
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
|
||||||
|
$stmt->execute(array(
|
||||||
|
':service' => $service,
|
||||||
|
':app_id' => $row['app_passwd_id'],
|
||||||
|
':username' => $user,
|
||||||
|
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
|
||||||
|
));
|
||||||
|
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
return "user";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function mailcow_domainadmin_login($user, $pass){
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
|
WHERE `superadmin` = '0'
|
||||||
|
AND `active`='1'
|
||||||
|
AND `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
// verify password
|
||||||
|
if (verify_hash($row['password'], $pass) !== false) {
|
||||||
|
// check for tfa authenticators
|
||||||
|
$authenticators = get_tfa($user);
|
||||||
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||||
|
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||||
|
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
|
||||||
|
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'info',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => 'awaiting_tfa_confirmation'
|
||||||
|
);
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => array('logged_in_as', $user)
|
||||||
|
);
|
||||||
|
return "domainadmin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function mailcow_admin_login($user, $pass){
|
||||||
|
global $pdo;
|
||||||
|
|
||||||
|
$user = strtolower(trim($user));
|
||||||
|
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||||
|
WHERE `superadmin` = '1'
|
||||||
|
AND `active` = '1'
|
||||||
|
AND `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
// verify password
|
||||||
|
if (verify_hash($row['password'], $pass)) {
|
||||||
|
// check for tfa authenticators
|
||||||
|
$authenticators = get_tfa($user);
|
||||||
|
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||||
|
// active tfa authenticators found, set pending user login
|
||||||
|
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||||
|
$_SESSION['pending_mailcow_cc_role'] = "admin";
|
||||||
|
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'info',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => 'awaiting_tfa_confirmation'
|
||||||
|
);
|
||||||
|
return "pending";
|
||||||
|
} else {
|
||||||
|
unset($_SESSION['ldelay']);
|
||||||
|
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||||
|
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||||
|
$stmt->execute(array(':user' => $user));
|
||||||
|
$_SESSION['return'][] = array(
|
||||||
|
'type' => 'success',
|
||||||
|
'log' => array(__FUNCTION__, $user, '*'),
|
||||||
|
'msg' => array('logged_in_as', $user)
|
||||||
|
);
|
||||||
|
return "admin";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function keycloak_mbox_login($user, $pass, $is_internal = false){
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -810,204 +810,6 @@ function verify_hash($hash, $password) {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function check_login($user, $pass, $app_passwd_data = false) {
|
|
||||||
global $pdo;
|
|
||||||
global $redis;
|
|
||||||
global $imap_server;
|
|
||||||
|
|
||||||
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => 'malformed_username'
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate admin
|
|
||||||
$user = strtolower(trim($user));
|
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
|
||||||
WHERE `superadmin` = '1'
|
|
||||||
AND `active` = '1'
|
|
||||||
AND `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
// verify password
|
|
||||||
if (verify_hash($row['password'], $pass)) {
|
|
||||||
// check for tfa authenticators
|
|
||||||
$authenticators = get_tfa($user);
|
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
|
||||||
// active tfa authenticators found, set pending user login
|
|
||||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
|
||||||
$_SESSION['pending_mailcow_cc_role'] = "admin";
|
|
||||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'info',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => 'awaiting_tfa_confirmation'
|
|
||||||
);
|
|
||||||
return "pending";
|
|
||||||
} else {
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => array('logged_in_as', $user)
|
|
||||||
);
|
|
||||||
return "admin";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate domain admin
|
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
|
||||||
WHERE `superadmin` = '0'
|
|
||||||
AND `active`='1'
|
|
||||||
AND `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
// verify password
|
|
||||||
if (verify_hash($row['password'], $pass) !== false) {
|
|
||||||
// check for tfa authenticators
|
|
||||||
$authenticators = get_tfa($user);
|
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
|
||||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
|
||||||
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
|
|
||||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'info',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => 'awaiting_tfa_confirmation'
|
|
||||||
);
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => array('logged_in_as', $user)
|
|
||||||
);
|
|
||||||
return "domainadmin";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate mailbox user
|
|
||||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
|
||||||
INNER JOIN domain on mailbox.domain = domain.domain
|
|
||||||
WHERE `kind` NOT REGEXP 'location|thing|group'
|
|
||||||
AND `mailbox`.`active`='1'
|
|
||||||
AND `domain`.`active`='1'
|
|
||||||
AND `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if ($app_passwd_data['eas'] === true) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
|
|
||||||
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
|
|
||||||
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
|
|
||||||
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
|
|
||||||
AND `mailbox`.`active` = '1'
|
|
||||||
AND `domain`.`active` = '1'
|
|
||||||
AND `app_passwd`.`active` = '1'
|
|
||||||
AND `app_passwd`.`eas_access` = '1'
|
|
||||||
AND `app_passwd`.`mailbox` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
}
|
|
||||||
elseif ($app_passwd_data['dav'] === true) {
|
|
||||||
$stmt = $pdo->prepare("SELECT `app_passwd`.`password` as `password`, `app_passwd`.`id` as `app_passwd_id` FROM `app_passwd`
|
|
||||||
INNER JOIN `mailbox` ON `mailbox`.`username` = `app_passwd`.`mailbox`
|
|
||||||
INNER JOIN `domain` ON `mailbox`.`domain` = `domain`.`domain`
|
|
||||||
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group'
|
|
||||||
AND `mailbox`.`active` = '1'
|
|
||||||
AND `domain`.`active` = '1'
|
|
||||||
AND `app_passwd`.`active` = '1'
|
|
||||||
AND `app_passwd`.`dav_access` = '1'
|
|
||||||
AND `app_passwd`.`mailbox` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
|
|
||||||
}
|
|
||||||
foreach ($rows as $row) {
|
|
||||||
// verify password
|
|
||||||
if (verify_hash($row['password'], $pass) !== false) {
|
|
||||||
if (!array_key_exists("app_passwd_id", $row)){
|
|
||||||
// password is not a app password
|
|
||||||
// check for tfa authenticators
|
|
||||||
$authenticators = get_tfa($user);
|
|
||||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
|
|
||||||
$app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) {
|
|
||||||
// authenticators found, init TFA flow
|
|
||||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
|
||||||
$_SESSION['pending_mailcow_cc_role'] = "user";
|
|
||||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => array('logged_in_as', $user)
|
|
||||||
);
|
|
||||||
return "pending";
|
|
||||||
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
// no authenticators found, login successfull
|
|
||||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
|
||||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
|
||||||
$stmt->execute(array(':user' => $user));
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'success',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => array('logged_in_as', $user)
|
|
||||||
);
|
|
||||||
return "user";
|
|
||||||
}
|
|
||||||
} elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
|
|
||||||
// password is a app password
|
|
||||||
$service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
|
|
||||||
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
|
|
||||||
$stmt->execute(array(
|
|
||||||
':service' => $service,
|
|
||||||
':app_id' => $row['app_passwd_id'],
|
|
||||||
':username' => $user,
|
|
||||||
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
|
|
||||||
));
|
|
||||||
|
|
||||||
unset($_SESSION['ldelay']);
|
|
||||||
return "user";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($_SESSION['ldelay'])) {
|
|
||||||
$_SESSION['ldelay'] = "0";
|
|
||||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
elseif (!isset($_SESSION['mailcow_cc_username'])) {
|
|
||||||
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
|
|
||||||
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$_SESSION['return'][] = array(
|
|
||||||
'type' => 'danger',
|
|
||||||
'log' => array(__FUNCTION__, $user, '*'),
|
|
||||||
'msg' => 'login_failed'
|
|
||||||
);
|
|
||||||
|
|
||||||
sleep($_SESSION['ldelay']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function formatBytes($size, $precision = 2) {
|
function formatBytes($size, $precision = 2) {
|
||||||
if(!is_numeric($size)) {
|
if(!is_numeric($size)) {
|
||||||
return "0";
|
return "0";
|
||||||
|
|
|
@ -988,6 +988,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
$local_part = strtolower(trim($_data['local_part']));
|
$local_part = strtolower(trim($_data['local_part']));
|
||||||
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
|
||||||
$username = $local_part . '@' . $domain;
|
$username = $local_part . '@' . $domain;
|
||||||
|
$authsource = 'mailcow';
|
||||||
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
@ -1004,11 +1005,19 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (in_array($_data['authsource'], array('mailcow', 'keycloak'))){
|
||||||
|
$authsource = $_data['authsource'];
|
||||||
|
}
|
||||||
$password = $_data['password'];
|
$password = $_data['password'];
|
||||||
$password2 = $_data['password2'];
|
$password2 = $_data['password2'];
|
||||||
$name = ltrim(rtrim($_data['name'], '>'), '<');
|
$name = ltrim(rtrim($_data['name'], '>'), '<');
|
||||||
$tags = $_data['tags'];
|
$tags = $_data['tags'];
|
||||||
$quota_m = intval($_data['quota']);
|
$quota_m = intval($_data['quota']);
|
||||||
|
if ($authsource != 'mailcow'){
|
||||||
|
$password = '';
|
||||||
|
$password2 = '';
|
||||||
|
$password_hashed = '';
|
||||||
|
}
|
||||||
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
|
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
@ -1129,10 +1138,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (password_check($password, $password2) !== true) {
|
if ($authsource == 'mailcow'){
|
||||||
return false;
|
if (password_check($password, $password2) !== true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$password_hashed = hash_password($password);
|
||||||
}
|
}
|
||||||
$password_hashed = hash_password($password);
|
|
||||||
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
|
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
'type' => 'danger',
|
'type' => 'danger',
|
||||||
|
@ -1158,8 +1169,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`)
|
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `authsource`, `active`)
|
||||||
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)");
|
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :authsource, :active)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
':username' => $username,
|
':username' => $username,
|
||||||
':password_hashed' => $password_hashed,
|
':password_hashed' => $password_hashed,
|
||||||
|
@ -1168,6 +1179,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
':local_part' => $local_part,
|
':local_part' => $local_part,
|
||||||
':domain' => $domain,
|
':domain' => $domain,
|
||||||
':mailbox_attrs' => $mailbox_attrs,
|
':mailbox_attrs' => $mailbox_attrs,
|
||||||
|
':authsource' => $authsource,
|
||||||
':active' => $active
|
':active' => $active
|
||||||
));
|
));
|
||||||
$stmt = $pdo->prepare("UPDATE `mailbox` SET
|
$stmt = $pdo->prepare("UPDATE `mailbox` SET
|
||||||
|
@ -4190,6 +4202,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
`mailbox`.`quota`,
|
`mailbox`.`quota`,
|
||||||
`mailbox`.`created`,
|
`mailbox`.`created`,
|
||||||
`mailbox`.`modified`,
|
`mailbox`.`modified`,
|
||||||
|
`mailbox`.`authsource`,
|
||||||
`quota2`.`bytes`,
|
`quota2`.`bytes`,
|
||||||
`attributes`,
|
`attributes`,
|
||||||
`quota2`.`messages`
|
`quota2`.`messages`
|
||||||
|
@ -4210,6 +4223,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
`mailbox`.`quota`,
|
`mailbox`.`quota`,
|
||||||
`mailbox`.`created`,
|
`mailbox`.`created`,
|
||||||
`mailbox`.`modified`,
|
`mailbox`.`modified`,
|
||||||
|
`mailbox`.`authsource`,
|
||||||
`quota2replica`.`bytes`,
|
`quota2replica`.`bytes`,
|
||||||
`attributes`,
|
`attributes`,
|
||||||
`quota2replica`.`messages`
|
`quota2replica`.`messages`
|
||||||
|
@ -4238,6 +4252,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||||
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
|
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
|
||||||
$mailboxdata['created'] = $row['created'];
|
$mailboxdata['created'] = $row['created'];
|
||||||
$mailboxdata['modified'] = $row['modified'];
|
$mailboxdata['modified'] = $row['modified'];
|
||||||
|
$mailboxdata['authsource'] = ($row['authsource']) ? $row['authsource'] : 'mailcow';
|
||||||
|
|
||||||
if ($mailboxdata['percent_in_use'] === '- ') {
|
if ($mailboxdata['percent_in_use'] === '- ') {
|
||||||
$mailboxdata['percent_class'] = "info";
|
$mailboxdata['percent_class'] = "info";
|
||||||
|
|
|
@ -3,7 +3,7 @@ function init_db_schema() {
|
||||||
try {
|
try {
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$db_version = "06012023_1924";
|
$db_version = "12032023_1705";
|
||||||
|
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
@ -225,22 +225,22 @@ function init_db_schema() {
|
||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
),
|
),
|
||||||
"templates" => array(
|
"templates" => array(
|
||||||
"cols" => array(
|
"cols" => array(
|
||||||
"id" => "INT NOT NULL AUTO_INCREMENT",
|
"id" => "INT NOT NULL AUTO_INCREMENT",
|
||||||
"template" => "VARCHAR(255) NOT NULL",
|
"template" => "VARCHAR(255) NOT NULL",
|
||||||
"type" => "VARCHAR(255) NOT NULL",
|
"type" => "VARCHAR(255) NOT NULL",
|
||||||
"attributes" => "JSON",
|
"attributes" => "JSON",
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
|
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
|
||||||
),
|
),
|
||||||
"keys" => array(
|
"keys" => array(
|
||||||
"primary" => array(
|
"primary" => array(
|
||||||
"" => array("id")
|
"" => array("id")
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
|
||||||
),
|
),
|
||||||
"domain" => array(
|
"domain" => array(
|
||||||
// Todo: Move some attributes to json
|
// Todo: Move some attributes to json
|
||||||
"cols" => array(
|
"cols" => array(
|
||||||
|
@ -346,6 +346,7 @@ function init_db_schema() {
|
||||||
"attributes" => "JSON",
|
"attributes" => "JSON",
|
||||||
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
|
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
|
||||||
"multiple_bookings" => "INT NOT NULL DEFAULT -1",
|
"multiple_bookings" => "INT NOT NULL DEFAULT -1",
|
||||||
|
"authsource" => "ENUM('mailcow', 'keycloak') DEFAULT 'mailcow'",
|
||||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||||
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
|
||||||
|
@ -1076,7 +1077,7 @@ function init_db_schema() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate tls_enforce_* options
|
// Migrate tls_enforce_* options
|
||||||
if ($table == 'mailbox') {
|
if ($table == 'mailbox') {
|
||||||
$stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'");
|
$stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'");
|
||||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||||
|
@ -1322,95 +1323,95 @@ function init_db_schema() {
|
||||||
// Fix domain_admins
|
// Fix domain_admins
|
||||||
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
|
$pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
|
||||||
|
|
||||||
// add default templates
|
// add default templates
|
||||||
$default_domain_template = array(
|
$default_domain_template = array(
|
||||||
"template" => "Default",
|
"template" => "Default",
|
||||||
"type" => "domain",
|
"type" => "domain",
|
||||||
"attributes" => array(
|
"attributes" => array(
|
||||||
"tags" => array(),
|
"tags" => array(),
|
||||||
"max_num_aliases_for_domain" => 400,
|
"max_num_aliases_for_domain" => 400,
|
||||||
"max_num_mboxes_for_domain" => 10,
|
"max_num_mboxes_for_domain" => 10,
|
||||||
"def_quota_for_mbox" => 3072 * 1048576,
|
"def_quota_for_mbox" => 3072 * 1048576,
|
||||||
"max_quota_for_mbox" => 10240 * 1048576,
|
"max_quota_for_mbox" => 10240 * 1048576,
|
||||||
"max_quota_for_domain" => 10240 * 1048576,
|
"max_quota_for_domain" => 10240 * 1048576,
|
||||||
"rl_frame" => "s",
|
"rl_frame" => "s",
|
||||||
"rl_value" => "",
|
"rl_value" => "",
|
||||||
"active" => 1,
|
"active" => 1,
|
||||||
"gal" => 1,
|
"gal" => 1,
|
||||||
"backupmx" => 0,
|
"backupmx" => 0,
|
||||||
"relay_all_recipients" => 0,
|
"relay_all_recipients" => 0,
|
||||||
"relay_unknown_only" => 0,
|
"relay_unknown_only" => 0,
|
||||||
"dkim_selector" => "dkim",
|
"dkim_selector" => "dkim",
|
||||||
"key_size" => 2048,
|
"key_size" => 2048,
|
||||||
"max_quota_for_domain" => 10240 * 1048576,
|
"max_quota_for_domain" => 10240 * 1048576,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$default_mailbox_template = array(
|
$default_mailbox_template = array(
|
||||||
"template" => "Default",
|
"template" => "Default",
|
||||||
"type" => "mailbox",
|
"type" => "mailbox",
|
||||||
"attributes" => array(
|
"attributes" => array(
|
||||||
"tags" => array(),
|
"tags" => array(),
|
||||||
"quota" => 0,
|
"quota" => 0,
|
||||||
"quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']),
|
"quarantine_notification" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_notification']),
|
||||||
"quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']),
|
"quarantine_category" => strval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['quarantine_category']),
|
||||||
"rl_frame" => "s",
|
"rl_frame" => "s",
|
||||||
"rl_value" => "",
|
"rl_value" => "",
|
||||||
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
|
"force_pw_update" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['force_pw_update']),
|
||||||
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
|
"sogo_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sogo_access']),
|
||||||
"active" => 1,
|
"active" => 1,
|
||||||
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
|
"tls_enforce_in" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_in']),
|
||||||
"tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
|
"tls_enforce_out" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['tls_enforce_out']),
|
||||||
"imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']),
|
"imap_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['imap_access']),
|
||||||
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
|
"pop3_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['pop3_access']),
|
||||||
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
|
"smtp_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['smtp_access']),
|
||||||
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
|
"sieve_access" => intval($GLOBALS['MAILBOX_DEFAULT_ATTRIBUTES']['sieve_access']),
|
||||||
"acl_spam_alias" => 1,
|
"acl_spam_alias" => 1,
|
||||||
"acl_tls_policy" => 1,
|
"acl_tls_policy" => 1,
|
||||||
"acl_spam_score" => 1,
|
"acl_spam_score" => 1,
|
||||||
"acl_spam_policy" => 1,
|
"acl_spam_policy" => 1,
|
||||||
"acl_delimiter_action" => 1,
|
"acl_delimiter_action" => 1,
|
||||||
"acl_syncjobs" => 0,
|
"acl_syncjobs" => 0,
|
||||||
"acl_eas_reset" => 1,
|
"acl_eas_reset" => 1,
|
||||||
"acl_sogo_profile_reset" => 0,
|
"acl_sogo_profile_reset" => 0,
|
||||||
"acl_pushover" => 1,
|
"acl_pushover" => 1,
|
||||||
"acl_quarantine" => 1,
|
"acl_quarantine" => 1,
|
||||||
"acl_quarantine_attachments" => 1,
|
"acl_quarantine_attachments" => 1,
|
||||||
"acl_quarantine_notification" => 1,
|
"acl_quarantine_notification" => 1,
|
||||||
"acl_quarantine_category" => 1,
|
"acl_quarantine_category" => 1,
|
||||||
"acl_app_passwds" => 1,
|
"acl_app_passwds" => 1,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
|
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
":type" => "domain",
|
":type" => "domain",
|
||||||
":template" => $default_domain_template["template"]
|
":template" => $default_domain_template["template"]
|
||||||
));
|
));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (empty($row)){
|
if (empty($row)){
|
||||||
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
|
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
|
||||||
VALUES (:type, :template, :attributes)");
|
VALUES (:type, :template, :attributes)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
":type" => "domain",
|
":type" => "domain",
|
||||||
":template" => $default_domain_template["template"],
|
":template" => $default_domain_template["template"],
|
||||||
":attributes" => json_encode($default_domain_template["attributes"])
|
":attributes" => json_encode($default_domain_template["attributes"])
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
|
$stmt = $pdo->prepare("SELECT id FROM `templates` WHERE `type` = :type AND `template` = :template");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
":type" => "mailbox",
|
":type" => "mailbox",
|
||||||
":template" => $default_mailbox_template["template"]
|
":template" => $default_mailbox_template["template"]
|
||||||
));
|
));
|
||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (empty($row)){
|
if (empty($row)){
|
||||||
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
|
$stmt = $pdo->prepare("INSERT INTO `templates` (`type`, `template`, `attributes`)
|
||||||
VALUES (:type, :template, :attributes)");
|
VALUES (:type, :template, :attributes)");
|
||||||
$stmt->execute(array(
|
$stmt->execute(array(
|
||||||
":type" => "mailbox",
|
":type" => "mailbox",
|
||||||
":template" => $default_mailbox_template["template"],
|
":template" => $default_mailbox_template["template"],
|
||||||
":attributes" => json_encode($default_mailbox_template["attributes"])
|
":attributes" => json_encode($default_mailbox_template["attributes"])
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (php_sapi_name() == "cli") {
|
if (php_sapi_name() == "cli") {
|
||||||
echo "DB initialization completed" . PHP_EOL;
|
echo "DB initialization completed" . PHP_EOL;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -173,6 +173,7 @@ function get_remote_ip() {
|
||||||
|
|
||||||
// Load core functions first
|
// Load core functions first
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
||||||
|
|
||||||
// IMAP lib
|
// IMAP lib
|
||||||
|
|
|
@ -288,18 +288,18 @@ if (isset($_GET['query'])) {
|
||||||
case "domain-admin":
|
case "domain-admin":
|
||||||
process_add_return(domain_admin('add', $attr));
|
process_add_return(domain_admin('add', $attr));
|
||||||
break;
|
break;
|
||||||
case "sso":
|
case "sso":
|
||||||
switch ($object) {
|
switch ($object) {
|
||||||
case "domain-admin":
|
case "domain-admin":
|
||||||
$data = domain_admin_sso('issue', $attr);
|
$data = domain_admin_sso('issue', $attr);
|
||||||
if($data) {
|
if($data) {
|
||||||
echo json_encode($data);
|
echo json_encode($data);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
process_add_return($data);
|
process_add_return($data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "admin":
|
case "admin":
|
||||||
process_add_return(admin('add', $attr));
|
process_add_return(admin('add', $attr));
|
||||||
break;
|
break;
|
||||||
|
@ -401,6 +401,26 @@ if (isset($_GET['query'])) {
|
||||||
);
|
);
|
||||||
echo json_encode($return);
|
echo json_encode($return);
|
||||||
break;
|
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;
|
break;
|
||||||
case "get":
|
case "get":
|
||||||
|
|
|
@ -11,7 +11,8 @@ $session_var_pass = 'sogo-sso-pass';
|
||||||
// validate credentials for basic auth requests
|
// validate credentials for basic auth requests
|
||||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
// load prerequisites only when required
|
// load prerequisites only when required
|
||||||
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
|
||||||
|
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.auth.inc.php';
|
||||||
$username = $_SERVER['PHP_AUTH_USER'];
|
$username = $_SERVER['PHP_AUTH_USER'];
|
||||||
$password = $_SERVER['PHP_AUTH_PW'];
|
$password = $_SERVER['PHP_AUTH_PW'];
|
||||||
$is_eas = false;
|
$is_eas = false;
|
||||||
|
|
|
@ -16,6 +16,12 @@
|
||||||
<input type="hidden" value="0" name="force_pw_update">
|
<input type="hidden" value="0" name="force_pw_update">
|
||||||
<input type="hidden" value="0" name="sogo_access">
|
<input type="hidden" value="0" name="sogo_access">
|
||||||
<input type="hidden" value="0" name="protocol_access">
|
<input type="hidden" value="0" name="protocol_access">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2">{{ lang.edit.full_name }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<span>{{ result.authsource }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<label class="control-label col-sm-2" for="name">{{ lang.edit.full_name }}</label>
|
<label class="control-label col-sm-2" for="name">{{ lang.edit.full_name }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
|
|
@ -28,6 +28,15 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="authsource">{{ lang.add.domain }}</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<select class="full-width-select" data-live-search="true" id="addAuthsource" name="authsource" required>
|
||||||
|
<option selected>mailcow</option>
|
||||||
|
<option>keycloak</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="name">{{ lang.add.full_name }}</label>
|
<label class="control-label col-sm-2 text-sm-end text-sm-end" for="name">{{ lang.add.full_name }}</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
|
|
Loading…
Reference in New Issue