From 3e194c79060b84d04ce0b2c577ea4b68c7d8d732 Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Sat, 11 Nov 2023 17:12:00 +0100 Subject: [PATCH 001/793] Domains datatable - server side processing Signed-off-by: Kristian Feldsam --- data/web/inc/lib/ssp.class.php | 587 +++++++++++++++++++++++++++++++++ data/web/js/site/mailbox.js | 33 +- data/web/json_api.php | 65 +++- 3 files changed, 666 insertions(+), 19 deletions(-) create mode 100644 data/web/inc/lib/ssp.class.php diff --git a/data/web/inc/lib/ssp.class.php b/data/web/inc/lib/ssp.class.php new file mode 100644 index 000000000..c36c627cb --- /dev/null +++ b/data/web/inc/lib/ssp.class.php @@ -0,0 +1,587 @@ + 'utf8'` - you might need this depending on your PHP / MySQL config + * @return resource PDO connection + */ + static function db ( $conn ) + { + if ( is_array( $conn ) ) { + return self::sql_connect( $conn ); + } + + return $conn; + } + + + /** + * Paging + * + * Construct the LIMIT clause for server-side processing SQL query + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @return string SQL limit clause + */ + static function limit ( $request, $columns ) + { + $limit = ''; + + if ( isset($request['start']) && $request['length'] != -1 ) { + $limit = "LIMIT ".intval($request['start']).", ".intval($request['length']); + } + + return $limit; + } + + + /** + * Ordering + * + * Construct the ORDER BY clause for server-side processing SQL query + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @return string SQL order by clause + */ + static function order ( $tableAS, $request, $columns ) + { + $order = ''; + + if ( isset($request['order']) && count($request['order']) ) { + $orderBy = array(); + $dtColumns = self::pluck( $columns, 'dt' ); + + for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) { + // Convert the column index into the column data property + $columnIdx = intval($request['order'][$i]['column']); + $requestColumn = $request['columns'][$columnIdx]; + + $columnIdx = array_search( $columnIdx, $dtColumns ); + $column = $columns[ $columnIdx ]; + + if ( $requestColumn['orderable'] == 'true' ) { + $dir = $request['order'][$i]['dir'] === 'asc' ? + 'ASC' : + 'DESC'; + + $orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir; + } + } + + if ( count( $orderBy ) ) { + $order = 'ORDER BY '.implode(', ', $orderBy); + } + } + + return $order; + } + + + /** + * Searching / Filtering + * + * Construct the WHERE clause for server-side processing SQL query. + * + * NOTE this does not match the built-in DataTables filtering which does it + * word by word on any field. It's possible to do here performance on large + * databases would be very poor + * + * @param array $request Data sent to server by DataTables + * @param array $columns Column information array + * @param array $bindings Array of values for PDO bindings, used in the + * sql_exec() function + * @return string SQL where clause + */ + static function filter ( $tablesAS, $request, $columns, &$bindings ) + { + $globalSearch = array(); + $columnSearch = array(); + $dtColumns = self::pluck( $columns, 'dt' ); + + if ( isset($request['search']) && $request['search']['value'] != '' ) { + $str = $request['search']['value']; + + for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) { + $requestColumn = $request['columns'][$i]; + $columnIdx = array_search( $requestColumn['data'], $dtColumns ); + $column = $columns[ $columnIdx ]; + + if ( $requestColumn['searchable'] == 'true' ) { + if(!empty($column['db'])){ + $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); + $globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + } + } + } + } + + // Individual column filtering + if ( isset( $request['columns'] ) ) { + for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) { + $requestColumn = $request['columns'][$i]; + $columnIdx = array_search( $requestColumn['data'], $dtColumns ); + $column = $columns[ $columnIdx ]; + + $str = $requestColumn['search']['value']; + + if ( $requestColumn['searchable'] == 'true' && + $str != '' ) { + if(!empty($column['db'])){ + $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); + $columnSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + } + } + } + } + + // Combine the filters into a single string + $where = ''; + + if ( count( $globalSearch ) ) { + $where = '('.implode(' OR ', $globalSearch).')'; + } + + if ( count( $columnSearch ) ) { + $where = $where === '' ? + implode(' AND ', $columnSearch) : + $where .' AND '. implode(' AND ', $columnSearch); + } + + if ( $where !== '' ) { + $where = 'WHERE '.$where; + } + + return $where; + } + + + /** + * Perform the SQL queries needed for an server-side processing requested, + * utilising the helper functions of this class, limit(), order() and + * filter() among others. The returned array is ready to be encoded as JSON + * in response to an SSP request, or can be modified if needed before + * sending back to the client. + * + * @param array $request Data sent to server by DataTables + * @param array|PDO $conn PDO connection resource or connection parameters array + * @param string $table SQL table to query + * @param string $primaryKey Primary key of the table + * @param array $columns Column information array + * @return array Server-side processing response array + */ + static function simple ( $request, $conn, $table, $primaryKey, $columns ) + { + $bindings = array(); + $db = self::db( $conn ); + + // Allow for a JSON string to be passed in + if (isset($request['json'])) { + $request = json_decode($request['json'], true); + } + + // table AS + $tablesAS = null; + if(is_array($table)) { + $tablesAS = $table[1]; + $table = $table[0]; + } + + // Build the SQL query string from the request + $limit = self::limit( $request, $columns ); + $order = self::order( $tablesAS, $request, $columns ); + $where = self::filter( $tablesAS, $request, $columns, $bindings ); + + // Main query to actually get the data + $data = self::sql_exec( $db, $bindings, + "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + FROM `$table` AS `$tablesAS` + $where + $order + $limit" + ); + + // Data set length after filtering + $resFilterLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $where" + ); + $recordsFiltered = $resFilterLength[0][0]; + + // Total data set length + $resTotalLength = self::sql_exec( $db, + "SELECT COUNT(`{$primaryKey}`) + FROM `$table` AS `$tablesAS`" + ); + $recordsTotal = $resTotalLength[0][0]; + + /* + * Output + */ + return array( + "draw" => isset ( $request['draw'] ) ? + intval( $request['draw'] ) : + 0, + "recordsTotal" => intval( $recordsTotal ), + "recordsFiltered" => intval( $recordsFiltered ), + "data" => self::data_output( $columns, $data ) + ); + } + + + /** + * The difference between this method and the `simple` one, is that you can + * apply additional `where` conditions to the SQL queries. These can be in + * one of two forms: + * + * * 'Result condition' - This is applied to the result set, but not the + * overall paging information query - i.e. it will not effect the number + * of records that a user sees they can have access to. This should be + * used when you want apply a filtering condition that the user has sent. + * * 'All condition' - This is applied to all queries that are made and + * reduces the number of records that the user can access. This should be + * used in conditions where you don't want the user to ever have access to + * particular records (for example, restricting by a login id). + * + * In both cases the extra condition can be added as a simple string, or if + * you are using external values, as an assoc. array with `condition` and + * `bindings` parameters. The `condition` is a string with the SQL WHERE + * condition and `bindings` is an assoc. array of the binding names and + * values. + * + * @param array $request Data sent to server by DataTables + * @param array|PDO $conn PDO connection resource or connection parameters array + * @param string|array $table SQL table to query, if array second key is AS + * @param string $primaryKey Primary key of the table + * @param array $columns Column information array + * @param string $join JOIN sql string + * @param string|array $whereResult WHERE condition to apply to the result set + * @return array Server-side processing response array + */ + static function complex ( + $request, + $conn, + $table, + $primaryKey, + $columns, + $join=null, + $whereResult=null + ) { + $bindings = array(); + $db = self::db( $conn ); + + // table AS + $tablesAS = null; + if(is_array($table)) { + $tablesAS = $table[1]; + $table = $table[0]; + } + + // Build the SQL query string from the request + $limit = self::limit( $request, $columns ); + $order = self::order( $tablesAS, $request, $columns ); + $where = self::filter( $tablesAS, $request, $columns, $bindings ); + + // whereResult can be a simple string, or an assoc. array with a + // condition and bindings + if ( $whereResult ) { + $str = $whereResult; + + if ( is_array($whereResult) ) { + $str = $whereResult['condition']; + + if ( isset($whereResult['bindings']) ) { + self::add_bindings($bindings, $whereResult); + } + } + + $where = $where ? + $where .' AND '.$str : + 'WHERE '.$str; + } + + // Main query to actually get the data + $data = self::sql_exec( $db, $bindings, + "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + FROM `$table` AS `$tablesAS` + $join + $where + $order + $limit" + ); + + // Data set length after filtering + $resFilterLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $join + $where" + ); + $recordsFiltered = $resFilterLength[0][0]; + + // Total data set length + $resTotalLength = self::sql_exec( $db, $bindings, + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + FROM `$table` AS `$tablesAS` + $join + $where" + ); + $recordsTotal = $resTotalLength[0][0]; + + /* + * Output + */ + return array( + "draw" => isset ( $request['draw'] ) ? + intval( $request['draw'] ) : + 0, + "recordsTotal" => intval( $recordsTotal ), + "recordsFiltered" => intval( $recordsFiltered ), + "data" => self::data_output( $columns, $data ) + ); + } + + + /** + * Connect to the database + * + * @param array $sql_details SQL server connection details array, with the + * properties: + * * host - host name + * * db - database name + * * user - user name + * * pass - user password + * @return resource Database connection handle + */ + static function sql_connect ( $sql_details ) + { + try { + $db = @new PDO( + "mysql:host={$sql_details['host']};dbname={$sql_details['db']}", + $sql_details['user'], + $sql_details['pass'], + array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ) + ); + } + catch (PDOException $e) { + self::fatal( + "An error occurred while connecting to the database. ". + "The error reported by the server was: ".$e->getMessage() + ); + } + + return $db; + } + + + /** + * Execute an SQL query on the database + * + * @param resource $db Database handler + * @param array $bindings Array of PDO binding values from bind() to be + * used for safely escaping strings. Note that this can be given as the + * SQL query string if no bindings are required. + * @param string $sql SQL query to execute. + * @return array Result from the query (all rows) + */ + static function sql_exec ( $db, $bindings, $sql=null ) + { + // Argument shifting + if ( $sql === null ) { + $sql = $bindings; + } + + $stmt = $db->prepare( $sql ); + + // Bind parameters + if ( is_array( $bindings ) ) { + for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) { + $binding = $bindings[$i]; + $stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] ); + } + } + + // Execute + try { + $stmt->execute(); + } + catch (PDOException $e) { + self::fatal( "An SQL error occurred: ".$e->getMessage() ); + } + + // Return all + return $stmt->fetchAll( PDO::FETCH_BOTH ); + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Internal methods + */ + + /** + * Throw a fatal error. + * + * This writes out an error message in a JSON string which DataTables will + * see and show to the user in the browser. + * + * @param string $msg Message to send to the client + */ + static function fatal ( $msg ) + { + echo json_encode( array( + "error" => $msg + ) ); + + exit(0); + } + + /** + * Create a PDO binding key which can be used for escaping variables safely + * when executing a query with sql_exec() + * + * @param array &$a Array of bindings + * @param * $val Value to bind + * @param int $type PDO field type + * @return string Bound key to be used in the SQL where this parameter + * would be used. + */ + static function bind ( &$a, $val, $type ) + { + $key = ':binding_'.count( $a ); + + $a[] = array( + 'key' => $key, + 'val' => $val, + 'type' => $type + ); + + return $key; + } + + static function add_bindings(&$bindings, $vals) + { + foreach($vals['bindings'] as $key => $value) { + $bindings[] = array( + 'key' => $key, + 'val' => $value, + 'type' => PDO::PARAM_STR + ); + } + } + + + /** + * Pull a particular property from each assoc. array in a numeric array, + * returning and array of the property values from each item. + * + * @param array $a Array to get data from + * @param string $prop Property to read + * @return array Array of property values + */ + static function pluck ( $a, $prop ) + { + $out = array(); + + for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) { + if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) { + continue; + } + + //removing the $out array index confuses the filter method in doing proper binding, + //adding it ensures that the array data are mapped correctly + $out[$i] = $a[$i][$prop]; + } + + return $out; + } + + + /** + * Return a string from an array or a string + * + * @param array|string $a Array to join + * @param string $join Glue for the concatenation + * @return string Joined string + */ + static function _flatten ( $a, $join = ' AND ' ) + { + if ( ! $a ) { + return ''; + } + else if ( $a && is_array($a) ) { + return implode( $join, $a ); + } + return $a; + } +} + diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index c2b1761de..5e1ba3151 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -435,7 +435,7 @@ jQuery(function($){ var table = $('#domain_table').DataTable({ responsive: true, processing: true, - serverSide: false, + serverSide: true, stateSave: true, pageLength: pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + @@ -447,9 +447,9 @@ jQuery(function($){ }, ajax: { type: "GET", - url: "/api/v1/get/domain/all", + url: "/api/v1/get/domain/datatables", dataSrc: function(json){ - $.each(json, function(i, item) { + $.each(json.data, function(i, item) { item.domain_name = escapeHtml(item.domain_name); item.aliases = item.aliases_in_domain + " / " + item.max_num_aliases_for_domain; @@ -498,7 +498,7 @@ jQuery(function($){ } }); - return json; + return json.data; } }, columns: [ @@ -528,17 +528,20 @@ jQuery(function($){ { title: lang.aliases, data: 'aliases', + searchable: false, defaultContent: '' }, { title: lang.mailboxes, data: 'mailboxes', + searchable: false, responsivePriority: 4, defaultContent: '' }, { title: lang.domain_quota, data: 'quota', + searchable: false, defaultContent: '', render: function (data, type) { data = data.split("/"); @@ -548,6 +551,8 @@ jQuery(function($){ { title: lang.stats, data: 'stats', + searchable: false, + orderable: false, defaultContent: '', render: function (data, type) { data = data.split("/"); @@ -557,21 +562,29 @@ jQuery(function($){ { title: lang.mailbox_defquota, data: 'def_quota_for_mbox', + searchable: false, + orderable: false, defaultContent: '' }, { title: lang.mailbox_quota, data: 'max_quota_for_mbox', + searchable: false, + orderable: false, defaultContent: '' }, { title: 'RL', data: 'rl', + searchable: false, + orderable: false, defaultContent: '' }, { title: lang.backup_mx, data: 'backupmx', + searchable: false, + orderable: false, defaultContent: '', redner: function (data, type){ return 1==value ? '' : 0==value && ''; @@ -580,30 +593,40 @@ jQuery(function($){ { title: lang.domain_admins, data: 'domain_admins', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.created_on, data: 'created', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.last_modified, data: 'modified', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: 'Tags', data: 'tags', + searchable: false, + orderable: false, defaultContent: '', className: 'none' }, { title: lang.active, data: 'active', + searchable: false, + orderable: false, defaultContent: '', responsivePriority: 6, render: function (data, type) { @@ -613,6 +636,8 @@ jQuery(function($){ { title: lang.action, data: 'action', + searchable: false, + orderable: false, className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right', responsivePriority: 5, defaultContent: '' diff --git a/data/web/json_api.php b/data/web/json_api.php index b375bc8e4..6c008a604 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -15,7 +15,7 @@ function api_log($_data) { continue; } - $value = json_decode($value, true); + $value = json_decode($value, true); if ($value) { if (is_array($value)) unset($value["csrf_token"]); foreach ($value as $key => &$val) { @@ -23,7 +23,7 @@ function api_log($_data) { $val = '*'; } } - $value = json_encode($value); + $value = json_encode($value); } $data_var[] = $data . "='" . $value . "'"; } @@ -44,7 +44,7 @@ function api_log($_data) { 'msg' => 'Redis: '.$e ); return false; - } + } } if (isset($_GET['query'])) { @@ -178,12 +178,12 @@ if (isset($_GET['query'])) { // parse post data $post = trim(file_get_contents('php://input')); if ($post) $post = json_decode($post); - + // process registration data from authenticator try { // decode base64 strings $clientDataJSON = base64_decode($post->clientDataJSON); - $attestationObject = base64_decode($post->attestationObject); + $attestationObject = base64_decode($post->attestationObject); // processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true) $data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true); @@ -250,7 +250,7 @@ if (isset($_GET['query'])) { default: process_add_return(mailbox('add', 'domain', $attr)); break; - } + } break; case "resource": process_add_return(mailbox('add', 'resource', $attr)); @@ -470,7 +470,7 @@ if (isset($_GET['query'])) { // false, if only internal is allowed // null, if internal and cross-platform is allowed $createArgs = $WebAuthn->getCreateArgs($_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], $_SESSION["mailcow_cc_username"], 30, false, $GLOBALS['WEBAUTHN_UV_FLAG_REGISTER'], null, $excludeCredentialIds); - + print(json_encode($createArgs)); $_SESSION['challenge'] = $WebAuthn->getChallenge(); return; @@ -523,9 +523,44 @@ if (isset($_GET['query'])) { case "domain": switch ($object) { + case "datatables": + $table = ['domain', 'd']; + $primaryKey = 'domain'; + $columns = [ + ['db' => 'domain', 'dt' => 2], + ['db' => 'aliases', 'dt' => 3], + ['db' => 'mailboxes', 'dt' => 4], + ['db' => 'quota', 'dt' => 5], + ]; + + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; + global $pdo; + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::simple($_GET, $pdo, $table, $primaryKey, $columns); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = d.domain', + [ + 'condition' => 'da.active = 1 and da.username = :username', + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } + + if (!empty($data['data'])) { + $domainsData = []; + foreach ($data['data'] as $domain) { + if ($details = mailbox('get', 'domain_details', $domain[2])) { + $domainsData[] = $details; + } + } + $data['data'] = $domainsData; + } + + process_get_return($data); + break; case "all": $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); $domains = mailbox('get', 'domains', null, $tags); @@ -1014,7 +1049,7 @@ if (isset($_GET['query'])) { case "all": case "reduced": $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); if (empty($extra)) $domains = mailbox('get', 'domains'); @@ -1048,7 +1083,7 @@ if (isset($_GET['query'])) { break; default: $tags = null; - if (isset($_GET['tags']) && $_GET['tags'] != '') + if (isset($_GET['tags']) && $_GET['tags'] != '') $tags = explode(',', $_GET['tags']); if ($tags === null) { @@ -1058,7 +1093,7 @@ if (isset($_GET['query'])) { $mailboxes = mailbox('get', 'mailboxes', $object, $tags); if (is_array($mailboxes)) { foreach ($mailboxes as $mailbox) { - if ($details = mailbox('get', 'mailbox_details', $mailbox)) + if ($details = mailbox('get', 'mailbox_details', $mailbox)) $data[] = $details; } } @@ -1557,15 +1592,15 @@ if (isset($_GET['query'])) { 'solr_size' => $solr_size, 'solr_documents' => $solr_documents )); - break; + break; case "host": if (!$extra){ $stats = docker("host_stats"); echo json_encode($stats); - } + } else if ($extra == "ip") { // get public ips - + $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST, 0); @@ -1972,7 +2007,7 @@ if (isset($_GET['query'])) { exit(); } } -if ($_SESSION['mailcow_cc_api'] === true) { +if (array_key_exists('mailcow_cc_api', $_SESSION) && $_SESSION['mailcow_cc_api'] === true) { if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { unset($_SESSION['return']); } From 28cec9969931de92b128c086b1f113c35e600210 Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Sun, 12 Nov 2023 10:26:38 +0100 Subject: [PATCH 002/793] Mailboxes datatable - server side processing Signed-off-by: Kristian Feldsam --- data/web/js/site/mailbox.js | 39 ++++++++++++++++++------------------- data/web/json_api.php | 33 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index 5e1ba3151..fe6113a8f 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -869,7 +869,7 @@ jQuery(function($){ var table = $('#mailbox_table').DataTable({ responsive: true, processing: true, - serverSide: false, + serverSide: true, stateSave: true, pageLength: pagination_size, dom: "<'row'<'col-sm-12 col-md-6'f><'col-sm-12 col-md-6'l>>" + @@ -878,13 +878,12 @@ jQuery(function($){ language: lang_datatables, initComplete: function(settings, json){ hideTableExpandCollapseBtn('#tab-mailboxes', '#mailbox_table'); - filterByDomain(json, 8, table); }, ajax: { type: "GET", - url: "/api/v1/get/mailbox/reduced", + url: "/api/v1/get/mailbox/datatables", dataSrc: function(json){ - $.each(json, function (i, item) { + $.each(json.data, function (i, item) { item.quota = { sortBy: item.quota_used, value: item.quota @@ -970,7 +969,7 @@ jQuery(function($){ } }); - return json; + return json.data; } }, columns: [ @@ -1000,13 +999,15 @@ jQuery(function($){ { title: lang.domain_quota, data: 'quota.value', + searchable: false, responsivePriority: 8, - defaultContent: '', - orderData: 23 + defaultContent: '' }, { title: lang.last_mail_login, data: 'last_mail_login', + searchable: false, + orderable: false, defaultContent: '', responsivePriority: 7, render: function (data, type) { @@ -1019,11 +1020,15 @@ jQuery(function($){ { title: lang.last_pw_change, data: 'last_pw_change', + searchable: false, + orderable: false, defaultContent: '' }, { title: lang.in_use, data: 'in_use.value', + searchable: false, + orderable: false, defaultContent: '', responsivePriority: 9, className: 'dt-data-w100', @@ -1092,6 +1097,8 @@ jQuery(function($){ { title: lang.msg_num, data: 'messages', + searchable: false, + orderable: false, defaultContent: '', responsivePriority: 5 }, @@ -1116,6 +1123,8 @@ jQuery(function($){ { title: lang.active, data: 'active', + searchable: false, + orderable: false, defaultContent: '', responsivePriority: 4, render: function (data, type) { @@ -1125,22 +1134,12 @@ jQuery(function($){ { title: lang.action, data: 'action', + searchable: false, + orderable: false, className: 'dt-sm-head-hidden dt-data-w100 dtr-col-md dt-text-right', responsivePriority: 6, defaultContent: '' - }, - { - title: "", - data: 'quota.sortBy', - defaultContent: '', - className: "d-none" - }, - { - title: "", - data: 'in_use.sortBy', - defaultContent: '', - className: "d-none" - }, + } ] }); diff --git a/data/web/json_api.php b/data/web/json_api.php index 6c008a604..3077bf7b0 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1046,6 +1046,39 @@ if (isset($_GET['query'])) { break; case "mailbox": switch ($object) { + case "datatables": + $table = ['mailbox', 'm']; + $primaryKey = 'username'; + $columns = [ + ['db' => 'username', 'dt' => 2], + ['db' => 'quota', 'dt' => 3], + ]; + + require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; + global $pdo; + if($_SESSION['mailcow_cc_role'] === 'admin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, null, "(`m`.`kind` = '' OR `m`.`kind` = NULL)"); + } elseif ($_SESSION['mailcow_cc_role'] === 'domainadmin') { + $data = SSP::complex($_GET, $pdo, $table, $primaryKey, $columns, + 'INNER JOIN domain_admins as da ON da.domain = m.domain', + [ + 'condition' => "(`m`.`kind` = '' OR `m`.`kind` = NULL) AND `da`.`active` = 1 AND `da`.`username` = :username", + 'bindings' => ['username' => $_SESSION['mailcow_cc_username']] + ]); + } + + if (!empty($data['data'])) { + $mailboxData = []; + foreach ($data['data'] as $mailbox) { + if ($details = mailbox('get', 'mailbox_details', $mailbox[2])) { + $mailboxData[] = $details; + } + } + $data['data'] = $mailboxData; + } + + process_get_return($data); + break; case "all": case "reduced": $tags = null; From 4f109c1a9401582264353dc7ac9aa59fb0243ef0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 Nov 2023 17:28:57 +0000 Subject: [PATCH 003/793] Update dependency krakjoe/apcu to v5.1.23 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 490310336..123061a52 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -2,7 +2,7 @@ FROM php:8.2-fpm-alpine3.17 LABEL maintainer "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ -ARG APCU_PECL_VERSION=5.1.22 +ARG APCU_PECL_VERSION=5.1.23 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?.*)$ ARG IMAGICK_PECL_VERSION=3.7.0 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?.*)$ From a8dfa951268169bc9017165e2f6b0b46a30aaf6e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 09:03:02 +0000 Subject: [PATCH 004/793] Update dependency phpredis/phpredis to v6.0.2 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 490310336..88e5c9905 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -10,7 +10,7 @@ ARG MAILPARSE_PECL_VERSION=3.1.6 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?.*)$ ARG MEMCACHED_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ -ARG REDIS_PECL_VERSION=6.0.1 +ARG REDIS_PECL_VERSION=6.0.2 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ ARG COMPOSER_VERSION=2.6.5 From d4dd1e37ce02f264639434e9104a3bf742195c2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 09:03:09 +0000 Subject: [PATCH 005/793] Update dependency tianon/gosu to v1.17 Signed-off-by: milkmaker --- data/Dockerfiles/dovecot/Dockerfile | 2 +- data/Dockerfiles/sogo/Dockerfile | 2 +- data/Dockerfiles/solr/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 2ace9029e..d1413b2ae 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -5,7 +5,7 @@ ARG DEBIAN_FRONTEND=noninteractive # renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced extractVersion=(?.*)$ ARG DOVECOT=2.3.21 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?.*)$ -ARG GOSU_VERSION=1.16 +ARG GOSU_VERSION=1.17 ENV LC_ALL C diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index e8a7410f5..cbc5c93a9 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -4,7 +4,7 @@ LABEL maintainer "The Infrastructure Company GmbH " ARG DEBIAN_FRONTEND=noninteractive ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/ # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ -ARG GOSU_VERSION=1.16 +ARG GOSU_VERSION=1.17 ENV LC_ALL C # Prerequisites diff --git a/data/Dockerfiles/solr/Dockerfile b/data/Dockerfiles/solr/Dockerfile index a6359876b..429133519 100644 --- a/data/Dockerfiles/solr/Dockerfile +++ b/data/Dockerfiles/solr/Dockerfile @@ -3,7 +3,7 @@ FROM solr:7.7-slim USER root # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?.*)$ -ARG GOSU_VERSION=1.16 +ARG GOSU_VERSION=1.17 COPY solr.sh / COPY solr-config-7.7.0.xml / From 4dad0002cd72a6464f6193bd51092293a897078b Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Mon, 4 Dec 2023 13:41:09 +0100 Subject: [PATCH 006/793] Domains datatable - server side processing ordering Signed-off-by: Kristian Feldsam --- data/web/inc/functions.mailbox.inc.php | 1 - data/web/inc/lib/ssp.class.php | 30 ++++++++++++++++++++------ data/web/js/site/mailbox.js | 9 ++------ data/web/json_api.php | 11 +++++++--- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index 68cb50f1b..c35294354 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -4323,7 +4323,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $mailboxdata['active'] = $row['active']; $mailboxdata['active_int'] = $row['active']; $mailboxdata['domain'] = $row['domain']; - $mailboxdata['relayhost'] = $row['relayhost']; $mailboxdata['name'] = $row['name']; $mailboxdata['local_part'] = $row['local_part']; $mailboxdata['quota'] = $row['quota']; diff --git a/data/web/inc/lib/ssp.class.php b/data/web/inc/lib/ssp.class.php index c36c627cb..a06e7d09e 100644 --- a/data/web/inc/lib/ssp.class.php +++ b/data/web/inc/lib/ssp.class.php @@ -43,7 +43,7 @@ class SSP { } } else { - if(!empty($column['db'])){ + if(!empty($column['db']) && (!isset($column['dummy']) || $column['dummy'] !== true)){ $row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ]; } else{ @@ -115,10 +115,12 @@ class SSP { */ static function order ( $tableAS, $request, $columns ) { + $select = ''; $order = ''; if ( isset($request['order']) && count($request['order']) ) { - $orderBy = array(); + $selects = []; + $orderBy = []; $dtColumns = self::pluck( $columns, 'dt' ); for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) { @@ -133,17 +135,26 @@ class SSP { $dir = $request['order'][$i]['dir'] === 'asc' ? 'ASC' : 'DESC'; - - $orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir; + + if(isset($column['order_subquery'])) { + $selects[] = '('.$column['order_subquery'].') AS `'.$column['db'].'_count`'; + $orderBy[] = '`'.$column['db'].'_count` '.$dir; + } else { + $orderBy[] = '`'.$tableAS.'`.`'.$column['db'].'` '.$dir; + } } } + if ( count( $selects ) ) { + $select = ', '.implode(', ', $selects); + } + if ( count( $orderBy ) ) { $order = 'ORDER BY '.implode(', ', $orderBy); } } - return $order; + return [$select, $order]; } @@ -257,13 +268,14 @@ class SSP { } // Build the SQL query string from the request + list($select, $order) = self::order( $tablesAS, $request, $columns ); $limit = self::limit( $request, $columns ); - $order = self::order( $tablesAS, $request, $columns ); $where = self::filter( $tablesAS, $request, $columns, $bindings ); // Main query to actually get the data $data = self::sql_exec( $db, $bindings, "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + $select FROM `$table` AS `$tablesAS` $where $order @@ -348,8 +360,8 @@ class SSP { } // Build the SQL query string from the request + list($select, $order) = self::order( $tablesAS, $request, $columns ); $limit = self::limit( $request, $columns ); - $order = self::order( $tablesAS, $request, $columns ); $where = self::filter( $tablesAS, $request, $columns, $bindings ); // whereResult can be a simple string, or an assoc. array with a @@ -373,6 +385,7 @@ class SSP { // Main query to actually get the data $data = self::sql_exec( $db, $bindings, "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` + $select FROM `$table` AS `$tablesAS` $join $where @@ -556,6 +569,9 @@ class SSP { if ( empty($a[$i][$prop]) && $a[$i][$prop] !== 0 ) { continue; } + if ( $prop == 'db' && isset($a[$i]['dummy']) && $a[$i]['dummy'] === true ) { + continue; + } //removing the $out array index confuses the filter method in doing proper binding, //adding it ensures that the array data are mapped correctly diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index fe6113a8f..c6f57d457 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -552,7 +552,6 @@ jQuery(function($){ title: lang.stats, data: 'stats', searchable: false, - orderable: false, defaultContent: '', render: function (data, type) { data = data.split("/"); @@ -563,14 +562,12 @@ jQuery(function($){ title: lang.mailbox_defquota, data: 'def_quota_for_mbox', searchable: false, - orderable: false, defaultContent: '' }, { title: lang.mailbox_quota, data: 'max_quota_for_mbox', searchable: false, - orderable: false, defaultContent: '' }, { @@ -584,10 +581,9 @@ jQuery(function($){ title: lang.backup_mx, data: 'backupmx', searchable: false, - orderable: false, defaultContent: '', - redner: function (data, type){ - return 1==value ? '' : 0==value && ''; + render: function (data, type){ + return 1==data ? '' : 0==data && ''; } }, { @@ -626,7 +622,6 @@ jQuery(function($){ title: lang.active, data: 'active', searchable: false, - orderable: false, defaultContent: '', responsivePriority: 6, render: function (data, type) { diff --git a/data/web/json_api.php b/data/web/json_api.php index 3077bf7b0..0f0c23b15 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -528,9 +528,14 @@ if (isset($_GET['query'])) { $primaryKey = 'domain'; $columns = [ ['db' => 'domain', 'dt' => 2], - ['db' => 'aliases', 'dt' => 3], - ['db' => 'mailboxes', 'dt' => 4], - ['db' => 'quota', 'dt' => 5], + ['db' => 'aliases', 'dt' => 3, 'order_subquery' => "SELECT COUNT(*) FROM `alias` WHERE (`domain`= `d`.`domain` OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = `d`.`domain`)) AND `address` NOT IN (SELECT `username` FROM `mailbox`)"], + ['db' => 'mailboxes', 'dt' => 4, 'order_subquery' => "SELECT COUNT(*) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'quota', 'dt' => 5, 'order_subquery' => "SELECT COALESCE(SUM(`mailbox`.`quota`), 0) FROM `mailbox` WHERE `mailbox`.`domain` = `d`.`domain` AND (`mailbox`.`kind` = '' OR `mailbox`.`kind` = NULL)"], + ['db' => 'stats', 'dt' => 6, 'dummy' => true, 'order_subquery' => "SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` IN (SELECT `username` FROM `mailbox` WHERE `domain` = `d`.`domain`)"], + ['db' => 'defquota', 'dt' => 7], + ['db' => 'maxquota', 'dt' => 8], + ['db' => 'backupmx', 'dt' => 10], + ['db' => 'active', 'dt' => 15], ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; From efcca61f5a4f33bff53758390b3f18a452744cd1 Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Mon, 4 Dec 2023 14:49:07 +0100 Subject: [PATCH 007/793] Mailboxes datatable - server side processing ordering Signed-off-by: Kristian Feldsam --- data/web/js/site/mailbox.js | 8 +------- data/web/json_api.php | 5 +++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index c6f57d457..b1ec94824 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -1002,7 +1002,6 @@ jQuery(function($){ title: lang.last_mail_login, data: 'last_mail_login', searchable: false, - orderable: false, defaultContent: '', responsivePriority: 7, render: function (data, type) { @@ -1016,18 +1015,15 @@ jQuery(function($){ title: lang.last_pw_change, data: 'last_pw_change', searchable: false, - orderable: false, defaultContent: '' }, { title: lang.in_use, data: 'in_use.value', searchable: false, - orderable: false, defaultContent: '', responsivePriority: 9, - className: 'dt-data-w100', - orderData: 24 + className: 'dt-data-w100' }, { title: lang.fname, @@ -1093,7 +1089,6 @@ jQuery(function($){ title: lang.msg_num, data: 'messages', searchable: false, - orderable: false, defaultContent: '', responsivePriority: 5 }, @@ -1119,7 +1114,6 @@ jQuery(function($){ title: lang.active, data: 'active', searchable: false, - orderable: false, defaultContent: '', responsivePriority: 4, render: function (data, type) { diff --git a/data/web/json_api.php b/data/web/json_api.php index 0f0c23b15..403b5a792 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -1057,6 +1057,11 @@ if (isset($_GET['query'])) { $columns = [ ['db' => 'username', 'dt' => 2], ['db' => 'quota', 'dt' => 3], + ['db' => 'last_mail_login', 'dt' => 4, 'dummy' => true, 'order_subquery' => "SELECT MAX(`datetime`) FROM `sasl_log` WHERE `service` != 'SSO' AND `username` = `m`.`username`"], + ['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"], + ['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"], + ['db' => 'messages', 'dt' => 17, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"], + ['db' => 'active', 'dt' => 21] ]; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/ssp.class.php'; From 40fdf99a552eb992aa24f3669ad3d054359db9d6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Dec 2023 20:07:11 +0000 Subject: [PATCH 008/793] Update dependency composer/composer to v2.6.6 Signed-off-by: milkmaker --- data/Dockerfiles/phpfpm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 490310336..6717bd582 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -12,7 +12,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?.*)$ ARG REDIS_PECL_VERSION=6.0.1 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?.*)$ -ARG COMPOSER_VERSION=2.6.5 +ARG COMPOSER_VERSION=2.6.6 RUN apk add -U --no-cache autoconf \ aspell-dev \ From efab11720dd144d800ca0bf127c3410516e6bbb3 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 22 Dec 2023 10:39:07 +0100 Subject: [PATCH 009/793] add option to skip footer on reply e-mails --- data/conf/rspamd/dynmaps/footer.php | 3 ++- data/conf/rspamd/lua/rspamd.local.lua | 8 ++++++++ data/web/inc/functions.mailbox.inc.php | 6 ++++-- data/web/inc/init_db.inc.php | 3 ++- data/web/lang/lang.de-de.json | 7 ++----- data/web/lang/lang.en-gb.json | 1 + data/web/templates/edit/domain.twig | 8 ++++++++ 7 files changed, 27 insertions(+), 9 deletions(-) diff --git a/data/conf/rspamd/dynmaps/footer.php b/data/conf/rspamd/dynmaps/footer.php index 6e44f5195..36b307c1b 100644 --- a/data/conf/rspamd/dynmaps/footer.php +++ b/data/conf/rspamd/dynmaps/footer.php @@ -49,13 +49,14 @@ $from = $headers['From']; $empty_footer = json_encode(array( 'html' => '', 'plain' => '', + 'skip_replies' => 0, 'vars' => array() )); error_log("FOOTER: checking for domain " . $domain . ", user " . $username . " and address " . $from . PHP_EOL); try { - $stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude` FROM `domain_wide_footer` + $stmt = $pdo->prepare("SELECT `plain`, `html`, `mbox_exclude`, `skip_replies` FROM `domain_wide_footer` WHERE `domain` = :domain"); $stmt->execute(array( ':domain' => $domain diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index 24fa4f8c3..4cc314b0b 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -567,6 +567,14 @@ rspamd_config:register_symbol({ if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars) + if footer.skip_replies then + in_reply_to = task:get_header_raw('in-reply-to') + if in_reply_to then + rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer") + return + end + end + local envfrom_mime = task:get_from(2) local from_name = "" if envfrom_mime and envfrom_mime[1].name then diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index e965feafb..37e4b83bf 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -3411,6 +3411,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $footers = array(); $footers['html'] = isset($_data['html']) ? $_data['html'] : ''; $footers['plain'] = isset($_data['plain']) ? $_data['plain'] : ''; + $footers['skip_replies'] = isset($_data['skip_replies']) ? (int)$_data['skip_replies'] : 0; $footers['mbox_exclude'] = array(); if (isset($_data["mbox_exclude"])){ if (!is_array($_data["mbox_exclude"])) { @@ -3460,12 +3461,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { try { $stmt = $pdo->prepare("DELETE FROM `domain_wide_footer` WHERE `domain`= :domain"); $stmt->execute(array(':domain' => $domain)); - $stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`) VALUES (:domain, :html, :plain, :mbox_exclude)"); + $stmt = $pdo->prepare("INSERT INTO `domain_wide_footer` (`domain`, `html`, `plain`, `mbox_exclude`, `skip_replies`) VALUES (:domain, :html, :plain, :mbox_exclude, :skip_replies)"); $stmt->execute(array( ':domain' => $domain, ':html' => $footers['html'], ':plain' => $footers['plain'], ':mbox_exclude' => json_encode($footers['mbox_exclude']), + ':skip_replies' => $footers['skip_replies'], )); } catch (PDOException $e) { @@ -4622,7 +4624,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { } try { - $stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude` FROM `domain_wide_footer` + $stmt = $pdo->prepare("SELECT `html`, `plain`, `mbox_exclude`, `skip_replies` FROM `domain_wide_footer` WHERE `domain` = :domain"); $stmt->execute(array( ':domain' => $domain diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 4ea79d9bb..68956d158 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "21112023_1644"; + $db_version = "21122023_1526"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); @@ -273,6 +273,7 @@ function init_db_schema() { "html" => "LONGTEXT", "plain" => "LONGTEXT", "mbox_exclude" => "JSON NOT NULL DEFAULT ('[]')", + "skip_replies" => "TINYINT(1) NOT NULL DEFAULT '0'" ), "keys" => array( "primary" => array( diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index dc7b5dfac..dd1377b4c 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -592,6 +592,7 @@ "domain_footer_html": "HTML footer", "domain_footer_info": "Domain wide footer werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.
Die folgenden Variablen können für den Footer benutzt werden:", "domain_footer_plain": "PLAIN footer", + "domain_footer_skip_replies": "Ignoriere Footer bei Antwort E-Mails", "domain_quota": "Domain Speicherplatz gesamt (MiB)", "domains": "Domains", "dont_check_sender_acl": "Absender für Domain %s u. Alias-Domain nicht prüfen", @@ -680,11 +681,7 @@ "unchanged_if_empty": "Unverändert, wenn leer", "username": "Benutzername", "validate_save": "Validieren und speichern", - "pushover_sound": "Ton", - "domain_footer_info_vars": { - "auth_user": "{= auth_user =} - Angemeldeter Benutzername vom MTA", - "from_user": "{= from_user =} - Von Teil des Benutzers z.B. \"moo@mailcow.tld\" wird \"moo\" zurückgeben." - } + "pushover_sound": "Ton" }, "fido2": { "confirm": "Bestätigen", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index edde80078..173b258a1 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -600,6 +600,7 @@ "custom": "{= foo =} - If mailbox has the custom attribute \"foo\" with value \"bar\" it returns \"bar\"" }, "domain_footer_plain": "PLAIN footer", + "domain_footer_skip_replies": "Ignore footer on reply e-mails", "domain_quota": "Domain quota", "domains": "Domains", "dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)", diff --git a/data/web/templates/edit/domain.twig b/data/web/templates/edit/domain.twig index 60e88d091..8a700d06a 100644 --- a/data/web/templates/edit/domain.twig +++ b/data/web/templates/edit/domain.twig @@ -305,6 +305,14 @@ +
+ +
+
+ +
+
+
From 7bd27b920ab5da35f022c0de6bcd395636ed3e49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Dec 2023 18:24:01 +0100 Subject: [PATCH 010/793] chore(deps): update dependency nextcloud/server to v28.0.1 (#5614) Signed-off-by: milkmaker Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- helper-scripts/nextcloud.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 7461e7875..60d6ed78e 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # renovate: datasource=github-releases depName=nextcloud/server versioning=semver extractVersion=^v(?.*)$ -NEXTCLOUD_VERSION=28.0.0 +NEXTCLOUD_VERSION=28.0.1 echo -ne "Checking prerequisites..." sleep 1 From 38497b04acb872753f3b5d6f00c7c941d2da8ef5 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 27 Dec 2023 14:57:27 +0100 Subject: [PATCH 011/793] [Web] use template for default values in mbox and domain creation --- data/web/inc/functions.mailbox.inc.php | 119 +++++++++++++++---------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index e965feafb..7a21b91b0 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -477,17 +477,25 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'msg' => 'access_denied' ); return false; + } + $DOMAIN_DEFAULT_ATTRIBUTES = null; + if ($_data['template']){ + $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates', $_data['template'])['attributes']; } + if (empty($DOMAIN_DEFAULT_ATTRIBUTES)){ + $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates')[0]['attributes']; + } + $domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46); $description = $_data['description']; if (empty($description)) $description = $domain; - $tags = (array)$_data['tags']; - $aliases = (int)$_data['aliases']; - $mailboxes = (int)$_data['mailboxes']; - $defquota = (int)$_data['defquota']; - $maxquota = (int)$_data['maxquota']; + $tags = (isset($_data['tags'])) ? (array)$_data['tags'] : $DOMAIN_DEFAULT_ATTRIBUTES['tags']; + $aliases = (isset($_data['aliases'])) ? (int)$_data['aliases'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_aliases_for_domain']; + $mailboxes = (isset($_data['mailboxes'])) ? (int)$_data['mailboxes'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_num_mboxes_for_domain']; + $defquota = (isset($_data['defquota'])) ? (int)$_data['defquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['def_quota_for_mbox'] / 1024 ** 2; + $maxquota = (isset($_data['maxquota'])) ? (int)$_data['maxquota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_mbox'] / 1024 ** 2; $restart_sogo = (int)$_data['restart_sogo']; - $quota = (int)$_data['quota']; + $quota = (isset($_data['quota'])) ? (int)$_data['quota'] : $DOMAIN_DEFAULT_ATTRIBUTES['max_quota_for_domain'] / 1024 ** 2; if ($defquota > $maxquota) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -520,11 +528,11 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - $active = intval($_data['active']); - $relay_all_recipients = intval($_data['relay_all_recipients']); - $relay_unknown_only = intval($_data['relay_unknown_only']); - $backupmx = intval($_data['backupmx']); - $gal = intval($_data['gal']); + $active = (isset($_data['active'])) ? intval($_data['active']) : $DOMAIN_DEFAULT_ATTRIBUTES['active']; + $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_all_recipients']; + $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only']; + $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx']; + $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal']; if ($relay_all_recipients == 1) { $backupmx = '1'; } @@ -625,9 +633,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (!empty(intval($_data['rl_value']))) { + $_data['rl_value'] = (isset($_data['rl_value'])) ? intval($_data['rl_value']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_value']; + $_data['rl_frame'] = (isset($_data['rl_frame'])) ? intval($_data['rl_frame']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame']; + if (!empty($_data['rl_value']) && !empty($_data['rl_frame'])){ ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain)); } + $_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size']; + $_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? intval($_data['dkim_selector']) : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector']; if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) { $_SESSION['return'][] = array( @@ -1006,11 +1018,21 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } + if (empty($name)) { + $name = $local_part; + } + $MAILBOX_DEFAULT_ATTRIBUTES = null; + if ($_data['template']){ + $MAILBOX_DEFAULT_ATTRIBUTES = mailbox('get', 'mailbox_templates', $_data['template'])['attributes']; + } + if (empty($MAILBOX_DEFAULT_ATTRIBUTES)){ + $MAILBOX_DEFAULT_ATTRIBUTES = mailbox('get', 'mailbox_templates')[0]['attributes']; + } $password = $_data['password']; $password2 = $_data['password2']; $name = ltrim(rtrim($_data['name'], '>'), '<'); - $tags = $_data['tags']; - $quota_m = intval($_data['quota']); + $tags = (isset($_data['tags'])) ? $_data['tags'] : $MAILBOX_DEFAULT_ATTRIBUTES['tags']; + $quota_m = (isset($_data['quota'])) ? intval($_data['quota']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['quota']) / 1024 ** 2; if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) { $_SESSION['return'][] = array( 'type' => 'danger', @@ -1019,9 +1041,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ); return false; } - if (empty($name)) { - $name = $local_part; - } + if (isset($_data['protocol_access'])) { $_data['protocol_access'] = (array)$_data['protocol_access']; $_data['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; @@ -1029,7 +1049,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; $_data['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; } - $active = intval($_data['active']); + $active = (isset($_data['active'])) ? intval($_data['active']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['active']); $force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update']); $tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in']); $tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); @@ -1227,12 +1247,29 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $_data['quarantine_notification'] = (in_array('quarantine_notification', $_data['acl'])) ? 1 : 0; $_data['quarantine_category'] = (in_array('quarantine_category', $_data['acl'])) ? 1 : 0; $_data['app_passwds'] = (in_array('app_passwds', $_data['acl'])) ? 1 : 0; + } else { + $_data['spam_alias'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_alias']); + $_data['tls_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_tls_policy']); + $_data['spam_score'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_score']); + $_data['spam_policy'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_spam_policy']); + $_data['delimiter_action'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_delimiter_action']); + $_data['syncjobs'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_syncjobs']); + $_data['eas_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_eas_reset']); + $_data['sogo_profile_reset'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_sogo_profile_reset']); + $_data['pushover'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_pushover']); + $_data['quarantine'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine']); + $_data['quarantine_attachments'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_attachments']); + $_data['quarantine_notification'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_notification']); + $_data['quarantine_category'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_quarantine_category']); + $_data['app_passwds'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['acl_app_passwds']); + } + try { $stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, - `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) + `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, - :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); + :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); $stmt->execute(array( ':username' => $username, ':spam_alias' => $_data['spam_alias'], @@ -1251,31 +1288,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { ':app_passwds' => $_data['app_passwds'] )); } - else { - $stmt = $pdo->prepare("INSERT INTO `user_acl` - (`username`, `spam_alias`, `tls_policy`, `spam_score`, `spam_policy`, `delimiter_action`, `syncjobs`, `eas_reset`, `sogo_profile_reset`, - `pushover`, `quarantine`, `quarantine_attachments`, `quarantine_notification`, `quarantine_category`, `app_passwds`) - VALUES (:username, :spam_alias, :tls_policy, :spam_score, :spam_policy, :delimiter_action, :syncjobs, :eas_reset, :sogo_profile_reset, - :pushover, :quarantine, :quarantine_attachments, :quarantine_notification, :quarantine_category, :app_passwds) "); - $stmt->execute(array( - ':username' => $username, - ':spam_alias' => 0, - ':tls_policy' => 0, - ':spam_score' => 0, - ':spam_policy' => 0, - ':delimiter_action' => 0, - ':syncjobs' => 0, - ':eas_reset' => 0, - ':sogo_profile_reset' => 0, - ':pushover' => 0, - ':quarantine' => 0, - ':quarantine_attachments' => 0, - ':quarantine_notification' => 0, - ':quarantine_category' => 0, - ':app_passwds' => 0 - )); + catch (PDOException $e) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), + 'msg' => $e->getMessage() + ); + return false; } + $_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_frame']; + $_data['rl_value'] = (isset($_data['rl_value'])) ? $_data['rl_value'] : $MAILBOX_DEFAULT_ATTRIBUTES['rl_value']; if (isset($_data['rl_frame']) && isset($_data['rl_value'])){ ratelimit('edit', 'mailbox', array( 'object' => $username, @@ -1524,17 +1547,17 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { $attr["tls_enforce_out"] = isset($_data['tls_enforce_out']) ? intval($_data['tls_enforce_out']) : intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out']); if (isset($_data['protocol_access'])) { $_data['protocol_access'] = (array)$_data['protocol_access']; - $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); - $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); - $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); - $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); + $attr['imap_access'] = (in_array('imap', $_data['protocol_access'])) ? 1 : 0; + $attr['pop3_access'] = (in_array('pop3', $_data['protocol_access'])) ? 1 : 0; + $attr['smtp_access'] = (in_array('smtp', $_data['protocol_access'])) ? 1 : 0; + $attr['sieve_access'] = (in_array('sieve', $_data['protocol_access'])) ? 1 : 0; } else { $attr['imap_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['imap_access']); $attr['pop3_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['pop3_access']); $attr['smtp_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['smtp_access']); $attr['sieve_access'] = intval($MAILBOX_DEFAULT_ATTRIBUTES['sieve_access']); - } + } if (isset($_data['acl'])) { $_data['acl'] = (array)$_data['acl']; $attr['acl_spam_alias'] = (in_array('spam_alias', $_data['acl'])) ? 1 : 0; From 100e8ab00d7d5c18ecab92b899993403b447a534 Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Mon, 4 Sep 2023 20:54:49 +0200 Subject: [PATCH 012/793] [Postfix] Do not remove X-Mailer header some providers, like seznam.cz use X-Mailer in DKIM signatures Signed-off-by: Kristian Feldsam --- data/conf/postfix/anonymize_headers.pcre | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/postfix/anonymize_headers.pcre b/data/conf/postfix/anonymize_headers.pcre index 739237be2..061a4bc08 100644 --- a/data/conf/postfix/anonymize_headers.pcre +++ b/data/conf/postfix/anonymize_headers.pcre @@ -12,7 +12,8 @@ if /^\s*Received: from.* \(.*rspamd-mailcow.*mailcow-network.*\).*\(Postcow\)/ REPLACE Received: from rspamd (rspamd $3) by $4 (Postcow) with $5 endif /^\s*X-Enigmail/ IGNORE -/^\s*X-Mailer/ IGNORE +# Not removing Mailer by default, might be signed +#/^\s*X-Mailer/ IGNORE /^\s*X-Originating-IP/ IGNORE /^\s*X-Forward/ IGNORE # Not removing UA by default, might be signed From c24543fea07058100494eaab2cd8c029af16657c Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Wed, 27 Dec 2023 17:32:26 +0100 Subject: [PATCH 013/793] [Web] Fixed form fields bg color in dark mode Signed-off-by: Kristian Feldsam --- data/web/css/themes/mailcow-darkmode.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/web/css/themes/mailcow-darkmode.css b/data/web/css/themes/mailcow-darkmode.css index ea95df970..83befeaf5 100644 --- a/data/web/css/themes/mailcow-darkmode.css +++ b/data/web/css/themes/mailcow-darkmode.css @@ -175,6 +175,9 @@ pre { background-color: #282828; border: 1px solid #555; } +.form-control { + background-color: transparent; +} input.form-control, textarea.form-control { color: #e2e2e2 !important; background-color: #424242 !important; From a1cb7fd778316e80e2f8078c5d2959f50fac44b3 Mon Sep 17 00:00:00 2001 From: milkmaker Date: Wed, 27 Dec 2023 18:03:24 +0100 Subject: [PATCH 014/793] [Web] Updated lang.zh-tw.json (#5617) Co-authored-by: BallBill --- data/web/lang/lang.zh-tw.json | 88 +++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json index 2c9d61fde..7d391ddfc 100644 --- a/data/web/lang/lang.zh-tw.json +++ b/data/web/lang/lang.zh-tw.json @@ -107,7 +107,8 @@ "timeout2": "本地主機連線逾時時間", "username": "使用者名稱", "validate": "驗證", - "validation_success": "驗證成功" + "validation_success": "驗證成功", + "dry": "模擬同步" }, "admin": { "access": "存取", @@ -335,7 +336,22 @@ "username": "使用者名稱", "validate_license_now": "與證書伺服器驗證 GUID", "verify": "驗證", - "yes": "✓" + "yes": "✓", + "f2b_manage_external_info": "Fail2ban仍會維護禁令列表,但不會主動設定規則來阻止流量。 使用下面產生的禁止清單從外部阻止流量。", + "allowed_origins": "存取控制允許來源", + "logo_dark_label": "深色模式", + "logo_normal_label": "標準", + "f2b_ban_time_increment": "禁令時間會隨著每次禁令增加", + "copy_to_clipboard": "文字已複製到剪貼簿!", + "cors_settings": "CORS 設定", + "f2b_manage_external": "外部管理 Fail2Ban", + "f2b_max_ban_time": "最大限度。 禁止時間(s)", + "allowed_methods": "存取控制允許方法", + "ip_check": "IP檢查", + "ip_check_opt_in": "選擇使用第三方服務 ipv4.mailcow.emailipv6.mailcow.email 來解析外部 IP 位址。", + "ip_check_disabled": "IP 檢查已停用。 您可以在
系統 > 配置 > 選項 > 自訂下啟用它", + "options": "選項", + "queue_unban": "解除禁令" }, "danger": { "access_denied": "存取拒絕或表單資料有誤", @@ -454,7 +470,17 @@ "username_invalid": "使用者名稱 %s 無法使用", "validity_missing": "請設定有效期", "value_missing": "請填入所有欄位", - "yotp_verification_failed": "Yubico OTP 認證失敗: %s" + "yotp_verification_failed": "Yubico OTP 認證失敗: %s", + "webauthn_authenticator_failed": "找不到所選的驗證器", + "webauthn_publickey_failed": "沒有為選定的身份驗證器儲存公鑰", + "webauthn_username_failed": "所選驗證器屬於另一個帳戶", + "cors_invalid_method": "指定的允許方法無效", + "cors_invalid_origin": "指定的允許來源無效", + "demo_mode_enabled": "演示模式已啟用", + "extended_sender_acl_denied": "缺少設定外部寄件者地址的 ACL", + "template_exists": "模板 %s 已存在", + "template_id_invalid": "範本 ID %s 無效", + "template_name_invalid": "模板名稱無效" }, "debug": { "chart_this_server": "圖表 (此伺服器)", @@ -473,7 +499,7 @@ "restart_container": "重新啟動", "service": "服務", "size": "大小", - "solr_dead": "Solr 正在啟動、停用或已停止運行", + "solr_dead": "Solr 正在啟動,停用或已停止運行.", "solr_status": "Solr 狀態", "started_at": "啟動於", "started_on": "啟動於", @@ -481,7 +507,19 @@ "success": "成功", "system_containers": "系統和容器", "uptime": "運行時間", - "username": "使用者名稱" + "username": "使用者名稱", + "architecture": "結構", + "current_time": "系統時間", + "container_running": "正在執行", + "memory": "記憶", + "container_disabled": "容器停止或停用", + "container_stopped": "已停止", + "cores": "核心", + "error_show_ip": "無法解析公用IP位址", + "show_ip": "顯示公網IP", + "update_available": "有可用更新", + "no_update_available": "系統已經是最新版本", + "update_failed": "無法檢查更新" }, "diagnostics": { "cname_from_a": "由 A/AAAA 紀錄獲取。只要紀錄指向正確的資源,此功能就會持續運作。", @@ -607,7 +645,9 @@ "title": "編輯物件", "unchanged_if_empty": "如果不更改則留空", "username": "使用者名稱", - "validate_save": "驗證並儲存" + "validate_save": "驗證並儲存", + "domain_footer_info": "網域範圍的頁尾將會新增至與該網域內的位址關聯的所有外發電子郵件。
以下變數可用於頁尾:", + "custom_attributes": "自訂屬性" }, "fido2": { "confirm": "確認", @@ -929,7 +969,7 @@ "delete_filters": "過濾器 %s 已刪除", "deleted_syncjob": "同步任務 ID %s 已刪除", "deleted_syncjobs": "同步任務 %s 已刪除", - "dkim_added": " DKIM 金鑰 %s 已儲存", + "dkim_added": "DKIM 金鑰 %s 已儲存", "domain_add_dkim_available": "DKIM 金鑰已存在", "dkim_duplicated": "域名的 DKIM 金鑰 %s 已複製到 %s", "dkim_removed": " DKIM 金鑰 %s 已刪除", @@ -976,14 +1016,17 @@ "settings_map_added": "設定規則已新增", "settings_map_removed": "設定規則 ID %s 已刪除", "sogo_profile_reset": "使用者 %s 的 SOGo 個人頁面已重設", - "tls_policy_map_entry_deleted": " TLS 規則 ID %s 已刪除", + "tls_policy_map_entry_deleted": "TLS 規則 ID %s 已刪除", "tls_policy_map_entry_saved": "TLS 規則 \"%s\" 已儲存", "ui_texts": "UI 內文更改已儲存", "upload_success": "檔案已成功上傳", "verified_fido2_login": "FIDO2 登入驗證成功", "verified_totp_login": "TOTP 登入驗證成功", "verified_webauthn_login": "WebAuthn 登入驗證成功", - "verified_yotp_login": "Yubico OTP 登入驗證成功" + "verified_yotp_login": "Yubico OTP 登入驗證成功", + "template_removed": "模板 ID %s 已刪除", + "template_added": "新增了模板 %s", + "template_modified": "模板 %s 的變更已儲存" }, "tfa": { "api_register": "%s 使用 Yubico Cloud API,請在這裡為這把金鑰獲取 API 金鑰", @@ -1171,7 +1214,9 @@ "weeks": "週", "with_app_password": "使用應用程式密碼", "year": "年", - "years": "年" + "years": "年", + "attribute": "屬性", + "pushover_sound": "聲音" }, "warning": { "cannot_delete_self": "不能刪除已登入的使用者", @@ -1185,5 +1230,28 @@ "quota_exceeded_scope": "域名容量配額已滿: 此域名現在只能創建無限容量的信箱。", "session_token": "表單驗證失敗: 驗證碼錯誤", "session_ua": "表單驗證失敗: User-Agent 校驗錯誤" + }, + "datatables": { + "infoEmpty": "顯示 0 到 0 個條目,共 0 個條目", + "infoFiltered": "(從_MAX_個總條目中過濾)", + "lengthMenu": "顯示_選單_條目", + "loadingRecords": "載入中...", + "processing": "請稍等...", + "search": "搜尋:", + "zeroRecords": "未找到符合的記錄", + "paginate": { + "first": "第一的", + "last": "最後的", + "next": "下一個", + "previous": "上一個" + }, + "aria": { + "sortAscending": ":啟動以升序對列進行排序", + "sortDescending": ":啟動以降序對列進行排序" + }, + "expand_all": "展開全部", + "info": "顯示 _START_ 到 _END_ 條,共 _TOTAL_ 條", + "collapse_all": "全部折疊", + "emptyTable": "表中沒有可用數據" } } From c61938db23c5bbc76be8172e22a25e89253655e9 Mon Sep 17 00:00:00 2001 From: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:59:16 +0200 Subject: [PATCH 015/793] [Postfix] Remove pipeling from ehlo keywords as we block it in data restrictions --- data/conf/postfix/main.cf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 237b42635..881c7d797 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -160,7 +160,8 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre, proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf smtp_sasl_auth_soft_bounce = no -postscreen_discard_ehlo_keywords = silent-discard, dsn +postscreen_discard_ehlo_keywords = silent-discard, dsn, pipelining, chunking +smtpd_discard_ehlo_keywords = pipelining, chunking compatibility_level = 2 smtputf8_enable = no # Define protocols for SMTPS and submission service From b4bb11320f88070421aff4fafed1a0e784325892 Mon Sep 17 00:00:00 2001 From: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:04:52 +0200 Subject: [PATCH 016/793] Update main.cf --- data/conf/postfix/main.cf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 881c7d797..7e0a30d92 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -160,8 +160,8 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre, proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf smtp_sasl_auth_soft_bounce = no -postscreen_discard_ehlo_keywords = silent-discard, dsn, pipelining, chunking -smtpd_discard_ehlo_keywords = pipelining, chunking +postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking +smtpd_discard_ehlo_keywords = chunking compatibility_level = 2 smtputf8_enable = no # Define protocols for SMTPS and submission service From 0b628fb22d2c60b5fb6b863b14e7e928d18f49d0 Mon Sep 17 00:00:00 2001 From: milkmaker Date: Fri, 29 Dec 2023 19:22:19 +0100 Subject: [PATCH 017/793] Translations update from Weblate (#5622) * [Web] Updated lang.zh-tw.json Co-authored-by: BallBill * [Web] Updated lang.pt-br.json Co-authored-by: Abner Santana --------- Co-authored-by: BallBill Co-authored-by: Abner Santana --- data/web/lang/lang.pt-br.json | 60 +++++++++++++++++------------------ data/web/lang/lang.zh-tw.json | 40 ++++++++++++++++++++--- 2 files changed, 65 insertions(+), 35 deletions(-) diff --git a/data/web/lang/lang.pt-br.json b/data/web/lang/lang.pt-br.json index 1613b49b7..9acaf87c7 100644 --- a/data/web/lang/lang.pt-br.json +++ b/data/web/lang/lang.pt-br.json @@ -9,8 +9,8 @@ "eas_reset": "Redefinir dispositivos EAS", "extend_sender_acl": "Permitir estender a ACL do remetente por endereços externos", "filters": "Filtros", - "login_as": "Faça login como usuário da caixa de correio", - "mailbox_relayhost": "Alterar relayhost para uma caixa de correio", + "login_as": "Faça login como usuário da mailbox", + "mailbox_relayhost": "Alterar relayhost para uma mailbox", "prohibited": "Proibido pela ACL", "protocol_access": "Alterar o acesso ao protocolo", "pushover": "Pushover", @@ -28,7 +28,7 @@ "spam_score": "Pontuação de spam", "syncjobs": "Trabalhos de sincronização", "tls_policy": "Política de TLS", - "unlimited_quota": "Cota ilimitada para caixas de correio" + "unlimited_quota": "Cota ilimitada para mailbox" }, "add": { "activate_filter_warn": "Todos os outros filtros serão desativados quando a opção ativa estiver marcada.", @@ -70,11 +70,11 @@ "hostname": "Anfitrião", "inactive": "Inativo", "kind": "Gentil", - "mailbox_quota_def": "Cota de caixa de correio padrão", - "mailbox_quota_m": "Cota máxima por caixa de correio (MiB)", + "mailbox_quota_def": "Cota de caixa de mailbox", + "mailbox_quota_m": "Cota máxima por mailbox (MiB)", "mailbox_username": "Nome de usuário (parte esquerda de um endereço de e-mail)", "max_aliases": "Máximo de aliases possíveis", - "max_mailboxes": "Número máximo de caixas de correio possíveis", + "max_mailboxes": "Número máximo de mailboxes possíveis", "mins_interval": "Intervalo de votação (minutos)", "multiple_bookings": "Várias reservas", "nexthop": "Próximo salto", @@ -86,10 +86,10 @@ "public_comment": "Comentário público", "quota_mb": "Cota (MiB)", "relay_all": "Retransmita todos os destinatários", - "relay_all_info": "↪ Se você optar por não retransmitir todos os destinatários, precisará adicionar uma caixa de correio (“cega”) para cada destinatário que deve ser retransmitido.", + "relay_all_info": "↪ Se você optar por não retransmitir todos os destinatários, precisará adicionar uma mailbox (“cega”) para cada destinatário que deve ser retransmitido.", "relay_domain": "Retransmitir este domínio", "relay_transport_info": "
Informações
Você pode definir mapas de transporte para um destino personalizado para esse domínio. Se não for definido, uma pesquisa MX será feita.", - "relay_unknown_only": "Retransmita somente caixas de correio não existentes. As caixas de correio existentes serão entregues localmente.", + "relay_unknown_only": "Retransmita somente mailboxes não existentes. As mailboxes existentes serão entregues localmente.", "relayhost_wrapped_tls_info": "Por favor, não use portas com cobertura TLS (usadas principalmente na porta 465).
\r\nUse qualquer porta não encapsulada e emita STARTTLS. Uma política de TLS para impor o TLS pode ser criada em “mapas de políticas de TLS”.", "select": "Selecione...", "select_domain": "Selecione primeiro um domínio", @@ -212,7 +212,7 @@ "in_use_by": "Em uso por", "inactive": "Inativo", "include_exclude": "Incluir/Excluir", - "include_exclude_info": "Por padrão - sem seleção - todas as caixas de correio são endereçadas", + "include_exclude_info": "Por padrão - sem seleção - todas as mailboxes são endereçadas", "includes": "Inclua esses destinatários", "ip_check": "Verificação de IP", "ip_check_disabled": "A verificação de IP está desativada. Você pode ativá-lo em
Sistema > Configuração > Opções > Personalizar", @@ -268,13 +268,13 @@ "quarantine_release_format": "Formato dos itens lançados", "quarantine_release_format_att": "Como anexo", "quarantine_release_format_raw": "Original não modificado", - "quarantine_retention_size": "Retenções por caixa de correio: 0 indica inativo.
", + "quarantine_retention_size": "Retenções por mailbox:
0 indica inativo.", "quota_notification_html": "Modelo de e-mail de notificação:
deixe em branco para restaurar o modelo padrão.", "quota_notification_sender": "Remetente do e-mail de notificação", "quota_notification_subject": "Assunto do e-mail de notificação", "quota_notifications": "Notificações de cotas", "quota_notifications_info": "As notificações de cota são enviadas aos usuários uma vez ao ultrapassar 80% e uma vez ao ultrapassar 95% de uso.", - "quota_notifications_vars": "{{percent}} é igual à cota atual do usuário
{{username}} é o nome da caixa de correio", + "quota_notifications_vars": "{{percent}} é igual à cota atual do usuário
{{username}} é o nome da mailbox", "queue_unban": "não banido", "r_active": "Restrições ativas", "r_inactive": "Restrições inativas", @@ -302,7 +302,7 @@ "rsettings_insert_preset": "Inserir exemplo de predefinição “%s”", "rsettings_preset_1": "Desative tudo, exceto o DKIM e o limite de taxa para usuários autenticados", "rsettings_preset_2": "Postmasters querem spam", - "rsettings_preset_3": "Permitir somente remetentes específicos para uma caixa de correio (ou seja, uso somente como caixa de correio interna)", + "rsettings_preset_3": "Permitir somente remetentes específicos para uma mailbox (ou seja, uso somente como mailbox interna)", "rsettings_preset_4": "Desativar Rspamd para um domínio", "rspamd_com_settings": "Um nome de configuração será gerado automaticamente, veja os exemplos de predefinições abaixo. Para obter mais detalhes, consulte a documentação do Rspamd", "rspamd_global_filters": "Mapas de filtro globais", @@ -369,7 +369,7 @@ "comment_too_long": "Comentário muito longo, máximo de 160 caracteres permitidos", "cors_invalid_method": "Método de permissão inválido especificado", "cors_invalid_origin": "Origem de permissão inválida especificada", - "defquota_empty": "A cota padrão por caixa de correio não deve ser 0.", + "defquota_empty": "A cota padrão por mailbox não deve ser 0.", "demo_mode_enabled": "O modo de demonstração está ativado", "description_invalid": "A descrição do recurso para %s é inválida", "dkim_domain_or_sel_exists": "Existe uma chave DKIM para “%s” e não será substituída", @@ -407,22 +407,22 @@ "invalid_recipient_map_old": "Destinatário original inválido especificado: %s", "ip_list_empty": "A lista de IPs permitidos não pode estar vazia", "is_alias": "%s já é conhecido como endereço de alias", - "is_alias_or_mailbox": "%s já é conhecido como alias, caixa de correio ou endereço de alias expandido a partir de um domínio de alias.", + "is_alias_or_mailbox": "%s já é conhecido como alias, mailbox ou alias de endereço expandido a partir de um domínio de alias.", "is_spam_alias": "%s já é conhecido como endereço de alias temporário (endereço de alias de spam)", "last_key": "A última chave não pode ser excluída. Em vez disso, desative o TFA.", "login_failed": "Falha no login", "mailbox_defquota_exceeds_mailbox_maxquota": "A cota padrão excede o limite máximo da cota", - "mailbox_invalid": "O nome da caixa de correio é inválido", + "mailbox_invalid": "O nome da mailbox é inválido", "mailbox_quota_exceeded": "A cota excede o limite do domínio (máx. %d MiB)", "mailbox_quota_exceeds_domain_quota": "A cota máxima excede o limite da cota do domínio", "mailbox_quota_left_exceeded": "Não há espaço restante (espaço restante: %d MiB)", - "mailboxes_in_use": "O máximo de caixas de correio deve ser maior ou igual a %d", + "mailboxes_in_use": "O máximo de mailboxes deve ser maior ou igual a %d", "malformed_username": "Nome de usuário malformado", "map_content_empty": "O conteúdo do mapa não pode estar vazio", "max_alias_exceeded": "Número máximo de aliases excedido", - "max_mailbox_exceeded": "Número máximo de caixas de correio excedido (%d de %d)", - "max_quota_in_use": "A cota da caixa de correio deve ser maior ou igual a %d MiB", - "maxquota_empty": "A cota máxima por caixa de correio não deve ser 0.", + "max_mailbox_exceeded": "Número máximo de mailboxes excedido (%d de %d)", + "max_quota_in_use": "A cota da mailbox deve ser maior ou igual a %d MiB", + "maxquota_empty": "A cota máxima por mailbox não deve ser 0.", "mysql_error": "Erro do MySQL: %s", "network_host_invalid": "Rede ou host inválidos: %s", "next_hop_interferes": "%s interfere com o nexthop %s", @@ -619,11 +619,11 @@ "kind": "Gentil", "last_modified": "Última modificação", "lookup_mx": "Destination é uma expressão regular que corresponde ao nome MX (.*\\ .google\\ .com para rotear todos os e-mails direcionados a um MX que termina em google.com nesse salto)", - "mailbox": "Editar caixa de correio", - "mailbox_quota_def": "Cota de caixa de correio padrão", + "mailbox": "Editar mailbox", + "mailbox_quota_def": "Cota mailbox padrão", "mailbox_relayhost_info": "Aplicado somente à caixa de correio e aos aliases diretos, substitui um host de retransmissão de domínio.", "max_aliases": "Máximo de aliases", - "max_mailboxes": "Número máximo de caixas de correio possíveis", + "max_mailboxes": "Número máximo de mailboxes possíveis", "max_quota": "Cota máxima por caixa de correio (MiB)", "maxage": "Duração máxima das mensagens em dias que serão pesquisadas remotamente
(0 = ignorar a idade)", "maxbytespersecond": "Máximo de bytes por segundo
(0 = ilimitado)", @@ -657,7 +657,7 @@ "relay_all_info": "↪ Se você optar por não retransmitir todos os destinatários, precisará adicionar uma caixa de correio (“cega”) para cada destinatário que deve ser retransmitido.", "relay_domain": "Retransmitir este domínio", "relay_transport_info": "
Informações
Você pode definir mapas de transporte para um destino personalizado para esse domínio. Se não for definido, uma pesquisa MX será feita.", - "relay_unknown_only": "Retransmita somente caixas de correio não existentes. As caixas de correio existentes serão entregues localmente.", + "relay_unknown_only": "Retransmita somente mailboxes não existentes. As caixas de mailboxes serão entregues localmente.", "relayhost": "Transportes dependentes do remetente", "remove": "Remover", "resource": "Recurso", @@ -688,7 +688,7 @@ "username": "Nome de usuário", "validate_save": "Valide e salve", "custom_attributes": "Atributos personalizados", - "mbox_exclude": "Excluir caixas de email" + "mbox_exclude": "Excluir mailboxes" }, "fido2": { "confirm": "Confirme", @@ -831,13 +831,13 @@ "last_run_reset": "Programe a seguir", "mailbox": "Caixa de correio", "mailbox_defaults": "Configurações padrão", - "mailbox_defaults_info": "Defina as configurações padrão para novas caixas de correio.", + "mailbox_defaults_info": "Defina as configurações padrão para novas mailboxes.", "mailbox_defquota": "Tamanho padrão da caixa de correio", "mailbox_templates": "Modelos de caixa de correio", "mailbox_quota": "Tamanho máximo de uma caixa de correio", - "mailboxes": "Caixas de correio", + "mailboxes": "mailboxes", "max_aliases": "Máximo de aliases", - "max_mailboxes": "Número máximo de caixas de correio possíveis", + "max_mailboxes": "Número máximo de mailboxes possíveis", "max_quota": "Cota máxima por caixa de correio", "mins_interval": "Intervalo (min)", "msg_num": "Mensagem #", @@ -865,7 +865,7 @@ "recipient_map_old_info": "O destino original do mapa de um destinatário deve ser um endereço de e-mail válido ou um nome de domínio.", "recipient_maps": "Mapas de destinatários", "relay_all": "Retransmita todos os destinatários", - "relay_unknown": "Retransmitir caixas de correio desconhecidas", + "relay_unknown": "Retransmitir mailboxes desconhecidas", "remove": "Remover", "resources": "Recursos", "running": "Executando", @@ -1010,7 +1010,7 @@ "help": "Mostrar/ocultar painel de ajuda", "imap_smtp_server_auth_info": "Use seu endereço de e-mail completo e o mecanismo de autenticação PLAIN.
\r\nSeus dados de login serão criptografados pela criptografia obrigatória do lado do servidor.", "mailcow_apps_detail": "Use um aplicativo mailcow para acessar seus e-mails, calendário, contatos e muito mais.", - "mailcow_panel_detail": "Os administradores de domínio criam, modificam ou excluem caixas de correio e aliases, alteram domínios e leem mais informações sobre seus domínios atribuídos.
\r\nOs usuários de caixas de correio podem criar aliases com limite de tempo (aliases de spam), alterar suas configurações de senha e filtro de spam." + "mailcow_panel_detail": "Os administradores de domínio criam, modificam ou excluem mailboxes e aliases, alteram domínios e leem mais informações sobre seus domínios atribuídos.
\nOs usuários de caixas de correio podem criar aliases com limite de tempo (aliases de spam), alterar suas configurações de senha e filtro de spam." }, "success": { "acl_saved": "ACL para o objeto %s salvo", @@ -1298,7 +1298,7 @@ "ip_invalid": "IP inválido ignorado: %s", "is_not_primary_alias": "Alias não primário ignorado %s", "no_active_admin": "Não é possível desativar o último administrador ativo", - "quota_exceeded_scope": "Cota de domínio excedida: somente caixas de correio ilimitadas podem ser criadas nesse escopo de domínio.", + "quota_exceeded_scope": "Cota de domínio excedida: somente mailboxes ilimitadas podem ser criadas nesse escopo de domínio.", "session_token": "Token de formulário inválido: incompatibilidade de token", "session_ua": "Token de formulário inválido: erro de validação do agente de usuário" } diff --git a/data/web/lang/lang.zh-tw.json b/data/web/lang/lang.zh-tw.json index 7d391ddfc..4d84b2116 100644 --- a/data/web/lang/lang.zh-tw.json +++ b/data/web/lang/lang.zh-tw.json @@ -647,7 +647,9 @@ "username": "使用者名稱", "validate_save": "驗證並儲存", "domain_footer_info": "網域範圍的頁尾將會新增至與該網域內的位址關聯的所有外發電子郵件。
以下變數可用於頁尾:", - "custom_attributes": "自訂屬性" + "custom_attributes": "自訂屬性", + "mbox_exclude": "排除信箱", + "pushover_sound": "聲音" }, "fido2": { "confirm": "確認", @@ -686,7 +688,10 @@ "quarantine": "隔離", "restart_netfilter": "重新啟動 netfilter", "restart_sogo": "重新啟動 SOGo", - "user_settings": "使用者設定" + "user_settings": "使用者設定", + "email": "電子郵件", + "mailcow_system": "系統", + "mailcow_config": "配置" }, "info": { "awaiting_tfa_confirmation": "等待 TFA 確認", @@ -869,7 +874,13 @@ "username": "使用者名稱", "waiting": "等待中", "weekly": "每週", - "yes": "✓" + "yes": "✓", + "templates": "範本", + "domain_templates": "域模板", + "add_template": "新增模板", + "mailbox_templates": "信箱模板", + "relay_unknown": "轉發未知信箱", + "template": "範本" }, "oauth2": { "access_denied": "請使用信箱使用者登入來進行 OAuth2 授權", @@ -1026,7 +1037,11 @@ "verified_yotp_login": "Yubico OTP 登入驗證成功", "template_removed": "模板 ID %s 已刪除", "template_added": "新增了模板 %s", - "template_modified": "模板 %s 的變更已儲存" + "template_modified": "模板 %s 的變更已儲存", + "cors_headers_edited": "CORS 設定已儲存", + "domain_footer_modified": "網域頁尾 %s 的變更已儲存", + "f2b_banlist_refreshed": "禁止清單 ID 已成功刷新。", + "ip_check_opt_in_modified": "IP檢查已成功儲存" }, "tfa": { "api_register": "%s 使用 Yubico Cloud API,請在這裡為這把金鑰獲取 API 金鑰", @@ -1252,6 +1267,21 @@ "expand_all": "展開全部", "info": "顯示 _START_ 到 _END_ 條,共 _TOTAL_ 條", "collapse_all": "全部折疊", - "emptyTable": "表中沒有可用數據" + "emptyTable": "表中沒有可用數據", + "thousands": ",", + "decimal": "." + }, + "queue": { + "deliver_mail_legend": "嘗試重新投遞選定的郵件。", + "show_message": "顯示訊息", + "unban": "解除禁令隊列", + "legend": "郵件隊列操作功能:", + "delete": "刪除所有", + "flush": "刷新隊列", + "info": "郵件隊列包含所有等待投遞的電子郵件。 如果電子郵件長時間滯留在郵件隊列中,系統會自動將其刪除。
對應郵件的錯誤訊息會提供有關郵件無法送達的原因的資訊。", + "ays": "請確認您要刪除目前隊列中的所有項目。", + "deliver_mail": "遞送", + "queue_manager": "隊列管理器", + "unhold_mail_legend": "釋放選定的郵件以供投遞。 (需事先持有)" } } From 6ff6f7a28d5128fff497c2fa0ceec7da03976dea Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 29 Dec 2023 20:19:26 +0100 Subject: [PATCH 018/793] [Postfix] set smtpd_forbid_bare_newline = yes --- data/conf/postfix/main.cf | 1 + docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 237b42635..1dce6a4c7 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -11,6 +11,7 @@ smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination +smtpd_forbid_bare_newline = yes # alias maps are auto-generated in postfix.sh on startup alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases diff --git a/docker-compose.yml b/docker-compose.yml index 1156854e7..2521e8164 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -298,7 +298,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.72 + image: mailcow/postfix:1.73 depends_on: mysql-mailcow: condition: service_started From 68036eeccf4cd2a5ad979c5ec728bfc4296868fa Mon Sep 17 00:00:00 2001 From: Dmitriy Alekseev <1865999+dragoangel@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:06:18 +0200 Subject: [PATCH 019/793] Update main.cf --- data/conf/postfix/main.cf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 7e0a30d92..a2c2950f2 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -84,6 +84,7 @@ smtp_tls_security_level = dane smtpd_data_restrictions = reject_unauth_pipelining, permit smtpd_delay_reject = yes smtpd_error_sleep_time = 10s +smtpd_forbid_bare_newline = yes smtpd_hard_error_limit = ${stress?1}${stress:5} smtpd_helo_required = yes smtpd_proxy_timeout = 600s From a249e2028dd5ca345aac187d77bcda8d0a1737a7 Mon Sep 17 00:00:00 2001 From: Mathilde <41020854+mthld@users.noreply.github.com> Date: Sat, 30 Dec 2023 10:16:25 +0100 Subject: [PATCH 020/793] Add new SOGoMailHideInlineAttachments option to sogo.conf SOGoMailHideInlineAttachments = YES; will allow to hide inline (body and footer) images being shown as attachments. --- data/conf/sogo/sogo.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index b424efd81..8d4dd93de 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -12,6 +12,7 @@ SOGoJunkFolderName= "Junk"; SOGoMailDomain = "sogo.local"; SOGoEnableEMailAlarms = YES; + SOGoMailHideInlineAttachments = YES; SOGoFoldersSendEMailNotifications = YES; SOGoForwardEnabled = YES; From de00c424f49623be282cc5e796e9da027a19030e Mon Sep 17 00:00:00 2001 From: milkmaker Date: Mon, 1 Jan 2024 00:15:22 +0000 Subject: [PATCH 021/793] update postscreen_access.cidr --- data/conf/postfix/postscreen_access.cidr | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 3c51e8362..0497e64a0 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,6 +1,6 @@ -# Whitelist generated by Postwhite v3.4 on Fri Dec 1 00:15:18 UTC 2023 +# Whitelist generated by Postwhite v3.4 on Mon Jan 1 00:15:22 UTC 2024 # https://github.com/stevejenkins/postwhite/ -# 2038 total rules +# 2052 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:8000::/50 permit @@ -13,7 +13,7 @@ 3.70.123.177 permit 3.93.157.0/24 permit 3.129.120.190 permit -3.137.78.75 permit +3.137.16.58 permit 3.210.190.0/24 permit 8.20.114.31 permit 8.25.194.0/23 permit @@ -183,6 +183,8 @@ 50.18.125.237 permit 50.18.126.162 permit 50.31.32.0/19 permit +50.56.130.220 permit +50.56.130.221 permit 51.137.58.21 permit 51.140.75.55 permit 51.144.100.179 permit @@ -596,6 +598,7 @@ 74.208.5.64/26 permit 74.208.122.0/26 permit 74.209.250.0/24 permit +75.2.70.75 permit 76.223.128.0/19 permit 76.223.176.0/20 permit 77.238.176.0/22 permit @@ -1186,6 +1189,7 @@ 98.139.245.208/30 permit 98.139.245.212/31 permit 99.78.197.208/28 permit +99.83.190.102 permit 103.2.140.0/22 permit 103.9.96.0/22 permit 103.28.42.0/24 permit @@ -1460,6 +1464,8 @@ 144.178.38.0/24 permit 145.253.228.160/29 permit 145.253.239.128/29 permit +146.20.14.105 permit +146.20.14.107 permit 146.20.112.0/26 permit 146.20.113.0/24 permit 146.20.191.0/24 permit @@ -1534,6 +1540,10 @@ 163.47.180.0/23 permit 163.114.130.16 permit 163.114.132.120 permit +164.177.132.168 permit +164.177.132.169 permit +164.177.132.170 permit +164.177.132.171 permit 165.173.128.0/24 permit 166.78.68.0/22 permit 166.78.68.221 permit @@ -1726,6 +1736,7 @@ 199.34.22.36 permit 199.59.148.0/22 permit 199.67.80.2 permit +199.67.82.2 permit 199.67.84.0/24 permit 199.67.86.0/24 permit 199.67.88.0/24 permit @@ -1789,6 +1800,7 @@ 204.92.114.187 permit 204.92.114.203 permit 204.92.114.204/31 permit +204.132.224.66 permit 204.141.32.0/23 permit 204.141.42.0/23 permit 204.220.160.0/20 permit @@ -1832,6 +1844,8 @@ 207.67.98.192/27 permit 207.68.176.0/26 permit 207.68.176.96/27 permit +207.97.204.96 permit +207.97.204.97 permit 207.126.144.0/20 permit 207.171.160.0/19 permit 207.211.30.64/26 permit From 6ab45cf6684de9807674ca1c71abd99937e06975 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 8 Jan 2024 14:43:25 +0100 Subject: [PATCH 022/793] db: bumped version to newer timestamp --- data/web/inc/init_db.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php index 68956d158..6fec0c2f8 100644 --- a/data/web/inc/init_db.inc.php +++ b/data/web/inc/init_db.inc.php @@ -3,7 +3,7 @@ function init_db_schema() { try { global $pdo; - $db_version = "21122023_1526"; + $db_version = "08012024_1442"; $stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); From b960143045ac5705a7250bffa5daa52838be0d10 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 9 Jan 2024 11:09:35 +0100 Subject: [PATCH 023/793] translation: update de-de.json --- data/web/lang/lang.de-de.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index dd1377b4c..3b74d202f 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -588,10 +588,18 @@ "disable_login": "Login verbieten (Mails werden weiterhin angenommen)", "domain": "Domain bearbeiten", "domain_admin": "Domain-Administrator bearbeiten", - "domain_footer": "Domain wide footer", - "domain_footer_html": "HTML footer", - "domain_footer_info": "Domain wide footer werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.
Die folgenden Variablen können für den Footer benutzt werden:", - "domain_footer_plain": "PLAIN footer", + "domain_footer": "Domänenweite Fußzeile", + "domain_footer_html": "Fußzeile im HTML Format", + "domain_footer_info": "Domänenweite Footer (Domain wide footer) werden allen ausgehenden E-Mails hinzugefügt, die einer Adresse innerhalb dieser Domain gehört.
Die folgenden Variablen können für die Fußzeile benutzt werden:", + "domain_footer_info_vars": { + "auth_user": "{= auth_user =} - Angemeldeter Benutzername vom MTA", + "from_user": "{= from_user =} - Absender Teil der E-Mail z.B. für \"moo@mailcow.tld\" wird \"moo\" zurückgeben.", + "from_name": "{= from_name =} - Namen des Absenders z.B. für \"Mailcow <moo@mailcow.tld>\", wird \"Mailcow\" zurückgegeben.", + "from_addr": "{= from_addr =} - Adresse des Absenders.", + "from_domain": "{= from_domain =} - Domain des Absenders", + "custom": "{= foo =} - Wenn die Mailbox das benutzerdefinierte Attribut \"foo\" mit dem Wert \"bar\" hat, wird \"bar\" zurückgegeben." + }, + "domain_footer_plain": "Fußzeile im PLAIN Format", "domain_footer_skip_replies": "Ignoriere Footer bei Antwort E-Mails", "domain_quota": "Domain Speicherplatz gesamt (MiB)", "domains": "Domains", From 5896766fc3e8c732ef7a0822fb3b84a473f4c5e7 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 13 Dec 2023 18:39:21 +0100 Subject: [PATCH 024/793] Update to Alpine 3.19 --- data/Dockerfiles/acme/Dockerfile | 3 ++- data/Dockerfiles/dockerapi/Dockerfile | 3 ++- data/Dockerfiles/netfilter/Dockerfile | 3 ++- data/Dockerfiles/olefy/Dockerfile | 3 ++- data/Dockerfiles/phpfpm/Dockerfile | 2 +- data/Dockerfiles/unbound/Dockerfile | 2 +- data/Dockerfiles/watchdog/Dockerfile | 2 +- 7 files changed, 11 insertions(+), 7 deletions(-) diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index 898dd8b66..27f65e651 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,7 +1,8 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "The Infrastructure Company GmbH " +ARG PIP_BREAK_SYSTEM_PACKAGES=1 RUN apk upgrade --no-cache \ && apk add --update --no-cache \ bash \ diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 3431f9393..23dcbb736 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -1,7 +1,8 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "The Infrastructure Company GmbH " +ARG PIP_BREAK_SYSTEM_PACKAGES=1 WORKDIR /app RUN apk add --update --no-cache python3 \ diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile index 8f76ec639..b6706e307 100644 --- a/data/Dockerfiles/netfilter/Dockerfile +++ b/data/Dockerfiles/netfilter/Dockerfile @@ -1,8 +1,9 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "The Infrastructure Company GmbH " WORKDIR /app +ARG PIP_BREAK_SYSTEM_PACKAGES=1 ENV XTABLES_LIBDIR /usr/lib/xtables ENV PYTHON_IPTABLES_XTABLES_VERSION 12 ENV IPTABLES_LIBDIR /usr/lib diff --git a/data/Dockerfiles/olefy/Dockerfile b/data/Dockerfiles/olefy/Dockerfile index 06d4679f2..bd6e0af35 100644 --- a/data/Dockerfiles/olefy/Dockerfile +++ b/data/Dockerfiles/olefy/Dockerfile @@ -1,6 +1,7 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "The Infrastructure Company GmbH " +ARG PIP_BREAK_SYSTEM_PACKAGES=1 WORKDIR /app #RUN addgroup -S olefy && adduser -S olefy -G olefy \ diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index 490310336..c954ac997 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2-fpm-alpine3.17 +FROM php:8.2-fpm-alpine3.19 LABEL maintainer "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index b19090835..47dba6d6a 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "The Infrastructure Company GmbH " diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 654dea08b..94a28fd0e 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.17 +FROM alpine:3.19 LABEL maintainer "André Peters " # Installation From 333b7ebc0cadfedbf2393eddac4ebc79cae6b187 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 8 Jan 2024 12:07:22 +0100 Subject: [PATCH 025/793] Fix Alpine 3.19 dependencies --- data/Dockerfiles/dockerapi/Dockerfile | 5 +++-- data/Dockerfiles/dockerapi/main.py | 2 +- data/Dockerfiles/netfilter/Dockerfile | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index 23dcbb736..d11f5dda6 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -10,12 +10,13 @@ RUN apk add --update --no-cache python3 \ openssl \ tzdata \ py3-psutil \ + py3-redis \ + py3-async-timeout \ && pip3 install --upgrade pip \ fastapi \ uvicorn \ aiodocker \ - docker \ - aioredis + docker RUN mkdir /app/modules COPY docker-entrypoint.sh /app/ diff --git a/data/Dockerfiles/dockerapi/main.py b/data/Dockerfiles/dockerapi/main.py index f9f02b63f..2842789b5 100644 --- a/data/Dockerfiles/dockerapi/main.py +++ b/data/Dockerfiles/dockerapi/main.py @@ -5,13 +5,13 @@ import json import uuid import async_timeout import asyncio -import aioredis import aiodocker import docker import logging from logging.config import dictConfig from fastapi import FastAPI, Response, Request from modules.DockerApi import DockerApi +from redis import asyncio as aioredis dockerapi = None app = FastAPI() diff --git a/data/Dockerfiles/netfilter/Dockerfile b/data/Dockerfiles/netfilter/Dockerfile index b6706e307..8a561f060 100644 --- a/data/Dockerfiles/netfilter/Dockerfile +++ b/data/Dockerfiles/netfilter/Dockerfile @@ -15,6 +15,7 @@ RUN apk add --virtual .build-deps \ openssl-dev \ && apk add -U python3 \ iptables \ + iptables-dev \ ip6tables \ xtables-addons \ nftables \ From f442378377df798afec9419cb6a9d9be57ed4c42 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 9 Jan 2024 11:18:55 +0100 Subject: [PATCH 026/793] dockerfiles: updated maintainer --- data/Dockerfiles/watchdog/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index 94a28fd0e..b94789aac 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,5 +1,5 @@ FROM alpine:3.19 -LABEL maintainer "André Peters " +LABEL maintainer "The Infrastructure Company GmbH " # Installation RUN apk add --update \ From 25007b19633a3ae121981206854fb3dc1e9e86a8 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 9 Jan 2024 11:50:22 +0100 Subject: [PATCH 027/793] dockerapi: implemented lifespan function --- data/Dockerfiles/dockerapi/main.py | 97 +++++++++++++++--------------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/data/Dockerfiles/dockerapi/main.py b/data/Dockerfiles/dockerapi/main.py index 2842789b5..fca61bb02 100644 --- a/data/Dockerfiles/dockerapi/main.py +++ b/data/Dockerfiles/dockerapi/main.py @@ -12,9 +12,56 @@ from logging.config import dictConfig from fastapi import FastAPI, Response, Request from modules.DockerApi import DockerApi from redis import asyncio as aioredis +from contextlib import asynccontextmanager dockerapi = None -app = FastAPI() + +@asynccontextmanager +async def lifespan(app: FastAPI): + global dockerapi + + # Initialize a custom logger + logger = logging.getLogger("dockerapi") + logger.setLevel(logging.INFO) + # Configure the logger to output logs to the terminal + handler = logging.StreamHandler() + handler.setLevel(logging.INFO) + formatter = logging.Formatter("%(levelname)s: %(message)s") + handler.setFormatter(formatter) + logger.addHandler(handler) + + logger.info("Init APP") + + # Init redis client + if os.environ['REDIS_SLAVEOF_IP'] != "": + redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0") + else: + redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0") + + # Init docker clients + sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') + async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') + + dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger) + + logger.info("Subscribe to redis channel") + # Subscribe to redis channel + dockerapi.pubsub = redis.pubsub() + await dockerapi.pubsub.subscribe("MC_CHANNEL") + asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub)) + + + yield + + # Close docker connections + dockerapi.sync_docker_client.close() + await dockerapi.async_docker_client.close() + + # Close redis + await dockerapi.pubsub.unsubscribe("MC_CHANNEL") + await dockerapi.redis_client.close() + +app = FastAPI(lifespan=lifespan) # Define Routes @app.get("/host/stats") @@ -144,53 +191,7 @@ async def post_container_update_stats(container_id : str): stats = json.loads(await dockerapi.redis_client.get(container_id + '_stats')) return Response(content=json.dumps(stats, indent=4), media_type="application/json") - -# Events -@app.on_event("startup") -async def startup_event(): - global dockerapi - - # Initialize a custom logger - logger = logging.getLogger("dockerapi") - logger.setLevel(logging.INFO) - # Configure the logger to output logs to the terminal - handler = logging.StreamHandler() - handler.setLevel(logging.INFO) - formatter = logging.Formatter("%(levelname)s: %(message)s") - handler.setFormatter(formatter) - logger.addHandler(handler) - - logger.info("Init APP") - - # Init redis client - if os.environ['REDIS_SLAVEOF_IP'] != "": - redis_client = redis = await aioredis.from_url(f"redis://{os.environ['REDIS_SLAVEOF_IP']}:{os.environ['REDIS_SLAVEOF_PORT']}/0") - else: - redis_client = redis = await aioredis.from_url("redis://redis-mailcow:6379/0") - - # Init docker clients - sync_docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto') - async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock') - - dockerapi = DockerApi(redis_client, sync_docker_client, async_docker_client, logger) - - logger.info("Subscribe to redis channel") - # Subscribe to redis channel - dockerapi.pubsub = redis.pubsub() - await dockerapi.pubsub.subscribe("MC_CHANNEL") - asyncio.create_task(handle_pubsub_messages(dockerapi.pubsub)) - -@app.on_event("shutdown") -async def shutdown_event(): - global dockerapi - - # Close docker connections - dockerapi.sync_docker_client.close() - await dockerapi.async_docker_client.close() - - # Close redis - await dockerapi.pubsub.unsubscribe("MC_CHANNEL") - await dockerapi.redis_client.close() + # PubSub Handler async def handle_pubsub_messages(channel: aioredis.client.PubSub): From 1ee3bb42f359fc606a5946325e04769a1f2a71bb Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 9 Jan 2024 11:55:32 +0100 Subject: [PATCH 028/793] compose: updated image tags --- docker-compose.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2521e8164..267d1f133 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.18 + image: mailcow/unbound:1.19 environment: - TZ=${TZ} volumes: @@ -107,7 +107,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.85 + image: mailcow/phpfpm:1.86 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -398,7 +398,7 @@ services: condition: service_started unbound-mailcow: condition: service_healthy - image: mailcow/acme:1.85 + image: mailcow/acme:1.86 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: @@ -434,7 +434,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.54 + image: mailcow/netfilter:1.55 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -457,7 +457,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:2.00 + image: mailcow/watchdog:2.01 dns: - ${IPV4_NETWORK:-172.22.1}.254 tmpfs: @@ -529,7 +529,7 @@ services: - watchdog dockerapi-mailcow: - image: mailcow/dockerapi:2.06 + image: mailcow/dockerapi:2.07 security_opt: - label=disable restart: always @@ -564,7 +564,7 @@ services: - solr olefy-mailcow: - image: mailcow/olefy:1.11 + image: mailcow/olefy:1.12 restart: always environment: - TZ=${TZ} From b5a1a18b041cea4b725ea2850721ac9581416971 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 9 Jan 2024 12:20:30 +0100 Subject: [PATCH 029/793] lang: fixed totp langs --- data/web/lang/lang.de-de.json | 1 + data/web/lang/lang.en-gb.json | 1 + data/web/templates/modals/footer.twig | 8 ++++---- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 3b74d202f..3efd5afa0 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -1093,6 +1093,7 @@ "verified_yotp_login": "Yubico-OTP-Anmeldung verifiziert" }, "tfa": { + "authenticators": "Authentikatoren", "api_register": "%s verwendet die Yubico-Cloud-API. Ein API-Key für den Yubico-Stick kann hier bezogen werden.", "confirm": "Bestätigen", "confirm_totp_token": "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 173b258a1..58ad66655 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -1100,6 +1100,7 @@ "verified_yotp_login": "Verified Yubico OTP login" }, "tfa": { + "authenticators": "Authenticators", "api_register": "%s uses the Yubico Cloud API. Please get an API key for your key here", "confirm": "Confirm", "confirm_totp_token": "Please confirm your changes by entering the generated token", diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index ab5630d19..8ff112d5d 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -155,7 +155,7 @@ {% if pending_tfa_authmechs["totp"] is defined and pending_tfa_authmechs["u2f"] is not defined %} {% endif %} @@ -173,7 +173,7 @@
- Authenticators + {{ lang.tfa.authenticators }}
@@ -216,7 +216,7 @@ - Authenticate + {{ lang.tfa.authenticators }}
@@ -244,7 +244,7 @@ - Authenticators + {{ lang.tfa.authenticators }}
From 6dc0bdbfa377eef1198063bccd41bd86a0858800 Mon Sep 17 00:00:00 2001 From: Tomasz Orzechowski Date: Tue, 9 Jan 2024 22:03:24 +0100 Subject: [PATCH 030/793] Proper number of threads regex. --- helper-scripts/backup_and_restore.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index ee9f0202d..9a0561059 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -54,10 +54,10 @@ COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml ENV_FILE=${SCRIPT_DIR}/../.env THREADS=$(echo ${THREADS:-1}) -if ! [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then +if ! [[ "${THREADS}" =~ ^[1-9][0-9]?$ ]] ; then echo "Thread input is not a number!" exit 1 -elif [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then +elif [[ "${THREADS}" =~ ^[1-9][0-9]?$ ]] ; then echo "Using ${THREADS} Thread(s) for this run." echo "Notice: You can set the Thread count with the THREADS Variable before you run this script." fi From cf9f02adbbec76ae39457952dfb88ec5acce5129 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 10 Jan 2024 14:43:59 +0100 Subject: [PATCH 031/793] ui: fix alignment secondary --- data/web/css/build/014-mailcow.css | 2 +- data/web/templates/base.twig | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/data/web/css/build/014-mailcow.css b/data/web/css/build/014-mailcow.css index edc6b3d78..6c70a2a55 100644 --- a/data/web/css/build/014-mailcow.css +++ b/data/web/css/build/014-mailcow.css @@ -228,8 +228,8 @@ legend { margin-top: 20px; } .slave-info { - padding: 15px 0px 15px 15px; font-weight: bold; + color: orange; } .alert-hr { margin:3px 0px; diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 0b1c60a29..ca744d2a3 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -114,7 +114,9 @@ {% endif %} {% if not is_master %} - + {% endif %}
From b29dc3799114089471253d31295a290c239b8488 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 15 Jan 2024 15:13:37 +0100 Subject: [PATCH 032/793] unbound: rewrote healthcheck to be more detailed unbound: added comments to rewritten healthcheck --- data/Dockerfiles/unbound/Dockerfile | 4 +- data/Dockerfiles/unbound/healthcheck.sh | 91 +++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 8 deletions(-) diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 47dba6d6a..1e0192f87 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -4,6 +4,8 @@ LABEL maintainer "The Infrastructure Company GmbH " RUN apk add --update --no-cache \ curl \ + bind-tools \ + netcat-openbsd \ unbound \ bash \ openssl \ @@ -21,7 +23,7 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh # healthcheck (nslookup) COPY healthcheck.sh /healthcheck.sh RUN chmod +x /healthcheck.sh -HEALTHCHECK --interval=30s --timeout=10s CMD [ "/healthcheck.sh" ] +HEALTHCHECK --interval=5s --timeout=10s CMD [ "/healthcheck.sh" ] ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/data/Dockerfiles/unbound/healthcheck.sh b/data/Dockerfiles/unbound/healthcheck.sh index 8c4508fb1..ea94f63b3 100644 --- a/data/Dockerfiles/unbound/healthcheck.sh +++ b/data/Dockerfiles/unbound/healthcheck.sh @@ -1,12 +1,89 @@ #!/bin/bash -nslookup mailcow.email 127.0.0.1 1> /dev/null +# Declare log function for logfile inside container +function log_to_file() { + echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" > /var/log/healthcheck.log +} -if [ $? == 0 ]; then - echo "DNS resolution is working!" - exit 0 -else - echo "DNS resolution is not working correctly..." - echo "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!" +# General Ping function to check general pingability +function check_ping() { + declare -a ipstoping=("1.1.1.1" "8.8.8.8" "9.9.9.9") + + for ip in "${ipstoping[@]}" ; do + ping -q -c 3 -w 5 "$ip" + if [ $? -ne 0 ]; then + log_to_file "Healthcheck: Couldn't ping $ip for 5 seconds... Gave up!" + log_to_file "Please check your internet connection or firewall rules to fix this error, because a simple ping test should always go through from the unbound container!" + return 1 + fi + done + + log_to_file "Healthcheck: Ping Checks WORKING properly!" + return 0 +} + +# General DNS Resolve Check against Unbound Resolver himself +function check_dns() { + declare -a domains=("mailcow.email" "github.com" "hub.docker.com") + + for domain in "${domains[@]}" ; do + for ((i=1; i<=3; i++)); do + dig +short +timeout=2 +tries=1 "$domain" @127.0.0.1 > /dev/null + if [ $? -ne 0 ]; then + log_to_file "Healthcheck: DNS Resolution Failed on $i attempt! Trying again..." + if [ $i -eq 3 ]; then + log_to_file "Healthcheck: DNS Resolution not possible after $i attempts... Gave up!" + log_to_file "Maybe check your outbound firewall, as it needs to resolve DNS over TCP AND UDP!" + return 1 + fi + fi + done + done + + log_to_file "Healthcheck: DNS Resolver WORKING properly!" + return 0 + +} + +# Simple Netcat Check to connect to common webports +function check_netcat() { + declare -a domains=("mailcow.email" "github.com" "hub.docker.com") + declare -a ports=("80" "443") + + for domain in "${domains[@]}" ; do + for port in "${ports[@]}" ; do + nc -z -w 2 $domain $port + if [ $? -ne 0 ]; then + log_to_file "Healthcheck: Could not reach $domain on Port $port... Gave up!" + log_to_file "Please check your internet connection or firewall rules to fix this error." + return 1 + fi + done + done + + log_to_file "Healthcheck: Netcat Checks WORKING properly!" + return 0 + +} + +# run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy) +check_ping + +if [ $? -ne 0 ]; then exit 1 fi + +check_dns + +if [ $? -ne 0 ]; then + exit 1 +fi + +check_netcat + +if [ $? -ne 0 ]; then + exit 1 +fi + +log_to_file "Healthcheck: ALL CHECKS WERE SUCCESSFUL! Unbound is healthy!" +exit 0 \ No newline at end of file From 7f6f7e0e9ff608618e5b144bcf18d279610aa3ed Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 15 Jan 2024 16:34:47 +0100 Subject: [PATCH 033/793] [Web] limit logo file upload --- data/web/inc/functions.customize.inc.php | 18 ++++++++++++++++++ data/web/inc/vars.inc.php | 9 +++++++++ data/web/lang/lang.de-de.json | 2 ++ data/web/lang/lang.en-gb.json | 2 ++ 4 files changed, 31 insertions(+) diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index 0da8c3563..b72923573 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -2,6 +2,7 @@ function customize($_action, $_item, $_data = null) { global $redis; global $lang; + global $LOGO_LIMITS; switch ($_action) { case 'add': @@ -35,6 +36,23 @@ function customize($_action, $_item, $_data = null) { ); return false; } + if ($_data[$_item]['size'] > $LOGO_LIMITS['max_size']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_size_exceeded' + ); + return false; + } + list($width, $height) = getimagesize($_data[$_item]['tmp_name']); + if ($width > $LOGO_LIMITS['max_width'] || $height > $LOGO_LIMITS['max_height']) { + $_SESSION['return'][] = array( + 'type' => 'danger', + 'log' => array(__FUNCTION__, $_action, $_item, $_data), + 'msg' => 'img_dimensions_exceeded' + ); + return false; + } $image = new Imagick($_data[$_item]['tmp_name']); if ($image->valid() !== true) { $_SESSION['return'][] = array( diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php index 5578dfd3c..afc801e44 100644 --- a/data/web/inc/vars.inc.php +++ b/data/web/inc/vars.inc.php @@ -126,6 +126,15 @@ $MAILCOW_APPS = array( ) ); +// Logo max file size in bytes +$LOGO_LIMITS['max_size'] = 15 * 1024 * 1024; // 15MB + +// Logo max width in pixels +$LOGO_LIMITS['max_width'] = 1920; + +// Logo max height in pixels +$LOGO_LIMITS['max_height'] = 1920; + // Rows until pagination begins $PAGINATION_SIZE = 25; diff --git a/data/web/lang/lang.de-de.json b/data/web/lang/lang.de-de.json index 3efd5afa0..ddadfac63 100644 --- a/data/web/lang/lang.de-de.json +++ b/data/web/lang/lang.de-de.json @@ -394,7 +394,9 @@ "goto_invalid": "Ziel-Adresse %s ist ungültig", "ham_learn_error": "Ham Lernfehler: %s", "imagick_exception": "Fataler Bildverarbeitungsfehler", + "img_dimensions_exceeded": "Grafik überschreitet die maximale Bildgröße", "img_invalid": "Grafik konnte nicht validiert werden", + "img_size_exceeded": "Grafik überschreitet die maximale Dateigröße", "img_tmp_missing": "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen.", "invalid_bcc_map_type": "Ungültiger BCC-Map-Typ", "invalid_destination": "Ziel-Format \"%s\" ist ungültig", diff --git a/data/web/lang/lang.en-gb.json b/data/web/lang/lang.en-gb.json index 58ad66655..ec97d0aef 100644 --- a/data/web/lang/lang.en-gb.json +++ b/data/web/lang/lang.en-gb.json @@ -394,7 +394,9 @@ "goto_invalid": "Goto address %s is invalid", "ham_learn_error": "Ham learn error: %s", "imagick_exception": "Error: Imagick exception while reading image", + "img_dimensions_exceeded": "Image exceeds the maximum image size", "img_invalid": "Cannot validate image file", + "img_size_exceeded": "Image exceeds the maximum file size", "img_tmp_missing": "Cannot validate image file: Temporary file not found", "invalid_bcc_map_type": "Invalid BCC map type", "invalid_destination": "Destination format \"%s\" is invalid", From ac4f131fa8f1629b1c831478d020f5f1b6be4261 Mon Sep 17 00:00:00 2001 From: Kristian Feldsam Date: Wed, 27 Dec 2023 16:29:25 +0100 Subject: [PATCH 034/793] Domains and Mailboxes datatable - server side processing - filtering by tags Signed-off-by: Kristian Feldsam --- data/web/inc/lib/ssp.class.php | 44 ++++++++++++++++++++++++---------- data/web/js/site/mailbox.js | 3 ++- data/web/json_api.php | 2 ++ 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/data/web/inc/lib/ssp.class.php b/data/web/inc/lib/ssp.class.php index a06e7d09e..503f9b295 100644 --- a/data/web/inc/lib/ssp.class.php +++ b/data/web/inc/lib/ssp.class.php @@ -177,6 +177,7 @@ class SSP { { $globalSearch = array(); $columnSearch = array(); + $joins = array(); $dtColumns = self::pluck( $columns, 'dt' ); if ( isset($request['search']) && $request['search']['value'] != '' ) { @@ -184,13 +185,19 @@ class SSP { for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) { $requestColumn = $request['columns'][$i]; - $columnIdx = array_search( $requestColumn['data'], $dtColumns ); + $columnIdx = array_search( $i, $dtColumns ); $column = $columns[ $columnIdx ]; if ( $requestColumn['searchable'] == 'true' ) { if(!empty($column['db'])){ - $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); - $globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); + + if(isset($column['search']['join'])) { + $joins[] = $column['search']['join']; + $globalSearch[] = $column['search']['where_column'].' LIKE '.$binding; + } else { + $globalSearch[] = "`".$tablesAS."`.`".$column['db']."` LIKE ".$binding; + } } } } @@ -227,12 +234,17 @@ class SSP { implode(' AND ', $columnSearch) : $where .' AND '. implode(' AND ', $columnSearch); } + + $join = ''; + if( count($joins) ) { + $join = implode(' ', $joins); + } if ( $where !== '' ) { $where = 'WHERE '.$where; } - return $where; + return [$join, $where]; } @@ -270,13 +282,14 @@ class SSP { // Build the SQL query string from the request list($select, $order) = self::order( $tablesAS, $request, $columns ); $limit = self::limit( $request, $columns ); - $where = self::filter( $tablesAS, $request, $columns, $bindings ); + list($join, $where) = self::filter( $tablesAS, $request, $columns, $bindings ); // Main query to actually get the data $data = self::sql_exec( $db, $bindings, "SELECT `$tablesAS`.`".implode("`, `$tablesAS`.`", self::pluck($columns, 'db'))."` $select FROM `$table` AS `$tablesAS` + $join $where $order $limit" @@ -284,15 +297,16 @@ class SSP { // Data set length after filtering $resFilterLength = self::sql_exec( $db, $bindings, - "SELECT COUNT(`{$primaryKey}`) + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` + $join $where" ); $recordsFiltered = $resFilterLength[0][0]; // Total data set length $resTotalLength = self::sql_exec( $db, - "SELECT COUNT(`{$primaryKey}`) + "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS`" ); $recordsTotal = $resTotalLength[0][0]; @@ -362,7 +376,7 @@ class SSP { // Build the SQL query string from the request list($select, $order) = self::order( $tablesAS, $request, $columns ); $limit = self::limit( $request, $columns ); - $where = self::filter( $tablesAS, $request, $columns, $bindings ); + list($join_filter, $where) = self::filter( $tablesAS, $request, $columns, $bindings ); // whereResult can be a simple string, or an assoc. array with a // condition and bindings @@ -388,7 +402,9 @@ class SSP { $select FROM `$table` AS `$tablesAS` $join + $join_filter $where + GROUP BY `{$tablesAS}`.`{$primaryKey}` $order $limit" ); @@ -398,18 +414,22 @@ class SSP { "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` $join - $where" + $join_filter + $where + GROUP BY `{$tablesAS}`.`{$primaryKey}`" ); - $recordsFiltered = $resFilterLength[0][0]; + $recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0; // Total data set length $resTotalLength = self::sql_exec( $db, $bindings, "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` $join - $where" + $join_filter + $where + GROUP BY `{$tablesAS}`.`{$primaryKey}`" ); - $recordsTotal = $resTotalLength[0][0]; + $recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0; /* * Output diff --git a/data/web/js/site/mailbox.js b/data/web/js/site/mailbox.js index b1ec94824..cc316b713 100644 --- a/data/web/js/site/mailbox.js +++ b/data/web/js/site/mailbox.js @@ -613,7 +613,7 @@ jQuery(function($){ { title: 'Tags', data: 'tags', - searchable: false, + searchable: true, orderable: false, defaultContent: '', className: 'none' @@ -1107,6 +1107,7 @@ jQuery(function($){ { title: 'Tags', data: 'tags', + searchable: true, defaultContent: '', className: 'none' }, diff --git a/data/web/json_api.php b/data/web/json_api.php index 403b5a792..05d549030 100644 --- a/data/web/json_api.php +++ b/data/web/json_api.php @@ -535,6 +535,7 @@ if (isset($_GET['query'])) { ['db' => 'defquota', 'dt' => 7], ['db' => 'maxquota', 'dt' => 8], ['db' => 'backupmx', 'dt' => 10], + ['db' => 'tags', 'dt' => 14, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_domain` AS `td` ON `td`.`domain` = `d`.`domain`', 'where_column' => '`td`.`tag_name`']], ['db' => 'active', 'dt' => 15], ]; @@ -1061,6 +1062,7 @@ if (isset($_GET['query'])) { ['db' => 'last_pw_change', 'dt' => 5, 'dummy' => true, 'order_subquery' => "JSON_EXTRACT(attributes, '$.passwd_update')"], ['db' => 'in_use', 'dt' => 6, 'dummy' => true, 'order_subquery' => "(SELECT SUM(bytes) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`) / `m`.`quota`"], ['db' => 'messages', 'dt' => 17, 'dummy' => true, 'order_subquery' => "SELECT SUM(messages) FROM `quota2` WHERE `quota2`.`username` = `m`.`username`"], + ['db' => 'tags', 'dt' => 20, 'dummy' => true, 'search' => ['join' => 'LEFT JOIN `tags_mailbox` AS `tm` ON `tm`.`username` = `m`.`username`', 'where_column' => '`tm`.`tag_name`']], ['db' => 'active', 'dt' => 21] ]; From 99d9a2eacdfffc27604e2b7c572638c456398d19 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 17 Jan 2024 09:52:43 +0100 Subject: [PATCH 035/793] [Web] fix mailbox and domain creation --- data/web/inc/functions.mailbox.inc.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php index d95ee5349..699709ce9 100644 --- a/data/web/inc/functions.mailbox.inc.php +++ b/data/web/inc/functions.mailbox.inc.php @@ -477,12 +477,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { 'msg' => 'access_denied' ); return false; - } + } $DOMAIN_DEFAULT_ATTRIBUTES = null; if ($_data['template']){ $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates', $_data['template'])['attributes']; } - if (empty($DOMAIN_DEFAULT_ATTRIBUTES)){ + if (empty($DOMAIN_DEFAULT_ATTRIBUTES)) { $DOMAIN_DEFAULT_ATTRIBUTES = mailbox('get', 'domain_templates')[0]['attributes']; } @@ -634,12 +634,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { return false; } $_data['rl_value'] = (isset($_data['rl_value'])) ? intval($_data['rl_value']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_value']; - $_data['rl_frame'] = (isset($_data['rl_frame'])) ? intval($_data['rl_frame']) : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame']; + $_data['rl_frame'] = (isset($_data['rl_frame'])) ? $_data['rl_frame'] : $DOMAIN_DEFAULT_ATTRIBUTES['rl_frame']; if (!empty($_data['rl_value']) && !empty($_data['rl_frame'])){ ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain)); } $_data['key_size'] = (isset($_data['key_size'])) ? intval($_data['key_size']) : $DOMAIN_DEFAULT_ATTRIBUTES['key_size']; - $_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? intval($_data['dkim_selector']) : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector']; + $_data['dkim_selector'] = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : $DOMAIN_DEFAULT_ATTRIBUTES['dkim_selector']; if (!empty($_data['key_size']) && !empty($_data['dkim_selector'])) { if (!empty($redis->hGet('DKIM_SELECTORS', $domain))) { $_SESSION['return'][] = array( @@ -1021,13 +1021,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) { if (empty($name)) { $name = $local_part; } - $MAILBOX_DEFAULT_ATTRIBUTES = null; + $template_attr = null; if ($_data['template']){ - $MAILBOX_DEFAULT_ATTRIBUTES = mailbox('get', 'mailbox_templates', $_data['template'])['attributes']; + $template_attr = mailbox('get', 'mailbox_templates', $_data['template'])['attributes']; } - if (empty($MAILBOX_DEFAULT_ATTRIBUTES)){ - $MAILBOX_DEFAULT_ATTRIBUTES = mailbox('get', 'mailbox_templates')[0]['attributes']; + if (empty($template_attr)) { + $template_attr = mailbox('get', 'mailbox_templates')[0]['attributes']; } + $MAILBOX_DEFAULT_ATTRIBUTES = array_merge($MAILBOX_DEFAULT_ATTRIBUTES, $template_attr); + $password = $_data['password']; $password2 = $_data['password2']; $name = ltrim(rtrim($_data['name'], '>'), '<'); From accedf028003ad6f0511a41a8773562458038bed Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 26 Apr 2023 08:37:20 +0000 Subject: [PATCH 036/793] Updated mailcow Components to be ARM64 compatible --- data/Dockerfiles/acme/Dockerfile | 2 +- data/Dockerfiles/clamd/Dockerfile | 10 +- data/Dockerfiles/clamd/clamdcheck.sh | 14 ++ data/Dockerfiles/dockerapi/Dockerfile | 2 +- data/Dockerfiles/dovecot/Dockerfile | 196 +++++++++--------- data/Dockerfiles/dovecot/docker-entrypoint.sh | 4 + data/Dockerfiles/dovecot/quarantine_notify.py | 5 +- data/Dockerfiles/dovecot/quota_notify.py | 2 +- data/Dockerfiles/dovecot/supervisord.conf | 4 + .../dovecot/syslog-ng-redis_slave.conf | 10 +- data/Dockerfiles/dovecot/syslog-ng.conf | 10 +- data/Dockerfiles/postfix/Dockerfile | 2 +- data/Dockerfiles/rspamd/Dockerfile | 4 +- data/Dockerfiles/sogo/Dockerfile | 6 +- data/Dockerfiles/unbound/Dockerfile | 2 +- data/Dockerfiles/watchdog/watchdog.sh | 4 +- docker-compose.yml | 17 +- generate_config.sh | 6 +- helper-scripts/_cold-standby.sh | 30 ++- helper-scripts/backup_and_restore.sh | 45 +++- update.sh | 10 +- 21 files changed, 232 insertions(+), 153 deletions(-) create mode 100644 data/Dockerfiles/clamd/clamdcheck.sh diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index 27f65e651..08271bddb 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.19 -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG PIP_BREAK_SYSTEM_PACKAGES=1 RUN apk upgrade --no-cache \ diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index 31a332d7a..8e107516b 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -1,12 +1,14 @@ -FROM clamav/clamav:1.0.3_base +FROM alpine:3.19 -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " RUN apk upgrade --no-cache \ && apk add --update --no-cache \ rsync \ + clamav \ bind-tools \ - bash + bash \ + tini # init COPY clamd.sh /clamd.sh @@ -14,7 +16,9 @@ RUN chmod +x /sbin/tini # healthcheck COPY healthcheck.sh /healthcheck.sh +COPY clamdcheck.sh /usr/local/bin RUN chmod +x /healthcheck.sh +RUN chmod +x /usr/local/bin/clamdcheck.sh HEALTHCHECK --start-period=6m CMD "/healthcheck.sh" ENTRYPOINT [] diff --git a/data/Dockerfiles/clamd/clamdcheck.sh b/data/Dockerfiles/clamd/clamdcheck.sh new file mode 100644 index 000000000..7884d48a9 --- /dev/null +++ b/data/Dockerfiles/clamd/clamdcheck.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -eu + +if [ "${CLAMAV_NO_CLAMD:-}" != "false" ]; then + if [ "$(echo "PING" | nc localhost 3310)" != "PONG" ]; then + echo "ERROR: Unable to contact server" + exit 1 + fi + + echo "Clamd is up" +fi + +exit 0 \ No newline at end of file diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile index d11f5dda6..3dd1d232e 100644 --- a/data/Dockerfiles/dockerapi/Dockerfile +++ b/data/Dockerfiles/dockerapi/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.19 -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG PIP_BREAK_SYSTEM_PACKAGES=1 WORKDIR /app diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index d1413b2ae..9433dd2ea 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -1,119 +1,115 @@ -FROM debian:bullseye-slim -LABEL maintainer "The Infrastructure Company GmbH " +FROM alpine:3.19 +LABEL maintainer "The Infrastructure Company GmbH GmbH " -ARG DEBIAN_FRONTEND=noninteractive -# renovate: datasource=github-tags depName=dovecot/core versioning=semver-coerced extractVersion=(?.*)$ -ARG DOVECOT=2.3.21 -# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=(?.*)$ -ARG GOSU_VERSION=1.17 -ENV LC_ALL C +# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ +ARG GOSU_VERSION=1.16 +ENV LANG C.UTF-8 +ENV LC_ALL C.UTF-8 # Add groups and users before installing Dovecot to not break compatibility -RUN groupadd -g 5000 vmail \ - && groupadd -g 401 dovecot \ - && groupadd -g 402 dovenull \ - && groupadd -g 999 sogo \ - && usermod -a -G sogo nobody \ - && useradd -g vmail -u 5000 vmail -d /var/vmail \ - && useradd -c "Dovecot unprivileged user" -d /dev/null -u 401 -g dovecot -s /bin/false dovecot \ - && useradd -c "Dovecot login user" -d /dev/null -u 402 -g dovenull -s /bin/false dovenull \ - && touch /etc/default/locale \ - && apt-get update \ - && apt-get -y --no-install-recommends install \ - build-essential \ - apt-transport-https \ +RUN addgroup -g 5000 vmail \ + && addgroup -g 401 dovecot \ + && addgroup -g 402 dovenull \ + && sed -i "s/999/99/" /etc/group \ + && addgroup -g 999 sogo \ + && addgroup nobody sogo \ + && adduser -D -u 5000 -G vmail -h /var/vmail vmail \ + && adduser -D -G dovecot -u 401 -h /dev/null -s /sbin/nologin dovecot \ + && adduser -D -G dovenull -u 402 -h /dev/null -s /sbin/nologin dovenull \ + && apk add --no-cache --update \ + bash \ + bind-tools \ + findutils \ + envsubst \ ca-certificates \ - cpanminus \ curl \ - dnsutils \ - dirmngr \ - gettext \ - gnupg2 \ jq \ - libauthen-ntlm-perl \ - libcgi-pm-perl \ - libcrypt-openssl-rsa-perl \ - libcrypt-ssleay-perl \ - libdata-uniqid-perl \ - libdbd-mysql-perl \ - libdbi-perl \ - libdigest-hmac-perl \ - libdist-checkconflicts-perl \ - libencode-imaputf7-perl \ - libfile-copy-recursive-perl \ - libfile-tail-perl \ - libhtml-parser-perl \ - libio-compress-perl \ - libio-socket-inet6-perl \ - libio-socket-ssl-perl \ - libio-tee-perl \ - libipc-run-perl \ - libjson-webtoken-perl \ - liblockfile-simple-perl \ - libmail-imapclient-perl \ - libmodule-implementation-perl \ - libmodule-scandeps-perl \ - libnet-ssleay-perl \ - libpackage-stash-perl \ - libpackage-stash-xs-perl \ - libpar-packer-perl \ - libparse-recdescent-perl \ - libproc-processtable-perl \ - libreadonly-perl \ - libregexp-common-perl \ - libssl-dev \ - libsys-meminfo-perl \ - libterm-readkey-perl \ - libtest-deep-perl \ - libtest-fatal-perl \ - libtest-mock-guard-perl \ - libtest-mockobject-perl \ - libtest-nowarnings-perl \ - libtest-pod-perl \ - libtest-requires-perl \ - libtest-simple-perl \ - libtest-warn-perl \ - libtry-tiny-perl \ - libunicode-string-perl \ - liburi-perl \ - libwww-perl \ - lua-sql-mysql \ + lua \ + lua-cjson \ lua-socket \ + lua-sql-mysql \ + lua5.3-sql-mysql \ + icu-data-full \ + mariadb-connector-c \ + gcompat \ mariadb-client \ + perl \ + perl-ntlm \ + perl-cgi \ + perl-crypt-openssl-rsa \ + perl-utils \ + perl-crypt-ssleay \ + perl-data-uniqid \ + perl-dbd-mysql \ + perl-dbi \ + perl-digest-hmac \ + perl-dist-checkconflicts \ + perl-encode-imaputf7 \ + perl-file-copy-recursive \ + perl-file-tail \ + perl-io-socket-inet6 \ + perl-io-gzip \ + perl-io-socket-ssl \ + perl-io-tee \ + perl-ipc-run \ + perl-json-webtoken \ + perl-mail-imapclient \ + perl-module-implementation \ + perl-module-scandeps \ + perl-net-ssleay \ + perl-package-stash \ + perl-package-stash-xs \ + perl-par-packer \ + perl-parse-recdescent \ + perl-lockfile-simple --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/ \ + libproc \ + perl-readonly \ + perl-regexp-common \ + perl-sys-meminfo \ + perl-term-readkey \ + perl-test-deep \ + perl-test-fatal \ + perl-test-mockobject \ + perl-test-mock-guard \ + perl-test-pod \ + perl-test-requires \ + perl-test-simple \ + perl-test-warn \ + perl-try-tiny \ + perl-unicode-string \ + perl-proc-processtable \ + perl-app-cpanminus \ procps \ - python3-pip \ - redis-server \ - supervisor \ + python3 \ + py3-mysqlclient \ + py3-html2text \ + py3-jinja2 \ + py3-redis \ + redis \ syslog-ng \ - syslog-ng-core \ - syslog-ng-mod-redis \ + syslog-ng-redis \ + syslog-ng-json \ + supervisor \ + tzdata \ wget \ - && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ - && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ - && chmod +x /usr/local/bin/gosu \ - && gosu nobody true \ - && apt-key adv --fetch-keys https://repo.dovecot.org/DOVECOT-REPO-GPG \ - && echo "deb https://repo.dovecot.org/ce-${DOVECOT}/debian/bullseye bullseye main" > /etc/apt/sources.list.d/dovecot.list \ - && apt-get update \ - && apt-get -y --no-install-recommends install \ - dovecot-lua \ - dovecot-managesieved \ - dovecot-sieve \ + dovecot \ + dovecot-dev \ dovecot-lmtpd \ + dovecot-lua \ dovecot-ldap \ dovecot-mysql \ - dovecot-core \ + dovecot-sql \ + dovecot-submissiond \ + dovecot-pigeonhole-plugin \ dovecot-pop3d \ - dovecot-imapd \ - dovecot-solr \ - && pip3 install mysql-connector-python html2text jinja2 redis \ - && apt-get autoremove --purge -y \ - && apt-get autoclean \ - && rm -rf /var/lib/apt/lists/* \ - && rm -rf /tmp/* /var/tmp/* /root/.cache/ -# imapsync dependencies -RUN cpan Crypt::OpenSSL::PKCS12 + dovecot-fts-solr \ + && arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$arch" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true + +# RUN cpan LockFile::Simple COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index b2633c276..f1e2e9668 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -432,4 +432,8 @@ done # May be related to something inside Docker, I seriously don't know touch /etc/dovecot/lua/passwd-verify.lua +if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then + cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf +fi + exec "$@" diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py index 65f2a0e60..e8d743b31 100755 --- a/data/Dockerfiles/dovecot/quarantine_notify.py +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -3,11 +3,10 @@ import smtplib import os import sys -import mysql.connector +import MySQLdb from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import COMMASPACE, formatdate -import cgi import jinja2 from jinja2 import Template import json @@ -50,7 +49,7 @@ try: def query_mysql(query, headers = True, update = False): while True: try: - cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci") + cnx = MySQLdb.connect(user=os.environ.get('DBUSER'), password=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci") except Exception as ex: print('%s - trying again...' % (ex)) time.sleep(3) diff --git a/data/Dockerfiles/dovecot/quota_notify.py b/data/Dockerfiles/dovecot/quota_notify.py index 2d7361b81..34b3e0ed9 100755 --- a/data/Dockerfiles/dovecot/quota_notify.py +++ b/data/Dockerfiles/dovecot/quota_notify.py @@ -55,7 +55,7 @@ try: msg.attach(text_part) msg.attach(html_part) msg['To'] = username - p = Popen(['/usr/lib/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) + p = Popen(['/usr/libexec/dovecot/dovecot-lda', '-d', username, '-o', '"plugin/quota=maildir:User quota:noenforcing"'], stdout=PIPE, stdin=PIPE, stderr=STDOUT) p.communicate(input=bytes(msg.as_string(), 'utf-8')) domain = username.split("@")[-1] diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index a76986409..5b0050002 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -13,6 +13,10 @@ autostart=true [program:dovecot] command=/usr/sbin/dovecot -F +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 autorestart=true [eventlistener:processes] diff --git a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf index ea2bcfbfa..f7fc20b7e 100644 --- a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf @@ -1,4 +1,4 @@ -@version: 3.28 +@version: 4.5 @include "scl.conf" options { chain_hostnames(off); @@ -6,11 +6,11 @@ options { use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; -source s_src { - unix-stream("/dev/log"); +source s_dgram { + unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; @@ -36,7 +36,7 @@ filter f_replica { not match("Error: sync: Unknown user in remote" value("MESSAGE")); }; log { - source(s_src); + source(s_dgram); filter(f_replica); destination(d_stdout); filter(f_mail); diff --git a/data/Dockerfiles/dovecot/syslog-ng.conf b/data/Dockerfiles/dovecot/syslog-ng.conf index 2ee4f6241..fcc13587d 100644 --- a/data/Dockerfiles/dovecot/syslog-ng.conf +++ b/data/Dockerfiles/dovecot/syslog-ng.conf @@ -1,4 +1,4 @@ -@version: 3.28 +@version: 4.5 @include "scl.conf" options { chain_hostnames(off); @@ -6,11 +6,11 @@ options { use_dns(no); use_fqdn(no); owner("root"); group("adm"); perm(0640); - stats_freq(0); + stats(freq(0)); bad_hostname("^gconfd$"); }; -source s_src { - unix-stream("/dev/log"); +source s_dgram { + unix-dgram("/dev/log"); internal(); }; destination d_stdout { pipe("/dev/stdout"); }; @@ -36,7 +36,7 @@ filter f_replica { not match("Error: sync: Unknown user in remote" value("MESSAGE")); }; log { - source(s_src); + source(s_dgram); filter(f_replica); destination(d_stdout); filter(f_mail); diff --git a/data/Dockerfiles/postfix/Dockerfile b/data/Dockerfiles/postfix/Dockerfile index bda6e07f2..236062d78 100644 --- a/data/Dockerfiles/postfix/Dockerfile +++ b/data/Dockerfiles/postfix/Dockerfile @@ -1,5 +1,5 @@ FROM debian:bullseye-slim -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG DEBIAN_FRONTEND=noninteractive ENV LC_ALL C diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile index 9d022f826..2511a6f2f 100644 --- a/data/Dockerfiles/rspamd/Dockerfile +++ b/data/Dockerfiles/rspamd/Dockerfile @@ -1,5 +1,5 @@ FROM debian:bullseye-slim -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG DEBIAN_FRONTEND=noninteractive ARG CODENAME=bullseye @@ -13,7 +13,7 @@ RUN apt-get update && apt-get install -y \ dnsutils \ netcat \ && apt-key adv --fetch-keys https://rspamd.com/apt-stable/gpg.key \ - && echo "deb [arch=amd64] https://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list \ + && echo "deb https://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list \ && apt-get update \ && apt-get --no-install-recommends -y install rspamd redis-tools procps nano \ && rm -rf /var/lib/apt/lists/* \ diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index cbc5c93a9..54d676b9a 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,8 +1,8 @@ FROM debian:bullseye-slim -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/ +ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ ARG GOSU_VERSION=1.17 ENV LC_ALL C @@ -32,7 +32,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ && mkdir /usr/share/doc/sogo \ && touch /usr/share/doc/sogo/empty.sh \ && apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \ - && echo "deb ${SOGO_DEBIAN_REPOSITORY} bullseye bullseye" > /etc/apt/sources.list.d/sogo.list \ + && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} bullseye sogo-v5" > /etc/apt/sources.list.d/sogo.list \ && apt-get update && apt-get install -y --no-install-recommends \ sogo \ sogo-activesync \ diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 1e0192f87..cd4dfde8e 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.19 -LABEL maintainer "The Infrastructure Company GmbH " +LABEL maintainer "The Infrastructure Company GmbH GmbH " RUN apk add --update --no-cache \ curl \ diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index b95bf84b5..d43cb38ac 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -716,8 +716,8 @@ rspamd_checks() { From: watchdog@localhost Empty -' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score) - if [[ ${SCORE} != "9999" ]]; then +' | usr/bin/curl --max-time 10 -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .default.required_score | sed 's/\..*//' ) + if [[ ${SCORE} -ne 9999 ]]; then echo "Rspamd settings check failed, score returned: ${SCORE}" 2>> /tmp/rspamd-mailcow 1>&2 err_count=$(( ${err_count} + 1)) else diff --git a/docker-compose.yml b/docker-compose.yml index 267d1f133..26a0cfe13 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,7 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.63 + image: mailcow/clamd:1.64 restart: always depends_on: unbound-mailcow: @@ -77,7 +77,7 @@ services: - clamd rspamd-mailcow: - image: mailcow/rspamd:1.94 + image: mailcow/rspamd:1.95 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -171,7 +171,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.120 + image: mailcow/sogo:1.121 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -203,7 +203,7 @@ services: labels: ofelia.enabled: "true" ofelia.job-exec.sogo_sessions.schedule: "@every 1m" - ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\"" + ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool -v expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\"" ofelia.job-exec.sogo_ealarms.schedule: "@every 1m" ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds || exit 0\"" ofelia.job-exec.sogo_eautoreply.schedule: "@every 5m" @@ -218,7 +218,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.26 + image: mailcow/dovecot:1.27 depends_on: - mysql-mailcow dns: @@ -298,7 +298,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.73 + image: mailcow/postfix:1.74 depends_on: mysql-mailcow: condition: service_started @@ -547,8 +547,10 @@ services: aliases: - dockerapi + + ##### Will be removed soon ##### solr-mailcow: - image: mailcow/solr:1.8.1 + image: mailcow/solr:1.8.2 restart: always volumes: - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data @@ -562,6 +564,7 @@ services: mailcow-network: aliases: - solr + ################################ olefy-mailcow: image: mailcow/olefy:1.12 diff --git a/generate_config.sh b/generate_config.sh index 3b2928937..2986f1689 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -34,7 +34,7 @@ if docker compose > /dev/null 2>&1; then echo -e "\e[33mNotice: You´ll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then @@ -47,14 +47,14 @@ elif docker-compose > /dev/null 2>&1; then echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi fi else echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi diff --git a/helper-scripts/_cold-standby.sh b/helper-scripts/_cold-standby.sh index 0e8885a3e..ff0512e07 100755 --- a/helper-scripts/_cold-standby.sh +++ b/helper-scripts/_cold-standby.sh @@ -2,6 +2,7 @@ PATH=${PATH}:/opt/bin DATE=$(date +%Y-%m-%d_%H_%M_%S) +LOCAL_ARCH=$(uname -m) export LC_ALL=C echo @@ -148,6 +149,9 @@ else echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m" exit 1 fi + + REMOTE_ARCH=$(ssh -o StrictHostKeyChecking=no -i "${REMOTE_SSH_KEY}" ${REMOTE_SSH_HOST} -p ${REMOTE_SSH_PORT} "uname -m") + } SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) @@ -164,6 +168,17 @@ echo -e "\033[1mFound compose project name ${CMPS_PRJ} for ${MAILCOW_HOSTNAME}\0 echo -e "\033[1mFound SQL ${SQLIMAGE}\033[0m" echo +# Print Message if Local Arch and Remote Arch is not the same +if [[ $LOCAL_ARCH != $REMOTE_ARCH ]]; then + echo + echo -e "\e[1;33m!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!\e[0m" + echo -e "\e[3;33mDetected Architecture missmatch from source to destination...\e[0m" + echo -e "\e[3;33mYour backup is transferred but some volumes might be skipped!\e[0m" + echo -e "\e[1;33m!!!!!!!!!!!!!!!!!!!!!!!!!! CAUTION !!!!!!!!!!!!!!!!!!!!!!!!!!\e[0m" + echo + sleep 2 +fi + # Make sure destination exists, rsync can fail under some circumstances echo -e "\033[1mPreparing remote...\033[0m" if ! ssh -o StrictHostKeyChecking=no \ @@ -248,8 +263,21 @@ for vol in $(docker volume ls -qf name="${CMPS_PRJ}"); do # Cleanup rm -rf "${SCRIPT_DIR}/../_tmp_mariabackup/" - else + elif [[ "${vol}" =~ "rspamd-vol-1" ]]; then + # Exclude rspamd-vol-1 if the Architectures are not the same on source and destination due to compatibility issues. + if [[ $LOCAL_ARCH == $REMOTE_ARCH ]]; then + echo -e "\033[1mSynchronizing ${vol} from local ${mountpoint}...\033[0m" + rsync --delete --info=progress2 -aH -e "ssh -o StrictHostKeyChecking=no \ + -i \"${REMOTE_SSH_KEY}\" \ + -p ${REMOTE_SSH_PORT}" \ + "${mountpoint}/" root@${REMOTE_SSH_HOST}:"${mountpoint}" + else + echo -e "\e[1;31mSkipping ${vol} from local maschine due to incompatiblity between different architecture...\e[0m" + sleep 2 + continue + fi + else echo -e "\033[1mSynchronizing ${vol} from local ${mountpoint}...\033[0m" rsync --delete --info=progress2 -aH -e "ssh -o StrictHostKeyChecking=no \ -i \"${REMOTE_SSH_KEY}\" \ diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index ee9f0202d..03390927b 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -53,6 +53,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml ENV_FILE=${SCRIPT_DIR}/../.env THREADS=$(echo ${THREADS:-1}) +ARCH=$(uname -m) if ! [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then echo "Thread input is not a number!" @@ -96,6 +97,7 @@ function backup() { mkdir -p "${BACKUP_LOCATION}/mailcow-${DATE}" chmod 755 "${BACKUP_LOCATION}/mailcow-${DATE}" cp "${SCRIPT_DIR}/../mailcow.conf" "${BACKUP_LOCATION}/mailcow-${DATE}" + touch "${BACKUP_LOCATION}/mailcow-${DATE}/.$ARCH" for bin in docker; do if [[ -z $(which ${bin}) ]]; then >&2 echo -e "\e[31mCannot find ${bin} in local PATH, exiting...\e[0m" @@ -231,12 +233,29 @@ function restore() { docker start $(docker ps -aqf name=dovecot-mailcow) ;; rspamd) - docker stop $(docker ps -qf name=rspamd-mailcow) - docker run -it --name mailcow-backup --rm \ - -v ${RESTORE_LOCATION}:/backup:z \ - -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \ - ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz - docker start $(docker ps -aqf name=rspamd-mailcow) + if [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then + echo -e "\e[33mCould not find a architecture signature of the loaded backup... Maybe the backup was done before the multiarch update?" + sleep 2 + echo -e "Continuing anyhow. If rspamd is crashing opon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m" + sleep 2 + docker stop $(docker ps -qf name=rspamd-mailcow) + docker run -it --name mailcow-backup --rm \ + -v ${RESTORE_LOCATION}:/backup:z \ + -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \ + ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz + docker start $(docker ps -aqf name=rspamd-mailcow) + elif [[ $ARCH != $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then + echo -e "\e[31mThe Architecture of the backed up mailcow OS is different then your restoring mailcow OS..." + sleep 2 + echo -e "Skipping rspamd due to compatibility issues!\e[0m" + else + docker stop $(docker ps -qf name=rspamd-mailcow) + docker run -it --name mailcow-backup --rm \ + -v ${RESTORE_LOCATION}:/backup:z \ + -v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \ + ${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz + docker start $(docker ps -aqf name=rspamd-mailcow) + fi ;; postfix) docker stop $(docker ps -qf name=postfix-mailcow) @@ -360,9 +379,17 @@ elif [[ ${1} == "restore" ]]; then FILE_SELECTION[${i}]="redis" ((i++)) elif [[ ${file} =~ rspamd ]]; then - echo "[ ${i} ] - Rspamd data" - FILE_SELECTION[${i}]="rspamd" - ((i++)) + if [[ $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then + echo "[ ${i} ] - Rspamd data (unkown Arch detected, restore with caution!)" + FILE_SELECTION[${i}]="rspamd" + ((i++)) + elif [[ $ARCH != $(find "${FOLDER_SELECTION[${input_sel}]}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then + echo -e "\e[31m[ NaN ] - Rspamd data (incompatible Arch, cannot restore it)\e[0m" + else + echo "[ ${i} ] - Rspamd data" + FILE_SELECTION[${i}]="rspamd" + ((i++)) + fi elif [[ ${file} =~ postfix ]]; then echo "[ ${i} ] - Postfix data" FILE_SELECTION[${i}]="postfix" diff --git a/update.sh b/update.sh index 47ae1f8ba..623242d5b 100755 --- a/update.sh +++ b/update.sh @@ -181,7 +181,7 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi elif docker-compose > /dev/null 2>&1; then @@ -196,14 +196,14 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" else echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" - echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi fi else echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi @@ -216,7 +216,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "native" ]; then if ! $COMPOSE_COMMAND > /dev/null 2>&1 || ! $COMPOSE_COMMAND --version | grep "^2." > /dev/null 2>&1; then # IF it cannot find Standalone in > 2.X, then script stops echo -e "\e[31mCannot find Docker Compose or the Version is lower then 2.X.X.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi # If it finds the standalone Plugin it will use this instead and change the mailcow.conf Variable accordingly @@ -236,7 +236,7 @@ elif [ "${DOCKER_COMPOSE_VERSION}" == "standalone" ]; then if ! $COMPOSE_COMMAND > /dev/null 2>&1; then # IF it cannot find Native in > 2.X, then script stops echo -e "\e[31mCannot find Docker Compose.\e[0m" - echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/i_u_m/i_u_m_install/\e[0m" + echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m" exit 1 fi # If it finds the native Plugin it will use this instead and change the mailcow.conf Variable accordingly From cc3adbe78c815e07ef68cc7f2e355b7dc9d03d2c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 17 Jan 2024 12:04:01 +0100 Subject: [PATCH 037/793] [Web] fix datatables ssp queries --- data/web/inc/lib/ssp.class.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/data/web/inc/lib/ssp.class.php b/data/web/inc/lib/ssp.class.php index 503f9b295..ea6109ac3 100644 --- a/data/web/inc/lib/ssp.class.php +++ b/data/web/inc/lib/ssp.class.php @@ -291,13 +291,14 @@ class SSP { FROM `$table` AS `$tablesAS` $join $where + GROUP BY `{$tablesAS}`.`{$primaryKey}` $order $limit" ); // Data set length after filtering $resFilterLength = self::sql_exec( $db, $bindings, - "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + "SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` $join $where" @@ -411,12 +412,11 @@ class SSP { // Data set length after filtering $resFilterLength = self::sql_exec( $db, $bindings, - "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) + "SELECT COUNT(DISTINCT `{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` $join $join_filter - $where - GROUP BY `{$tablesAS}`.`{$primaryKey}`" + $where" ); $recordsFiltered = (isset($resFilterLength[0])) ? $resFilterLength[0][0] : 0; @@ -424,10 +424,9 @@ class SSP { $resTotalLength = self::sql_exec( $db, $bindings, "SELECT COUNT(`{$tablesAS}`.`{$primaryKey}`) FROM `$table` AS `$tablesAS` - $join - $join_filter - $where - GROUP BY `{$tablesAS}`.`{$primaryKey}`" + $join + $join_filter + $where" ); $recordsTotal = (isset($resTotalLength[0])) ? $resTotalLength[0][0] : 0; From 90a7cff2c9061b0ffd0cba63ade68e3a313d4b61 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Wed, 17 Jan 2024 12:05:51 +0100 Subject: [PATCH 038/793] [Rspamd] check if footer.skip_replies is not 0 --- data/conf/rspamd/lua/rspamd.local.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua index 4cc314b0b..45506f3b5 100644 --- a/data/conf/rspamd/lua/rspamd.local.lua +++ b/data/conf/rspamd/lua/rspamd.local.lua @@ -567,7 +567,7 @@ rspamd_config:register_symbol({ if footer and type(footer) == "table" and (footer.html and footer.html ~= "" or footer.plain and footer.plain ~= "") then rspamd_logger.infox(rspamd_config, "found domain wide footer for user %s: html=%s, plain=%s, vars=%s", uname, footer.html, footer.plain, footer.vars) - if footer.skip_replies then + if footer.skip_replies ~= 0 then in_reply_to = task:get_header_raw('in-reply-to') if in_reply_to then rspamd_logger.infox(rspamd_config, "mail is a reply - skip footer") From cb3bc207b94562ab13ce08455a5571613a26c8dd Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 18 Jan 2024 11:55:01 +0100 Subject: [PATCH 039/793] unbound: increased healthcheck timeout --- data/Dockerfiles/unbound/Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index cd4dfde8e..4411a1d26 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -23,7 +23,7 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh # healthcheck (nslookup) COPY healthcheck.sh /healthcheck.sh RUN chmod +x /healthcheck.sh -HEALTHCHECK --interval=5s --timeout=10s CMD [ "/healthcheck.sh" ] +HEALTHCHECK --interval=5s --timeout=30s CMD [ "/healthcheck.sh" ] ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 26a0cfe13..5a0730c7e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.19 + image: mailcow/unbound:1.19.1 environment: - TZ=${TZ} volumes: From ed493f9c3a63d94ef10b149829fe3a752c3532e3 Mon Sep 17 00:00:00 2001 From: KagurazakaNyaa Date: Thu, 18 Jan 2024 23:28:03 +0800 Subject: [PATCH 040/793] Allow user skip unbound healthcheck --- data/Dockerfiles/unbound/healthcheck.sh | 10 ++++++++++ generate_config.sh | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/data/Dockerfiles/unbound/healthcheck.sh b/data/Dockerfiles/unbound/healthcheck.sh index ea94f63b3..760aa02bb 100644 --- a/data/Dockerfiles/unbound/healthcheck.sh +++ b/data/Dockerfiles/unbound/healthcheck.sh @@ -1,5 +1,10 @@ #!/bin/bash +# Skipping DNS check +if [[ "${SKIP_DNS_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_DNS_CHECK=y +fi + # Declare log function for logfile inside container function log_to_file() { echo "$(date +"%Y-%m-%d %H:%M:%S"): $1" > /var/log/healthcheck.log @@ -66,6 +71,11 @@ function check_netcat() { } +if [[ ${SKIP_DNS_CHECK} == "y" ]]; then + log_to_file "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!" + exit 0 +fi + # run checks, if check is not returning 0 (return value if check is ok), healthcheck will exit with 1 (marked in docker as unhealthy) check_ping diff --git a/generate_config.sh b/generate_config.sh index 2986f1689..0c8a9bcfe 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -363,6 +363,10 @@ SKIP_IP_CHECK=n SKIP_HTTP_VERIFICATION=n +# Skip DNS check in Unbound container - y/n + +SKIP_DNS_CHECK=n + # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n SKIP_CLAMD=${SKIP_CLAMD} From b89d71e6e4d42d1082f92914ff453d1272c67088 Mon Sep 17 00:00:00 2001 From: KagurazakaNyaa Date: Thu, 18 Jan 2024 23:48:59 +0800 Subject: [PATCH 041/793] change variable name --- data/Dockerfiles/unbound/healthcheck.sh | 8 ++++---- generate_config.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/Dockerfiles/unbound/healthcheck.sh b/data/Dockerfiles/unbound/healthcheck.sh index 760aa02bb..a96eaab41 100644 --- a/data/Dockerfiles/unbound/healthcheck.sh +++ b/data/Dockerfiles/unbound/healthcheck.sh @@ -1,8 +1,8 @@ #!/bin/bash -# Skipping DNS check -if [[ "${SKIP_DNS_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - SKIP_DNS_CHECK=y +# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) +if [[ "${SKIP_UNBOUND_HEALTHCHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then + SKIP_UNBOUND_HEALTHCHECK=y fi # Declare log function for logfile inside container @@ -71,7 +71,7 @@ function check_netcat() { } -if [[ ${SKIP_DNS_CHECK} == "y" ]]; then +if [[ ${SKIP_UNBOUND_HEALTHCHECK} == "y" ]]; then log_to_file "Healthcheck: ALL CHECKS WERE SKIPPED! Unbound is healthy!" exit 0 fi diff --git a/generate_config.sh b/generate_config.sh index 0c8a9bcfe..e936348ec 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -365,7 +365,7 @@ SKIP_HTTP_VERIFICATION=n # Skip DNS check in Unbound container - y/n -SKIP_DNS_CHECK=n +SKIP_UNBOUND_HEALTHCHECK=n # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n From aa1d92dfbbcb555caf5f1a39032b7361254240aa Mon Sep 17 00:00:00 2001 From: KagurazakaNyaa Date: Thu, 18 Jan 2024 23:50:26 +0800 Subject: [PATCH 042/793] add SKIP_UNBOUND_HEALTHCHECK to docker-compose.yml --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 5a0730c7e..4f5a4d31c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: image: mailcow/unbound:1.19.1 environment: - TZ=${TZ} + - SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n} volumes: - ./data/hooks/unbound:/hooks:Z - ./data/conf/unbound/unbound.conf:/etc/unbound/unbound.conf:ro,Z From d2edf359ac3ebdc51d56aab488ffc5dd927db13b Mon Sep 17 00:00:00 2001 From: KagurazakaNyaa Date: Thu, 18 Jan 2024 23:53:08 +0800 Subject: [PATCH 043/793] update config comment --- generate_config.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_config.sh b/generate_config.sh index e936348ec..e62d16892 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -363,7 +363,7 @@ SKIP_IP_CHECK=n SKIP_HTTP_VERIFICATION=n -# Skip DNS check in Unbound container - y/n +# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n SKIP_UNBOUND_HEALTHCHECK=n From 9d4055fc4d3a67221160f0d8342f41f77e28dd8e Mon Sep 17 00:00:00 2001 From: KagurazakaNyaa Date: Fri, 19 Jan 2024 00:07:51 +0800 Subject: [PATCH 044/793] add parameter SKIP_UNBOUND_HEALTHCHECK to old installations --- update.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/update.sh b/update.sh index 623242d5b..ad77beac7 100755 --- a/update.sh +++ b/update.sh @@ -480,6 +480,7 @@ CONFIG_ARRAY=( "WATCHDOG_VERBOSE" "WEBAUTHN_ONLY_TRUSTED_VENDORS" "SPAMHAUS_DQS_KEY" + "SKIP_UNBOUND_HEALTHCHECK" ) detect_bad_asn @@ -747,6 +748,12 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# Enable watchdog verbose logging' >> mailcow.conf echo 'WATCHDOG_VERBOSE=n' >> mailcow.conf fi + elif [[ ${option} == "SKIP_UNBOUND_HEALTHCHECK" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf + echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf + fi elif ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=n" >> mailcow.conf From 25bdc4c9ed93392e71ee0b32bcdb51572bd75172 Mon Sep 17 00:00:00 2001 From: Luca Barbato Date: Mon, 22 Jan 2024 09:50:24 +0100 Subject: [PATCH 045/793] Test for openrc configuration file instead of alpine This way other distro using openrc can be supported. --- update.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update.sh b/update.sh index 623242d5b..4e16f1193 100755 --- a/update.sh +++ b/update.sh @@ -116,11 +116,11 @@ migrate_docker_nat() { echo "Working on IPv6 NAT, please wait..." echo ${NAT_CONFIG} > /etc/docker/daemon.json ip6tables -F -t nat - [[ -e /etc/alpine-release ]] && rc-service docker restart || systemctl restart docker.service + [[ -e /etc/rc.conf ]] && rc-service docker restart || systemctl restart docker.service if [[ $? -ne 0 ]]; then echo -e "\e[31mError:\e[0m Failed to activate IPv6 NAT! Reverting and exiting." rm /etc/docker/daemon.json - if [[ -e /etc/alpine-release ]]; then + if [[ -e /etc/rc.conf ]]; then rc-service docker restart else systemctl reset-failed docker.service From 53be119e39b0db25c45bb0f058f5259e6b9d1347 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 22 Jan 2024 10:22:24 +0100 Subject: [PATCH 046/793] compose: bump unbound version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4f5a4d31c..4e7420567 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.19.1 + image: mailcow/unbound:1.20 environment: - TZ=${TZ} - SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n} From 6e7a0eb66222e52d8144802c132c2131d1badd45 Mon Sep 17 00:00:00 2001 From: Nya Candy Date: Thu, 18 Jan 2024 15:00:54 +0800 Subject: [PATCH 047/793] fix: watchdog webhook body variables injector --- data/Dockerfiles/watchdog/watchdog.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index d43cb38ac..cb342c138 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -170,7 +170,7 @@ function notify_error() { fi # Replace subject and body placeholders - WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed "s|\$SUBJECT\|\${SUBJECT}|$SUBJECT|g" | sed "s|\$BODY\|\${BODY}|$BODY|") + WEBHOOK_BODY=$(echo ${WATCHDOG_NOTIFY_WEBHOOK_BODY} | sed "s/\$SUBJECT\|\${SUBJECT}/$SUBJECT/g" | sed "s/\$BODY\|\${BODY}/$BODY/g") # POST to webhook curl -X POST -H "Content-Type: application/json" ${CURL_VERBOSE} -d "${WEBHOOK_BODY}" ${WATCHDOG_NOTIFY_WEBHOOK} From 7da5e3697e4373fb8220bbc5bcfa79aa98793743 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 22 Jan 2024 10:31:27 +0100 Subject: [PATCH 048/793] compose: bump watchdog version --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5a0730c7e..7dbfe0e5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -457,7 +457,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:2.01 + image: mailcow/watchdog:2.02 dns: - ${IPV4_NETWORK:-172.22.1}.254 tmpfs: From a0613e4b1085d3a4de8f5872b0a6deeed8efc066 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Mon, 22 Jan 2024 11:26:26 +0100 Subject: [PATCH 049/793] fix: rollback of Alpine 3.19 were possible --- data/Dockerfiles/acme/Dockerfile | 2 +- data/Dockerfiles/phpfpm/Dockerfile | 2 +- data/Dockerfiles/unbound/Dockerfile | 2 +- data/Dockerfiles/watchdog/Dockerfile | 2 +- docker-compose.yml | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile index 08271bddb..254b5b331 100644 --- a/data/Dockerfiles/acme/Dockerfile +++ b/data/Dockerfiles/acme/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM alpine:3.18 LABEL maintainer "The Infrastructure Company GmbH GmbH " diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile index c1a35f4d3..22036b9b3 100644 --- a/data/Dockerfiles/phpfpm/Dockerfile +++ b/data/Dockerfiles/phpfpm/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.2-fpm-alpine3.19 +FROM php:8.2-fpm-alpine3.18 LABEL maintainer "The Infrastructure Company GmbH " # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?.*)$ diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index 4411a1d26..f56cbc6e5 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM alpine:3.18 LABEL maintainer "The Infrastructure Company GmbH GmbH " diff --git a/data/Dockerfiles/watchdog/Dockerfile b/data/Dockerfiles/watchdog/Dockerfile index b94789aac..73acde68f 100644 --- a/data/Dockerfiles/watchdog/Dockerfile +++ b/data/Dockerfiles/watchdog/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM alpine:3.18 LABEL maintainer "The Infrastructure Company GmbH " # Installation diff --git a/docker-compose.yml b/docker-compose.yml index 739108bcb..c1883f907 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -108,7 +108,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.86 + image: mailcow/phpfpm:1.87 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -399,7 +399,7 @@ services: condition: service_started unbound-mailcow: condition: service_healthy - image: mailcow/acme:1.86 + image: mailcow/acme:1.87 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: From deb6f0babc5017a2d140d971a8dbab289e5f3072 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Tue, 23 Jan 2024 08:46:06 +0100 Subject: [PATCH 050/793] issue: added architecture as dropdown --- .github/ISSUE_TEMPLATE/Bug_report.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/Bug_report.yml b/.github/ISSUE_TEMPLATE/Bug_report.yml index 3cfbbe0dc..afa1a27f8 100644 --- a/.github/ISSUE_TEMPLATE/Bug_report.yml +++ b/.github/ISSUE_TEMPLATE/Bug_report.yml @@ -62,6 +62,16 @@ body: - nightly validations: required: true + - type: dropdown + attributes: + label: "Which architecture are you using?" + description: "#### `uname -m`" + multiple: false + options: + - x86 + - ARM64 (aarch64) + validations: + required: true - type: input attributes: label: "Operating System:" From f74573f5d04cf578e4101a5e32a0814d9fd849ae Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:14:42 +0100 Subject: [PATCH 051/793] chore(deps): update peter-evans/create-pull-request action to v6 (#5683) Signed-off-by: milkmaker Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/update_postscreen_access_list.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update_postscreen_access_list.yml b/.github/workflows/update_postscreen_access_list.yml index 42502f303..ddfa7ac86 100644 --- a/.github/workflows/update_postscreen_access_list.yml +++ b/.github/workflows/update_postscreen_access_list.yml @@ -22,7 +22,7 @@ jobs: bash helper-scripts/update_postscreen_whitelist.sh - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }} commit-message: update postscreen_access.cidr From cc77caad671f0ae40068351861d81712da8f2b9e Mon Sep 17 00:00:00 2001 From: milkmaker Date: Thu, 1 Feb 2024 00:13:51 +0000 Subject: [PATCH 052/793] update postscreen_access.cidr --- data/conf/postfix/postscreen_access.cidr | 81 +++++++++++++++++------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/data/conf/postfix/postscreen_access.cidr b/data/conf/postfix/postscreen_access.cidr index 0497e64a0..e8273ecc5 100644 --- a/data/conf/postfix/postscreen_access.cidr +++ b/data/conf/postfix/postscreen_access.cidr @@ -1,9 +1,10 @@ -# Whitelist generated by Postwhite v3.4 on Mon Jan 1 00:15:22 UTC 2024 +# Whitelist generated by Postwhite v3.4 on Thu Feb 1 00:13:50 UTC 2024 # https://github.com/stevejenkins/postwhite/ -# 2052 total rules +# 2089 total rules 2a00:1450:4000::/36 permit 2a01:111:f400::/48 permit 2a01:111:f403:8000::/50 permit +2a01:111:f403:8000::/51 permit 2a01:111:f403::/49 permit 2a01:111:f403:c000::/51 permit 2a01:111:f403:f000::/52 permit @@ -116,7 +117,6 @@ 40.92.0.0/16 permit 40.107.0.0/16 permit 40.112.65.63 permit -40.117.80.0/24 permit 43.228.184.0/22 permit 44.206.138.57 permit 44.209.42.157 permit @@ -206,7 +206,6 @@ 52.95.49.88/29 permit 52.96.91.34 permit 52.96.111.82 permit -52.96.172.98 permit 52.96.214.50 permit 52.96.222.194 permit 52.96.222.226 permit @@ -405,7 +404,6 @@ 66.196.81.228/30 permit 66.196.81.232/31 permit 66.196.81.234 permit -66.211.168.230/31 permit 66.211.170.88/29 permit 66.211.184.0/23 permit 66.218.74.64/30 permit @@ -594,9 +592,10 @@ 74.112.67.243 permit 74.125.0.0/16 permit 74.202.227.40 permit -74.208.4.192/26 permit -74.208.5.64/26 permit -74.208.122.0/26 permit +74.208.4.200 permit +74.208.4.201 permit +74.208.4.220 permit +74.208.4.221 permit 74.209.250.0/24 permit 75.2.70.75 permit 76.223.128.0/19 permit @@ -624,12 +623,20 @@ 77.238.189.148/30 permit 81.7.169.128/25 permit 81.223.46.0/27 permit -82.165.159.0/24 permit -82.165.159.0/26 permit -82.165.229.31 permit -82.165.229.130 permit -82.165.230.21 permit -82.165.230.22 permit +82.165.159.2 permit +82.165.159.3 permit +82.165.159.4 permit +82.165.159.12 permit +82.165.159.13 permit +82.165.159.14 permit +82.165.159.34 permit +82.165.159.35 permit +82.165.159.40 permit +82.165.159.41 permit +82.165.159.42 permit +82.165.159.45 permit +82.165.159.130 permit +82.165.159.131 permit 84.116.6.0/23 permit 84.116.36.0/24 permit 84.116.50.0/23 permit @@ -1430,6 +1437,7 @@ 135.84.216.0/22 permit 136.143.160.0/24 permit 136.143.161.0/24 permit +136.143.178.49 permit 136.143.182.0/23 permit 136.143.184.0/24 permit 136.143.188.0/24 permit @@ -1952,12 +1960,41 @@ 212.82.111.228/31 permit 212.82.111.230 permit 212.123.28.40 permit -212.227.15.0/24 permit -212.227.15.0/25 permit -212.227.17.0/27 permit -212.227.126.128/25 permit +212.227.15.3 permit +212.227.15.4 permit +212.227.15.5 permit +212.227.15.6 permit +212.227.15.14 permit +212.227.15.15 permit +212.227.15.18 permit +212.227.15.19 permit +212.227.15.25 permit +212.227.15.26 permit +212.227.15.29 permit +212.227.15.44 permit +212.227.15.45 permit +212.227.15.46 permit +212.227.15.47 permit +212.227.15.50 permit +212.227.15.52 permit +212.227.15.53 permit +212.227.15.54 permit +212.227.15.55 permit +212.227.17.11 permit +212.227.17.12 permit +212.227.17.18 permit +212.227.17.19 permit +212.227.17.20 permit +212.227.17.21 permit +212.227.17.22 permit +212.227.17.26 permit +212.227.17.28 permit +212.227.17.29 permit +212.227.126.224 permit +212.227.126.225 permit +212.227.126.226 permit +212.227.126.227 permit 213.46.255.0/24 permit -213.165.64.0/23 permit 213.199.128.139 permit 213.199.128.145 permit 213.199.138.181 permit @@ -2021,10 +2058,10 @@ 216.203.30.55 permit 216.203.33.178/31 permit 216.205.24.0/24 permit +216.221.160.0/19 permit 216.239.32.0/19 permit -217.72.192.64/26 permit -217.72.192.248/29 permit -217.72.207.0/27 permit +217.72.192.77 permit +217.72.192.78 permit 217.77.141.52 permit 217.77.141.59 permit 217.175.194.0/24 permit From 93e4d586060aa1045395aae1bcf7a7126bbdc9f1 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 1 Feb 2024 08:41:11 +0100 Subject: [PATCH 053/793] sogo: fix ACL allow authenticated users + rebuild on Bookworm --- data/Dockerfiles/sogo/Dockerfile | 7 ++++--- docker-compose.yml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 54d676b9a..a4601c405 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,7 +1,8 @@ -FROM debian:bullseye-slim +FROM debian:bookworm-slim LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG DEBIAN_FRONTEND=noninteractive +ARG DEBIAN_VERSION=bookworm ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ ARG GOSU_VERSION=1.17 @@ -21,7 +22,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ syslog-ng-core \ syslog-ng-mod-redis \ dirmngr \ - netcat \ + netcat-traditional \ psmisc \ wget \ patch \ @@ -32,7 +33,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ && mkdir /usr/share/doc/sogo \ && touch /usr/share/doc/sogo/empty.sh \ && apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \ - && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} bullseye sogo-v5" > /etc/apt/sources.list.d/sogo.list \ + && echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \ && apt-get update && apt-get install -y --no-install-recommends \ sogo \ sogo-activesync \ diff --git a/docker-compose.yml b/docker-compose.yml index c1883f907..8dc193ebe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -172,7 +172,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.121 + image: mailcow/sogo:1.122 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} From b236fd3ac683ea1434521807c5d118040b54882c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Tue, 30 Jan 2024 10:15:33 +0100 Subject: [PATCH 054/793] [Netfilter] add mailcow isolation rule to MAILCOW chain [Netfilter] add mailcow rule to docker-user chain [Netfilter] add mailcow isolation rule to MAILCOW chain [Netfilter] add mailcow isolation rule to MAILCOW chain [Netfilter] set mailcow isolation rule before redis [Netfilter] clear bans in redis after connecting [Netfilter] simplify mailcow isolation rule for compatibility with iptables-nft [Netfilter] stop container after mariadb, redis, dovecot, solr [Netfilter] simplify mailcow isolation rule for compatibility with iptables-nft [Netfilter] add exception for mailcow isolation rule for HA setups [Netfilter] add exception for mailcow isolation rule for HA setups [Netfilter] add DISABLE_NETFILTER_ISOLATION_RULE [Netfilter] fix wrong var name [Netfilter] add DISABLE_NETFILTER_ISOLATION_RULE to update and generate_config sh --- .gitignore | 1 + data/Dockerfiles/dovecot/docker-entrypoint.sh | 9 + data/Dockerfiles/netfilter/main.py | 90 ++++++---- .../Dockerfiles/netfilter/modules/IPTables.py | 39 +++++ data/Dockerfiles/netfilter/modules/Logger.py | 3 +- .../Dockerfiles/netfilter/modules/NFTables.py | 165 +++++++++++++++++- data/conf/dovecot/dovecot.conf | 3 + docker-compose.yml | 16 +- generate_config.sh | 3 + update.sh | 8 + 10 files changed, 290 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index 3595ecb1e..e25639a70 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ data/conf/dovecot/acl_anyone data/conf/dovecot/dovecot-master.passwd data/conf/dovecot/dovecot-master.userdb data/conf/dovecot/extra.conf +data/conf/dovecot/mail_replica.conf data/conf/dovecot/global_sieve_* data/conf/dovecot/last_login data/conf/dovecot/lua diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index f1e2e9668..a9545f338 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -335,6 +335,15 @@ sys.exit() EOF fi +# Set mail_replica for HA setups +if [[ -n ${MAILCOW_REPLICA_IP} && -n ${DOVEADM_REPLICA_PORT} ]]; then + cat < /etc/dovecot/mail_replica.conf +# Autogenerated by mailcow +mail_replica = tcp:${MAILCOW_REPLICA_IP}:${DOVEADM_REPLICA_PORT} +EOF +fi + + # 401 is user dovecot if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index d5c1db502..8480c9efa 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -21,28 +21,6 @@ from modules.IPTables import IPTables from modules.NFTables import NFTables -# connect to redis -while True: - try: - redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') - redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') - if "".__eq__(redis_slaveof_ip): - r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0) - else: - r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0) - r.ping() - except Exception as ex: - print('%s - trying again in 3 seconds' % (ex)) - time.sleep(3) - else: - break -pubsub = r.pubsub() - -# rename fail2ban to netfilter -if r.exists('F2B_LOG'): - r.rename('F2B_LOG', 'NETFILTER_LOG') - - # globals WHITELIST = [] BLACKLIST= [] @@ -50,18 +28,8 @@ bans = {} quit_now = False exit_code = 0 lock = Lock() - - -# init Logger -logger = Logger(r) -# init backend -backend = sys.argv[1] -if backend == "nftables": - logger.logInfo('Using NFTables backend') - tables = NFTables("MAILCOW", logger) -else: - logger.logInfo('Using IPTables backend') - tables = IPTables("MAILCOW", logger) +chain_name = "MAILCOW" +r = None def refreshF2boptions(): @@ -250,9 +218,10 @@ def clear(): with lock: tables.clearIPv4Table() tables.clearIPv6Table() - r.delete('F2B_ACTIVE_BANS') - r.delete('F2B_PERM_BANS') - pubsub.unsubscribe() + if r: + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + pubsub.unsubscribe() def watch(): logger.logInfo('Watching Redis channel F2B_CHANNEL') @@ -409,15 +378,60 @@ def quit(signum, frame): if __name__ == '__main__': - refreshF2boptions() + # init Logger + logger = Logger(None) + + # init backend + backend = sys.argv[1] + if backend == "nftables": + logger.logInfo('Using NFTables backend') + tables = NFTables(chain_name, logger) + else: + logger.logInfo('Using IPTables backend') + tables = IPTables(chain_name, logger) + # In case a previous session was killed without cleanup clear() + # Reinit MAILCOW chain # Is called before threads start, no locking logger.logInfo("Initializing mailcow netfilter chain") tables.initChainIPv4() tables.initChainIPv6() + if os.getenv("DISABLE_NETFILTER_ISOLATION_RULE").lower() in ("y", "yes"): + logger.logInfo(f"Skipping {chain_name} isolation") + else: + logger.logInfo(f"Setting {chain_name} isolation") + tables.create_mailcow_isolation_rule("br-mailcow", [3306, 6379, 8983, 12345], os.getenv("MAILCOW_REPLICA_IP")) + + # connect to redis + while True: + try: + redis_slaveof_ip = os.getenv('REDIS_SLAVEOF_IP', '') + redis_slaveof_port = os.getenv('REDIS_SLAVEOF_PORT', '') + if "".__eq__(redis_slaveof_ip): + r = redis.StrictRedis(host=os.getenv('IPV4_NETWORK', '172.22.1') + '.249', decode_responses=True, port=6379, db=0) + else: + r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0) + r.ping() + except Exception as ex: + print('%s - trying again in 3 seconds' % (ex)) + time.sleep(3) + else: + break + pubsub = r.pubsub() + Logger.r = r + + # rename fail2ban to netfilter + if r.exists('F2B_LOG'): + r.rename('F2B_LOG', 'NETFILTER_LOG') + # clear bans in redis + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + + refreshF2boptions() + watch_thread = Thread(target=watch) watch_thread.daemon = True watch_thread.start() diff --git a/data/Dockerfiles/netfilter/modules/IPTables.py b/data/Dockerfiles/netfilter/modules/IPTables.py index c60ecc613..29a9fb65a 100644 --- a/data/Dockerfiles/netfilter/modules/IPTables.py +++ b/data/Dockerfiles/netfilter/modules/IPTables.py @@ -1,5 +1,6 @@ import iptc import time +import os class IPTables: def __init__(self, chain_name, logger): @@ -211,3 +212,41 @@ class IPTables: target = rule.create_target("SNAT") target.to_source = snat_target return rule + + def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): + try: + chain = iptc.Chain(iptc.Table(iptc.Table.FILTER), self.chain_name) + + # insert mailcow isolation rule + rule = iptc.Rule() + rule.in_interface = f'! {_interface}' + rule.out_interface = _interface + rule.protocol = 'tcp' + rule.create_target("DROP") + match = rule.create_match("multiport") + match.dports = ','.join(map(str, _dports)) + + if rule in chain.rules: + chain.delete_rule(rule) + chain.insert_rule(rule, position=0) + + # insert mailcow isolation exception rule + if _allow != "": + rule = iptc.Rule() + rule.src = _allow + rule.in_interface = f'! {_interface}' + rule.out_interface = _interface + rule.protocol = 'tcp' + rule.create_target("ACCEPT") + match = rule.create_match("multiport") + match.dports = ','.join(map(str, _dports)) + + if rule in chain.rules: + chain.delete_rule(rule) + chain.insert_rule(rule, position=0) + + + return True + except Exception as e: + self.logger.logCrit(f"Error adding {self.chain_name} isolation: {e}") + return False \ No newline at end of file diff --git a/data/Dockerfiles/netfilter/modules/Logger.py b/data/Dockerfiles/netfilter/modules/Logger.py index d60d52fad..2a40de0ce 100644 --- a/data/Dockerfiles/netfilter/modules/Logger.py +++ b/data/Dockerfiles/netfilter/modules/Logger.py @@ -10,7 +10,8 @@ class Logger: tolog['time'] = int(round(time.time())) tolog['priority'] = priority tolog['message'] = message - self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) + if self.r: + self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) print(message) def logWarn(self, message): diff --git a/data/Dockerfiles/netfilter/modules/NFTables.py b/data/Dockerfiles/netfilter/modules/NFTables.py index d341dc36b..e8e02c476 100644 --- a/data/Dockerfiles/netfilter/modules/NFTables.py +++ b/data/Dockerfiles/netfilter/modules/NFTables.py @@ -1,5 +1,6 @@ import nftables import ipaddress +import os class NFTables: def __init__(self, chain_name, logger): @@ -266,6 +267,17 @@ class NFTables: return self.nft_exec_dict(delete_command) + def delete_filter_rule(self, _family:str, _chain: str, _handle:str): + delete_command = self.get_base_dict() + _rule_opts = {'family': _family, + 'table': 'filter', + 'chain': _chain, + 'handle': _handle } + _delete = {'delete': {'rule': _rule_opts} } + delete_command["nftables"].append(_delete) + + return self.nft_exec_dict(delete_command) + def snat_rule(self, _family: str, snat_target: str, source_address: str): chain_name = self.nft_chain_names[_family]['nat']['postrouting'] @@ -381,7 +393,7 @@ class NFTables: break return chain_handle - def get_rules_handle(self, _family: str, _table: str, chain_name: str): + def get_rules_handle(self, _family: str, _table: str, chain_name: str, _comment_filter = "mailcow"): rule_handle = [] # Command: 'nft list chain {family} {table} {chain_name}' _chain_opts = {'family': _family, 'table': _table, 'name': chain_name} @@ -397,7 +409,7 @@ class NFTables: rule = _object["rule"] if rule["family"] == _family and rule["table"] == _table and rule["chain"] == chain_name: - if rule.get("comment") and rule["comment"] == "mailcow": + if rule.get("comment") and rule["comment"] == _comment_filter: rule_handle.append(rule["handle"]) return rule_handle @@ -493,3 +505,152 @@ class NFTables: position+=1 return position if rule_found else False + + def create_mailcow_isolation_rule(self, _interface:str, _dports:list, _allow:str = ""): + family = "ip" + table = "filter" + comment_filter_drop = "mailcow isolation" + comment_filter_allow = "mailcow isolation allow" + json_command = self.get_base_dict() + + # Delete old mailcow isolation rules + handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_drop) + for handle in handles: + self.delete_filter_rule(family, self.chain_name, handle) + handles = self.get_rules_handle(family, table, self.chain_name, comment_filter_allow) + for handle in handles: + self.delete_filter_rule(family, self.chain_name, handle) + + # insert mailcow isolation rule + _match_dict_drop = [ + { + "match": { + "op": "!=", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": { + "set": _dports + } + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "drop": None + } + ] + rule_drop = { "insert": { "rule": { + "family": family, + "table": table, + "chain": self.chain_name, + "comment": comment_filter_drop, + "expr": _match_dict_drop + }}} + json_command["nftables"].append(rule_drop) + + # insert mailcow isolation allow rule + if _allow != "": + _match_dict_allow = [ + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "ip", + "field": "saddr" + } + }, + "right": _allow + } + }, + { + "match": { + "op": "!=", + "left": { + "meta": { + "key": "iifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "meta": { + "key": "oifname" + } + }, + "right": _interface + } + }, + { + "match": { + "op": "==", + "left": { + "payload": { + "protocol": "tcp", + "field": "dport" + } + }, + "right": { + "set": _dports + } + } + }, + { + "counter": { + "packets": 0, + "bytes": 0 + } + }, + { + "accept": None + } + ] + rule_allow = { "insert": { "rule": { + "family": family, + "table": table, + "chain": self.chain_name, + "comment": comment_filter_allow, + "expr": _match_dict_allow + }}} + json_command["nftables"].append(rule_allow) + + success = self.nft_exec_dict(json_command) + if success == False: + self.logger.logCrit(f"Error adding {self.chain_name} isolation") + return False + + return True \ No newline at end of file diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 159e39f41..729686fb1 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -247,6 +247,9 @@ plugin { mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename mail_log_fields = uid box msgid size mail_log_cached_only = yes + + # Try set mail_replica + !include_try /etc/dovecot/mail_replica.conf } service quota-warning { executable = script /usr/local/bin/quota_notify.py diff --git a/docker-compose.yml b/docker-compose.yml index c1883f907..fa92546ad 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: image: mariadb:10.5 depends_on: - unbound-mailcow + - netfilter-mailcow stop_grace_period: 45s volumes: - mysql-vol-1:/var/lib/mysql/ @@ -46,6 +47,8 @@ services: volumes: - redis-vol-1:/data/ restart: always + depends_on: + - netfilter-mailcow ports: - "${REDIS_PORT:-127.0.0.1:7654}:6379" environment: @@ -222,6 +225,7 @@ services: image: mailcow/dovecot:1.27 depends_on: - mysql-mailcow + - netfilter-mailcow dns: - ${IPV4_NETWORK:-172.22.1}.254 cap_add: @@ -242,6 +246,8 @@ services: environment: - DOVECOT_MASTER_USER=${DOVECOT_MASTER_USER:-} - DOVECOT_MASTER_PASS=${DOVECOT_MASTER_PASS:-} + - MAILCOW_REPLICA_IP=${MAILCOW_REPLICA_IP:-} + - DOVEADM_REPLICA_PORT=${DOVEADM_REPLICA_PORT:-} - LOG_LINES=${LOG_LINES:-9999} - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -437,12 +443,6 @@ services: netfilter-mailcow: image: mailcow/netfilter:1.55 stop_grace_period: 30s - depends_on: - - dovecot-mailcow - - postfix-mailcow - - sogo-mailcow - - php-fpm-mailcow - - redis-mailcow restart: always privileged: true environment: @@ -453,6 +453,8 @@ services: - SNAT6_TO_SOURCE=${SNAT6_TO_SOURCE:-n} - REDIS_SLAVEOF_IP=${REDIS_SLAVEOF_IP:-} - REDIS_SLAVEOF_PORT=${REDIS_SLAVEOF_PORT:-} + - MAILCOW_REPLICA_IP=${MAILCOW_REPLICA_IP:-} + - DISABLE_NETFILTER_ISOLATION_RULE=${DISABLE_NETFILTER_ISOLATION_RULE:-n} network_mode: "host" volumes: - /lib/modules:/lib/modules:ro @@ -553,6 +555,8 @@ services: solr-mailcow: image: mailcow/solr:1.8.2 restart: always + depends_on: + - netfilter-mailcow volumes: - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data ports: diff --git a/generate_config.sh b/generate_config.sh index e62d16892..05d9ee2f1 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -494,6 +494,9 @@ WEBAUTHN_ONLY_TRUSTED_VENDORS=n # Otherwise it will work normally. SPAMHAUS_DQS_KEY= +# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n +# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost +DISABLE_NETFILTER_ISOLATION_RULE=n EOF mkdir -p data/assets/ssl diff --git a/update.sh b/update.sh index 5df32e00e..f1e31652c 100755 --- a/update.sh +++ b/update.sh @@ -481,6 +481,7 @@ CONFIG_ARRAY=( "WEBAUTHN_ONLY_TRUSTED_VENDORS" "SPAMHAUS_DQS_KEY" "SKIP_UNBOUND_HEALTHCHECK" + "DISABLE_NETFILTER_ISOLATION_RULE" ) detect_bad_asn @@ -754,6 +755,13 @@ for option in ${CONFIG_ARRAY[@]}; do echo '# Skip Unbound (DNS Resolver) Healthchecks (NOT Recommended!) - y/n' >> mailcow.conf echo 'SKIP_UNBOUND_HEALTHCHECK=n' >> mailcow.conf fi + elif [[ ${option} == "DISABLE_NETFILTER_ISOLATION_RULE" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Prevent netfilter from setting an iptables/nftables rule to isolate the mailcow docker network - y/n' >> mailcow.conf + echo '# CAUTION: Disabling this may expose container ports to other neighbors on the same subnet, even if the ports are bound to localhost' >> mailcow.conf + echo 'DISABLE_NETFILTER_ISOLATION_RULE=n' >> mailcow.conf + fi elif ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=n" >> mailcow.conf From 2072301d89683cdbe9a398150b199630d1109d81 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 2 Feb 2024 11:08:44 +0100 Subject: [PATCH 055/793] [Netfilter] only perform cleanup at exit if SIGTERM was recieved --- data/Dockerfiles/netfilter/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index 8480c9efa..7f8dd9fbf 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -376,6 +376,11 @@ def quit(signum, frame): global quit_now quit_now = True +def quit_clear(signum, frame): + global exit_code + clear() + sys.exit(exit_code) + if __name__ == '__main__': # init Logger @@ -474,8 +479,7 @@ if __name__ == '__main__': whitelistupdate_thread.daemon = True whitelistupdate_thread.start() - signal.signal(signal.SIGTERM, quit) - atexit.register(clear) + signal.signal(signal.SIGTERM, quit_clear) while not quit_now: time.sleep(0.5) From 2e57325dde15062fbecdf5ca49e29072f70a4814 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Fri, 2 Feb 2024 11:27:46 +0100 Subject: [PATCH 056/793] docker-compose.yml: Bump dovecot + netfilter version --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fa92546ad..31923d46c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -222,7 +222,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.27 + image: mailcow/dovecot:1.28 depends_on: - mysql-mailcow - netfilter-mailcow @@ -441,7 +441,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.55 + image: mailcow/netfilter:1.56 stop_grace_period: 30s restart: always privileged: true From 39589bd441291c7068f6642782be6edff0bcb371 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 2 Feb 2024 12:46:50 +0100 Subject: [PATCH 057/793] [Netfilter] only perform cleanup at exit if SIGTERM was recieved --- data/Dockerfiles/netfilter/main.py | 37 +++++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index 7f8dd9fbf..c3b19c4e8 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -30,6 +30,8 @@ exit_code = 0 lock = Lock() chain_name = "MAILCOW" r = None +pubsub = None +clear_before_quit = False def refreshF2boptions(): @@ -218,10 +220,12 @@ def clear(): with lock: tables.clearIPv4Table() tables.clearIPv6Table() - if r: - r.delete('F2B_ACTIVE_BANS') - r.delete('F2B_PERM_BANS') - pubsub.unsubscribe() + try: + if r is not None: + r.delete('F2B_ACTIVE_BANS') + r.delete('F2B_PERM_BANS') + except Exception as ex: + logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex) def watch(): logger.logInfo('Watching Redis channel F2B_CHANNEL') @@ -229,6 +233,7 @@ def watch(): global quit_now global exit_code + global pubsub while not quit_now: try: @@ -249,6 +254,7 @@ def watch(): ban(addr) except Exception as ex: logger.logWarn('Error reading log line from pubsub: %s' % ex) + pubsub = None quit_now = True exit_code = 2 @@ -372,17 +378,22 @@ def blacklistUpdate(): permBan(net=net, unban=True) time.sleep(60.0 - ((time.time() - start_time) % 60.0)) -def quit(signum, frame): - global quit_now - quit_now = True - -def quit_clear(signum, frame): - global exit_code - clear() +def sigterm_quit(signum, frame): + global clear_before_quit + clear_before_quit = True sys.exit(exit_code) +def berfore_quit(): + if clear_before_quit: + clear() + if pubsub is not None: + pubsub.unsubscribe() + if __name__ == '__main__': + atexit.register(berfore_quit) + signal.signal(signal.SIGTERM, sigterm_quit) + # init Logger logger = Logger(None) @@ -420,12 +431,12 @@ if __name__ == '__main__': else: r = redis.StrictRedis(host=redis_slaveof_ip, decode_responses=True, port=redis_slaveof_port, db=0) r.ping() + pubsub = r.pubsub() except Exception as ex: print('%s - trying again in 3 seconds' % (ex)) time.sleep(3) else: break - pubsub = r.pubsub() Logger.r = r # rename fail2ban to netfilter @@ -479,8 +490,6 @@ if __name__ == '__main__': whitelistupdate_thread.daemon = True whitelistupdate_thread.start() - signal.signal(signal.SIGTERM, quit_clear) - while not quit_now: time.sleep(0.5) From c941e802d4177a7d8278f3a80359d833d9770bec Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 2 Feb 2024 12:57:21 +0100 Subject: [PATCH 058/793] [Netfilter] only perform cleanup at exit if SIGTERM was recieved --- data/Dockerfiles/netfilter/main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index c3b19c4e8..f4acd4612 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -228,12 +228,12 @@ def clear(): logger.logWarn('Error clearing redis keys F2B_ACTIVE_BANS and F2B_PERM_BANS: %s' % ex) def watch(): - logger.logInfo('Watching Redis channel F2B_CHANNEL') - pubsub.subscribe('F2B_CHANNEL') - + global pubsub global quit_now global exit_code - global pubsub + + logger.logInfo('Watching Redis channel F2B_CHANNEL') + pubsub.subscribe('F2B_CHANNEL') while not quit_now: try: From a3104934850cd6663c9e7347478f6f8902def03a Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 2 Feb 2024 16:52:41 +0100 Subject: [PATCH 059/793] [Dovecot] fix repl_health.sh --- data/Dockerfiles/dovecot/repl_health.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/Dockerfiles/dovecot/repl_health.sh b/data/Dockerfiles/dovecot/repl_health.sh index 05d891421..93b66da49 100755 --- a/data/Dockerfiles/dovecot/repl_health.sh +++ b/data/Dockerfiles/dovecot/repl_health.sh @@ -11,7 +11,7 @@ fi # Is replication active? # grep on file is less expensive than doveconf -if ! grep -qi mail_replica /etc/dovecot/dovecot.conf; then +if [ -n ${MAILCOW_REPLICA_IP} ]; then ${REDIS_CMDLINE} SET DOVECOT_REPL_HEALTH 1 > /dev/null exit fi From 909f07939e93af0135cc9bd6b5aeb2f9ee50d8fe Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Fri, 2 Feb 2024 17:06:31 +0100 Subject: [PATCH 060/793] dovecot: bump version for repl fix --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d75d61cb2..26a224b53 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -222,7 +222,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.28 + image: mailcow/dovecot:1.28.1 depends_on: - mysql-mailcow - netfilter-mailcow From 57e67ea8f79a1fe218cb0f0ea8ad95a53b9f0179 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Fri, 2 Feb 2024 17:40:44 +0100 Subject: [PATCH 061/793] [Netfilter] fix mailcow isolation rule for iptables --- data/Dockerfiles/netfilter/modules/IPTables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/Dockerfiles/netfilter/modules/IPTables.py b/data/Dockerfiles/netfilter/modules/IPTables.py index 29a9fb65a..3d3d43974 100644 --- a/data/Dockerfiles/netfilter/modules/IPTables.py +++ b/data/Dockerfiles/netfilter/modules/IPTables.py @@ -219,7 +219,7 @@ class IPTables: # insert mailcow isolation rule rule = iptc.Rule() - rule.in_interface = f'! {_interface}' + rule.in_interface = f'!{_interface}' rule.out_interface = _interface rule.protocol = 'tcp' rule.create_target("DROP") @@ -234,7 +234,7 @@ class IPTables: if _allow != "": rule = iptc.Rule() rule.src = _allow - rule.in_interface = f'! {_interface}' + rule.in_interface = f'!{_interface}' rule.out_interface = _interface rule.protocol = 'tcp' rule.create_target("ACCEPT") From 464b6f2e9328b64b0548deba7ab1c9edb096bf62 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 5 Feb 2024 09:47:19 +0100 Subject: [PATCH 062/793] [Netfilter] fix redis logs --- data/Dockerfiles/netfilter/main.py | 4 ++-- data/Dockerfiles/netfilter/modules/Logger.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/Dockerfiles/netfilter/main.py b/data/Dockerfiles/netfilter/main.py index f4acd4612..62e0dda75 100644 --- a/data/Dockerfiles/netfilter/main.py +++ b/data/Dockerfiles/netfilter/main.py @@ -395,7 +395,7 @@ if __name__ == '__main__': signal.signal(signal.SIGTERM, sigterm_quit) # init Logger - logger = Logger(None) + logger = Logger() # init backend backend = sys.argv[1] @@ -437,7 +437,7 @@ if __name__ == '__main__': time.sleep(3) else: break - Logger.r = r + logger.set_redis(r) # rename fail2ban to netfilter if r.exists('F2B_LOG'): diff --git a/data/Dockerfiles/netfilter/modules/Logger.py b/data/Dockerfiles/netfilter/modules/Logger.py index 2a40de0ce..25562965f 100644 --- a/data/Dockerfiles/netfilter/modules/Logger.py +++ b/data/Dockerfiles/netfilter/modules/Logger.py @@ -2,7 +2,10 @@ import time import json class Logger: - def __init__(self, redis): + def __init__(self): + self.r = None + + def set_redis(self, redis): self.r = redis def log(self, priority, message): @@ -10,7 +13,7 @@ class Logger: tolog['time'] = int(round(time.time())) tolog['priority'] = priority tolog['message'] = message - if self.r: + if self.r is not None: self.r.lpush('NETFILTER_LOG', json.dumps(tolog, ensure_ascii=False)) print(message) From 77e6ef218c9d5b0ceeb873b43435aee4173cc86c Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Mon, 5 Feb 2024 09:54:16 +0100 Subject: [PATCH 063/793] [Netfilter] Update to 1.57 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 26a224b53..17559bedd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -441,7 +441,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.56 + image: mailcow/netfilter:1.57 stop_grace_period: 30s restart: always privileged: true From 38cc85fa4ca08723363c7a672a36383fccf259e2 Mon Sep 17 00:00:00 2001 From: vicente Date: Wed, 7 Feb 2024 15:36:04 +0100 Subject: [PATCH 064/793] set strict=False --- data/Dockerfiles/netfilter/modules/NFTables.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/Dockerfiles/netfilter/modules/NFTables.py b/data/Dockerfiles/netfilter/modules/NFTables.py index e8e02c476..38b31ebff 100644 --- a/data/Dockerfiles/netfilter/modules/NFTables.py +++ b/data/Dockerfiles/netfilter/modules/NFTables.py @@ -309,8 +309,8 @@ class NFTables: rule_handle = rule["handle"] break - dest_net = ipaddress.ip_network(source_address) - target_net = ipaddress.ip_network(snat_target) + dest_net = ipaddress.ip_network(source_address, strict=False) + target_net = ipaddress.ip_network(snat_target, strict=False) if rule_found: saddr_ip = rule["expr"][0]["match"]["right"]["prefix"]["addr"] @@ -321,9 +321,9 @@ class NFTables: target_ip = rule["expr"][3]["snat"]["addr"] - saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len)) - daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len)) - current_target_net = ipaddress.ip_network(target_ip) + saddr_net = ipaddress.ip_network(saddr_ip + '/' + str(saddr_len), strict=False) + daddr_net = ipaddress.ip_network(daddr_ip + '/' + str(daddr_len), strict=False) + current_target_net = ipaddress.ip_network(target_ip, strict=False) match = all(( dest_net == saddr_net, @@ -417,7 +417,7 @@ class NFTables: json_command = self.get_base_dict() expr_opt = [] - ipaddr_net = ipaddress.ip_network(ipaddr) + ipaddr_net = ipaddress.ip_network(ipaddr, strict=False) right_dict = {'prefix': {'addr': str(ipaddr_net.network_address), 'len': int(ipaddr_net.prefixlen) } } left_dict = {'payload': {'protocol': _family, 'field': 'saddr'} } @@ -466,7 +466,7 @@ class NFTables: current_rule_net = ipaddress.ip_network(current_rule_ip) # ip to ban - candidate_net = ipaddress.ip_network(ipaddr) + candidate_net = ipaddress.ip_network(ipaddr, strict=False) if current_rule_net == candidate_net: rule_handle = _object["rule"]["handle"] From eb91d9905bc1f4dbdde486e635592bea4ee02726 Mon Sep 17 00:00:00 2001 From: vicente Date: Wed, 7 Feb 2024 15:48:49 +0100 Subject: [PATCH 065/793] fix typpo in chain order message --- data/Dockerfiles/netfilter/modules/NFTables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/data/Dockerfiles/netfilter/modules/NFTables.py b/data/Dockerfiles/netfilter/modules/NFTables.py index 38b31ebff..4cb0110ae 100644 --- a/data/Dockerfiles/netfilter/modules/NFTables.py +++ b/data/Dockerfiles/netfilter/modules/NFTables.py @@ -41,6 +41,7 @@ class NFTables: exit_code = 2 if chain_position > 0: + chain_position += 1 self.logger.logCrit(f'MAILCOW target is in position {chain_position} in the {filter_table} {chain} table, restarting container to fix it...') err = True exit_code = 2 From 5a9702771cba4fedbc79331e92ff757f734df58e Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 7 Feb 2024 17:18:20 +0100 Subject: [PATCH 066/793] [SOGo] Fixed SOGo crash on older kernels < 5.10.0-X --- data/Dockerfiles/sogo/Dockerfile | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index a4601c405..59fc66804 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -1,8 +1,8 @@ -FROM debian:bookworm-slim +FROM debian:bullseye-slim LABEL maintainer "The Infrastructure Company GmbH GmbH " ARG DEBIAN_FRONTEND=noninteractive -ARG DEBIAN_VERSION=bookworm +ARG DEBIAN_VERSION=bullseye ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?.*)$ ARG GOSU_VERSION=1.17 diff --git a/docker-compose.yml b/docker-compose.yml index 17559bedd..b36f45b8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -175,7 +175,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.122 + image: mailcow/sogo:1.122.1 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} From d08ccbce789880eb81ebebca48d440637ab36983 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 7 Feb 2024 17:28:49 +0100 Subject: [PATCH 067/793] dovecot: fix wrong timestamps inside logs --- data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf | 1 + data/Dockerfiles/dovecot/syslog-ng.conf | 1 + 2 files changed, 2 insertions(+) diff --git a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf index f7fc20b7e..519928954 100644 --- a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf @@ -7,6 +7,7 @@ options { use_fqdn(no); owner("root"); group("adm"); perm(0640); stats(freq(0)); + keep_timestamp(no); bad_hostname("^gconfd$"); }; source s_dgram { diff --git a/data/Dockerfiles/dovecot/syslog-ng.conf b/data/Dockerfiles/dovecot/syslog-ng.conf index fcc13587d..3e929e7b9 100644 --- a/data/Dockerfiles/dovecot/syslog-ng.conf +++ b/data/Dockerfiles/dovecot/syslog-ng.conf @@ -7,6 +7,7 @@ options { use_fqdn(no); owner("root"); group("adm"); perm(0640); stats(freq(0)); + keep_timestamp(no); bad_hostname("^gconfd$"); }; source s_dgram { From 583c5b48a00bb1f1a61cbc411bc90532fce3ca3b Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Wed, 7 Feb 2024 17:29:36 +0100 Subject: [PATCH 068/793] dovecot: bump to docker image 1.28.1 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index b36f45b8b..0dfd344b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -222,7 +222,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.28.1 + image: mailcow/dovecot:1.28.2 depends_on: - mysql-mailcow - netfilter-mailcow From 63bb8e8cefb4afebd50f12a485f6af5d12c98125 Mon Sep 17 00:00:00 2001 From: DerLinkman Date: Thu, 8 Feb 2024 12:23:46 +0100 Subject: [PATCH 069/793] unbound: increase check interval to 30s --- data/Dockerfiles/unbound/Dockerfile | 4 ++-- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile index f56cbc6e5..f6d072cc6 100644 --- a/data/Dockerfiles/unbound/Dockerfile +++ b/data/Dockerfiles/unbound/Dockerfile @@ -20,10 +20,10 @@ EXPOSE 53/udp 53/tcp COPY docker-entrypoint.sh /docker-entrypoint.sh -# healthcheck (nslookup) +# healthcheck (dig, ping, nc) COPY healthcheck.sh /healthcheck.sh RUN chmod +x /healthcheck.sh -HEALTHCHECK --interval=5s --timeout=30s CMD [ "/healthcheck.sh" ] +HEALTHCHECK --interval=30s --timeout=30s CMD [ "/healthcheck.sh" ] ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 0dfd344b5..df545c15e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '2.1' services: unbound-mailcow: - image: mailcow/unbound:1.20 + image: mailcow/unbound:1.21 environment: - TZ=${TZ} - SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n} From c90d637a48cdac86363e4937bdd787db8bc94c37 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 16 Feb 2023 15:32:53 +0100 Subject: [PATCH 070/793] [Web] redirect to sogo after failed sogo-auth --- data/web/sogo-auth.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/data/web/sogo-auth.php b/data/web/sogo-auth.php index 40fff5856..c34a60def 100644 --- a/data/web/sogo-auth.php +++ b/data/web/sogo-auth.php @@ -65,8 +65,7 @@ elseif (isset($_GET['login'])) { } } } - header('HTTP/1.0 403 Forbidden'); - echo "Forbidden"; + header("Location: /SOGo/"); exit; } // only check for admin-login on sogo GUI requests From cfce7086a578bc4d3883527b969d80a3067b00fb Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 16 Feb 2023 15:34:47 +0100 Subject: [PATCH 071/793] [Web] few style changes --- data/web/css/build/013-datatables.css | 3 --- data/web/css/build/014-mailcow.css | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/data/web/css/build/013-datatables.css b/data/web/css/build/013-datatables.css index 57e2b6d94..2b19ba24e 100644 --- a/data/web/css/build/013-datatables.css +++ b/data/web/css/build/013-datatables.css @@ -8,9 +8,6 @@ .dtr-details { width: 100%; } -.table-striped>tbody>tr:nth-of-type(odd) { - background-color: #F2F2F2; -} td.child>ul>li { display: flex; } diff --git a/data/web/css/build/014-mailcow.css b/data/web/css/build/014-mailcow.css index 6c70a2a55..8c1d7c2af 100644 --- a/data/web/css/build/014-mailcow.css +++ b/data/web/css/build/014-mailcow.css @@ -33,6 +33,13 @@ url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff2') format('woff2'), url('/fonts/noto-sans-v12-latin_greek_cyrillic-700italic.woff') format('woff'); } + +body { + min-height: 100vh; + display: flex; + flex-direction: column; + background-color: #fbfbfb; +} #maxmsgsize { min-width: 80px; } #slider1 .slider-selection { background: #FFD700; @@ -78,6 +85,19 @@ .navbar-fixed-top .navbar-collapse { max-height: 1000px } +.nav-tabs .nav-link, .nav-tabs .nav-link.disabled, .nav-tabs .nav-link.disabled:hover, .nav-tabs .nav-link.disabled:focus { + border-color: #dfdfdf; +} +.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { + border-color: #dfdfdf; + border-bottom: 1px solid #ffffff; +} +.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #dfdfdf; +} +.nav-tabs { + border-bottom: 1px solid #dfdfdf; +} .bi { display: inline-block; font-size: 12pt; From 415c1d057499a4f927d4eb4edbca467785fee6a7 Mon Sep 17 00:00:00 2001 From: FreddleSpl0it Date: Thu, 16 Feb 2023 15:39:12 +0100 Subject: [PATCH 072/793] [Web] add seperate link for logged in users --- data/web/inc/functions.customize.inc.php | 25 +++++++++-- data/web/inc/header.inc.php | 9 ++++ data/web/js/site/admin.js | 1 + .../templates/admin/tab-config-customize.twig | 5 ++- data/web/templates/base.twig | 41 +++++++++++-------- data/web/templates/index.twig | 4 +- 6 files changed, 64 insertions(+), 21 deletions(-) diff --git a/data/web/inc/functions.customize.inc.php b/data/web/inc/functions.customize.inc.php index b72923573..c4df924d9 100644 --- a/data/web/inc/functions.customize.inc.php +++ b/data/web/inc/functions.customize.inc.php @@ -122,10 +122,14 @@ function customize($_action, $_item, $_data = null) { case 'app_links': $apps = (array)$_data['app']; $links = (array)$_data['href']; + $user_links = (array)$_data['user_href']; $out = array(); - if (count($apps) == count($links)) { + if (count($apps) == count($links) && count($apps) == count($user_links)) { for ($i = 0; $i < count($apps); $i++) { - $out[] = array($apps[$i] => $links[$i]); + $out[] = array($apps[$i] => array( + 'link' => $links[$i], + 'user_link' => $user_links[$i] + )); } try { $redis->set('APP_LINKS', json_encode($out)); @@ -256,7 +260,22 @@ function customize($_action, $_item, $_data = null) { ); return false; } - return ($app_links) ? $app_links : false; + + if (empty($app_links)){ + return false; + } + + foreach($app_links as $key => $value){ + foreach($value as $app => $details){ + if (empty($details['user_link']) || empty($_SESSION['mailcow_cc_username'])){ + $app_links[$key][$app]['user_link'] = $app_links[$key][$app]['link']; + } else { + $app_links[$key][$app]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $app_links[$key][$app]['user_link']); + } + } + } + + return $app_links; break; case 'main_logo': case 'main_logo_dark': diff --git a/data/web/inc/header.inc.php b/data/web/inc/header.inc.php index 9afc288dd..c0e166403 100644 --- a/data/web/inc/header.inc.php +++ b/data/web/inc/header.inc.php @@ -30,6 +30,14 @@ if(!file_exists($CSSPath)) { cleanupCSS($hash); } +$mailcow_apps_processed = $MAILCOW_APPS; +for ($i = 0; $i < count($mailcow_apps_processed); $i++) { + if (!empty($_SESSION['mailcow_cc_username'])){ + $mailcow_apps_processed[$i]['user_link'] = str_replace('%u', $_SESSION['mailcow_cc_username'], $mailcow_apps_processed[$i]['user_link']); + } +} + + $globalVariables = [ 'mailcow_hostname' => getenv('MAILCOW_HOSTNAME'), 'mailcow_locale' => @$_SESSION['mailcow_locale'], @@ -45,6 +53,7 @@ $globalVariables = [ 'lang' => $lang, 'skip_sogo' => (getenv('SKIP_SOGO') == 'y'), 'allow_admin_email_login' => (getenv('ALLOW_ADMIN_EMAIL_LOGIN') == 'n'), + 'mailcow_apps_processed' => $mailcow_apps_processed, 'mailcow_apps' => $MAILCOW_APPS, 'app_links' => customize('get', 'app_links'), 'is_root_uri' => (parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) == '/'), diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js index 80da64167..095557311 100644 --- a/data/web/js/site/admin.js +++ b/data/web/js/site/admin.js @@ -711,6 +711,7 @@ jQuery(function($){ if (type == "app_link") { cols = ''; cols += ''; + cols += ''; cols += '' + lang.remove_row + ''; } else if (type == "f2b_regex") { cols = ''; diff --git a/data/web/templates/admin/tab-config-customize.twig b/data/web/templates/admin/tab-config-customize.twig index 7fc990a64..4b8f53231 100644 --- a/data/web/templates/admin/tab-config-customize.twig +++ b/data/web/templates/admin/tab-config-customize.twig @@ -58,13 +58,15 @@ {{ lang.admin.app_name }} {{ lang.admin.link }} + {{ lang.admin.user_link }}   {% for row in app_links %} {% for key, val in row %} - + + {{ lang.admin.remove_row }} {% endfor %} @@ -73,6 +75,7 @@ + {{ lang.admin.remove_row }} {% endfor %} diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index ca744d2a3..1c027138c 100644 --- a/data/web/templates/base.twig +++ b/data/web/templates/base.twig @@ -29,7 +29,7 @@
{% block navbar %} -