2016-12-10 03:39:02 +08:00
< ? php
function hash_password ( $password ) {
$salt_str = bin2hex ( openssl_random_pseudo_bytes ( 8 ));
2016-12-13 19:25:46 +08:00
return " { SSHA256} " . base64_encode ( hash ( 'sha256' , $password . $salt_str , true ) . $salt_str );
2016-12-10 03:39:02 +08:00
}
function hasDomainAccess ( $username , $role , $domain ) {
global $pdo ;
if ( ! filter_var ( $username , FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username ))) {
return false ;
}
2017-01-21 18:49:29 +08:00
if ( empty ( $domain ) || ! is_valid_domain_name ( $domain )) {
2016-12-10 03:39:02 +08:00
return false ;
}
2017-01-03 17:39:32 +08:00
if ( $role != 'admin' && $role != 'domainadmin' && $role != 'user' ) {
return false ;
}
2016-12-10 03:39:02 +08:00
try {
2017-01-21 18:49:29 +08:00
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `domain_admins`
WHERE (
`active` = '1'
AND `username` = : username
AND ( `domain` = : domain1 OR `domain` = ( SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = : domain2 ))
)
OR 'admin' = : role " );
2017-01-19 04:28:31 +08:00
$stmt -> execute ( array ( ':username' => $username , ':domain1' => $domain , ':domain2' => $domain , ':role' => $role ));
2016-12-10 03:39:02 +08:00
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
2017-01-19 04:28:31 +08:00
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
if ( ! empty ( $num_results )) {
return true ;
}
return false ;
}
function hasMailboxObjectAccess ( $username , $role , $object ) {
global $pdo ;
if ( ! filter_var ( $username , FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username ))) {
return false ;
}
if ( $role != 'admin' && $role != 'domainadmin' && $role != 'user' ) {
2016-12-10 03:39:02 +08:00
return false ;
}
2017-01-19 04:28:31 +08:00
if ( $username == $object ) {
2016-12-10 03:39:02 +08:00
return true ;
}
2017-01-19 04:28:31 +08:00
try {
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `mailbox` WHERE `username` = :object " );
$stmt -> execute ( array ( ':object' => $object ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( isset ( $row [ 'domain' ]) && hasDomainAccess ( $username , $role , $row [ 'domain' ])) {
return true ;
}
}
catch ( PDOException $e ) {
error_log ( $e );
return false ;
}
2016-12-10 03:39:02 +08:00
return false ;
}
2017-06-23 20:23:12 +08:00
function generate_tlsa_digest ( $hostname , $port , $starttls = null ) {
if ( ! is_valid_domain_name ( $hostname )) {
return " Not a valid hostname " ;
}
function pem_to_der ( $pem_key ) {
// Need to remove BEGIN/END PUBLIC KEY
$lines = explode ( " \n " , trim ( $pem_key ));
unset ( $lines [ count ( $lines ) - 1 ]);
unset ( $lines [ 0 ]);
return base64_decode ( implode ( '' , $lines ));
}
if ( empty ( $starttls )) {
$context = stream_context_create ( array ( " ssl " => array ( " capture_peer_cert " => true , 'verify_peer' => false , 'allow_self_signed' => true )));
$stream = stream_socket_client ( 'tls://' . $hostname . ':' . $port , $error_nr , $error_msg , 5 , STREAM_CLIENT_CONNECT , $context );
// error_nr can be 0, so checking against error_msg
if ( $error_msg ) {
return $error_nr . ': ' . $error_msg ;
}
}
else {
$stream = stream_socket_client ( 'tcp://' . $hostname . ':' . $port , $error_nr , $error_msg , 5 );
if ( $error_msg ) {
return $error_nr . ': ' . $error_msg ;
}
$banner = fread ( $stream , 512 );
if ( preg_match ( " /^220/i " , $banner )) {
fwrite ( $stream , " HELO tlsa.generator.local \r \n " );
fread ( $stream , 512 );
fwrite ( $stream , " STARTTLS \r \n " );
fread ( $stream , 512 );
}
elseif ( preg_match ( " /imap.+starttls/i " , $banner )) {
fwrite ( $stream , " A1 STARTTLS \r \n " );
fread ( $stream , 512 );
}
elseif ( preg_match ( " /^ \ +OK/ " , $banner )) {
fwrite ( $stream , " STLS \r \n " );
fread ( $stream , 512 );
}
else {
return 'Unknown banner: "' . htmlspecialchars ( trim ( $banner )) . '"' ;
}
// Upgrade connection
stream_set_blocking ( $stream , true );
stream_context_set_option ( $stream , 'ssl' , 'capture_peer_cert' , true );
stream_context_set_option ( $stream , 'ssl' , 'verify_peer' , false );
stream_context_set_option ( $stream , 'ssl' , 'allow_self_signed' , true );
stream_socket_enable_crypto ( $stream , true , STREAM_CRYPTO_METHOD_ANY_CLIENT );
stream_set_blocking ( $stream , false );
}
$params = stream_context_get_params ( $stream );
if ( ! empty ( $params [ 'options' ][ 'ssl' ][ 'peer_certificate' ])) {
$key_resource = openssl_pkey_get_public ( $params [ 'options' ][ 'ssl' ][ 'peer_certificate' ]);
// We cannot get ['rsa']['n'], the binary data would contain BEGIN/END PUBLIC KEY
$key_data = openssl_pkey_get_details ( $key_resource )[ 'key' ];
return '3 1 1 ' . openssl_digest ( pem_to_der ( $key_data ), 'sha256' );
}
else {
return 'Error: Cannot read peer certificate' ;
}
}
2016-12-13 19:25:46 +08:00
function verify_ssha256 ( $hash , $password ) {
2016-12-13 04:53:58 +08:00
// Remove tag if any
$hash = ltrim ( $hash , '{SSHA256}' );
// Decode hash
$dhash = base64_decode ( $hash );
// Get first 32 bytes of binary which equals a SHA256 hash
$ohash = substr ( $dhash , 0 , 32 );
// Remove SHA256 hash from decoded hash to get original salt string
$osalt = str_replace ( $ohash , '' , $dhash );
// Check single salted SHA256 hash against extracted hash
if ( hash ( 'sha256' , $password . $osalt , true ) == $ohash ) {
return true ;
}
else {
return false ;
}
}
2016-12-10 03:39:02 +08:00
function doveadm_authenticate ( $hash , $algorithm , $password ) {
$descr = array ( 0 => array ( 'pipe' , 'r' ), 1 => array ( 'pipe' , 'w' ), 2 => array ( 'pipe' , 'w' ));
$pipes = array ();
$process = proc_open ( " /usr/bin/doveadm pw -s " . $algorithm . " -t ' " . $hash . " ' " , $descr , $pipes );
if ( is_resource ( $process )) {
fputs ( $pipes [ 0 ], $password );
fclose ( $pipes [ 0 ]);
while ( $f = fgets ( $pipes [ 1 ])) {
if ( preg_match ( '/(verified)/' , $f )) {
proc_close ( $process );
return true ;
}
return false ;
}
fclose ( $pipes [ 1 ]);
while ( $f = fgets ( $pipes [ 2 ])) {
proc_close ( $process );
return false ;
}
fclose ( $pipes [ 2 ]);
proc_close ( $process );
}
return false ;
}
function check_login ( $user , $pass ) {
global $pdo ;
if ( ! filter_var ( $user , FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $user ))) {
return false ;
}
$user = strtolower ( trim ( $user ));
$stmt = $pdo -> prepare ( " SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `username` = : user " );
$stmt -> execute ( array ( ':user' => $user ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $rows as $row ) {
2017-01-26 02:07:30 +08:00
if ( verify_ssha256 ( $row [ 'password' ], $pass )) {
if ( get_tfa ( $user )[ 'name' ] != " none " ) {
$_SESSION [ 'pending_mailcow_cc_username' ] = $user ;
$_SESSION [ 'pending_mailcow_cc_role' ] = " admin " ;
$_SESSION [ 'pending_tfa_method' ] = get_tfa ( $user )[ 'name' ];
unset ( $_SESSION [ 'ldelay' ]);
return " pending " ;
}
else {
unset ( $_SESSION [ 'ldelay' ]);
return " admin " ;
}
2016-12-10 03:39:02 +08:00
}
}
$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 ) {
2016-12-13 19:25:46 +08:00
if ( verify_ssha256 ( $row [ 'password' ], $pass ) !== false ) {
2017-01-26 02:07:30 +08:00
if ( get_tfa ( $user )[ 'name' ] != " none " ) {
$_SESSION [ 'pending_mailcow_cc_username' ] = $user ;
$_SESSION [ 'pending_mailcow_cc_role' ] = " domainadmin " ;
$_SESSION [ 'pending_tfa_method' ] = get_tfa ( $user )[ 'name' ];
unset ( $_SESSION [ 'ldelay' ]);
return " pending " ;
}
else {
unset ( $_SESSION [ 'ldelay' ]);
2017-02-13 20:42:54 +08:00
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `active`='1' WHERE `username` = :user " );
$stmt -> execute ( array ( ':user' => $user ));
2017-01-26 02:07:30 +08:00
return " domainadmin " ;
}
2016-12-10 03:39:02 +08:00
}
}
$stmt = $pdo -> prepare ( " SELECT `password` FROM `mailbox`
2017-01-28 16:53:39 +08:00
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `active` = '1'
AND `username` = : user " );
2016-12-10 03:39:02 +08:00
$stmt -> execute ( array ( ':user' => $user ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $rows as $row ) {
2016-12-13 19:25:46 +08:00
if ( verify_ssha256 ( $row [ 'password' ], $pass ) !== false ) {
2016-12-10 03:39:02 +08:00
unset ( $_SESSION [ 'ldelay' ]);
return " user " ;
}
}
if ( ! isset ( $_SESSION [ 'ldelay' ])) {
$_SESSION [ 'ldelay' ] = " 0 " ;
2017-06-26 03:33:26 +08:00
error_log ( " Mailcow UI: Invalid password for " . $user . " by " . $_SERVER [ 'REMOTE_ADDR' ]);
2016-12-10 03:39:02 +08:00
}
elseif ( ! isset ( $_SESSION [ 'mailcow_cc_username' ])) {
$_SESSION [ 'ldelay' ] = $_SESSION [ 'ldelay' ] + 0.5 ;
2017-05-25 21:36:44 +08:00
error_log ( " Mailcow UI: Invalid password for " . $user . " by " . $_SERVER [ 'REMOTE_ADDR' ]);
2016-12-10 03:39:02 +08:00
}
sleep ( $_SESSION [ 'ldelay' ]);
}
function formatBytes ( $size , $precision = 2 ) {
if ( ! is_numeric ( $size )) {
return " 0 " ;
}
$base = log ( $size , 1024 );
$suffixes = array ( ' Byte' , ' KiB' , ' MiB' , ' GiB' , ' TiB' );
if ( $size == " 0 " ) {
return " 0 " ;
}
return round ( pow ( 1024 , $base - floor ( $base )), $precision ) . $suffixes [ floor ( $base )];
}
2017-01-26 02:07:30 +08:00
function edit_admin_account ( $postarray ) {
2016-12-10 03:39:02 +08:00
global $lang ;
global $pdo ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2017-01-26 02:07:30 +08:00
$username_now = $_SESSION [ 'mailcow_cc_username' ];
2017-05-24 04:23:46 +08:00
$username = $postarray [ 'admin_user' ];
2017-02-22 05:27:11 +08:00
$password = $postarray [ 'admin_pass' ];
$password2 = $postarray [ 'admin_pass2' ];
2017-01-26 02:07:30 +08:00
if ( ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username )) || empty ( $username )) {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'username_invalid' ])
);
return false ;
}
2017-01-26 02:07:30 +08:00
2017-02-22 05:27:11 +08:00
if ( ! empty ( $password ) && ! empty ( $password2 )) {
if ( ! preg_match ( '/' . $GLOBALS [ 'PASSWD_REGEP' ] . '/' , $password )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_complexity' ])
);
return false ;
}
if ( $password != $password2 ) {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_mismatch' ])
);
return false ;
}
2017-02-22 05:27:11 +08:00
$password_hashed = hash_password ( $password );
2016-12-10 03:39:02 +08:00
try {
$stmt = $pdo -> prepare ( " UPDATE `admin` SET
`password` = : password_hashed ,
2017-01-26 02:07:30 +08:00
`username` = : username1
WHERE `username` = : username2 " );
2016-12-10 03:39:02 +08:00
$stmt -> execute ( array (
':password_hashed' => $password_hashed ,
2017-01-26 02:07:30 +08:00
':username1' => $username ,
':username2' => $username_now
2016-12-10 03:39:02 +08:00
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
}
else {
try {
$stmt = $pdo -> prepare ( " UPDATE `admin` SET
2017-01-26 02:07:30 +08:00
`username` = : username1
WHERE `username` = : username2 " );
2016-12-10 03:39:02 +08:00
$stmt -> execute ( array (
2017-01-26 02:07:30 +08:00
':username1' => $username ,
':username2' => $username_now
2016-12-10 03:39:02 +08:00
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
}
try {
2017-01-26 02:07:30 +08:00
$stmt = $pdo -> prepare ( " UPDATE `domain_admins` SET `domain` = 'ALL', `username` = :username1 WHERE `username` = :username2 " );
$stmt -> execute ( array ( ':username1' => $username , ':username2' => $username_now ));
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `username` = :username1 WHERE `username` = :username2 " );
$stmt -> execute ( array ( ':username1' => $username , ':username2' => $username_now ));
2016-12-10 03:39:02 +08:00
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-01-26 02:07:30 +08:00
$_SESSION [ 'mailcow_cc_username' ] = $username ;
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'admin_modified' ])
);
}
2017-01-21 18:49:29 +08:00
function edit_user_account ( $postarray ) {
2016-12-10 03:39:02 +08:00
global $lang ;
global $pdo ;
2017-01-21 18:49:29 +08:00
if ( isset ( $postarray [ 'username' ]) && filter_var ( $postarray [ 'username' ], FILTER_VALIDATE_EMAIL )) {
if ( ! hasMailboxObjectAccess ( $_SESSION [ 'mailcow_cc_username' ], $_SESSION [ 'mailcow_cc_role' ], $postarray [ 'username' ])) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
else {
$username = $postarray [ 'username' ];
}
}
else {
$username = $_SESSION [ 'mailcow_cc_username' ];
}
2016-12-10 03:39:02 +08:00
$password_old = $postarray [ 'user_old_pass' ];
2017-01-26 02:07:30 +08:00
if ( isset ( $postarray [ 'user_new_pass' ]) && isset ( $postarray [ 'user_new_pass2' ])) {
2016-12-10 03:39:02 +08:00
$password_new = $postarray [ 'user_new_pass' ];
$password_new2 = $postarray [ 'user_new_pass2' ];
}
2017-01-26 02:07:30 +08:00
$stmt = $pdo -> prepare ( " SELECT `password` FROM `mailbox`
2017-01-28 16:53:39 +08:00
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = : user " );
2017-01-26 02:07:30 +08:00
$stmt -> execute ( array ( ':user' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( ! verify_ssha256 ( $row [ 'password' ], $password_old )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2016-12-10 03:39:02 +08:00
if ( isset ( $password_new ) && isset ( $password_new2 )) {
if ( ! empty ( $password_new2 ) && ! empty ( $password_new )) {
if ( $password_new2 != $password_new ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_mismatch' ])
);
return false ;
}
2017-02-22 05:27:11 +08:00
if ( ! preg_match ( '/' . $GLOBALS [ 'PASSWD_REGEP' ] . '/' , $password_new )) {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_complexity' ])
);
return false ;
}
$password_hashed = hash_password ( $password_new );
try {
2017-04-21 16:20:31 +08:00
$stmt = $pdo -> prepare ( " UPDATE `mailbox` SET `password` = :password_hashed WHERE `username` = :username " );
2016-12-10 03:39:02 +08:00
$stmt -> execute ( array (
':password_hashed' => $password_hashed ,
':username' => $username
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
}
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
2017-01-26 02:07:30 +08:00
'msg' => sprintf ( $lang [ 'success' ][ 'mailbox_modified' ], htmlspecialchars ( $username ))
2017-01-21 18:49:29 +08:00
);
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
function user_get_alias_details ( $username ) {
global $lang ;
2017-01-19 04:28:31 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
if ( $_SESSION [ 'mailcow_cc_role' ] == " user " ) {
$username = $_SESSION [ 'mailcow_cc_username' ];
}
if ( ! filter_var ( $username , FILTER_VALIDATE_EMAIL )) {
return false ;
}
try {
$data [ 'address' ] = $username ;
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') AS `aliases` FROM `alias`
WHERE `goto` REGEXP : username_goto
AND `address` NOT LIKE '@%'
AND `address` != : username_address " );
$stmt -> execute ( array ( ':username_goto' => '(^|,)' . $username . '($|,)' , ':username_address' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'aliases' ] = $row [ 'aliases' ];
}
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '✘') AS `ad_alias` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain`
WHERE `username` = : username ; " );
$stmt -> execute ( array ( ':username' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'ad_alias' ] = $row [ 'ad_alias' ];
}
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%'; " );
$stmt -> execute ( array ( ':username' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'aliases_also_send_as' ] = $row [ 'send_as' ];
}
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '✘') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` LIKE '@%'; " );
$stmt -> execute ( array ( ':username' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'aliases_send_as_all' ] = $row [ 'send_as' ];
}
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '✘') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%'; " );
$stmt -> execute ( array ( ':username' => '(^|,)' . $username . '($|,)' ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'is_catch_all' ] = $row [ 'address' ];
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
return $data ;
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
}
function is_valid_domain_name ( $domain_name ) {
if ( empty ( $domain_name )) {
2016-12-10 03:39:02 +08:00
return false ;
}
2017-05-23 15:36:59 +08:00
$domain_name = idn_to_ascii ( $domain_name );
return ( preg_match ( " /^([a-z \ d](-*[a-z \ d])*)( \ .([a-z \ d](-*[a-z \ d])*))* $ /i " , $domain_name )
&& preg_match ( " /^. { 1,253} $ / " , $domain_name )
&& preg_match ( " /^[^ \ .] { 1,63}( \ .[^ \ .] { 1,63})* $ / " , $domain_name ));
2016-12-10 03:39:02 +08:00
}
2017-05-23 15:36:59 +08:00
function add_domain_admin ( $postarray ) {
2016-12-10 03:39:02 +08:00
global $lang ;
global $pdo ;
2017-05-23 15:36:59 +08:00
$username = strtolower ( trim ( $postarray [ 'username' ]));
$password = $postarray [ 'password' ];
$password2 = $postarray [ 'password2' ];
2017-05-24 04:23:46 +08:00
$domains = ( array ) $postarray [ 'domains' ];
$active = intval ( $postarray [ 'active' ]);
2017-01-21 18:49:29 +08:00
2017-05-23 15:36:59 +08:00
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2016-12-10 16:18:00 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
2016-12-10 03:39:02 +08:00
);
return false ;
}
2017-05-24 04:23:46 +08:00
if ( empty ( $domains )) {
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'domain_invalid' ])
);
return false ;
2016-12-10 03:39:02 +08:00
}
2017-05-23 15:36:59 +08:00
if ( ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username )) || empty ( $username )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'username_invalid' ])
);
return false ;
}
try {
$stmt = $pdo -> prepare ( " SELECT `username` FROM `mailbox`
WHERE `username` = : username " );
$stmt -> execute ( array ( ':username' => $username ));
$num_results [] = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
$stmt = $pdo -> prepare ( " SELECT `username` FROM `admin`
WHERE `username` = : username " );
$stmt -> execute ( array ( ':username' => $username ));
$num_results [] = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
$stmt = $pdo -> prepare ( " SELECT `username` FROM `domain_admins`
WHERE `username` = : username " );
$stmt -> execute ( array ( ':username' => $username ));
$num_results [] = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
}
catch ( PDOException $e ) {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-23 15:36:59 +08:00
foreach ( $num_results as $num_results_each ) {
if ( $num_results_each != 0 ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'object_exists' ], htmlspecialchars ( $username ))
);
return false ;
}
}
if ( ! empty ( $password ) && ! empty ( $password2 )) {
if ( ! preg_match ( '/' . $GLOBALS [ 'PASSWD_REGEP' ] . '/' , $password )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_complexity' ])
);
return false ;
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
if ( $password != $password2 ) {
2016-12-10 16:18:00 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-23 15:36:59 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'password_mismatch' ])
2016-12-10 16:18:00 +08:00
);
return false ;
}
2017-05-23 15:36:59 +08:00
$password_hashed = hash_password ( $password );
2017-05-24 04:23:46 +08:00
foreach ( $domains as $domain ) {
2017-05-23 15:36:59 +08:00
if ( ! is_valid_domain_name ( $domain )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'domain_invalid' ])
);
return false ;
}
try {
$stmt = $pdo -> prepare ( " INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES ( : username , : domain , : created , : active ) " );
$stmt -> execute ( array (
':username' => $username ,
':domain' => $domain ,
':created' => date ( 'Y-m-d H:i:s' ),
':active' => $active
));
}
catch ( PDOException $e ) {
delete_domain_admin ( array ( 'username' => $username ));
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
}
try {
$stmt = $pdo -> prepare ( " INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
VALUES ( : username , : password_hashed , '0' , : active ) " );
$stmt -> execute ( array (
':username' => $username ,
':password_hashed' => $password_hashed ,
':active' => $active
));
}
catch ( PDOException $e ) {
2017-01-19 04:28:31 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-23 15:36:59 +08:00
'msg' => 'MySQL: ' . $e
2017-01-19 04:28:31 +08:00
);
return false ;
}
2017-05-23 15:36:59 +08:00
}
else {
2016-12-10 03:39:02 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-23 15:36:59 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'password_empty' ])
2016-12-10 03:39:02 +08:00
);
return false ;
}
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'domain_admin_added' ], htmlspecialchars ( $username ))
);
}
function delete_domain_admin ( $postarray ) {
global $pdo ;
global $lang ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ] = array (
2017-01-19 04:28:31 +08:00
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2017-05-24 04:23:46 +08:00
$usernames = ( array ) $postarray [ 'username' ];
foreach ( $usernames as $username ) {
if ( ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username ))) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'username_invalid' ])
);
return false ;
}
try {
$stmt = $pdo -> prepare ( " DELETE FROM `domain_admins` WHERE `username` = :username " );
$stmt -> execute ( array (
':username' => $username ,
));
$stmt = $pdo -> prepare ( " DELETE FROM `admin` WHERE `username` = :username " );
$stmt -> execute ( array (
':username' => $username ,
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'domain_admin_removed' ], htmlspecialchars ( implode ( ', ' , $usernames )))
);
2016-12-10 03:39:02 +08:00
}
2017-05-23 15:36:59 +08:00
function get_domain_admins () {
2016-12-10 03:39:02 +08:00
global $pdo ;
2017-01-21 18:49:29 +08:00
global $lang ;
2017-05-23 15:36:59 +08:00
$domainadmins = array ();
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2017-01-19 04:28:31 +08:00
try {
2017-05-23 15:36:59 +08:00
$stmt = $pdo -> query ( " SELECT DISTINCT
`username`
FROM `domain_admins`
WHERE `username` IN (
SELECT `username` FROM `admin`
WHERE `superadmin` != '1'
) " );
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
$domainadmins [] = $row [ 'username' ];
}
2017-01-19 04:28:31 +08:00
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
}
2017-05-23 15:36:59 +08:00
return $domainadmins ;
2017-01-19 04:28:31 +08:00
}
2017-05-23 15:36:59 +08:00
function get_domain_admin_details ( $domain_admin ) {
2017-01-19 04:28:31 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
global $lang ;
$domainadmindata = array ();
if ( isset ( $domain_admin ) && $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
return false ;
}
if ( ! isset ( $domain_admin ) && $_SESSION [ 'mailcow_cc_role' ] != " domainadmin " ) {
return false ;
}
( ! isset ( $domain_admin )) ? $domain_admin = $_SESSION [ 'mailcow_cc_username' ] : null ;
if ( ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $domain_admin ))) {
return false ;
2016-12-10 03:39:02 +08:00
}
2017-01-26 02:07:30 +08:00
try {
$stmt = $pdo -> prepare ( " SELECT
2017-05-23 15:36:59 +08:00
`tfa` . `active` AS `tfa_active_int` ,
CASE `tfa` . `active` WHEN 1 THEN '".$lang[' mailbox '][' yes ']."' ELSE '".$lang[' mailbox '][' no ']."' END AS `tfa_active` ,
`domain_admins` . `username` ,
`domain_admins` . `created` ,
`domain_admins` . `active` AS `active_int` ,
CASE `domain_admins` . `active` WHEN 1 THEN '".$lang[' mailbox '][' yes ']."' ELSE '".$lang[' mailbox '][' no ']."' END AS `active`
FROM `domain_admins`
LEFT OUTER JOIN `tfa` ON `tfa` . `username` = `domain_admins` . `username`
WHERE `domain_admins` . `username` = : domain_admin " );
2017-01-26 02:07:30 +08:00
$stmt -> execute ( array (
2017-05-23 15:36:59 +08:00
':domain_admin' => $domain_admin
2017-01-26 02:07:30 +08:00
));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
2017-05-23 15:36:59 +08:00
if ( empty ( $row )) {
return false ;
}
$domainadmindata [ 'username' ] = $row [ 'username' ];
$domainadmindata [ 'tfa_active' ] = $row [ 'tfa_active' ];
$domainadmindata [ 'active' ] = $row [ 'active' ];
$domainadmindata [ 'tfa_active_int' ] = $row [ 'tfa_active_int' ];
$domainadmindata [ 'active_int' ] = $row [ 'active_int' ];
$domainadmindata [ 'modified' ] = $row [ 'created' ];
// GET SELECTED
2017-01-26 02:07:30 +08:00
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `domain`
2017-05-23 15:36:59 +08:00
WHERE `domain` IN (
SELECT `domain` FROM `domain_admins`
WHERE `username` = : domain_admin ) " );
$stmt -> execute ( array ( ':domain_admin' => $domain_admin ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
$domainadmindata [ 'selected_domains' ][] = $row [ 'domain' ];
}
// GET UNSELECTED
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `domain`
WHERE `domain` NOT IN (
SELECT `domain` FROM `domain_admins`
WHERE `username` = : domain_admin ) " );
$stmt -> execute ( array ( ':domain_admin' => $domain_admin ));
2017-01-26 02:07:30 +08:00
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
2017-05-23 15:36:59 +08:00
$domainadmindata [ 'unselected_domains' ][] = $row [ 'domain' ];
}
if ( ! isset ( $domainadmindata [ 'unselected_domains' ])) {
$domainadmindata [ 'unselected_domains' ] = " " ;
2017-01-26 02:07:30 +08:00
}
}
2017-05-23 15:36:59 +08:00
catch ( PDOException $e ) {
2017-01-26 02:07:30 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
}
2017-05-23 15:36:59 +08:00
return $domainadmindata ;
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
function set_tfa ( $postarray ) {
2017-01-26 02:07:30 +08:00
global $lang ;
global $pdo ;
2017-05-23 15:36:59 +08:00
global $yubi ;
global $u2f ;
global $tfa ;
2017-01-26 02:07:30 +08:00
2017-05-23 15:36:59 +08:00
if ( $_SESSION [ 'mailcow_cc_role' ] != " domainadmin " &&
$_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
2017-03-28 17:48:39 +08:00
return false ;
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
$username = $_SESSION [ 'mailcow_cc_username' ];
$stmt = $pdo -> prepare ( " SELECT `password` FROM `admin`
WHERE `username` = : user " );
$stmt -> execute ( array ( ':user' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( ! verify_ssha256 ( $row [ 'password' ], $postarray [ " confirm_password " ])) {
2017-01-26 02:07:30 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-23 15:36:59 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
2017-01-26 02:07:30 +08:00
);
return false ;
}
2017-05-23 15:36:59 +08:00
switch ( $postarray [ " tfa_method " ]) {
case " yubi_otp " :
$key_id = ( ! isset ( $postarray [ " key_id " ])) ? 'unidentified' : $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' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
if ( ! ctype_alnum ( $postarray [ " otp_token " ]) || strlen ( $postarray [ " otp_token " ]) != 44 ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'tfa_token_invalid' ])
);
return false ;
}
$yauth = $yubi -> verify ( $postarray [ " otp_token " ]);
if ( PEAR :: isError ( $yauth )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'Yubico API: ' . $yauth -> getMessage ()
);
return false ;
}
try {
// 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 (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'object_modified' ], htmlspecialchars ( $username ))
);
break ;
2017-01-26 02:07:30 +08:00
2017-05-23 15:36:59 +08:00
case " u2f " :
$key_id = ( ! isset ( $postarray [ " key_id " ])) ? 'unidentified' : $postarray [ " key_id " ];
try {
$reg = $u2f -> doRegister ( json_decode ( $_SESSION [ 'regReq' ]), json_decode ( $postarray [ 'token' ]));
$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 )
);
$_SESSION [ 'regReq' ] = null ;
}
catch ( Exception $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => " U2F: " . $e -> getMessage ()
);
$_SESSION [ 'regReq' ] = null ;
return false ;
}
break ;
2017-02-22 05:27:11 +08:00
2017-05-23 15:36:59 +08:00
case " totp " :
$key_id = ( ! isset ( $postarray [ " key_id " ])) ? 'unidentified' : $postarray [ " key_id " ];
if ( $tfa -> verifyCode ( $_POST [ 'totp_secret' ], $_POST [ 'totp_confirm_token' ]) === true ) {
try {
$stmt = $pdo -> prepare ( " DELETE FROM `tfa` WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
$stmt = $pdo -> prepare ( " INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1') " );
$stmt -> execute ( array ( $username , $key_id , $_POST [ 'totp_secret' ]));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'object_modified' ], $username )
);
}
else {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'TOTP verification failed'
);
}
break ;
2017-01-26 02:07:30 +08:00
2017-05-23 15:36:59 +08:00
case " none " :
try {
$stmt = $pdo -> prepare ( " DELETE FROM `tfa` WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'object_modified' ], htmlspecialchars ( $username ))
);
break ;
}
}
function unset_tfa_key ( $postarray ) {
// Can only unset own keys
// Needs at least one key left
global $pdo ;
global $lang ;
$id = intval ( $postarray [ 'unset_tfa_key' ]);
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 ;
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
$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 ;
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
$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 )
);
2017-01-26 02:07:30 +08:00
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-01-28 16:53:39 +08:00
}
2017-05-23 15:36:59 +08:00
function get_tfa ( $username = null ) {
2017-01-28 16:53:39 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
if ( isset ( $_SESSION [ 'mailcow_cc_username' ])) {
$username = $_SESSION [ 'mailcow_cc_username' ];
2017-01-28 16:53:39 +08:00
}
2017-05-23 15:36:59 +08:00
elseif ( empty ( $username )) {
2017-01-26 02:07:30 +08:00
return false ;
}
2017-05-23 15:36:59 +08:00
$stmt = $pdo -> prepare ( " SELECT * FROM `tfa`
WHERE `username` = : username AND `active` = '1' " );
$stmt -> execute ( array ( ':username' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
2017-01-26 02:07:30 +08:00
2017-05-23 15:36:59 +08:00
switch ( $row [ " authmech " ]) {
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 " :
$data [ 'name' ] = " hotp " ;
$data [ 'pretty' ] = " HMAC-based OTP " ;
return $data ;
break ;
case " totp " :
$data [ 'name' ] = " totp " ;
$data [ 'pretty' ] = " Time-based OTP " ;
$stmt = $pdo -> prepare ( " SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' 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 ;
default :
$data [ 'name' ] = 'none' ;
$data [ 'pretty' ] = " - " ;
return $data ;
break ;
}
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
function verify_tfa_login ( $username , $token ) {
2017-01-26 02:07:30 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
global $lang ;
global $yubi ;
global $u2f ;
global $tfa ;
2017-01-26 02:07:30 +08:00
2017-05-23 15:36:59 +08:00
$stmt = $pdo -> prepare ( " SELECT `authmech` FROM `tfa`
WHERE `username` = : username AND `active` = '1' " );
$stmt -> execute ( array ( ':username' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
switch ( $row [ " authmech " ]) {
case " yubi_otp " :
if ( ! ctype_alnum ( $token ) || strlen ( $token ) != 44 ) {
return false ;
}
$yubico_modhex_id = substr ( $token , 0 , 12 );
$stmt = $pdo -> prepare ( " SELECT `id`, `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 (
'type' => 'danger' ,
'msg' => 'Yubico Authentication error: ' . $yauth -> getMessage ()
);
return false ;
}
else {
$_SESSION [ 'tfa_id' ] = $row [ 'id' ];
return true ;
}
return false ;
break ;
case " u2f " :
2017-05-12 05:10:32 +08:00
try {
2017-05-23 15:36:59 +08:00
$reg = $u2f -> doAuthenticate ( json_decode ( $_SESSION [ 'authReq' ]), get_u2f_registrations ( $username ), json_decode ( $token ));
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `counter` = ? WHERE `id` = ? " );
$stmt -> execute ( array ( $reg -> counter , $reg -> id ));
$_SESSION [ 'tfa_id' ] = $reg -> id ;
$_SESSION [ 'authReq' ] = null ;
return true ;
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
catch ( Exception $e ) {
2017-05-12 05:10:32 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-23 15:36:59 +08:00
'msg' => " U2F: " . $e -> getMessage ()
2017-05-12 05:10:32 +08:00
);
2017-05-23 15:36:59 +08:00
$_SESSION [ 'regReq' ] = null ;
2017-05-12 05:10:32 +08:00
return false ;
}
2017-05-23 15:36:59 +08:00
return false ;
break ;
case " hotp " :
2017-05-12 05:10:32 +08:00
return false ;
2017-05-23 15:36:59 +08:00
break ;
case " totp " :
2017-05-12 05:10:32 +08:00
try {
2017-05-23 15:36:59 +08:00
$stmt = $pdo -> prepare ( " SELECT `id`, `secret` FROM `tfa`
WHERE `username` = : username
AND `authmech` = 'totp'
AND `active` = '1' " );
$stmt -> execute ( array ( ':username' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( $tfa -> verifyCode ( $row [ 'secret' ], $_POST [ 'token' ]) === true ) {
$_SESSION [ 'tfa_id' ] = $row [ 'id' ];
return true ;
}
return false ;
2017-05-12 05:10:32 +08:00
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-23 15:36:59 +08:00
break ;
default :
2017-05-12 05:10:32 +08:00
return false ;
2017-05-23 15:36:59 +08:00
break ;
}
return false ;
2017-01-26 02:07:30 +08:00
}
2017-05-23 15:36:59 +08:00
function edit_domain_admin ( $postarray ) {
2017-01-26 02:07:30 +08:00
global $lang ;
global $pdo ;
2017-05-23 15:36:59 +08:00
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " && $_SESSION [ 'mailcow_cc_role' ] != " domainadmin " ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
// Administrator
if ( $_SESSION [ 'mailcow_cc_role' ] == " admin " ) {
2017-05-24 04:23:46 +08:00
if ( ! is_array ( $postarray [ 'username' ])) {
$usernames = array ();
$usernames [] = $postarray [ 'username' ];
2017-04-27 05:38:18 +08:00
}
2017-05-24 04:23:46 +08:00
else {
$usernames = $postarray [ 'username' ];
2017-04-27 05:38:18 +08:00
}
2017-05-24 04:23:46 +08:00
foreach ( $usernames as $username ) {
$is_now = get_domain_admin_details ( $username );
$domains = ( isset ( $postarray [ 'domains' ])) ? ( array ) $postarray [ 'domains' ] : null ;
if ( ! empty ( $is_now )) {
$active = ( isset ( $postarray [ 'active' ])) ? $postarray [ 'active' ] : $is_now [ 'active_int' ];
$domains = ( ! empty ( $domains )) ? $domains : $is_now [ 'selected_domains' ];
$username_new = ( ! empty ( $postarray [ 'username_new' ])) ? $postarray [ 'username_new' ] : $is_now [ 'username' ];
}
else {
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-24 04:23:46 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
2017-05-23 15:36:59 +08:00
);
return false ;
}
2017-05-24 04:23:46 +08:00
$password = $postarray [ 'password' ];
$password2 = $postarray [ 'password2' ];
2017-05-12 05:10:32 +08:00
2017-05-24 04:23:46 +08:00
if ( ! empty ( $domains )) {
foreach ( $domains as $domain ) {
if ( ! is_valid_domain_name ( $domain )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'domain_invalid' ])
);
return false ;
}
2017-05-23 15:36:59 +08:00
}
}
2017-05-24 04:23:46 +08:00
if ( ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username_new ))) {
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
2017-05-24 04:23:46 +08:00
'msg' => sprintf ( $lang [ 'danger' ][ 'username_invalid' ])
2017-05-23 15:36:59 +08:00
);
return false ;
}
2017-05-24 04:23:46 +08:00
if ( $username_new != $username ) {
if ( ! empty ( get_domain_admin_details ( $username_new )[ 'username' ])) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'username_invalid' ])
);
return false ;
}
2017-05-23 15:36:59 +08:00
}
try {
2017-05-24 04:23:46 +08:00
$stmt = $pdo -> prepare ( " DELETE FROM `domain_admins` WHERE `username` = :username " );
2017-05-23 15:36:59 +08:00
$stmt -> execute ( array (
2017-05-24 04:23:46 +08:00
':username' => $username ,
2017-05-23 15:36:59 +08:00
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-24 04:23:46 +08:00
if ( ! empty ( $domains )) {
foreach ( $domains as $domain ) {
try {
$stmt = $pdo -> prepare ( " INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
VALUES ( : username_new , : domain , : created , : active ) " );
$stmt -> execute ( array (
':username_new' => $username_new ,
':domain' => $domain ,
':created' => date ( 'Y-m-d H:i:s' ),
':active' => $active
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-23 15:36:59 +08:00
}
2017-05-24 04:23:46 +08:00
}
if ( ! empty ( $password ) && ! empty ( $password2 )) {
if ( ! preg_match ( '/' . $GLOBALS [ 'PASSWD_REGEP' ] . '/' , $password )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_complexity' ])
);
return false ;
}
if ( $password != $password2 ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_mismatch' ])
);
return false ;
}
$password_hashed = hash_password ( $password );
try {
$stmt = $pdo -> prepare ( " UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username " );
$stmt -> execute ( array (
':password_hashed' => $password_hashed ,
':username_new' => $username_new ,
':username' => $username ,
':active' => $active
));
if ( isset ( $postarray [ 'disable_tfa' ])) {
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `active` = '0' WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
}
else {
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username " );
$stmt -> execute ( array ( ':username_new' => $username_new , ':username' => $username ));
}
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
2017-05-23 15:36:59 +08:00
}
}
2017-05-24 04:23:46 +08:00
else {
try {
$stmt = $pdo -> prepare ( " UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username " );
$stmt -> execute ( array (
':username_new' => $username_new ,
':username' => $username ,
':active' => $active
));
if ( isset ( $postarray [ 'disable_tfa' ])) {
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `active` = '0' WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
}
else {
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username " );
$stmt -> execute ( array ( ':username_new' => $username_new , ':username' => $username ));
}
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-23 15:36:59 +08:00
}
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
2017-05-24 04:23:46 +08:00
'msg' => sprintf ( $lang [ 'success' ][ 'domain_admin_modified' ], htmlspecialchars ( implode ( ', ' , $usernames )))
2017-05-23 15:36:59 +08:00
);
}
// Domain administrator
// Can only edit itself
elseif ( $_SESSION [ 'mailcow_cc_role' ] == " domainadmin " ) {
$username = $_SESSION [ 'mailcow_cc_username' ];
$password_old = $postarray [ 'user_old_pass' ];
$password_new = $postarray [ 'user_new_pass' ];
$password_new2 = $postarray [ 'user_new_pass2' ];
$stmt = $pdo -> prepare ( " SELECT `password` FROM `admin`
WHERE `username` = : user " );
$stmt -> execute ( array ( ':user' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( ! verify_ssha256 ( $row [ 'password' ], $password_old )) {
2017-05-12 05:10:32 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2017-05-23 15:36:59 +08:00
if ( ! empty ( $password_new2 ) && ! empty ( $password_new )) {
if ( $password_new2 != $password_new ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_mismatch' ])
);
return false ;
}
if ( ! preg_match ( '/' . $GLOBALS [ 'PASSWD_REGEP' ] . '/' , $password_new )) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'password_complexity' ])
);
return false ;
}
$password_hashed = hash_password ( $password_new );
try {
$stmt = $pdo -> prepare ( " UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username " );
$stmt -> execute ( array (
':password_hashed' => $password_hashed ,
':username' => $username
));
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
return false ;
}
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => sprintf ( $lang [ 'success' ][ 'domain_admin_modified' ], htmlspecialchars ( $username ))
);
}
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
function get_admin_details () {
// No parameter to be given, only one admin should exist
2017-05-12 05:10:32 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
global $lang ;
$data = array ();
if ( $_SESSION [ 'mailcow_cc_role' ] != 'admin' ) {
return false ;
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
try {
$stmt = $pdo -> prepare ( " SELECT `username`, `modified`, `created` FROM `admin` WHERE `superadmin`='1' AND active='1' " );
$stmt -> execute ();
$data = $stmt -> fetch ( PDO :: FETCH_ASSOC );
}
catch ( PDOException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'MySQL: ' . $e
);
}
return $data ;
}
2017-01-26 02:07:30 +08:00
function get_u2f_registrations ( $username ) {
global $pdo ;
2017-02-13 20:42:54 +08:00
$sel = $pdo -> prepare ( " SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1' " );
2017-01-26 02:07:30 +08:00
$sel -> execute ( array ( $username ));
return $sel -> fetchAll ( PDO :: FETCH_OBJ );
}
2017-05-07 05:52:40 +08:00
function get_logs ( $container , $lines = 100 ) {
global $lang ;
global $redis ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
return false ;
}
$lines = intval ( $lines );
if ( $container == " dovecot-mailcow " ) {
2017-06-24 06:07:53 +08:00
if ( $data = $redis -> lRange ( 'DOVECOT_MAILLOG' , 0 , $lines )) {
2017-05-07 05:52:40 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
if ( $container == " postfix-mailcow " ) {
2017-06-24 06:07:53 +08:00
if ( $data = $redis -> lRange ( 'POSTFIX_MAILLOG' , 0 , $lines )) {
2017-05-08 06:27:35 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
2017-05-07 05:52:40 +08:00
}
}
2017-05-14 04:58:29 +08:00
if ( $container == " sogo-mailcow " ) {
2017-06-24 06:07:53 +08:00
if ( $data = $redis -> lRange ( 'SOGO_LOG' , 0 , $lines )) {
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
if ( $container == " fail2ban-mailcow " ) {
if ( $data = $redis -> lRange ( 'F2B_LOG' , 0 , $lines )) {
2017-05-14 04:58:29 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2017-05-23 15:36:59 +08:00
if ( $container == " rspamd-history " ) {
$curl = curl_init ();
curl_setopt ( $curl , CURLOPT_URL , " http://rspamd-mailcow:11334/history " );
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , true );
$history = curl_exec ( $curl );
if ( ! curl_errno ( $ch )) {
$data_array = json_decode ( $history , true );
curl_close ( $curl );
return $data_array [ 'rows' ];
}
curl_close ( $curl );
return false ;
}
2017-05-07 05:52:40 +08:00
return false ;
}
2017-06-26 03:33:26 +08:00
function get_f2b_parameters () {
global $lang ;
global $redis ;
$data = array ();
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
return false ;
}
try {
$data [ 'ban_time' ] = $redis -> Get ( 'F2B_BAN_TIME' );
$data [ 'max_attempts' ] = $redis -> Get ( 'F2B_MAX_ATTEMPTS' );
$data [ 'retry_window' ] = $redis -> Get ( 'F2B_RETRY_WINDOW' );
2017-06-27 05:18:05 +08:00
$wl = $redis -> hGetAll ( 'F2B_WHITELIST' );
if ( is_array ( $wl )) {
foreach ( $wl as $key => $value ) {
$tmp_data [] = $key ;
}
$data [ 'whitelist' ] = implode ( PHP_EOL , $tmp_data );
}
else {
$data [ 'whitelist' ] = " " ;
}
2017-06-26 03:33:26 +08:00
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'Redis: ' . $e
);
return false ;
}
return $data ;
}
function edit_f2b_parameters ( $postarray ) {
global $lang ;
global $redis ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
$is_now = get_f2b_parameters ();
if ( ! empty ( $is_now )) {
$ban_time = intval (( isset ( $postarray [ 'ban_time' ])) ? $postarray [ 'ban_time' ] : $is_now [ 'ban_time' ]);
$max_attempts = intval (( isset ( $postarray [ 'max_attempts' ])) ? $postarray [ 'max_attempts' ] : $is_now [ 'active_int' ]);
$retry_window = intval (( isset ( $postarray [ 'retry_window' ])) ? $postarray [ 'retry_window' ] : $is_now [ 'retry_window' ]);
}
else {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => sprintf ( $lang [ 'danger' ][ 'access_denied' ])
);
return false ;
}
2017-06-27 05:18:05 +08:00
$wl = $postarray [ 'whitelist' ];
2017-06-26 03:33:26 +08:00
$ban_time = ( $ban_time < 60 ) ? 60 : $ban_time ;
$max_attempts = ( $max_attempts < 1 ) ? 1 : $max_attempts ;
$retry_window = ( $retry_window < 1 ) ? 1 : $retry_window ;
try {
$redis -> Set ( 'F2B_BAN_TIME' , $ban_time );
$redis -> Set ( 'F2B_MAX_ATTEMPTS' , $max_attempts );
$redis -> Set ( 'F2B_RETRY_WINDOW' , $retry_window );
2017-06-27 05:18:05 +08:00
$redis -> Del ( 'F2B_WHITELIST' );
if ( ! empty ( $wl )) {
$wl_array = array_map ( 'trim' , preg_split ( " /( |,|;| \n )/ " , $wl ));
if ( is_array ( $wl_array )) {
foreach ( $wl_array as $wl_item ) {
$cidr = explode ( '/' , $wl_item );
if ( filter_var ( $cidr [ 0 ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 ) && ( ! isset ( $cidr [ 1 ]) || ( $cidr [ 1 ] >= 8 && $cidr [ 1 ] <= 32 ))) {
$redis -> hSet ( 'F2B_WHITELIST' , $wl_item , 1 );
}
elseif ( filter_var ( $cidr [ 0 ], FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 ) && ( ! isset ( $cidr [ 1 ]) || ( $cidr [ 1 ] >= 16 && $cidr [ 1 ] <= 128 ))) {
$redis -> hSet ( 'F2B_WHITELIST' , $wl_item , 1 );
}
}
}
}
2017-06-26 03:33:26 +08:00
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ] = array (
'type' => 'danger' ,
'msg' => 'Redis: ' . $e
);
return false ;
}
$_SESSION [ 'return' ] = array (
'type' => 'success' ,
'msg' => 'Saved changes to Fail2ban configuration'
);
}
2016-12-10 03:39:02 +08:00
?>