Page MenuHomeWMGMC Issues

No OneTemporary

diff --git a/data/web/inc/ajax/qitem_details.php b/data/web/inc/ajax/qitem_details.php
index 4a61232c..0ea89047 100644
--- a/data/web/inc/ajax/qitem_details.php
+++ b/data/web/inc/ajax/qitem_details.php
@@ -1,195 +1,195 @@
<?php
session_start();
header("Content-Type: application/json");
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
function rrmdir($src) {
$dir = opendir($src);
while(false !== ( $file = readdir($dir)) ) {
if (( $file != '.' ) && ( $file != '..' )) {
$full = $src . '/' . $file;
if ( is_dir($full) ) {
rrmdir($full);
}
else {
unlink($full);
}
}
}
closedir($dir);
rmdir($src);
}
function addAddresses(&$list, $mail, $headerName) {
$addresses = $mail->getAddresses($headerName);
foreach ($addresses as $address) {
if (filter_var($address['address'], FILTER_VALIDATE_EMAIL)) {
$list[] = array('address' => $address['address'], 'type' => $headerName);
}
}
}
if (!empty($_GET['hash']) && ctype_alnum($_GET['hash'])) {
$mailc = quarantine('hash_details', $_GET['hash']);
if ($mailc === false) {
echo json_encode(array('error' => 'Message invalid'));
exit;
}
if (strlen($mailc['msg']) > 10485760) {
echo json_encode(array('error' => 'Message size exceeds 10 MiB.'));
exit;
}
if (!empty($mailc['msg'])) {
// Init message array
$data = array();
// Init parser
$mail_parser = new PhpMimeMailParser\Parser();
$html2text = new Html2Text\Html2Text();
// Load msg to parser
$mail_parser->setText($mailc['msg']);
// Get mail recipients
{
$recipientsList = array();
addAddresses($recipientsList, $mail_parser, 'to');
addAddresses($recipientsList, $mail_parser, 'cc');
addAddresses($recipientsList, $mail_parser, 'bcc');
- $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'SMTP');
+ $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp');
$data['recipients'] = $recipientsList;
}
// Get from
$data['header_from'] = $mail_parser->getHeader('from');
$data['env_from'] = $mailc['sender'];
// Get rspamd score
$data['score'] = $mailc['score'];
// Get rspamd symbols
$data['symbols'] = json_decode($mailc['symbols']);
$data['subject'] = $mail_parser->getHeader('subject');
(empty($data['subject'])) ? $data['subject'] = '-' : null;
echo json_encode($data);
}
}
elseif (!empty($_GET['id']) && ctype_alnum($_GET['id'])) {
if (!isset($_SESSION['mailcow_cc_role'])) {
echo json_encode(array('error' => 'Access denied'));
exit();
}
$tmpdir = '/tmp/' . $_GET['id'] . '/';
$mailc = quarantine('details', $_GET['id']);
if ($mailc === false) {
echo json_encode(array('error' => 'Access denied'));
exit;
}
if (strlen($mailc['msg']) > 10485760) {
echo json_encode(array('error' => 'Message size exceeds 10 MiB.'));
exit;
}
if (!empty($mailc['msg'])) {
if (isset($_GET['quick_release'])) {
$hash = hash('sha256', $mailc['id'] . $mailc['qid']);
header('Location: /qhandler/release/' . $hash);
exit;
}
if (isset($_GET['quick_delete'])) {
$hash = hash('sha256', $mailc['id'] . $mailc['qid']);
header('Location: /qhandler/delete/' . $hash);
exit;
}
// Init message array
$data = array();
// Init parser
$mail_parser = new PhpMimeMailParser\Parser();
$html2text = new Html2Text\Html2Text();
// Load msg to parser
$mail_parser->setText($mailc['msg']);
// Get mail recipients
{
$recipientsList = array();
addAddresses($recipientsList, $mail_parser, 'to');
addAddresses($recipientsList, $mail_parser, 'cc');
addAddresses($recipientsList, $mail_parser, 'bcc');
- $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'SMTP');
+ $recipientsList[] = array('address' => $mailc['rcpt'], 'type' => 'smtp');
$data['recipients'] = $recipientsList;
}
// Get from
$data['header_from'] = $mail_parser->getHeader('from');
$data['env_from'] = $mailc['sender'];
// Get rspamd score
$data['score'] = $mailc['score'];
// Get rspamd symbols
$data['symbols'] = json_decode($mailc['symbols']);
// Get text/plain content
$data['text_plain'] = $mail_parser->getMessageBody('text');
// Get html content and convert to text
$data['text_html'] = $html2text->convert($mail_parser->getMessageBody('html'));
if (empty($data['text_plain']) && empty($data['text_html'])) {
// Failed to parse content, try raw
$text = trim(substr($mailc['msg'], strpos($mailc['msg'], "\r\n\r\n") + 1));
// Only return html->text
$data['text_plain'] = 'Parser failed, assuming HTML';
$data['text_html'] = $html2text->convert($text);
}
(empty($data['text_plain'])) ? $data['text_plain'] = '-' : null;
// Get subject
$data['subject'] = $mail_parser->getHeader('subject');
(empty($data['subject'])) ? $data['subject'] = '-' : null;
// Get attachments
if (is_dir($tmpdir)) {
rrmdir($tmpdir);
}
mkdir('/tmp/' . $_GET['id']);
$mail_parser->saveAttachments($tmpdir, true);
$atts = $mail_parser->getAttachments(true);
if (count($atts) > 0) {
foreach ($atts as $key => $val) {
$data['attachments'][$key] = array(
// Index
// 0 => file name
// 1 => mime type
// 2 => file size
// 3 => vt link by sha256
$val->getFilename(),
$val->getContentType(),
filesize($tmpdir . $val->getFilename()),
'https://www.virustotal.com/file/' . hash_file('SHA256', $tmpdir . $val->getFilename()) . '/analysis/'
);
}
}
if (isset($_GET['eml'])) {
$dl_filename = filter_var($data['subject'], FILTER_SANITIZE_STRING);
$dl_filename = strlen($dl_filename) > 30 ? substr($dl_filename,0,30) : $dl_filename;
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-Type: message/rfc822');
header('Content-Disposition: attachment; filename="'. $dl_filename . '.eml";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . strlen($mailc['msg']));
echo $mailc['msg'];
exit;
}
if (isset($_GET['att'])) {
if ($_SESSION['acl']['quarantine_attachments'] == 0) {
exit(json_encode('Forbidden'));
}
$dl_id = intval($_GET['att']);
$dl_filename = filter_var($data['attachments'][$dl_id][0], FILTER_SANITIZE_STRING);
$dl_filename = strlen($dl_filename) > 30 ? substr($dl_filename,0,30) : $dl_filename;
if (!is_dir($tmpdir . $dl_filename) && file_exists($tmpdir . $dl_filename)) {
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false);
header('Content-Type: ' . $data['attachments'][$dl_id][1]);
header('Content-Disposition: attachment; filename="'. $dl_filename . '";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . $data['attachments'][$dl_id][2]);
readfile($tmpdir . $dl_filename);
exit;
}
}
echo json_encode($data);
}
}
?>
diff --git a/data/web/inc/ajax/transport_check.php b/data/web/inc/ajax/transport_check.php
index 3526e81b..b249e555 100644
--- a/data/web/inc/ajax/transport_check.php
+++ b/data/web/inc/ajax/transport_check.php
@@ -1,140 +1,143 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\SMTP;
+use PHPMailer\PHPMailer\Exception;
error_reporting(0);
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
$transport_id = intval($_GET['transport_id']);
$transport_type = $_GET['transport_type'];
if (isset($_GET['mail_from']) && filter_var($_GET['mail_from'], FILTER_VALIDATE_EMAIL)) {
$mail_from = $_GET['mail_from'];
}
else {
$mail_from = "relay@example.org";
}
if ($transport_type == 'transport-map') {
$transport_details = transport('details', $transport_id);
$nexthop = $transport_details['nexthop'];
}
elseif ($transport_type == 'sender-dependent') {
$transport_details = relayhost('details', $transport_id);
$nexthop = $transport_details['hostname'];
}
if (!empty($transport_details)) {
// Remove [ and ]
$hostname_w_port = preg_replace('/\[|\]/', '', $nexthop);
preg_match('/\[.+\](:.+)/', $nexthop, $hostname_port_match);
preg_match('/\[\d\.\d\.\d\.\d\](:.+)/', $nexthop, $ipv4_port_match);
$has_bracket_and_port = (isset($hostname_port_match[1])) ? true : false;
$is_ipv4_and_has_port = (isset($ipv4_port_match[1])) ? true : false;
$skip_lookup_mx = strpos($nexthop, '[');
// Explode to hostname and port
if ($has_bracket_and_port) {
$port = substr($hostname_w_port, strrpos($hostname_w_port, ':') + 1);
$hostname = preg_replace('/'. preg_quote(':' . $port, '/') . '$/', '', $hostname_w_port);
if (filter_var($hostname, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$hostname = '[' . $hostname . ']:';
}
}
else {
if ($is_ipv4_and_has_port) {
$port = substr($hostname_w_port, strrpos($hostname_w_port, ':') + 1);
$hostname = preg_replace('/'. preg_quote(':' . $port, '/') . '$/', '', $hostname_w_port);
}
if (filter_var($hostname_w_port, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$hostname = $hostname_w_port;
$port = null;
}
elseif (filter_var($hostname_w_port, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$hostname = '[' . $hostname_w_port . ']';
$port = null;
}
else {
$hostname = preg_replace('/'. preg_quote(':' . $port, '/') . '$/', '', $hostname_w_port);
$port = null;
}
}
// Try to get MX if host is not [host]
if ($skip_lookup_mx === false) {
getmxrr($hostname, $mx_records, $mx_weight);
if (!empty($mx_records)) {
for ($i = 0; $i < count($mx_records); $i++) {
$mxs[$mx_records[$i]] = $mx_weight[$i];
}
asort ($mxs);
$records = array_keys($mxs);
echo 'Using first matched primary MX for "' . $hostname . '": ';
$hostname = $records[0];
echo $hostname . '<br>';
}
else {
echo 'No MX records for ' . $hostname . ' were found in DNS, skipping and using hostname as next-hop.<br>';
}
}
// Use port 25 if no port was given
$port = (empty($port)) ? 25 : $port;
$username = $transport_details['username'];
$password = $transport_details['password'];
$mail = new PHPMailer;
$mail->Timeout = 10;
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
$mail->SMTPDebug = 3;
// smtp: and smtp_enforced_tls: do not support wrapped tls, todo?
// change postfix map to detect wrapped tls or add a checkbox to toggle wrapped tls
// if ($port == 465) {
// $mail->SMTPSecure = "ssl";
// }
$mail->Debugoutput = function($str, $level) {
foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){
if (empty($line)) { continue; }
if (preg_match("/SERVER \-\> CLIENT: 2\d\d.+/i", $line)) {
echo '<span style="color:darkgreen;font-weight:bold">' . htmlspecialchars($line) . '</span><br>';
}
elseif (preg_match("/SERVER \-\> CLIENT: 3\d\d.+/i", $line)) {
echo '<span style="color:lightgreen;font-weight:bold">' . htmlspecialchars($line) . '</span><br>';
}
elseif (preg_match("/SERVER \-\> CLIENT: 4\d\d.+/i", $line)) {
echo '<span style="color:yellow;font-weight:bold">' . htmlspecialchars($line) . '</span><br>';
}
elseif (preg_match("/SERVER \-\> CLIENT: 5\d\d.+/i", $line)) {
echo '<span style="color:red;font-weight:bold">' . htmlspecialchars($line) . '</span><br>';
}
elseif (preg_match("/CLIENT \-\> SERVER:.+/i", $line)) {
echo '<span style="color:#999;font-weight:bold">' . htmlspecialchars($line) . '</span><br>';
}
elseif (preg_match("/^(?!SERVER|CLIENT|Connection:|\)).+$/i", $line)) {
echo '<span>&nbsp;&nbsp;&nbsp;&nbsp;↪ ' . htmlspecialchars($line) . '</span><br>';
}
else {
echo htmlspecialchars($line) . '<br>';
}
}
};
$mail->isSMTP();
$mail->Host = $hostname;
if (!empty($username)) {
$mail->SMTPAuth = true;
$mail->Username = $username;
$mail->Password = $password;
}
$mail->Port = $port;
$mail->setFrom($mail_from, 'Mailer');
$mail->Subject = 'A subject for a SMTP test';
$mail->addAddress($RELAY_TO, 'Joe Null');
$mail->Body = 'This is our test body';
$mail->send();
}
else {
echo "Unknown transport.";
}
}
else {
echo "Permission denied.";
}
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 6848a0dd..dabe6d6a 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -1,1767 +1,1770 @@
<?php
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\SMTP;
+use PHPMailer\PHPMailer\Exception;
function is_valid_regex($exp) {
return @preg_match($exp, '') !== false;
}
function isset_has_content($var) {
if (isset($var) && $var != "") {
return true;
}
else {
return false;
}
}
// 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;
}
function hash_password($password) {
$salt_str = bin2hex(openssl_random_pseudo_bytes(8));
return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
}
function last_login($user) {
global $pdo;
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :user
AND `type` = "success" ORDER BY `time` DESC LIMIT 1');
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
return $row;
}
else {
return false;
}
}
function flush_memcached() {
try {
$m = new Memcached();
$m->addServer('memcached', 11211);
$m->flush();
}
catch ( Exception $e ) {
// Dunno
}
}
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'];
$mass_subject = $_data['mass_subject'];
if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'from_invalid'
);
return false;
}
if (empty($mass_subject)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'subject_empty'
);
return false;
}
if (empty($mass_text)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'text_empty'
);
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";
$mail->Body = $mass_text;
$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'
);
}
function logger($_data = false) {
/*
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
);
*/
global $pdo;
if (!$_data) {
$_data = $_SESSION;
}
if (!empty($_data['return'])) {
$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';
}
// 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
}
}
}
else {
return true;
}
}
function hasDomainAccess($username, $role, $domain) {
global $pdo;
if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
return false;
}
if (empty($domain) || !is_valid_domain_name($domain)) {
return false;
}
if ($role != 'admin' && $role != 'domainadmin') {
return false;
}
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;
}
}
return false;
}
function hasMailboxObjectAccess($username, $role, $object) {
global $pdo;
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;
}
if ($username == $object) {
return true;
}
$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;
}
return false;
}
function hasAliasObjectAccess($username, $role, $object) {
global $pdo;
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;
}
if ($username == $object) {
return true;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object");
$stmt->execute(array(':object' => $object));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
return true;
}
return false;
}
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));
}
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;
}
function generate_tlsa_digest($hostname, $port, $starttls = null) {
if (!is_valid_domain_name($hostname)) {
return "Not a valid hostname";
}
if (empty($starttls)) {
$context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)));
$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 : '-';
return $error_nr . ': ' . $error_msg;
}
}
else {
$stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5);
if (!$stream) {
return $error_nr . ': ' . $error_msg;
}
$banner = fread($stream, 512 );
if (preg_match("/^220/i", $banner)) { // SMTP
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)) { // IMAP
fwrite($stream,"A1 STARTTLS\r\n");
fread($stream, 512);
}
elseif (preg_match("/^\+OK/", $banner)) { // POP3
fwrite($stream,"STLS\r\n");
fread($stream, 512);
}
elseif (preg_match("/^OK/m", $banner)) { // Sieve
fwrite($stream,"STARTTLS\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', 'verify_peer_name', 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';
}
}
function alertbox_log_parser($_data){
global $lang;
if (isset($_data['return'])) {
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'];
}
$log_array[] = array('msg' => $msg, 'type' => json_encode($type));
}
if (!empty($log_array)) {
return $log_array;
}
}
return false;
}
function verify_hash($hash, $password) {
if (preg_match('/^{SSHA256}/i', $hash)) {
// Remove tag if any
$hash = preg_replace('/^{SSHA256}/i', '', $hash);
// 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_equals(hash('sha256', $password . $osalt, true), $ohash)) {
return true;
}
}
elseif (preg_match('/^{PLAIN-MD5}/i', $hash)) {
$hash = preg_replace('/^{PLAIN-MD5}/i', '', $hash);
if (md5($password) == $hash) {
return true;
}
}
elseif (preg_match('/^{SHA512-CRYPT}/i', $hash)) {
// Remove tag if any
$hash = preg_replace('/^{SHA512-CRYPT}/i', '', $hash);
// Decode hash
preg_match('/\\$6\\$(.*)\\$(.*)/i', $hash, $hash_array);
$osalt = $hash_array[1];
$ohash = $hash_array[2];
if (hash_equals(crypt($password, '$6$' . $osalt . '$'), $hash)) {
return true;
}
}
elseif (preg_match('/^{SSHA512}/i', $hash)) {
$hash = preg_replace('/^{SSHA512}/i', '', $hash);
// Decode hash
$dhash = base64_decode($hash);
// Get first 64 bytes of binary which equals a SHA512 hash
$ohash = substr($dhash, 0, 64);
// Remove SHA512 hash from decoded hash to get original salt string
$osalt = str_replace($ohash, '', $dhash);
// Check single salted SHA512 hash against extracted hash
if (hash_equals(hash('sha512', $password . $osalt, true), $ohash)) {
return true;
}
}
elseif (preg_match('/^{MD5-CRYPT}/i', $hash)) {
$hash = preg_replace('/^{MD5-CRYPT}/i', '', $hash);
if (password_verify($password, $hash)) {
return true;
}
}
return false;
}
function check_login($user, $pass) {
global $pdo;
global $redis;
global $imap_server;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
return false;
}
$user = strtolower(trim($user));
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `active` = '1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_hash($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']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "admin";
}
}
}
$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) {
if (verify_hash($row['password'], $pass) !== false) {
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']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "domainadmin";
}
}
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
INNER JOIN domain on mailbox.domain = domain.domain
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `mailbox`.`active`='1'
AND `domain`.`active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_hash($row['password'], $pass) !== false) {
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "user";
}
}
if (!isset($_SESSION['ldelay'])) {
$_SESSION['ldelay'] = "0";
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
elseif (!isset($_SESSION['mailcow_cc_username'])) {
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'login_failed'
);
sleep($_SESSION['ldelay']);
return false;
}
function formatBytes($size, $precision = 2) {
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)];
}
function update_sogo_static_view() {
if (getenv('SKIP_SOGO') == "y") {
return true;
}
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) {
$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");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
}
flush_memcached();
}
function edit_user_account($_data) {
global $lang;
global $pdo;
$_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'] = '*';
$username = $_SESSION['mailcow_cc_username'];
$role = $_SESSION['mailcow_cc_role'];
$password_old = $_data['user_old_pass'];
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['user_new_pass']) && isset($_data['user_new_pass2'])) {
$password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2'];
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (isset($password_new) && isset($password_new2)) {
if (!empty($password_new2) && !empty($password_new)) {
if ($password_new2 != $password_new) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'password_mismatch'
);
return false;
}
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'password_complexity'
);
return false;
}
$password_hashed = hash_password($password_new);
try {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0') WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
}
}
update_sogo_static_view();
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mailbox_modified', htmlspecialchars($username))
);
}
function user_get_alias_details($username) {
global $lang;
global $pdo;
$data['direct_aliases'] = false;
$data['shared_aliases'] = false;
if ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
return false;
}
$data['address'] = $username;
$stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias`
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)) {
$data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']);
//$data['shared_aliases'][] = $row['shared_aliases'];
}
$stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias`
WHERE `goto` = :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['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']);
}
$stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` 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)) {
if (empty($row['ad_alias'])) {
continue;
}
$data['direct_aliases'][$row['ad_alias']]['public_comment'] = '↪ ' . $row['alias_domain'];
}
$stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') 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 CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), '&#10008;'), 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 '@%';");
$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 ', '), '&#10008;') 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'];
}
return $data;
}
function is_valid_domain_name($domain_name) {
if (empty($domain_name)) {
return false;
}
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
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));
}
function set_tfa($_data) {
global $lang;
global $pdo;
global $yubi;
global $u2f;
global $tfa;
$_data_log = $_data;
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
$username = $_SESSION['mailcow_cc_username'];
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
$_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $_data["confirm_password"])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
switch ($_data["tfa_method"]) {
case "yubi_otp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
$yubico_id = $_data['yubico_id'];
$yubico_key = $_data['yubico_key'];
$yubi = new Auth_Yubico($yubico_id, $yubico_key);
if (!$yubi) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'tfa_token_invalid'
);
return false;
}
$yauth = $yubi->verify($_data["otp_token"]);
if (PEAR::isError($yauth)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
try {
// We could also do a modhex translation here
$yubico_modhex_id = substr($_data["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',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
case "u2f":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
try {
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['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',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
$_SESSION['regReq'] = null;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
break;
case "totp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["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',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'totp_verification_failed'
);
}
break;
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',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
}
}
function unset_tfa_key($_data) {
// Can only unset own keys
// Needs at least one key left
global $pdo;
global $lang;
$_data_log = $_data;
$id = intval($_data['unset_tfa_key']);
$username = $_SESSION['mailcow_cc_username'];
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
$_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
try {
if (!is_numeric($id)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row['keys'] == "1") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'last_key'
);
return false;
}
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id");
$stmt->execute(array(':username' => $username, ':id' => $id));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
}
function get_tfa($username = null) {
global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username'];
}
elseif (empty($username)) {
return false;
}
$stmt = $pdo->prepare("SELECT * 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":
$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;
}
}
function verify_tfa_login($username, $token) {
global $pdo;
global $lang;
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);
switch ($row["authmech"]) {
case "yubi_otp":
if (!ctype_alnum($token) || strlen($token) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'token length error')
);
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',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
else {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_yotp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'unknown')
);
return false;
break;
case "u2f":
try {
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
$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'];
$_SESSION['authReq'] = null;
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_u2f_login'
);
return true;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', 'unknown')
);
return false;
break;
case "hotp":
return false;
break;
case "totp":
try {
$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'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_totp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'totp_verification_failed'
);
return false;
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('mysql_error', $e)
);
return false;
}
break;
default:
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'unknown_tfa_method'
);
return false;
break;
}
return false;
}
function admin_api($access, $action, $data = null) {
global $pdo;
global $lang;
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
return false;
}
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;
}
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;
}
}
$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;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $data),
'msg' => 'admin_api_modified'
);
}
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") {
$_SESSION['gal']['valid'] = "true";
$_SESSION['gal']['c'] = $json_return['c'];
$_SESSION['gal']['s'] = $json_return['s'];
if ($json_return['m'] == 'NoMoore') {
$_SESSION['gal']['m'] = '🐄';
}
else {
$_SESSION['gal']['m'] = str_repeat('🐄', substr_count($json_return['m'], 'o'));
}
}
elseif ($json_return['response'] === "invalid") {
$_SESSION['gal']['valid'] = "false";
$_SESSION['gal']['c'] = $lang['mailbox']['no'];
$_SESSION['gal']['s'] = $lang['mailbox']['no'];
$_SESSION['gal']['m'] = $lang['mailbox']['no'];
}
}
else {
$_SESSION['gal']['valid'] = "false";
$_SESSION['gal']['c'] = $lang['danger']['temp_error'];
$_SESSION['gal']['s'] = $lang['danger']['temp_error'];
$_SESSION['gal']['m'] = $lang['danger']['temp_error'];
}
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)
);
return false;
}
return $_SESSION['gal']['valid'];
break;
case "guid":
$stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'");
$versions = $stmt->fetch(PDO::FETCH_ASSOC);
return $versions['version'];
break;
}
}
function rspamd_ui($action, $data = null) {
global $lang;
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
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)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'password_empty'
);
return false;
}
if ($rspamd_ui_pass != $rspamd_ui_pass2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'password_mismatch'
);
return false;
}
if (strlen($rspamd_ui_pass) < 6) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'rspamd_ui_pw_length'
);
return false;
}
$docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json'));
if ($docker_return_array = json_decode($docker_return, true)) {
if ($docker_return_array['type'] == 'success') {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'rspamd_ui_pw_set'
);
return true;
}
else {
$_SESSION['return'][] = array(
'type' => $docker_return_array['type'],
'log' => array(__FUNCTION__, '*', '*'),
'msg' => $docker_return_array['msg']
);
return false;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'unknown'
);
return false;
}
break;
}
}
function get_u2f_registrations($username) {
global $pdo;
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
$sel->execute(array($username));
return $sel->fetchAll(PDO::FETCH_OBJ);
}
function get_logs($application, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;
}
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; }
}
global $lang;
global $redis;
global $pdo;
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
// SQL
if ($application == "mailcow-ui") {
if (isset($from) && isset($to)) {
$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);
}
else {
$stmt = $pdo->prepare("SELECT * FROM `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;
}
}
// Redis
if ($application == "dovecot-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "postfix-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "sogo-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('SOGO_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "watchdog-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('WATCHDOG_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "acme-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('ACME_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('ACME_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
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") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('API_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('API_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "netfilter-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('NETFILTER_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "autodiscover-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "rspamd-history") {
$curl = curl_init();
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?from=" . intval($from) . "&to=" . intval($to));
}
else {
curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?to=" . intval($lines));
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$history = curl_exec($curl);
if (!curl_errno($curl)) {
$data_array = json_decode($history, true);
curl_close($curl);
return $data_array['rows'];
}
curl_close($curl);
return false;
}
return false;
}
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);
}
function solr_status() {
$curl = curl_init();
$endpoint = 'http://solr:8983/solr/admin/cores';
$params = array(
'action' => 'STATUS',
'core' => 'dovecot-fts',
'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);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
$response_core = curl_exec($curl);
if ($response_core === false) {
$err = curl_error($curl);
curl_close($curl);
return false;
}
else {
curl_close($curl);
$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;
}
return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false;
}
return false;
}
function cleanupJS($ignore = '', $folder = '/tmp/*.js') {
$now = time();
foreach (glob($folder) as $filename) {
if(strpos($filename, $ignore) !== false) {
continue;
}
if (is_file($filename)) {
if ($now - filemtime($filename) >= 60 * 60) {
unlink($filename);
}
}
}
}
function cleanupCSS($ignore = '', $folder = '/tmp/*.css') {
$now = time();
foreach (glob($folder) as $filename) {
if(strpos($filename, $ignore) !== false) {
continue;
}
if (is_file($filename)) {
if ($now - filemtime($filename) >= 60 * 60) {
unlink($filename);
}
}
}
}
?>
diff --git a/data/web/js/site/qhandler.js b/data/web/js/site/qhandler.js
index 5253e6db..c6851fdd 100644
--- a/data/web/js/site/qhandler.js
+++ b/data/web/js/site/qhandler.js
@@ -1,52 +1,52 @@
jQuery(function($){
var qitem = $('legend').data('hash');
var qError = $("#qid_error");
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { hash: qitem },
dataType: 'json',
success: function(data){
if (typeof data.error !== 'undefined') {
qError.text(data.error);
qError.show();
}
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').text(data.score);
$('#qid_detail_symbols').html('');
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1
if (b.score === 0) return -1
if (b.score < 0 && a.score < 0) {
return a.score - b.score
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score
}
return b.score - a.score
})
$.each(data.symbols, function (index, value) {
var highlightClass = ''
if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive'
else highlightClass = 'neutral'
$('#qid_detail_symbols').append('<span data-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
});
$('[data-toggle="tooltip"]').tooltip()
}
$('#qid_detail_recipients').html('');
if (typeof data.recipients !== 'undefined') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
- elem.text(value.address + (value.type != 'to' ? (' (' + value.type.toUpperCase() + ')') : ''));
+ elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
}
});
});
\ No newline at end of file
diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js
index af871dec..a4606645 100644
--- a/data/web/js/site/quarantine.js
+++ b/data/web/js/site/quarantine.js
@@ -1,205 +1,205 @@
// Base64 functions
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
jQuery(function($){
acl_data = JSON.parse(acl);
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
// Set paging
$('[data-page-size]').on('click', function(e){
e.preventDefault();
var new_size = $(this).data('page-size');
var parent_ul = $(this).closest('ul');
var table_id = $(parent_ul).data('table-id');
FooTable.get('#' + table_id).pageSize(new_size);
//$(this).parent().addClass('active').siblings().removeClass('active')
heading = $(this).parents('.panel').find('.panel-heading')
var n_results = $(heading).children('.table-lines').text().split(' / ')[1];
$(heading).children('.table-lines').text(function(){
if (new_size > n_results) {
new_size = n_results;
}
return new_size + ' / ' + n_results;
})
});
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).find("tr.footable-empty").remove();
draw_table = $(this).data('draw');
eval(draw_table + '()');
});
function table_quarantine_ready(ft, name) {
$('.refresh_table').prop("disabled", false);
heading = ft.$el.parents('.panel').find('.panel-heading')
var ft_paging = ft.use(FooTable.Paging)
$(heading).children('.table-lines').text(function(){
var total_rows = ft_paging.totalRows;
var size = ft_paging.size;
if (size > total_rows) {
size = total_rows;
}
return size + ' / ' + total_rows;
})
}
function draw_quarantine_table() {
ft_quarantinetable = FooTable.init('#quarantinetable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","type":"ID","filterable": false,"sorted": true,"direction":"DESC","title":"ID","style":{"width":"50px"}},
{"name":"qid","breakpoints":"all","type":"text","title":lang.qid,"style":{"width":"125px"}},
{"name":"sender","title":lang.sender},
{"name":"subject","title":lang.subj, "type": "text"},
{"name":"rcpt","title":lang.rcpt, "breakpoints":"xs sm md", "type": "text"},
{"name":"virus","title":lang.danger, "type": "text"},
{"name":"score","title": lang.spam_score, "type": "text"},
{"name":"notified","title":lang.notified, "type": "text"},
{"name":"created","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.received,"style":{"width":"170px"}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right"},"style":{"min-width":"200px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/quarantine/all',
jsonp: false,
error: function () {
console.log('Cannot draw quarantine table');
},
success: function (data) {
$.each(data, function (i, item) {
if (item.subject === null) {
item.subject = '';
} else {
item.subject = escapeHtml(item.subject);
}
if (item.score === null) {
item.score = '-';
}
if (item.virus_flag > 0) {
item.virus = '<span class="dot-danger"></span>';
} else {
item.virus = '<span class="dot-neutral"></span>';
}
if(item.notified > 0) {
item.notified = '&#10004;';
} else {
item.notified = '&#10006;';
}
if (acl_data.login_as === 1) {
item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' +
'<a href="#" data-action="delete_selected" data-id="del-single-qitem" data-api-url="delete/qitem" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
}
else {
item.action = '<div class="btn-group">' +
'<a href="#" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-info show_qid_info"><span class="glyphicon glyphicon-modal-window"></span> ' + lang.show_item + '</a>' +
'</div>';
}
item.chkbox = '<input type="checkbox" data-id="qitems" name="multi_select" value="' + item.id + '" />';
});
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": pagination_size},
"sorting": {"enabled": true},
"filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
"toggleSelector": "table tbody span.footable-toggle",
"on": {
"destroy.ft.table": function(e, ft){
$('.refresh_table').attr('disabled', 'true');
},
"ready.ft.table": function(e, ft){
table_quarantine_ready(ft, 'quarantinetable');
},
"after.ft.filtering": function(e, ft){
table_quarantine_ready(ft, 'quarantinetable');
}
},
});
}
$('body').on('click', '.show_qid_info', function (e) {
e.preventDefault();
var qitem = $(this).data('item');
var qError = $("#qid_error");
$('#qidDetailModal').modal('show');
qError.hide();
$.ajax({
url: '/inc/ajax/qitem_details.php',
data: { id: qitem },
dataType: 'json',
success: function(data){
if (typeof data.error !== 'undefined') {
qError.text(data.error);
qError.show();
}
$('[data-id="qitems_single"]').each(function(index) {
$(this).attr("data-item", qitem);
});
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_text').text(data.text_plain);
$('#qid_detail_text_from_html').text(data.text_html);
$('#qid_detail_hfrom').text(data.header_from);
$('#qid_detail_efrom').text(data.env_from);
$('#qid_detail_score').text(data.score);
$('#qid_detail_symbols').html('');
if (typeof data.symbols !== 'undefined') {
data.symbols.sort(function (a, b) {
if (a.score === 0) return 1
if (b.score === 0) return -1
if (b.score < 0 && a.score < 0) {
return a.score - b.score
}
if (b.score > 0 && a.score > 0) {
return b.score - a.score
}
return b.score - a.score
})
$.each(data.symbols, function (index, value) {
var highlightClass = ''
if (value.score > 0) highlightClass = 'negative'
else if (value.score < 0) highlightClass = 'positive'
else highlightClass = 'neutral'
$('#qid_detail_symbols').append('<span data-toggle="tooltip" class="rspamd-symbol ' + highlightClass + '" title="' + (value.options ? value.options.join(', ') : '') + '">' + value.name + ' (<span class="score">' + value.score + '</span>)</span>');
});
$('[data-toggle="tooltip"]').tooltip()
}
$('#qid_detail_recipients').html('');
if (typeof data.recipients !== 'undefined') {
$.each(data.recipients, function(index, value) {
var elem = $('<span class="mail-address-item"></span>');
- elem.text(value.address + (value.type != 'to' ? (' (' + value.type.toUpperCase() + ')') : ''));
+ elem.text(value.address + ' (' + value.type.toUpperCase() + ')');
$('#qid_detail_recipients').append(elem);
});
}
var qAtts = $("#qid_detail_atts");
if (typeof data.attachments !== 'undefined') {
qAtts.text('');
$.each(data.attachments, function(index, value) {
qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}
else {
qAtts.text('-');
}
}
});
});
$('body').on('click', 'span.footable-toggle', function () {
event.stopPropagation();
})
// Initial table drawings
draw_quarantine_table();
});

File Metadata

Mime Type
text/x-diff
Expires
9月 12 Fri, 3:00 AM (20 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5730
默认替代文本
(87 KB)

Event Timeline