diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py index a681c1fda..5757cdf53 100755 --- a/data/Dockerfiles/dovecot/quarantine_notify.py +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -47,7 +47,7 @@ try: if max_score == "": max_score = 9999.0 - def query_mysql(query, headers = True, update = False): + def query_mysql(query, params = None, headers = True, update = False): while True: try: cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci") @@ -57,7 +57,10 @@ try: else: break cur = cnx.cursor() - cur.execute(query) + if params: + cur.execute(query, params) + else: + cur.execute(query) if not update: result = [] columns = tuple( [d[0] for d in cur.description] ) @@ -76,7 +79,7 @@ try: def notify_rcpt(rcpt, msg_count, quarantine_acl, category): if category == "add_header": category = "add header" - meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category)) + meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = %s AND score < %s AND (action = %s OR "all" = %s)', (rcpt, max_score, category, category)) print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count)) if len(meta_query) == 0: return @@ -130,7 +133,7 @@ try: server.sendmail(msg['From'], [str(redirect)] + [str(bcc)], text) server.quit() for res in meta_query: - query_mysql('UPDATE quarantine SET notified = 1 WHERE id = "%d"' % (res['id']), update = True) + query_mysql('UPDATE quarantine SET notified = 1 WHERE id = %s', (res['id'],), update = True) r.hset('Q_LAST_NOTIFIED', record['rcpt'], time_now) break except Exception as ex: @@ -138,7 +141,7 @@ try: print('%s' % (ex)) time.sleep(3) - records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %f AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt' % (max_score)) + records = query_mysql('SELECT IFNULL(user_acl.quarantine, 0) AS quarantine_acl, count(id) AS counter, rcpt FROM quarantine LEFT OUTER JOIN user_acl ON user_acl.username = rcpt WHERE notified = 0 AND score < %s AND rcpt in (SELECT username FROM mailbox) GROUP BY rcpt', (max_score,)) for record in records: attrs = '' @@ -156,7 +159,7 @@ try: except Exception as ex: print('Could not determine last notification for %s, assuming never' % (record['rcpt'])) last_notification = 0 - attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = "%s"' % (record['rcpt'])) + attrs_json = query_mysql('SELECT attributes FROM mailbox WHERE username = %s', (record['rcpt'],)) attrs = attrs_json[0]['attributes'] if isinstance(attrs, str): # if attr is str then just load it diff --git a/data/web/inc/functions.fwdhost.inc.php b/data/web/inc/functions.fwdhost.inc.php index d7ac2567c..52c751591 100644 --- a/data/web/inc/functions.fwdhost.inc.php +++ b/data/web/inc/functions.fwdhost.inc.php @@ -108,6 +108,14 @@ function fwdhost($_action, $_data = null) { } break; case 'delete': + if ($_SESSION['mailcow_cc_role'] != "admin") { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_data_log), + 'msg' => 'access_denied' + ); + return false; + } $hosts = (array)$_data['forwardinghost']; foreach ($hosts as $host) { try { diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 9506bbf1f..adb330ea8 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -1111,6 +1111,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : 0; $quarantine_notification = (isset($_data['quarantine_notification'])) ? strval($_data['quarantine_notification']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); $quarantine_category = (isset($_data['quarantine_category'])) ? strval($_data['quarantine_category']) : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + // Validate quarantine_category + if (!in_array($quarantine_category, array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'quarantine_category_invalid' + ); + return false; + } $quota_b = ($quota_m * 1048576); $attribute_hash = (!empty($_data['attribute_hash'])) ? $_data['attribute_hash'] : ''; if (in_array($authsource, array('keycloak', 'generic-oidc', 'ldap'))){ @@ -1733,6 +1742,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']); $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']); $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']); + // Validate quarantine_category + if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'quarantine_category_invalid' + ); + return false; + } $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s"; $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : ""; $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); @@ -2062,6 +2080,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } foreach ($usernames as $username) { + if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => 'access_denied' + ); + continue; + } if ($_data['spam_score'] == "default") { $stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')"); @@ -3790,6 +3816,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr["tagged_mail_handler"] = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler']; $attr["quarantine_notification"] = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification']; $attr["quarantine_category"] = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category']; + // Validate quarantine_category + if (!in_array($attr["quarantine_category"], array('add_header', 'reject', 'all'))) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_extra), + 'msg' => 'quarantine_category_invalid' + ); + continue; + } $attr["rl_frame"] = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame']; $attr["rl_value"] = (!empty($_data['rl_value'])) ? $_data['rl_value'] : $is_now['rl_value']; $attr["force_pw_update"] = isset($_data['force_pw_update']) ? intval($_data['force_pw_update']) : $is_now['force_pw_update']; diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index d2ce6f3d0..e5737b63c 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -89,7 +89,7 @@ $globalVariables = [ 'app_links' => $app_links, 'app_links_processed' => $app_links_processed, 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), - 'uri' => $_SERVER['REQUEST_URI'], + 'uri' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?: '/', ]; foreach ($globalVariables as $globalVariableName => $globalVariableValue) { diff --git a/data/web/inc/twig.inc.php b/data/web/inc/twig.inc.php index a3bc02d92..c4d0669d9 100644 --- a/data/web/inc/twig.inc.php +++ b/data/web/inc/twig.inc.php @@ -13,7 +13,9 @@ $twig = new Environment($loader, [ // functions $twig->addFunction(new TwigFunction('query_string', function (array $params = []) { - return http_build_query(array_merge($_GET, $params)); + $allowed = ['lang', 'mobileconfig']; + $filtered = array_intersect_key($_GET, array_flip($allowed)); + return http_build_query(array_merge($filtered, $params)); })); $twig->addFunction(new TwigFunction('is_uri', function (string $uri, string $where = null) { diff --git a/data/web/js/site/dashboard.js b/data/web/js/site/dashboard.js index 760e878f1..aee361710 100644 --- a/data/web/js/site/dashboard.js +++ b/data/web/js/site/dashboard.js @@ -1128,6 +1128,11 @@ jQuery(function($){ item.ua = escapeHtml(item.ua); } item.ua = '' + item.ua + ''; + if (item.user == null) { + item.user = 'unknown'; + } else { + item.user = escapeHtml(item.user); + } if (item.service == "activesync") { item.service = 'ActiveSync'; } diff --git a/data/web/js/site/quarantine.js b/data/web/js/site/quarantine.js index fbf4fe862..8334ff504 100644 --- a/data/web/js/site/quarantine.js +++ b/data/web/js/site/quarantine.js @@ -226,18 +226,18 @@ jQuery(function($){ } if (typeof data.fuzzy_hashes === 'object' && data.fuzzy_hashes !== null && data.fuzzy_hashes.length !== 0) { $.each(data.fuzzy_hashes, function (index, value) { - $('#qid_detail_fuzzy').append('
' + value + '
'); + $('#qid_detail_fuzzy').append('' + escapeHtml(value) + '
'); }); } else { $('#qid_detail_fuzzy').append('-'); } if (typeof data.score !== 'undefined' && typeof data.action !== 'undefined') { if (data.action == "add header") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.junk_folder + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.junk_folder + ''); } else if (data.action == "reject") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.rejected + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.rejected + ''); } else if (data.action == "rewrite subject") { - $('#qid_detail_score').append('' + data.score + ' - ' + lang.rewrite_subject + ''); + $('#qid_detail_score').append('' + escapeHtml(data.score) + ' - ' + lang.rewrite_subject + ''); } } if (typeof data.recipients !== 'undefined') { @@ -254,8 +254,8 @@ jQuery(function($){ qAtts.text(''); $.each(data.attachments, function(index, value) { qAtts.append( - '' + value[0] + ' (' + value[1] + ')' + - ' - ' + lang.check_hash + '
' + '' + escapeHtml(value[0]) + ' (' + escapeHtml(value[1]) + ')' + + ' - ' + lang.check_hash + '
' ); }); } diff --git a/data/web/js/site/user.js b/data/web/js/site/user.js index 5eecf2080..288e7abd2 100644 --- a/data/web/js/site/user.js +++ b/data/web/js/site/user.js @@ -98,8 +98,8 @@ jQuery(function($){ var local_datetime = datetime.toLocaleDateString(undefined, {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}); var service = '