2016-12-10 03:39:02 +08:00
< ? php
2020-06-07 16:45:40 +08:00
use PHPMailer\PHPMailer\PHPMailer ;
use PHPMailer\PHPMailer\SMTP ;
use PHPMailer\PHPMailer\Exception ;
2020-04-17 03:58:30 +08:00
function is_valid_regex ( $exp ) {
return @ preg_match ( $exp , '' ) !== false ;
}
2019-03-29 05:05:12 +08:00
function isset_has_content ( $var ) {
if ( isset ( $var ) && $var != " " ) {
return true ;
}
else {
return false ;
}
}
2021-05-21 18:48:19 +08:00
function readable_random_string ( $length = 8 ) {
$string = '' ;
$vowels = array ( 'a' , 'e' , 'i' , 'o' , 'u' );
$consonants = array ( 'b' , 'c' , 'd' , 'f' , 'g' , 'h' , 'j' , 'k' , 'l' , 'm' , 'n' , 'p' , 'r' , 's' , 't' , 'v' , 'w' , 'x' , 'y' , 'z' );
$max = $length / 2 ;
for ( $i = 1 ; $i <= $max ; $i ++ ) {
$string .= $consonants [ rand ( 0 , 19 )];
$string .= $vowels [ rand ( 0 , 4 )];
}
return $string ;
}
2020-05-04 13:51:50 +08:00
// Validates ips and cidrs
function valid_network ( $network ) {
if ( filter_var ( $network , FILTER_VALIDATE_IP )) {
return true ;
}
$parts = explode ( '/' , $network );
if ( count ( $parts ) != 2 ) {
return false ;
}
$ip = $parts [ 0 ];
$netmask = $parts [ 1 ];
if ( ! preg_match ( " /^ \ d+ $ / " , $netmask )){
return false ;
}
$netmask = intval ( $parts [ 1 ]);
if ( $netmask < 0 ) {
return false ;
}
if ( filter_var ( $ip , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 )) {
return $netmask <= 32 ;
}
if ( filter_var ( $ip , FILTER_VALIDATE_IP , FILTER_FLAG_IPV6 )) {
return $netmask <= 128 ;
}
return false ;
}
function valid_hostname ( $hostname ) {
return filter_var ( $hostname , FILTER_VALIDATE_DOMAIN , FILTER_FLAG_HOSTNAME );
}
// Thanks to https://stackoverflow.com/a/49373789
// Validates exact ip matches and ip-in-cidr, ipv4 and ipv6
function ip_acl ( $ip , $networks ) {
foreach ( $networks as $network ) {
if ( filter_var ( $network , FILTER_VALIDATE_IP )) {
if ( $ip == $network ) {
return true ;
}
else {
continue ;
}
}
$ipb = inet_pton ( $ip );
$iplen = strlen ( $ipb );
if ( strlen ( $ipb ) < 4 ) {
continue ;
}
$ar = explode ( '/' , $network );
$ip1 = $ar [ 0 ];
$ip1b = inet_pton ( $ip1 );
$ip1len = strlen ( $ip1b );
if ( $ip1len != $iplen ) {
continue ;
}
if ( count ( $ar ) > 1 ) {
$bits = ( int )( $ar [ 1 ]);
}
else {
$bits = $iplen * 8 ;
}
for ( $c = 0 ; $bits > 0 ; $c ++ ) {
$bytemask = ( $bits < 8 ) ? 0xff ^ (( 1 << ( 8 - $bits )) - 1 ) : 0xff ;
if ((( ord ( $ipb [ $c ]) ^ ord ( $ip1b [ $c ])) & $bytemask ) != 0 ) {
continue 2 ;
}
$bits -= 8 ;
}
return true ;
}
return false ;
}
2016-12-10 03:39:02 +08:00
function hash_password ( $password ) {
2020-11-16 03:22:35 +08:00
// default_pass_scheme is determined in vars.inc.php (or corresponding local file)
// in case default pass scheme is not defined, falling back to BLF-CRYPT.
global $default_pass_scheme ;
$pw_hash = NULL ;
2021-03-11 04:06:32 +08:00
// support pre-hashed passwords
if ( preg_match ( '/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i' , $password )) {
$pw_hash = $password ;
}
else {
switch ( strtoupper ( $default_pass_scheme )) {
case " SSHA " :
$salt_str = bin2hex ( openssl_random_pseudo_bytes ( 8 ));
$pw_hash = " { SSHA} " . base64_encode ( hash ( 'sha1' , $password . $salt_str , true ) . $salt_str );
break ;
case " SSHA256 " :
$salt_str = bin2hex ( openssl_random_pseudo_bytes ( 8 ));
$pw_hash = " { SSHA256} " . base64_encode ( hash ( 'sha256' , $password . $salt_str , true ) . $salt_str );
break ;
case " SSHA512 " :
$salt_str = bin2hex ( openssl_random_pseudo_bytes ( 8 ));
$pw_hash = " { SSHA512} " . base64_encode ( hash ( 'sha512' , $password . $salt_str , true ) . $salt_str );
break ;
case " BLF-CRYPT " :
default :
$pw_hash = " { BLF-CRYPT} " . password_hash ( $password , PASSWORD_BCRYPT );
break ;
}
2020-11-16 03:22:35 +08:00
}
return $pw_hash ;
2016-12-10 03:39:02 +08:00
}
2021-04-09 19:46:17 +08:00
function password_complexity ( $_action , $_data = null ) {
global $redis ;
global $lang ;
switch ( $_action ) {
case 'edit' :
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data ),
'msg' => 'access_denied'
);
return false ;
}
$is_now = password_complexity ( 'get' );
if ( ! empty ( $is_now )) {
$length = ( isset ( $_data [ 'length' ]) && intval ( $_data [ 'length' ]) >= 3 ) ? intval ( $_data [ 'length' ]) : $is_now [ 'length' ];
$chars = ( isset ( $_data [ 'chars' ])) ? intval ( $_data [ 'chars' ]) : $is_now [ 'chars' ];
$lowerupper = ( isset ( $_data [ 'lowerupper' ])) ? intval ( $_data [ 'lowerupper' ]) : $is_now [ 'lowerupper' ];
$special_chars = ( isset ( $_data [ 'special_chars' ])) ? intval ( $_data [ 'special_chars' ]) : $is_now [ 'special_chars' ];
$numbers = ( isset ( $_data [ 'numbers' ])) ? intval ( $_data [ 'numbers' ]) : $is_now [ 'numbers' ];
}
try {
$redis -> hMSet ( 'PASSWD_POLICY' , [
'length' => $length ,
'chars' => $chars ,
'special_chars' => $special_chars ,
'lowerupper' => $lowerupper ,
'numbers' => $numbers
]);
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data ),
'msg' => array ( 'redis_error' , $e )
);
return false ;
}
$_SESSION [ 'return' ][] = array (
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $_action , $_data ),
2021-04-18 18:53:59 +08:00
'msg' => 'password_policy_saved'
2021-04-09 19:46:17 +08:00
);
break ;
case 'get' :
try {
$length = $redis -> hGet ( 'PASSWD_POLICY' , 'length' );
$chars = $redis -> hGet ( 'PASSWD_POLICY' , 'chars' );
$special_chars = $redis -> hGet ( 'PASSWD_POLICY' , 'special_chars' );
$lowerupper = $redis -> hGet ( 'PASSWD_POLICY' , 'lowerupper' );
$numbers = $redis -> hGet ( 'PASSWD_POLICY' , 'numbers' );
return array (
'length' => $length ,
'chars' => $chars ,
'special_chars' => $special_chars ,
'lowerupper' => $lowerupper ,
'numbers' => $numbers
);
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data ),
'msg' => array ( 'redis_error' , $e )
);
return false ;
}
return false ;
break ;
case 'html' :
$policies = password_complexity ( 'get' );
foreach ( $policies as $name => $value ) {
if ( $value != 0 ) {
$policy_text [] = sprintf ( $lang [ 'admin' ][ " password_policy_ $name " ], $value );
}
}
return '<p class="help-block small">- ' . implode ( '<br>- ' , $policy_text ) . '</p>' ;
break ;
}
}
function password_check ( $password1 , $password2 ) {
$password_complexity = password_complexity ( 'get' );
if ( empty ( $password1 ) || empty ( $password2 )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_type ),
'msg' => 'password_complexity'
);
return false ;
}
if ( $password1 != $password2 ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_type ),
'msg' => 'password_mismatch'
);
return false ;
}
$given_password [ 'length' ] = strlen ( $password1 );
$given_password [ 'special_chars' ] = preg_match ( '/[^a-zA-Z\d]/' , $password1 );
$given_password [ 'chars' ] = preg_match ( '/[a-zA-Z]/' , $password1 );
$given_password [ 'numbers' ] = preg_match ( '/\d/' , $password1 );
$lower = strlen ( preg_replace ( " /[^a-z]/ " , '' , $password1 ));
$upper = strlen ( preg_replace ( " /[^A-Z]/ " , '' , $password1 ));
$given_password [ 'lowerupper' ] = ( $lower > 0 && $upper > 0 ) ? true : false ;
if (
( $given_password [ 'length' ] < $password_complexity [ 'length' ]) ||
( $password_complexity [ 'special_chars' ] == 1 && ( intval ( $given_password [ 'special_chars' ]) != $password_complexity [ 'special_chars' ])) ||
( $password_complexity [ 'chars' ] == 1 && ( intval ( $given_password [ 'chars' ]) != $password_complexity [ 'chars' ])) ||
( $password_complexity [ 'numbers' ] == 1 && ( intval ( $given_password [ 'numbers' ]) != $password_complexity [ 'numbers' ])) ||
( $password_complexity [ 'lowerupper' ] == 1 && ( intval ( $given_password [ 'lowerupper' ]) != $password_complexity [ 'lowerupper' ]))
) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_type ),
'msg' => 'password_complexity'
);
return false ;
}
return true ;
}
2021-06-09 13:19:57 +08:00
function last_login ( $action , $username , $sasl_limit_days = 7 ) {
2018-08-04 02:31:33 +08:00
global $pdo ;
2021-06-06 17:57:15 +08:00
global $redis ;
2021-06-09 13:19:57 +08:00
$sasl_limit_days = intval ( $sasl_limit_days );
2021-06-04 20:29:39 +08:00
switch ( $action ) {
case 'get' :
if ( filter_var ( $username , FILTER_VALIDATE_EMAIL ) && hasMailboxObjectAccess ( $_SESSION [ 'mailcow_cc_username' ], $_SESSION [ 'mailcow_cc_role' ], $username )) {
2021-06-07 03:00:48 +08:00
$stmt = $pdo -> prepare ( ' SELECT `real_rip` , MAX ( `datetime` ) as `datetime` , `service` , `app_password` FROM `sasl_logs`
LEFT OUTER JOIN `app_passwd` on `sasl_logs` . `app_password` = `app_passwd` . `id`
2021-06-04 20:29:39 +08:00
WHERE `username` = : username
2021-06-09 13:19:57 +08:00
AND HOUR ( TIMEDIFF ( NOW (), `datetime` )) < : sasl_limit_days
2021-06-04 20:29:39 +08:00
AND `success` = 1
2021-06-07 03:00:48 +08:00
GROUP BY `real_rip` , `service` , `app_password`
2021-06-09 13:19:57 +08:00
ORDER BY `datetime` DESC ; ' );
$stmt -> execute ( array ( ':username' => $username , ':sasl_limit_days' => ( $sasl_limit_days * 24 )));
2021-06-04 20:29:39 +08:00
$sasl = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $sasl as $k => $v ) {
if ( ! filter_var ( $sasl [ $k ][ 'real_rip' ], FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )) {
$sasl [ $k ][ 'real_rip' ] = 'Web/EAS/Internal (' . $sasl [ $k ][ 'real_rip' ] . ')' ;
}
2021-06-06 17:57:15 +08:00
elseif ( filter_var ( $sasl [ $k ][ 'real_rip' ], FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )) {
try {
2021-06-08 19:16:10 +08:00
$sasl [ $k ][ 'location' ] = $redis -> hGet ( 'IP_SHORTCOUNTRY' , $sasl [ $k ][ 'real_rip' ]);
2021-06-06 17:57:15 +08:00
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data_log ),
'msg' => array ( 'redis_error' , $e )
);
return false ;
}
if ( ! $sasl [ $k ][ 'location' ]) {
$curl = curl_init ();
curl_setopt ( $curl , CURLOPT_URL , " https://dfdata.bella.network/lookup/ " . $sasl [ $k ][ 'real_rip' ]);
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , true );
2021-06-07 03:00:48 +08:00
curl_setopt ( $curl , CURLOPT_USERAGENT , 'Moocow' );
curl_setopt ( $curl , CURLOPT_TIMEOUT , 5 );
2021-06-06 17:57:15 +08:00
$ip_data = curl_exec ( $curl );
if ( ! curl_errno ( $curl )) {
$ip_data_array = json_decode ( $ip_data , true );
2021-06-08 19:16:10 +08:00
if ( $ip_data_array !== false and ! empty ( $ip_data_array [ 'location' ][ 'shortcountry' ])) {
$sasl [ $k ][ 'location' ] = $ip_data_array [ 'location' ][ 'shortcountry' ];
2021-06-06 17:57:15 +08:00
try {
2021-06-08 19:16:10 +08:00
$redis -> hSet ( 'IP_SHORTCOUNTRY' , $sasl [ $k ][ 'real_rip' ], $ip_data_array [ 'location' ][ 'shortcountry' ]);
2021-06-06 17:57:15 +08:00
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data_log ),
'msg' => array ( 'redis_error' , $e )
);
curl_close ( $curl );
return false ;
}
}
}
curl_close ( $curl );
}
}
2021-06-04 20:29:39 +08:00
}
}
else {
$sasl = array ();
}
if ( $_SESSION [ 'mailcow_cc_role' ] == " admin " || $username == $_SESSION [ 'mailcow_cc_username' ]) {
$stmt = $pdo -> prepare ( ' SELECT `remote` , `time` FROM `logs`
WHERE JSON_EXTRACT ( `call` , " $ [0] " ) = " check_login "
AND JSON_EXTRACT ( `call` , " $ [1] " ) = : username
AND `type` = " success " ORDER BY `time` DESC LIMIT 1 OFFSET 1 ' );
$stmt -> execute ( array ( ':username' => $username ));
$ui = $stmt -> fetch ( PDO :: FETCH_ASSOC );
}
else {
$ui = array ();
}
return array ( 'ui' => $ui , 'sasl' => $sasl );
break ;
case 'reset' :
if ( filter_var ( $username , FILTER_VALIDATE_EMAIL ) && hasMailboxObjectAccess ( $_SESSION [ 'mailcow_cc_username' ], $_SESSION [ 'mailcow_cc_role' ], $username )) {
$stmt = $pdo -> prepare ( ' DELETE FROM `sasl_logs`
WHERE `username` = : username
AND `success` = 1 ; ' );
$stmt -> execute ( array ( ':username' => $username ));
}
if ( $_SESSION [ 'mailcow_cc_role' ] == " admin " || $username == $_SESSION [ 'mailcow_cc_username' ]) {
$stmt = $pdo -> prepare ( ' DELETE FROM `logs`
WHERE JSON_EXTRACT ( `call` , " $ [0] " ) = " check_login "
AND JSON_EXTRACT ( `call` , " $ [1] " ) = : username
AND `type` = " success " ' );
$stmt -> execute ( array ( ':username' => $username ));
}
return true ;
break ;
2018-08-04 02:31:33 +08:00
}
2021-06-04 20:29:39 +08:00
2018-08-04 02:31:33 +08:00
}
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-30 04:01:23 +08:00
function flush_memcached () {
try {
$m = new Memcached ();
$m -> addServer ( 'memcached' , 11211 );
$m -> flush ();
}
catch ( Exception $e ) {
// Dunno
}
}
2018-10-11 17:59:23 +08:00
function sys_mail ( $_data ) {
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
'msg' => 'access_denied'
);
return false ;
}
$excludes = $_data [ 'mass_exclude' ];
$includes = $_data [ 'mass_include' ];
$mailboxes = array ();
$mass_from = $_data [ 'mass_from' ];
$mass_text = $_data [ 'mass_text' ];
2020-11-30 14:43:48 +08:00
$mass_html = $_data [ 'mass_html' ];
2018-10-11 17:59:23 +08:00
$mass_subject = $_data [ 'mass_subject' ];
if ( ! filter_var ( $mass_from , FILTER_VALIDATE_EMAIL )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
2018-10-12 16:57:47 +08:00
'msg' => 'from_invalid'
2018-10-11 17:59:23 +08:00
);
return false ;
}
if ( empty ( $mass_subject )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
2018-10-12 16:57:47 +08:00
'msg' => 'subject_empty'
2018-10-11 17:59:23 +08:00
);
return false ;
}
if ( empty ( $mass_text )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
2018-10-12 16:57:47 +08:00
'msg' => 'text_empty'
2018-10-11 17:59:23 +08:00
);
return false ;
}
$domains = array_merge ( mailbox ( 'get' , 'domains' ), mailbox ( 'get' , 'alias_domains' ));
foreach ( $domains as $domain ) {
foreach ( mailbox ( 'get' , 'mailboxes' , $domain ) as $mailbox ) {
$mailboxes [] = $mailbox ;
}
}
if ( ! empty ( $includes )) {
$rcpts = array_intersect ( $mailboxes , $includes );
}
elseif ( ! empty ( $excludes )) {
$rcpts = array_diff ( $mailboxes , $excludes );
}
else {
$rcpts = $mailboxes ;
}
if ( ! empty ( $rcpts )) {
ini_set ( 'max_execution_time' , 0 );
ini_set ( 'max_input_time' , 0 );
$mail = new PHPMailer ;
$mail -> Timeout = 10 ;
$mail -> SMTPOptions = array (
'ssl' => array (
'verify_peer' => false ,
'verify_peer_name' => false ,
'allow_self_signed' => true
)
);
$mail -> isSMTP ();
$mail -> Host = 'dovecot-mailcow' ;
$mail -> SMTPAuth = false ;
$mail -> Port = 24 ;
$mail -> setFrom ( $mass_from );
$mail -> Subject = $mass_subject ;
$mail -> CharSet = " UTF-8 " ;
2020-11-30 14:43:48 +08:00
if ( ! empty ( $mass_html )) {
$mail -> Body = $mass_html ;
$mail -> AltBody = $mass_text ;
}
else {
$mail -> Body = $mass_text ;
}
2018-10-11 17:59:23 +08:00
$mail -> XMailer = 'MooMassMail' ;
foreach ( $rcpts as $rcpt ) {
$mail -> AddAddress ( $rcpt );
if ( ! $mail -> send ()) {
$_SESSION [ 'return' ][] = array (
'type' => 'warning' ,
'log' => array ( __FUNCTION__ ),
'msg' => 'Mailer error (RCPT "' . htmlspecialchars ( $rcpt ) . '"): ' . str_replace ( 'https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting' , '' , $mail -> ErrorInfo )
);
}
$mail -> ClearAllRecipients ();
}
}
$_SESSION [ 'return' ][] = array (
'type' => 'success' ,
'log' => array ( __FUNCTION__ ),
'msg' => 'Mass mail job completed, sent ' . count ( $rcpts ) . ' mails'
);
}
2018-08-04 02:31:33 +08:00
function logger ( $_data = false ) {
2018-08-14 05:20:40 +08:00
/*
logger () will be called as last function
To manually log a message , logger needs to be called like below .
logger ( array (
'return' => array (
array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
'msg' => $err
)
)
));
These messages will not be printed as alert box .
To do so , push them to $_SESSION [ 'return' ] and do not call logger as they will be included automatically :
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => $err
);
*/
2018-08-04 02:31:33 +08:00
global $pdo ;
if ( ! $_data ) {
$_data = $_SESSION ;
}
if ( ! empty ( $_data [ 'return' ])) {
2018-08-14 05:20:40 +08:00
$task = substr ( strtoupper ( md5 ( uniqid ( rand (), true ))), 0 , 6 );
foreach ( $_data [ 'return' ] as $return ) {
$type = $return [ 'type' ];
$msg = json_encode ( $return [ 'msg' ], JSON_UNESCAPED_UNICODE );
$call = json_encode ( $return [ 'log' ], JSON_UNESCAPED_UNICODE );
if ( ! empty ( $_SESSION [ " dual-login " ][ " username " ])) {
$user = $_SESSION [ " dual-login " ][ " username " ] . ' => ' . $_SESSION [ 'mailcow_cc_username' ];
$role = $_SESSION [ " dual-login " ][ " role " ] . ' => ' . $_SESSION [ 'mailcow_cc_role' ];
}
elseif ( ! empty ( $_SESSION [ 'mailcow_cc_username' ])) {
$user = $_SESSION [ 'mailcow_cc_username' ];
$role = $_SESSION [ 'mailcow_cc_role' ];
}
else {
$user = 'unauthenticated' ;
$role = 'unauthenticated' ;
}
2018-09-10 22:23:08 +08:00
// We cannot log when logs is missing...
try {
$stmt = $pdo -> prepare ( " INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES
( : type , : task , : msg , : call , : user , : role , : remote , UNIX_TIMESTAMP ()) " );
$stmt -> execute ( array (
':type' => $type ,
':task' => $task ,
':call' => $call ,
':msg' => $msg ,
':user' => $user ,
':role' => $role ,
':remote' => get_remote_ip ()
));
}
catch ( Exception $e ) {
// Do nothing
}
2018-08-04 02:31:33 +08:00
}
}
else {
return true ;
}
}
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 ;
}
2018-10-11 17:59:23 +08:00
if ( $role != 'admin' && $role != 'domainadmin' ) {
2017-01-03 17:39:32 +08:00
return false ;
}
2018-10-11 17:59:23 +08:00
if ( $role == 'admin' ) {
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `domain`
WHERE `domain` = : domain " );
$stmt -> execute ( array ( ':domain' => $domain ));
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
$stmt = $pdo -> prepare ( " SELECT `alias_domain` FROM `alias_domain`
WHERE `alias_domain` = : domain " );
$stmt -> execute ( array ( ':domain' => $domain ));
$num_results = $num_results + count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( $num_results != 0 ) {
return true ;
}
}
elseif ( $role == 'domainadmin' ) {
$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 ))
) " );
$stmt -> execute ( array ( ':username' => $username , ':domain1' => $domain , ':domain2' => $domain ));
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( ! empty ( $num_results )) {
return true ;
}
}
2017-01-19 04:28:31 +08:00
return false ;
}
function hasMailboxObjectAccess ( $username , $role , $object ) {
global $pdo ;
2020-11-29 00:41:48 +08:00
if ( empty ( $username ) || empty ( $role ) || empty ( $object )) {
return false ;
}
2018-02-11 05:42:46 +08:00
if ( ! filter_var ( html_entity_decode ( rawurldecode ( $username )), FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username ))) {
2017-01-19 04:28:31 +08:00
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 ;
}
2018-08-14 05:20:40 +08:00
$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 ;
}
2016-12-10 03:39:02 +08:00
return false ;
}
2021-06-09 17:03:48 +08:00
// does also verify mailboxes as a mailbox is a alias == goto
2019-05-26 14:29:10 +08:00
function hasAliasObjectAccess ( $username , $role , $object ) {
global $pdo ;
2021-06-05 14:40:55 +08:00
if ( empty ( $username ) || empty ( $role ) || empty ( $object )) {
return false ;
}
2019-05-26 14:29:10 +08:00
if ( ! filter_var ( html_entity_decode ( rawurldecode ( $username )), FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $username ))) {
return false ;
}
if ( $role != 'admin' && $role != 'domainadmin' && $role != 'user' ) {
return false ;
}
2021-06-09 17:03:48 +08:00
$stmt = $pdo -> prepare ( " SELECT `domain` FROM `alias` WHERE `address` = :object " );
2019-05-26 14:29:10 +08:00
$stmt -> execute ( array ( ':object' => $object ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( isset ( $row [ 'domain' ]) && hasDomainAccess ( $username , $role , $row [ 'domain' ])) {
return true ;
}
return false ;
}
2017-07-11 02:52:51 +08:00
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 ));
}
2020-05-27 02:04:22 +08:00
function expand_ipv6 ( $ip ) {
$hex = unpack ( " H*hex " , inet_pton ( $ip ));
$ip = substr ( preg_replace ( " /([A-f0-9] { 4})/ " , " $ 1: " , $hex [ 'hex' ]), 0 , - 1 );
return $ip ;
}
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 " ;
}
if ( empty ( $starttls )) {
2018-02-04 02:13:15 +08:00
$context = stream_context_create ( array ( " ssl " => array ( " capture_peer_cert " => true , 'verify_peer' => false , 'verify_peer_name' => false , 'allow_self_signed' => true )));
2018-01-02 17:24:48 +08:00
$stream = stream_socket_client ( 'ssl://' . $hostname . ':' . $port , $error_nr , $error_msg , 5 , STREAM_CLIENT_CONNECT , $context );
if ( ! $stream ) {
$error_msg = isset ( $error_msg ) ? $error_msg : '-' ;
2017-06-23 20:23:12 +08:00
return $error_nr . ': ' . $error_msg ;
}
}
else {
$stream = stream_socket_client ( 'tcp://' . $hostname . ':' . $port , $error_nr , $error_msg , 5 );
2018-01-02 17:24:48 +08:00
if ( ! $stream ) {
2017-06-23 20:23:12 +08:00
return $error_nr . ': ' . $error_msg ;
}
$banner = fread ( $stream , 512 );
2017-07-11 02:52:51 +08:00
if ( preg_match ( " /^220/i " , $banner )) { // SMTP
2017-06-23 20:23:12 +08:00
fwrite ( $stream , " HELO tlsa.generator.local \r \n " );
fread ( $stream , 512 );
fwrite ( $stream , " STARTTLS \r \n " );
fread ( $stream , 512 );
}
2017-07-11 02:52:51 +08:00
elseif ( preg_match ( " /imap.+starttls/i " , $banner )) { // IMAP
2017-06-23 20:23:12 +08:00
fwrite ( $stream , " A1 STARTTLS \r \n " );
fread ( $stream , 512 );
}
2017-07-11 02:52:51 +08:00
elseif ( preg_match ( " /^ \ +OK/ " , $banner )) { // POP3
2017-06-23 20:23:12 +08:00
fwrite ( $stream , " STLS \r \n " );
fread ( $stream , 512 );
}
2017-07-11 02:52:51 +08:00
elseif ( preg_match ( " /^OK/m " , $banner )) { // Sieve
fwrite ( $stream , " STARTTLS \r \n " );
fread ( $stream , 512 );
}
2017-06-23 20:23:12 +08:00
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 );
2018-02-04 02:13:15 +08:00
stream_context_set_option ( $stream , 'ssl' , 'verify_peer_name' , false );
2017-06-23 20:23:12 +08:00
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' ;
}
}
2021-02-11 21:41:00 +08:00
function alertbox_log_parser ( $_data ) {
2018-08-04 02:31:33 +08:00
global $lang ;
if ( isset ( $_data [ 'return' ])) {
2018-08-14 05:20:40 +08:00
foreach ( $_data [ 'return' ] as $return ) {
// Get type
$type = $return [ 'type' ];
// If a lang[type][msg] string exists, use it as message
if ( is_string ( $lang [ $return [ 'type' ]][ $return [ 'msg' ]])) {
$msg = $lang [ $return [ 'type' ]][ $return [ 'msg' ]];
}
// If msg is an array, use first element as language string and run printf on it with remaining array elements
elseif ( is_array ( $return [ 'msg' ])) {
$msg = array_shift ( $return [ 'msg' ]);
$msg = vsprintf (
$lang [ $return [ 'type' ]][ $msg ],
$return [ 'msg' ]
);
}
// If none applies, use msg as returned message
else {
$msg = $return [ 'msg' ];
}
2020-05-21 02:37:52 +08:00
$log_array [] = array ( 'msg' => $msg , 'type' => json_encode ( $type ));
2018-08-04 02:31:33 +08:00
}
2020-07-11 19:20:38 +08:00
if ( ! empty ( $log_array )) {
2018-08-14 05:20:40 +08:00
return $log_array ;
2018-08-04 02:31:33 +08:00
}
}
return false ;
}
2021-02-11 21:41:00 +08:00
function verify_salted_hash ( $hash , $password , $algo , $salt_length ) {
2021-02-11 16:31:53 +08:00
// Decode hash
$dhash = base64_decode ( $hash );
2021-02-11 21:41:00 +08:00
// Get first n bytes of binary which equals a SSHA hash
2021-02-11 16:31:53 +08:00
$ohash = substr ( $dhash , 0 , $salt_length );
// Remove SSHA hash from decoded hash to get original salt string
$osalt = str_replace ( $ohash , '' , $dhash );
// Check single salted SSHA hash against extracted hash
if ( hash_equals ( hash ( $algo , $password . $osalt , true ), $ohash )) {
return true ;
2018-07-12 06:48:09 +08:00
}
2021-02-11 16:31:53 +08:00
return false ;
}
2021-02-11 21:41:00 +08:00
function verify_hash ( $hash , $password ) {
2021-02-11 16:31:53 +08:00
if ( preg_match ( '/^{(.+)}(.+)/i' , $hash , $hash_array )) {
$scheme = strtoupper ( $hash_array [ 1 ]);
$hash = $hash_array [ 2 ];
switch ( $scheme ) {
case " ARGON2I " :
case " ARGON2ID " :
case " BLF-CRYPT " :
case " CRYPT " :
case " DES-CRYPT " :
case " MD5-CRYPT " :
case " MD5 " :
case " SHA256-CRYPT " :
case " SHA512-CRYPT " :
return password_verify ( $password , $hash );
case " CLEAR " :
case " CLEARTEXT " :
case " PLAIN " :
return $password == $hash ;
case " LDAP-MD5 " :
$hash = base64_decode ( $hash );
return hash_equals ( hash ( 'md5' , $password , true ), $hash );
case " PBKDF2 " :
$components = explode ( '$' , $hash );
$salt = $components [ 2 ];
$rounds = $components [ 3 ];
$hash = $components [ 4 ];
return hash_equals ( hash_pbkdf2 ( 'sha1' , $password , $salt , $rounds ), $hash );
case " PLAIN-MD4 " :
return hash_equals ( hash ( 'md4' , $password ), $hash );
case " PLAIN-MD5 " :
return md5 ( $password ) == $hash ;
case " PLAIN-TRUNC " :
$components = explode ( '-' , $hash );
if ( count ( $components ) > 1 ) {
$trunc_len = $components [ 0 ];
$trunc_password = $components [ 1 ];
return substr ( $password , 0 , $trunc_len ) == $trunc_password ;
} else {
return $password == $hash ;
}
case " SHA " :
case " SHA1 " :
case " SHA256 " :
case " SHA512 " :
// SHA is an alias for SHA1
$scheme = $scheme == " SHA " ? " sha1 " : strtolower ( $scheme );
$hash = base64_decode ( $hash );
return hash_equals ( hash ( $scheme , $password , true ), $hash );
case " SMD5 " :
return verify_salted_hash ( $hash , $password , 'md5' , 16 );
case " SSHA " :
return verify_salted_hash ( $hash , $password , 'sha1' , 20 );
case " SSHA256 " :
return verify_salted_hash ( $hash , $password , 'sha256' , 32 );
case " SSHA512 " :
return verify_salted_hash ( $hash , $password , 'sha512' , 64 );
default :
return false ;
2020-11-16 03:22:35 +08:00
}
2018-09-20 16:25:36 +08:00
}
2018-07-12 06:24:23 +08:00
return false ;
2016-12-13 04:53:58 +08:00
}
2016-12-10 03:39:02 +08:00
function check_login ( $user , $pass ) {
global $pdo ;
2017-07-05 00:05:04 +08:00
global $redis ;
2018-11-12 17:01:33 +08:00
global $imap_server ;
2016-12-10 03:39:02 +08:00
if ( ! filter_var ( $user , FILTER_VALIDATE_EMAIL ) && ! ctype_alnum ( str_replace ( array ( '_' , '.' , '-' ), '' , $user ))) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => 'malformed_username'
);
2016-12-10 03:39:02 +08:00
return false ;
}
$user = strtolower ( trim ( $user ));
$stmt = $pdo -> prepare ( " SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
2019-02-08 05:37:11 +08:00
AND `active` = '1'
2016-12-10 03:39:02 +08:00
AND `username` = : user " );
$stmt -> execute ( array ( ':user' => $user ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
foreach ( $rows as $row ) {
2018-07-12 06:24:23 +08:00
if ( verify_hash ( $row [ 'password' ], $pass )) {
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' ] = " admin " ;
$_SESSION [ 'pending_tfa_method' ] = get_tfa ( $user )[ 'name' ];
unset ( $_SESSION [ 'ldelay' ]);
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'info' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => 'awaiting_tfa_confirmation'
);
2017-01-26 02:07:30 +08:00
return " pending " ;
}
else {
unset ( $_SESSION [ 'ldelay' ]);
2018-10-11 17:59:23 +08:00
// 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 ));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => array ( 'logged_in_as' , $user )
);
2017-01-26 02:07:30 +08:00
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 ) {
2018-07-12 06:24:23 +08:00
if ( verify_hash ( $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' ]);
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'info' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => 'awaiting_tfa_confirmation'
);
2017-01-26 02:07:30 +08:00
return " pending " ;
}
else {
unset ( $_SESSION [ 'ldelay' ]);
2018-08-04 02:31:33 +08:00
// Reactivate TFA if it was set to "deactivate TFA for next login"
2017-02-13 20:42:54 +08:00
$stmt = $pdo -> prepare ( " UPDATE `tfa` SET `active`='1' WHERE `username` = :user " );
$stmt -> execute ( array ( ':user' => $user ));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => array ( 'logged_in_as' , $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`
2020-05-17 15:41:38 +08:00
INNER JOIN domain on mailbox . domain = domain . domain
2017-01-28 16:53:39 +08:00
WHERE `kind` NOT REGEXP 'location|thing|group'
2020-05-17 15:41:38 +08:00
AND `mailbox` . `active` = '1'
AND `domain` . `active` = '1'
2017-01-28 16:53:39 +08:00
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 ) {
2018-07-12 06:24:23 +08:00
if ( verify_hash ( $row [ 'password' ], $pass ) !== false ) {
2016-12-10 03:39:02 +08:00
unset ( $_SESSION [ 'ldelay' ]);
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => array ( 'logged_in_as' , $user )
);
2016-12-10 03:39:02 +08:00
return " user " ;
}
}
if ( ! isset ( $_SESSION [ 'ldelay' ])) {
$_SESSION [ 'ldelay' ] = " 0 " ;
2017-07-05 00:05:04 +08:00
$redis -> publish ( " F2B_CHANNEL " , " mailcow UI: Invalid password for " . $user . " by " . $_SERVER [ 'REMOTE_ADDR' ]);
2017-07-02 17:10:35 +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-07-05 00:05:04 +08:00
$redis -> publish ( " F2B_CHANNEL " , " mailcow UI: Invalid password for " . $user . " by " . $_SERVER [ 'REMOTE_ADDR' ]);
2017-07-02 17:10:35 +08:00
error_log ( " mailcow UI: Invalid password for " . $user . " by " . $_SERVER [ 'REMOTE_ADDR' ]);
2016-12-10 03:39:02 +08:00
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $user , '*' ),
'msg' => 'login_failed'
);
2016-12-10 03:39:02 +08:00
sleep ( $_SESSION [ 'ldelay' ]);
2018-08-04 02:31:33 +08:00
return false ;
2016-12-10 03:39:02 +08:00
}
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 )];
}
2018-07-12 06:48:09 +08:00
function update_sogo_static_view () {
2020-04-30 03:09:13 +08:00
if ( getenv ( 'SKIP_SOGO' ) == " y " ) {
return true ;
}
2018-07-12 06:48:09 +08:00
global $pdo ;
global $lang ;
$stmt = $pdo -> query ( " SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'sogo_view' " );
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( $num_results != 0 ) {
2019-09-02 17:11:41 +08:00
$stmt = $pdo -> query ( " REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
SELECT `c_uid` , `domain` , `c_name` , `c_password` , `c_cn` , `mail` , `aliases` , `ad_aliases` , `ext_acl` , `kind` , `multiple_bookings` from sogo_view " );
2018-07-12 06:48:09 +08:00
$stmt = $pdo -> query ( " DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1'); " );
}
[Docker API] Use TLS encryption for communication with "on-the-fly" created key paris (non-exposed)
[Docker API] Create pipe to pass Rspamd UI worker password
[Dovecot] Pull Spamassassin ruleset to be read by Rspamd (MANY THANKS to Peer Heinlein!)
[Dovecot] Garbage collector for deleted maildirs (set keep time via MAILDIR_GC_TIME which defaults to 1440 minutes)
[Web] Flush memcached after mailbox item changes, fixes #1808
[Web] Fix duplicate IDs, fixes #1792
[Compose] Use SQL sockets
[PHP-FPM] Update APCu and Redis libs
[Dovecot] Encrypt maildir with global key pair in crypt-vol-1 (BACKUP!), also fixes #1791
[Web] Fix deletion of spam aliases
[Helper] Add "crypt" to backup script
[Helper] Override file for external SQL socket (not supported!)
[Compose] New images for Rspamd, PHP-FPM, SOGo, Dovecot, Docker API, Watchdog, ACME, Postfix
2018-09-30 04:01:23 +08:00
flush_memcached ();
2018-07-12 06:48:09 +08:00
}
2018-08-04 02:31:33 +08:00
function edit_user_account ( $_data ) {
2016-12-10 03:39:02 +08:00
global $lang ;
global $pdo ;
2018-08-04 02:31:33 +08:00
$_data_log = $_data ;
! isset ( $_data_log [ 'user_new_pass' ]) ? : $_data_log [ 'user_new_pass' ] = '*' ;
! isset ( $_data_log [ 'user_new_pass2' ]) ? : $_data_log [ 'user_new_pass2' ] = '*' ;
! isset ( $_data_log [ 'user_old_pass' ]) ? : $_data_log [ 'user_old_pass' ] = '*' ;
2017-07-16 17:03:28 +08:00
$username = $_SESSION [ 'mailcow_cc_username' ];
$role = $_SESSION [ 'mailcow_cc_role' ];
2018-08-04 02:31:33 +08:00
$password_old = $_data [ 'user_old_pass' ];
2017-07-16 17:03:28 +08:00
if ( filter_var ( $username , FILTER_VALIDATE_EMAIL === false ) || $role != 'user' ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-07-16 17:03:28 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
2017-07-16 17:03:28 +08:00
);
return false ;
2017-01-21 18:49:29 +08:00
}
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 );
2018-07-12 06:24:23 +08:00
if ( ! verify_hash ( $row [ 'password' ], $password_old )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-01-26 02:07:30 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
2017-01-26 02:07:30 +08:00
);
return false ;
}
2021-06-17 13:07:48 +08:00
if ( ! empty ( $_data [ 'user_new_pass' ]) && ! empty ( $_data [ 'user_new_pass2' ])) {
$password_new = $_data [ 'user_new_pass' ];
$password_new2 = $_data [ 'user_new_pass2' ];
if ( password_check ( $password_new , $password_new2 ) !== true ) {
return false ;
}
// support pre hashed passwords
if ( preg_match ( '/^{(ARGON2I|ARGON2ID|BLF-CRYPT|CLEAR|CLEARTEXT|CRYPT|DES-CRYPT|LDAP-MD5|MD5|MD5-CRYPT|PBKDF2|PLAIN|PLAIN-MD4|PLAIN-MD5|PLAIN-TRUNC|PLAIN-TRUNC|SHA|SHA1|SHA256|SHA256-CRYPT|SHA512|SHA512-CRYPT|SMD5|SSHA|SSHA256|SSHA512)}/i' , $password )) {
$password_hashed = $password_new ;
}
else {
$password_hashed = hash_password ( $password_new );
}
$stmt = $pdo -> prepare ( " UPDATE `mailbox` SET `password` = :password_hashed,
`attributes` = JSON_SET ( `attributes` , '$.force_pw_update' , '0' ),
`attributes` = JSON_SET ( `attributes` , '$.passwd_update' , NOW ())
WHERE `username` = : username " );
$stmt -> execute ( array (
':password_hashed' => $password_hashed ,
':username' => $username
));
2016-12-10 03:39:02 +08:00
}
2018-07-12 06:48:09 +08:00
update_sogo_static_view ();
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2016-12-10 03:39:02 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( '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 ) {
2017-01-19 04:28:31 +08:00
global $pdo ;
2021-05-26 20:02:27 +08:00
global $lang ;
$data [ 'direct_aliases' ] = array ();
$data [ 'shared_aliases' ] = array ();
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 ;
}
2021-06-03 14:00:59 +08:00
if ( ! hasMailboxObjectAccess ( $username , $_SESSION [ 'mailcow_cc_role' ], $username )) {
return false ;
}
2018-08-14 05:20:40 +08:00
$data [ 'address' ] = $username ;
2019-01-29 07:20:39 +08:00
$stmt = $pdo -> prepare ( " SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias`
2018-08-14 05:20:40 +08:00
WHERE `goto` REGEXP : username_goto
AND `address` NOT LIKE '@%'
AND `goto` != : username_goto2
AND `address` != : username_address " );
$stmt -> execute ( array (
':username_goto' => '(^|,)' . $username . '($|,)' ,
':username_goto2' => $username ,
':username_address' => $username
));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
2019-02-08 05:37:11 +08:00
$data [ 'shared_aliases' ][ $row [ 'shared_aliases' ]][ 'public_comment' ] = htmlspecialchars ( $row [ 'public_comment' ]);
//$data['shared_aliases'][] = $row['shared_aliases'];
2017-01-19 04:28:31 +08:00
}
2019-01-29 07:20:39 +08:00
$stmt = $pdo -> prepare ( " SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias`
2018-08-14 05:20:40 +08:00
WHERE `goto` = : username_goto
AND `address` NOT LIKE '@%'
AND `address` != : username_address " );
$stmt -> execute (
array (
':username_goto' => $username ,
':username_address' => $username
2019-01-29 07:20:39 +08:00
));
2018-08-14 05:20:40 +08:00
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
2019-01-29 07:20:39 +08:00
$data [ 'direct_aliases' ][ $row [ 'direct_aliases' ]][ 'public_comment' ] = htmlspecialchars ( $row [ 'public_comment' ]);
2018-08-14 05:20:40 +08:00
}
2019-01-29 07:20:39 +08:00
$stmt = $pdo -> prepare ( " SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox`
2018-08-14 05:20:40 +08:00
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 )) {
2019-02-06 16:23:44 +08:00
if ( empty ( $row [ 'ad_alias' ])) {
continue ;
}
2021-06-05 14:40:55 +08:00
$data [ 'direct_aliases' ][ $row [ 'ad_alias' ]][ 'public_comment' ] = $lang [ 'add' ][ 'alias_domain' ];
2021-05-21 18:48:19 +08:00
$data [ 'alias_domains' ][] = $row [ 'alias_domain' ];
2018-08-14 05:20:40 +08:00
}
2021-05-26 20:02:27 +08:00
$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 '@%'; " );
2018-08-14 05:20:40 +08:00
$stmt -> execute ( array ( ':username' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'aliases_also_send_as' ] = $row [ 'send_as' ];
2017-01-19 04:28:31 +08:00
}
2021-05-26 20:02:27 +08:00
$stmt = $pdo -> prepare ( " SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), ''), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%'; " );
2018-08-14 05:20:40 +08:00
$stmt -> execute ( array ( ':username' => $username ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'aliases_send_as_all' ] = $row [ 'send_as' ];
}
2021-05-26 20:02:27 +08:00
$stmt = $pdo -> prepare ( " SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%'; " );
2018-08-14 05:20:40 +08:00
$stmt -> execute ( array ( ':username' => '(^|,)' . $username . '($|,)' ));
$run = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $run )) {
$data [ 'is_catch_all' ] = $row [ 'address' ];
}
return $data ;
2017-05-23 15:36:59 +08:00
}
2020-07-11 19:20:38 +08:00
function is_valid_domain_name ( $domain_name ) {
2017-05-23 15:36:59 +08:00
if ( empty ( $domain_name )) {
2016-12-10 03:39:02 +08:00
return false ;
}
2019-02-05 07:10:21 +08:00
$domain_name = idn_to_ascii ( $domain_name , 0 , INTL_IDNA_VARIANT_UTS46 );
2019-09-21 20:16:50 +08:00
return ( preg_match ( " /^([a-z \ d](-*[a-z \ d])*)( \ .([a-z \ d](-*[a-z \ d])*))* $ /i " , $domain_name )
2017-05-23 15:36:59 +08:00
&& preg_match ( " /^. { 1,253} $ / " , $domain_name )
2019-09-21 20:16:50 +08:00
&& preg_match ( " /^[^ \ .] { 1,63}( \ .[^ \ .] { 1,63})* $ / " , $domain_name ));
2016-12-10 03:39:02 +08:00
}
2018-08-04 02:31:33 +08:00
function set_tfa ( $_data ) {
2017-01-26 02:07:30 +08:00
global $pdo ;
2017-05-23 15:36:59 +08:00
global $yubi ;
global $u2f ;
global $tfa ;
2018-08-04 02:31:33 +08:00
$_data_log = $_data ;
! isset ( $_data_log [ 'confirm_password' ]) ? : $_data_log [ 'confirm_password' ] = '*' ;
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
2017-05-23 15:36:59 +08:00
);
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
$stmt = $pdo -> prepare ( " SELECT `password` FROM `admin`
2021-06-22 13:17:55 +08:00
WHERE `username` = : username " );
$stmt -> execute ( array ( ':username' => $username ));
2017-05-23 15:36:59 +08:00
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
2021-06-22 13:17:55 +08:00
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( ! empty ( $num_results )) {
if ( ! verify_hash ( $row [ 'password' ], $_data [ " confirm_password " ])) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
);
return false ;
}
}
$stmt = $pdo -> prepare ( " SELECT `password` FROM `mailbox`
WHERE `username` = : username " );
$stmt -> execute ( array ( ':username' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( ! empty ( $num_results )) {
if ( ! verify_hash ( $row [ 'password' ], $_data [ " confirm_password " ])) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
);
return false ;
}
2017-01-26 02:07:30 +08:00
}
2018-08-04 02:31:33 +08:00
switch ( $_data [ " tfa_method " ]) {
2017-05-23 15:36:59 +08:00
case " yubi_otp " :
2018-08-04 02:31:33 +08:00
$key_id = ( ! isset ( $_data [ " key_id " ])) ? 'unidentified' : $_data [ " key_id " ];
$yubico_id = $_data [ 'yubico_id' ];
$yubico_key = $_data [ 'yubico_key' ];
2017-05-23 15:36:59 +08:00
$yubi = new Auth_Yubico ( $yubico_id , $yubico_key );
if ( ! $yubi ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
2017-05-23 15:36:59 +08:00
);
return false ;
}
2018-08-04 02:31:33 +08:00
if ( ! ctype_alnum ( $_data [ " otp_token " ]) || strlen ( $_data [ " otp_token " ]) != 44 ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'tfa_token_invalid'
2017-05-23 15:36:59 +08:00
);
return false ;
}
2018-08-04 02:31:33 +08:00
$yauth = $yubi -> verify ( $_data [ " otp_token " ]);
2017-05-23 15:36:59 +08:00
if ( PEAR :: isError ( $yauth )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'yotp_verification_failed' , $yauth -> getMessage ())
2017-05-23 15:36:59 +08:00
);
return false ;
}
try {
// We could also do a modhex translation here
2018-08-04 02:31:33 +08:00
$yubico_modhex_id = substr ( $_data [ " otp_token " ], 0 , 12 );
2020-07-11 19:20:38 +08:00
$stmt = $pdo -> prepare ( " DELETE FROM `tfa`
2017-05-23 15:36:59 +08:00
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 ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'mysql_error' , $e )
2017-05-23 15:36:59 +08:00
);
return false ;
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , htmlspecialchars ( $username ))
2017-05-23 15:36:59 +08:00
);
break ;
case " u2f " :
2018-08-04 02:31:33 +08:00
$key_id = ( ! isset ( $_data [ " key_id " ])) ? 'unidentified' : $_data [ " key_id " ];
2017-05-23 15:36:59 +08:00
try {
2018-08-04 02:31:33 +08:00
$reg = $u2f -> doRegister ( json_decode ( $_SESSION [ 'regReq' ]), json_decode ( $_data [ 'token' ]));
2017-05-23 15:36:59 +08:00
$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 ));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , $username )
2017-05-23 15:36:59 +08:00
);
$_SESSION [ 'regReq' ] = null ;
}
catch ( Exception $e ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'u2f_verification_failed' , $e -> getMessage ())
2017-05-23 15:36:59 +08:00
);
$_SESSION [ 'regReq' ] = null ;
return false ;
}
break ;
case " totp " :
2018-08-04 02:31:33 +08:00
$key_id = ( ! isset ( $_data [ " key_id " ])) ? 'unidentified' : $_data [ " key_id " ];
2017-05-23 15:36:59 +08:00
if ( $tfa -> verifyCode ( $_POST [ 'totp_secret' ], $_POST [ 'totp_confirm_token' ]) === true ) {
$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' ]));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , $username )
2017-05-23 15:36:59 +08:00
);
}
else {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'totp_verification_failed'
2017-05-23 15:36:59 +08:00
);
}
break ;
case " none " :
2020-11-16 02:32:37 +08:00
$stmt = $pdo -> prepare ( " DELETE FROM `tfa` WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
$_SESSION [ 'return' ][] = array (
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , htmlspecialchars ( $username ))
);
break ;
}
}
function fido2 ( $_data ) {
global $pdo ;
$_data_log = $_data ;
// Not logging registration data, only actions
// Silent errors for "get" requests
switch ( $_data [ " action " ]) {
case " register " :
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
2020-11-16 02:32:37 +08:00
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data [ " action " ]),
'msg' => 'access_denied'
);
return false ;
}
$stmt = $pdo -> prepare ( " INSERT INTO `fido2` (`username`, `rpId`, `credentialPublicKey`, `certificateChain`, `certificate`, `certificateIssuer`, `certificateSubject`, `signatureCounter`, `AAGUID`, `credentialId`)
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) " );
$stmt -> execute ( array (
$username ,
$_data [ 'registration' ] -> rpId ,
$_data [ 'registration' ] -> credentialPublicKey ,
$_data [ 'registration' ] -> certificateChain ,
$_data [ 'registration' ] -> certificate ,
$_data [ 'registration' ] -> certificateIssuer ,
$_data [ 'registration' ] -> certificateSubject ,
$_data [ 'registration' ] -> signatureCounter ,
$_data [ 'registration' ] -> AAGUID ,
$_data [ 'registration' ] -> credentialId )
);
$_SESSION [ 'return' ][] = array (
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $_data [ " action " ]),
'msg' => array ( 'object_modified' , $username )
);
break ;
case " get_user_cids " :
// Used to exclude existing CredentialIds while registering
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
return false ;
2020-11-16 02:32:37 +08:00
}
$stmt = $pdo -> prepare ( " SELECT `credentialId` FROM `fido2` WHERE `username` = :username " );
$stmt -> execute ( array ( ':username' => $username ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
$cids [] = $row [ 'credentialId' ];
}
return $cids ;
break ;
case " get_all_cids " :
// Only needed when using fido2 with username
$stmt = $pdo -> query ( " SELECT `credentialId` FROM `fido2` " );
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
$cids [] = $row [ 'credentialId' ];
}
return $cids ;
break ;
2020-11-17 20:38:28 +08:00
case " get_by_b64cid " :
2020-11-16 02:32:37 +08:00
if ( ! isset ( $_data [ 'cid' ]) || empty ( $_data [ 'cid' ])) {
return false ;
}
2020-11-17 20:38:28 +08:00
$stmt = $pdo -> prepare ( " SELECT `certificateSubject`, `username`, `credentialPublicKey`, SHA2(`credentialId`, 256) AS `cid` FROM `fido2` WHERE TO_BASE64(`credentialId`) = :cid " );
2020-11-16 02:32:37 +08:00
$stmt -> execute ( array ( ':cid' => $_data [ 'cid' ]));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
if ( empty ( $row ) || empty ( $row [ 'credentialPublicKey' ]) || empty ( $row [ 'username' ])) {
return false ;
}
$data [ 'pub_key' ] = $row [ 'credentialPublicKey' ];
$data [ 'username' ] = $row [ 'username' ];
2020-11-17 20:38:28 +08:00
$data [ 'subject' ] = $row [ 'certificateSubject' ];
$data [ 'cid' ] = $row [ 'cid' ];
2020-11-16 02:32:37 +08:00
return $data ;
break ;
case " get_friendly_names " :
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
return false ;
2020-11-16 02:32:37 +08:00
}
2020-11-25 23:10:33 +08:00
$stmt = $pdo -> prepare ( " SELECT SHA2(`credentialId`, 256) AS `cid`, `created`, `certificateSubject`, `friendlyName` FROM `fido2` WHERE `username` = :username " );
2020-11-16 02:32:37 +08:00
$stmt -> execute ( array ( ':username' => $username ));
$rows = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
while ( $row = array_shift ( $rows )) {
2020-11-17 20:38:28 +08:00
$fns [] = array (
2020-11-25 23:10:33 +08:00
" subject " => ( empty ( $row [ 'certificateSubject' ]) ? 'Unknown (' . $row [ 'created' ] . ')' : $row [ 'certificateSubject' ]),
2020-11-17 20:38:28 +08:00
" fn " => $row [ 'friendlyName' ],
" cid " => $row [ 'cid' ]
);
2020-11-16 02:32:37 +08:00
}
return $fns ;
break ;
case " unset_fido2_key " :
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data [ " action " ]),
'msg' => 'access_denied'
);
return false ;
2020-11-16 02:32:37 +08:00
}
2020-11-17 20:38:28 +08:00
$stmt = $pdo -> prepare ( " DELETE FROM `fido2` WHERE `username` = :username AND SHA2(`credentialId`, 256) = :cid " );
$stmt -> execute ( array (
':username' => $username ,
':cid' => $_data [ 'post_data' ][ 'unset_fido2_key' ]
));
2020-11-16 02:32:37 +08:00
$_SESSION [ 'return' ][] = array (
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , htmlspecialchars ( $username ))
);
break ;
case " edit_fn " :
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data [ " action " ]),
'msg' => 'access_denied'
);
return false ;
2020-11-16 02:32:37 +08:00
}
2020-11-17 20:38:28 +08:00
$stmt = $pdo -> prepare ( " UPDATE `fido2` SET `friendlyName` = :friendlyName WHERE SHA2(`credentialId`, 256) = :cid AND `username` = :username " );
2020-11-16 02:32:37 +08:00
$stmt -> execute ( array (
':username' => $username ,
':friendlyName' => $_data [ 'fido2_attrs' ][ 'fido2_fn' ],
2020-11-17 20:38:28 +08:00
':cid' => $_data [ 'fido2_attrs' ][ 'fido2_cid' ]
2020-11-16 02:32:37 +08:00
));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , htmlspecialchars ( $username ))
2017-05-23 15:36:59 +08:00
);
break ;
}
}
2018-08-04 02:31:33 +08:00
function unset_tfa_key ( $_data ) {
2017-05-23 15:36:59 +08:00
// Can only unset own keys
// Needs at least one key left
global $pdo ;
global $lang ;
2018-08-04 02:31:33 +08:00
$_data_log = $_data ;
$id = intval ( $_data [ 'unset_tfa_key' ]);
$username = $_SESSION [ 'mailcow_cc_username' ];
2021-06-22 13:17:55 +08:00
if ( ! isset ( $_SESSION [ 'mailcow_cc_role' ]) || empty ( $username )) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
);
return false ;
2017-05-23 15:36:59 +08:00
}
try {
if ( ! is_numeric ( $id )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'access_denied'
2017-05-23 15:36:59 +08:00
);
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 " ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => 'last_key'
2017-05-23 15:36:59 +08:00
);
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 ));
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'object_modified' , $username )
2017-05-23 15:36:59 +08:00
);
2017-01-26 02:07:30 +08:00
}
catch ( PDOException $e ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-01-26 02:07:30 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $_data_log ),
'msg' => array ( 'mysql_error' , $e )
2017-01-26 02:07:30 +08:00
);
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 );
2020-07-11 19:20:38 +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 $yubi ;
global $u2f ;
global $tfa ;
$stmt = $pdo -> prepare ( " SELECT `authmech` FROM `tfa`
WHERE `username` = : username AND `active` = '1' " );
$stmt -> execute ( array ( ':username' => $username ));
$row = $stmt -> fetch ( PDO :: FETCH_ASSOC );
2020-07-11 19:20:38 +08:00
2017-05-23 15:36:59 +08:00
switch ( $row [ " authmech " ]) {
case " yubi_otp " :
if ( ! ctype_alnum ( $token ) || strlen ( $token ) != 44 ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'yotp_verification_failed' , 'token length error' )
);
2017-05-23 15:36:59 +08:00
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 )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-23 15:36:59 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'yotp_verification_failed' , $yauth -> getMessage ())
2017-05-23 15:36:59 +08:00
);
return false ;
}
else {
$_SESSION [ 'tfa_id' ] = $row [ 'id' ];
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => 'verified_yotp_login'
);
2017-05-23 15:36:59 +08:00
return true ;
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'yotp_verification_failed' , 'unknown' )
);
2017-05-23 15:36:59 +08:00
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 ));
2019-10-26 01:13:24 +08:00
$stmt = $pdo -> prepare ( " SELECT `id` FROM `tfa` WHERE `keyHandle` = ? " );
$stmt -> execute ( array ( $reg -> keyHandle ));
$row_key_id = $stmt -> fetch ( PDO :: FETCH_ASSOC );
$_SESSION [ 'tfa_id' ] = $row_key_id [ 'id' ];
2017-05-23 15:36:59 +08:00
$_SESSION [ 'authReq' ] = null ;
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => 'verified_u2f_login'
);
2017-05-23 15:36:59 +08:00
return true ;
2017-05-12 05:10:32 +08:00
}
2017-05-23 15:36:59 +08:00
catch ( Exception $e ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-12 05:10:32 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'u2f_verification_failed' , $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 ;
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'u2f_verification_failed' , 'unknown' )
);
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' ];
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'success' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => 'verified_totp_login'
);
2017-05-23 15:36:59 +08:00
return true ;
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => 'totp_verification_failed'
);
2017-05-23 15:36:59 +08:00
return false ;
2017-05-12 05:10:32 +08:00
}
catch ( PDOException $e ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-05-12 05:10:32 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => array ( 'mysql_error' , $e )
2017-05-12 05:10:32 +08:00
);
return false ;
}
2017-05-23 15:36:59 +08:00
break ;
default :
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2018-08-04 02:31:33 +08:00
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $username , '*' ),
'msg' => 'unknown_tfa_method'
);
return false ;
2017-05-23 15:36:59 +08:00
break ;
}
return false ;
2017-01-26 02:07:30 +08:00
}
2020-04-11 02:57:01 +08:00
function admin_api ( $access , $action , $data = null ) {
2017-12-09 20:17:15 +08:00
global $pdo ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ ),
'msg' => 'access_denied'
2017-12-09 20:17:15 +08:00
);
return false ;
}
2020-05-04 13:51:50 +08:00
if ( $access !== " ro " && $access !== " rw " ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
'msg' => 'invalid access type'
);
return false ;
}
if ( $action == " edit " ) {
$active = ( ! empty ( $data [ 'active' ])) ? 1 : 0 ;
$skip_ip_check = ( isset ( $data [ 'skip_ip_check' ])) ? 1 : 0 ;
$allow_from = array_map ( 'trim' , preg_split ( " /( |,|;| \n )/ " , $data [ 'allow_from' ]));
foreach ( $allow_from as $key => $val ) {
if ( empty ( $val )) {
unset ( $allow_from [ $key ]);
continue ;
2020-04-11 02:57:01 +08:00
}
2020-05-04 13:51:50 +08:00
if ( valid_network ( $val ) !== true ) {
$_SESSION [ 'return' ][] = array (
'type' => 'warning' ,
'log' => array ( __FUNCTION__ , $data ),
'msg' => array ( 'ip_invalid' , htmlspecialchars ( $allow_from [ $key ]))
);
unset ( $allow_from [ $key ]);
continue ;
2018-10-11 17:59:23 +08:00
}
2020-05-04 13:51:50 +08:00
}
$allow_from = implode ( ',' , array_unique ( array_filter ( $allow_from )));
if ( empty ( $allow_from ) && $skip_ip_check == 0 ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $data ),
'msg' => 'ip_list_empty'
);
return false ;
}
$api_key = implode ( '-' , array (
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 )))
));
$stmt = $pdo -> query ( " SELECT `api_key` FROM `api` WHERE `access` = ' " . $access . " ' " );
$num_results = count ( $stmt -> fetchAll ( PDO :: FETCH_ASSOC ));
if ( empty ( $num_results )) {
$stmt = $pdo -> prepare ( " INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
VALUES ( : api_key , : skip_ip_check , : active , : allow_from , : access ); " );
$stmt -> execute ( array (
':api_key' => $api_key ,
':skip_ip_check' => $skip_ip_check ,
':active' => $active ,
':allow_from' => $allow_from ,
':access' => $access
));
}
else {
if ( $skip_ip_check == 0 ) {
$stmt = $pdo -> prepare ( " UPDATE `api` SET `skip_ip_check` = :skip_ip_check,
`active` = : active ,
`allow_from` = : allow_from
WHERE `access` = : access ; " );
$stmt -> execute ( array (
':active' => $active ,
':skip_ip_check' => $skip_ip_check ,
':allow_from' => $allow_from ,
':access' => $access
));
}
else {
$stmt = $pdo -> prepare ( " UPDATE `api` SET `skip_ip_check` = :skip_ip_check,
`active` = : active
WHERE `access` = : access ; " );
$stmt -> execute ( array (
':active' => $active ,
':skip_ip_check' => $skip_ip_check ,
':access' => $access
));
}
}
}
elseif ( $action == " regen_key " ) {
$api_key = implode ( '-' , array (
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 ))),
strtoupper ( bin2hex ( random_bytes ( 3 )))
));
$stmt = $pdo -> prepare ( " UPDATE `api` SET `api_key` = :api_key WHERE `access` = :access " );
$stmt -> execute ( array (
':api_key' => $api_key ,
':access' => $access
));
}
elseif ( $action == " get " ) {
$stmt = $pdo -> query ( " SELECT * FROM `api` WHERE `access` = ' " . $access . " ' " );
$apidata = $stmt -> fetch ( PDO :: FETCH_ASSOC );
$apidata [ 'allow_from' ] = str_replace ( ',' , PHP_EOL , $apidata [ 'allow_from' ]);
return $apidata ;
2017-12-09 20:17:15 +08:00
}
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , $data ),
2018-10-11 17:59:23 +08:00
'msg' => 'admin_api_modified'
2017-12-09 20:17:15 +08:00
);
}
2019-08-09 20:16:52 +08:00
function license ( $action , $data = null ) {
global $pdo ;
global $redis ;
global $lang ;
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ ),
'msg' => 'access_denied'
);
return false ;
}
switch ( $action ) {
case " verify " :
// Keep result until revalidate button is pressed or session expired
$stmt = $pdo -> query ( " SELECT `version` FROM `versions` WHERE `application` = 'GUID' " );
$versions = $stmt -> fetch ( PDO :: FETCH_ASSOC );
$post = array ( 'guid' => $versions [ 'version' ]);
$curl = curl_init ( 'https://verify.mailcow.email' );
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $curl , CURLOPT_CONNECTTIMEOUT , 10 );
curl_setopt ( $curl , CURLOPT_POSTFIELDS , $post );
$response = curl_exec ( $curl );
curl_close ( $curl );
$json_return = json_decode ( $response , true );
if ( $response && $json_return ) {
if ( $json_return [ 'response' ] === " ok " ) {
2019-08-12 04:19:26 +08:00
$_SESSION [ 'gal' ][ 'valid' ] = " true " ;
2019-08-09 20:16:52 +08:00
$_SESSION [ 'gal' ][ 'c' ] = $json_return [ 'c' ];
$_SESSION [ 'gal' ][ 's' ] = $json_return [ 's' ];
2020-03-04 19:54:38 +08:00
if ( $json_return [ 'm' ] == 'NoMoore' ) {
$_SESSION [ 'gal' ][ 'm' ] = '🐄' ;
}
else {
$_SESSION [ 'gal' ][ 'm' ] = str_repeat ( '🐄' , substr_count ( $json_return [ 'm' ], 'o' ));
}
2019-12-02 18:02:19 +08:00
}
2019-08-12 04:19:26 +08:00
elseif ( $json_return [ 'response' ] === " invalid " ) {
$_SESSION [ 'gal' ][ 'valid' ] = " false " ;
2019-08-09 20:16:52 +08:00
$_SESSION [ 'gal' ][ 'c' ] = $lang [ 'mailbox' ][ 'no' ];
$_SESSION [ 'gal' ][ 's' ] = $lang [ 'mailbox' ][ 'no' ];
2019-12-02 18:02:19 +08:00
$_SESSION [ 'gal' ][ 'm' ] = $lang [ 'mailbox' ][ 'no' ];
2019-08-09 20:16:52 +08:00
}
}
else {
2019-08-12 04:19:26 +08:00
$_SESSION [ 'gal' ][ 'valid' ] = " false " ;
2019-08-09 20:16:52 +08:00
$_SESSION [ 'gal' ][ 'c' ] = $lang [ 'danger' ][ 'temp_error' ];
$_SESSION [ 'gal' ][ 's' ] = $lang [ 'danger' ][ 'temp_error' ];
2019-12-02 18:02:19 +08:00
$_SESSION [ 'gal' ][ 'm' ] = $lang [ 'danger' ][ 'temp_error' ];
2019-08-12 04:19:26 +08:00
}
try {
// json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1
$redis -> Set ( 'LICENSE_STATUS_CACHE' , json_encode ( $_SESSION [ 'gal' ]));
}
catch ( RedisException $e ) {
$_SESSION [ 'return' ][] = array (
'type' => 'danger' ,
'log' => array ( __FUNCTION__ , $_action , $_data_log ),
'msg' => array ( 'redis_error' , $e )
);
2019-08-09 20:16:52 +08:00
return false ;
}
2019-08-12 04:19:26 +08:00
return $_SESSION [ 'gal' ][ 'valid' ];
2019-08-09 20:16:52 +08:00
break ;
case " guid " :
$stmt = $pdo -> query ( " SELECT `version` FROM `versions` WHERE `application` = 'GUID' " );
$versions = $stmt -> fetch ( PDO :: FETCH_ASSOC );
return $versions [ 'version' ];
break ;
}
}
2017-12-09 20:17:15 +08:00
function rspamd_ui ( $action , $data = null ) {
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ ),
'msg' => 'access_denied'
2017-12-09 20:17:15 +08:00
);
return false ;
}
switch ( $action ) {
case " edit " :
$rspamd_ui_pass = $data [ 'rspamd_ui_pass' ];
$rspamd_ui_pass2 = $data [ 'rspamd_ui_pass2' ];
if ( empty ( $rspamd_ui_pass ) || empty ( $rspamd_ui_pass2 )) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
'msg' => 'password_empty'
2017-12-09 20:17:15 +08:00
);
return false ;
}
if ( $rspamd_ui_pass != $rspamd_ui_pass2 ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
'msg' => 'password_mismatch'
2017-12-09 20:17:15 +08:00
);
return false ;
}
if ( strlen ( $rspamd_ui_pass ) < 6 ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
'msg' => 'rspamd_ui_pw_length'
2017-12-09 20:17:15 +08:00
);
return false ;
}
2018-10-24 03:14:57 +08:00
$docker_return = docker ( 'post' , 'rspamd-mailcow' , 'exec' , array ( 'cmd' => 'rspamd' , 'task' => 'worker_password' , 'raw' => $rspamd_ui_pass ), array ( 'Content-Type: application/json' ));
2017-12-09 20:17:15 +08:00
if ( $docker_return_array = json_decode ( $docker_return , true )) {
if ( $docker_return_array [ 'type' ] == 'success' ) {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'success' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
'msg' => 'rspamd_ui_pw_set'
2017-12-09 20:17:15 +08:00
);
return true ;
}
else {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => $docker_return_array [ 'type' ],
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
2017-12-09 20:17:15 +08:00
'msg' => $docker_return_array [ 'msg' ]
);
return false ;
}
}
else {
2018-08-14 05:20:40 +08:00
$_SESSION [ 'return' ][] = array (
2017-12-09 20:17:15 +08:00
'type' => 'danger' ,
2018-08-04 02:31:33 +08:00
'log' => array ( __FUNCTION__ , '*' , '*' ),
'msg' => 'unknown'
2017-12-09 20:17:15 +08:00
);
return false ;
}
break ;
}
}
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 );
}
2018-12-16 04:24:39 +08:00
function get_logs ( $application , $lines = false ) {
2017-11-04 03:37:24 +08:00
if ( $lines === false ) {
2020-07-11 19:20:38 +08:00
$lines = $GLOBALS [ 'LOG_LINES' ] - 1 ;
2018-08-04 02:31:33 +08:00
}
elseif ( is_numeric ( $lines ) && $lines >= 1 ) {
$lines = abs ( intval ( $lines ) - 1 );
}
else {
list ( $from , $to ) = explode ( '-' , $lines );
$from = intval ( $from );
$to = intval ( $to );
if ( $from < 1 || $to < $from ) { return false ; }
2017-11-04 03:37:24 +08:00
}
2017-05-07 05:52:40 +08:00
global $redis ;
2018-08-04 02:31:33 +08:00
global $pdo ;
2017-05-07 05:52:40 +08:00
if ( $_SESSION [ 'mailcow_cc_role' ] != " admin " ) {
return false ;
}
2018-08-04 02:31:33 +08:00
// SQL
2018-12-16 04:24:39 +08:00
if ( $application == " mailcow-ui " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
2018-08-14 05:20:40 +08:00
$stmt = $pdo -> prepare ( " SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to " );
$stmt -> execute ( array (
':from' => $from - 1 ,
':to' => $to
));
$data = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
2018-08-04 02:31:33 +08:00
}
else {
2018-08-14 05:20:40 +08:00
$stmt = $pdo -> prepare ( " SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :lines " );
$stmt -> execute ( array (
':lines' => $lines + 1 ,
));
$data = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
2018-08-04 02:31:33 +08:00
}
if ( is_array ( $data )) {
return $data ;
}
}
2021-06-04 20:29:39 +08:00
if ( $application == " sasl " ) {
if ( isset ( $from ) && isset ( $to )) {
$stmt = $pdo -> prepare ( " SELECT * FROM `sasl_logs` ORDER BY `id` DESC LIMIT :from, :to " );
$stmt -> execute ( array (
':from' => $from - 1 ,
':to' => $to
));
$data = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
}
else {
$stmt = $pdo -> prepare ( " SELECT * FROM `sasl_logs` ORDER BY `id` DESC LIMIT :lines " );
$stmt -> execute ( array (
':lines' => $lines + 1 ,
));
$data = $stmt -> fetchAll ( PDO :: FETCH_ASSOC );
}
if ( is_array ( $data )) {
return $data ;
}
}
2018-08-04 02:31:33 +08:00
// Redis
2018-12-16 04:24:39 +08:00
if ( $application == " dovecot-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'DOVECOT_MAILLOG' , $from - 1 , $to - 1 );
2017-11-04 03:37:24 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'DOVECOT_MAILLOG' , 0 , $lines );
2017-11-04 03:37:24 +08:00
}
if ( $data ) {
2017-05-07 05:52:40 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " postfix-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'POSTFIX_MAILLOG' , $from - 1 , $to - 1 );
2017-11-04 03:37:24 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'POSTFIX_MAILLOG' , 0 , $lines );
2017-11-04 03:37:24 +08:00
}
if ( $data ) {
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
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " sogo-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'SOGO_LOG' , $from - 1 , $to - 1 );
2017-11-04 03:37:24 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'SOGO_LOG' , 0 , $lines );
2017-11-04 03:37:24 +08:00
}
if ( $data ) {
2017-06-24 06:07:53 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " watchdog-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'WATCHDOG_LOG' , $from - 1 , $to - 1 );
2017-12-09 20:17:15 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'WATCHDOG_LOG' , 0 , $lines );
2017-12-09 20:17:15 +08:00
}
if ( $data ) {
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " acme-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'ACME_LOG' , $from - 1 , $to - 1 );
2017-12-09 20:17:15 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'ACME_LOG' , 0 , $lines );
2017-12-09 20:17:15 +08:00
}
if ( $data ) {
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " ratelimited " ) {
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'RL_LOG' , $from - 1 , $to - 1 );
}
else {
$data = $redis -> lRange ( 'RL_LOG' , 0 , $lines );
}
if ( $data ) {
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
if ( $application == " api-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'API_LOG' , $from - 1 , $to - 1 );
2017-12-09 20:17:15 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'API_LOG' , 0 , $lines );
2017-12-09 20:17:15 +08:00
}
if ( $data ) {
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " netfilter-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'NETFILTER_LOG' , $from - 1 , $to - 1 );
2017-11-04 03:37:24 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'NETFILTER_LOG' , 0 , $lines );
2017-11-04 03:37:24 +08:00
}
if ( $data ) {
2017-05-14 04:58:29 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " autodiscover-mailcow " ) {
2018-08-04 02:31:33 +08:00
if ( isset ( $from ) && isset ( $to )) {
$data = $redis -> lRange ( 'AUTODISCOVER_LOG' , $from - 1 , $to - 1 );
2017-11-04 03:37:24 +08:00
}
else {
2018-08-04 02:31:33 +08:00
$data = $redis -> lRange ( 'AUTODISCOVER_LOG' , 0 , $lines );
2017-11-04 03:37:24 +08:00
}
if ( $data ) {
2017-10-03 03:47:31 +08:00
foreach ( $data as $json_line ) {
$data_array [] = json_decode ( $json_line , true );
}
return $data_array ;
}
}
2018-12-16 04:24:39 +08:00
if ( $application == " rspamd-history " ) {
2017-05-23 15:36:59 +08:00
$curl = curl_init ();
2018-09-30 15:53:25 +08:00
curl_setopt ( $curl , CURLOPT_UNIX_SOCKET_PATH , '/var/lib/rspamd/rspamd.sock' );
2017-11-04 03:37:24 +08:00
if ( ! is_numeric ( $lines )) {
list ( $from , $to ) = explode ( '-' , $lines );
2018-02-09 05:56:07 +08:00
curl_setopt ( $curl , CURLOPT_URL , " http://rspamd/history?from= " . intval ( $from ) . " &to= " . intval ( $to ));
2017-11-04 03:37:24 +08:00
}
else {
2018-02-09 05:56:07 +08:00
curl_setopt ( $curl , CURLOPT_URL , " http://rspamd/history?to= " . intval ( $lines ));
2017-11-04 03:37:24 +08:00
}
2017-05-23 15:36:59 +08:00
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , true );
$history = curl_exec ( $curl );
2017-11-04 03:37:24 +08:00
if ( ! curl_errno ( $curl )) {
2017-05-23 15:36:59 +08:00
$data_array = json_decode ( $history , true );
curl_close ( $curl );
return $data_array [ 'rows' ];
}
curl_close ( $curl );
return false ;
}
2020-09-06 14:54:09 +08:00
if ( $application == " rspamd-stats " ) {
$curl = curl_init ();
curl_setopt ( $curl , CURLOPT_UNIX_SOCKET_PATH , '/var/lib/rspamd/rspamd.sock' );
curl_setopt ( $curl , CURLOPT_URL , " http://rspamd/stat " );
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , true );
$stats = curl_exec ( $curl );
if ( ! curl_errno ( $curl )) {
$data_array = json_decode ( $stats , true );
curl_close ( $curl );
return $data_array ;
}
curl_close ( $curl );
return false ;
}
2017-05-07 05:52:40 +08:00
return false ;
}
2019-01-03 04:28:22 +08:00
function getGUID () {
if ( function_exists ( 'com_create_guid' )) {
return com_create_guid ();
}
mt_srand (( double ) microtime () * 10000 ); //optional for php 4.2.0 and up.
$charid = strtoupper ( md5 ( uniqid ( rand (), true )));
$hyphen = chr ( 45 ); // "-"
return substr ( $charid , 0 , 8 ) . $hyphen
. substr ( $charid , 8 , 4 ) . $hyphen
. substr ( $charid , 12 , 4 ) . $hyphen
. substr ( $charid , 16 , 4 ) . $hyphen
. substr ( $charid , 20 , 12 );
}
2019-01-16 17:50:34 +08:00
function solr_status () {
$curl = curl_init ();
$endpoint = 'http://solr:8983/solr/admin/cores' ;
$params = array (
'action' => 'STATUS' ,
2019-03-10 16:36:33 +08:00
'core' => 'dovecot-fts' ,
2019-01-16 17:50:34 +08:00
'indexInfo' => 'true'
);
$url = $endpoint . '?' . http_build_query ( $params );
curl_setopt ( $curl , CURLOPT_URL , $url );
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , 1 );
curl_setopt ( $curl , CURLOPT_POST , 0 );
2019-09-18 02:11:53 +08:00
curl_setopt ( $curl , CURLOPT_TIMEOUT , 10 );
$response_core = curl_exec ( $curl );
if ( $response_core === false ) {
2019-01-16 17:50:34 +08:00
$err = curl_error ( $curl );
curl_close ( $curl );
2019-01-17 02:11:17 +08:00
return false ;
2019-01-16 17:50:34 +08:00
}
else {
curl_close ( $curl );
2019-09-18 02:11:53 +08:00
$curl = curl_init ();
$status_core = json_decode ( $response_core , true );
$url = 'http://solr:8983/solr/admin/info/system' ;
curl_setopt ( $curl , CURLOPT_URL , $url );
curl_setopt ( $curl , CURLOPT_RETURNTRANSFER , 1 );
curl_setopt ( $curl , CURLOPT_POST , 0 );
curl_setopt ( $curl , CURLOPT_TIMEOUT , 10 );
$response_sysinfo = curl_exec ( $curl );
if ( $response_sysinfo === false ) {
$err = curl_error ( $curl );
curl_close ( $curl );
return false ;
}
else {
curl_close ( $curl );
$status_sysinfo = json_decode ( $response_sysinfo , true );
$status = array_merge ( $status_core , $status_sysinfo );
return ( ! empty ( $status [ 'status' ][ 'dovecot-fts' ]) && ! empty ( $status [ 'jvm' ][ 'memory' ])) ? $status : false ;
}
2019-03-10 16:36:33 +08:00
return ( ! empty ( $status [ 'status' ][ 'dovecot-fts' ])) ? $status [ 'status' ][ 'dovecot-fts' ] : false ;
2019-01-16 17:50:34 +08:00
}
return false ;
}
2019-10-21 01:39:46 +08:00
function cleanupJS ( $ignore = '' , $folder = '/tmp/*.js' ) {
2020-03-31 16:03:40 +08:00
$now = time ();
foreach ( glob ( $folder ) as $filename ) {
if ( strpos ( $filename , $ignore ) !== false ) {
continue ;
}
if ( is_file ( $filename )) {
if ( $now - filemtime ( $filename ) >= 60 * 60 ) {
2019-10-21 01:39:46 +08:00
unlink ( $filename );
2020-03-31 16:03:40 +08:00
}
2019-10-21 01:39:46 +08:00
}
2020-03-31 16:03:40 +08:00
}
2019-10-21 01:39:46 +08:00
}
function cleanupCSS ( $ignore = '' , $folder = '/tmp/*.css' ) {
2020-03-31 16:03:40 +08:00
$now = time ();
foreach ( glob ( $folder ) as $filename ) {
if ( strpos ( $filename , $ignore ) !== false ) {
continue ;
}
if ( is_file ( $filename )) {
if ( $now - filemtime ( $filename ) >= 60 * 60 ) {
2019-10-21 01:39:46 +08:00
unlink ( $filename );
2020-03-31 16:03:40 +08:00
}
2019-10-21 01:39:46 +08:00
}
2020-03-31 16:03:40 +08:00
}
2019-10-21 01:39:46 +08:00
}
2020-03-31 16:03:40 +08:00
2017-11-04 03:37:24 +08:00
?>