[Postfix] Finally here: MX based transport map routing; Sorry it took years, Patrik

[Web] Small fixes
master
andryyy 2021-05-28 10:40:41 +02:00
parent f54ab6f867
commit 8a83587800
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
12 changed files with 112 additions and 49 deletions

View File

@ -337,6 +337,19 @@ query = SELECT goto FROM alias
AND alias_domain.active='1'
EOF
# MX based routing
cat <<EOF > /opt/postfix/conf/sql/mysql_mbr_access_maps.cf
# Autogenerated by mailcow
user = ${DBUSER}
password = ${DBPASS}
hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT CONCAT('FILTER smtp_via_transport_maps:', nexthop) as transport FROM transports
WHERE '%s' REGEXP destination
AND active='1'
AND is_mx_based='1';
EOF
# Reject sasl usernames with smtp disabled
cat <<EOF > /opt/postfix/conf/sql/mysql_sasl_access_maps.cf
# Autogenerated by mailcow

View File

@ -78,6 +78,7 @@ postscreen_non_smtp_command_enable = no
postscreen_pipelining_enable = no
proxy_read_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_passwd_maps_transport_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_access_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,
$sender_dependent_default_transport_maps,
$smtp_tls_policy_maps,
@ -116,6 +117,7 @@ smtpd_hard_error_limit = ${stress?1}${stress:5}
smtpd_helo_required = yes
smtpd_proxy_timeout = 600s
smtpd_recipient_restrictions = check_sasl_access proxy:mysql:/opt/postfix/conf/sql/mysql_sasl_access_maps.cf,
check_recipient_mx_access proxy:mysql:/opt/postfix/conf/sql/mysql_mbr_access_maps.cf,
permit_sasl_authenticated,
permit_mynetworks,
check_recipient_access proxy:mysql:/opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf,

View File

@ -431,19 +431,19 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<legend><?=$lang['admin']['add_relayhost'];?></legend>
<p class="help-block"><?=$lang['admin']['add_relayhost_hint'];?></p>
<div class="row">
<div class="col-md-6">
<div class="col-md-8">
<form class="form" data-id="rlyhost" role="form" method="post">
<div class="form-group">
<label for="rlyhost_hostname"><?=$lang['admin']['host'];?></label>
<input class="form-control input-sm" id="rlyhost_hostname" name="hostname" placeholder='[0.0.0.0], [0.0.0.0]:25, host:25, host, [host]:25' required>
<input class="form-control" id="rlyhost_hostname" name="hostname" placeholder='[0.0.0.0], [0.0.0.0]:25, host:25, host, [host]:25' required>
</div>
<div class="form-group">
<label for="rlyhost_username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" id="rlyhost_username" name="username">
<input class="form-control" id="rlyhost_username" name="username">
</div>
<div class="form-group">
<label for="rlyhost_password"><?=$lang['admin']['password'];?></label>
<input class="form-control input-sm" id="rlyhost_password" name="password">
<input class="form-control" id="rlyhost_password" name="password">
</div>
<button class="btn btn-default" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><i class="bi bi-plus-lg"></i> <?=$lang['admin']['add'];?></button>
</form>
@ -474,29 +474,29 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<legend><?=$lang['admin']['add_transport'];?></legend>
<p class="help-block"><?=$lang['admin']['add_transports_hint'];?></p>
<div class="row">
<div class="col-md-6">
<div class="col-md-8">
<form class="form" data-id="transport" role="form" method="post">
<div class="form-group">
<label for="transport_destination"><?=$lang['admin']['destination'];?></label>
<input class="form-control input-sm" id="transport_destination" name="destination" placeholder='<?=$lang['admin']['transport_dest_format'];?>' required>
<input class="form-control" id="transport_destination" name="destination" placeholder='<?=$lang['admin']['transport_dest_format'];?>' required>
</div>
<div class="form-group">
<label for="transport_nexthop"><?=$lang['admin']['nexthop'];?></label>
<input class="form-control input-sm" id="transport_nexthop" name="nexthop" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required>
<input class="form-control" id="transport_nexthop" name="nexthop" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required>
</div>
<div class="form-group">
<label for="transport_username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" id="transport_username" name="username">
<input class="form-control" id="transport_username" name="username">
</div>
<div class="form-group">
<label for="transport_password"><?=$lang['admin']['password'];?></label>
<input class="form-control input-sm" id="transport_password" name="password">
<input class="form-control" id="transport_password" name="password">
</div>
<!-- <div class="form-group">
<div class="form-group">
<label>
<input type="checkbox" name="lookup_mx" value="1"> <?=$lang['admin']['lookup_mx'];?>
<input type="checkbox" name="is_mx_based" value="1"> <?=$lang['admin']['lookup_mx'];?>
</label>
</div> -->
</div>
<div class="form-group">
<label>
<input type="checkbox" name="active" value="1"> <?=$lang['admin']['active'];?>

View File

@ -241,3 +241,6 @@ table.footable>tbody>tr.footable-empty>td {
legend > [class^="bi-"]::before, legend > [class*=" bi-"]::before {
vertical-align: 0em !important;
}
code {
font-size: inherit;
}

View File

@ -1095,6 +1095,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="edittransport">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="is_mx_based">
<div class="form-group">
<label class="control-label col-sm-2" for="destination"><?=$lang['add']['destination'];?></label>
<div class="col-sm-10">
@ -1119,6 +1120,13 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<input type="text" data-hibp="true" class="form-control" name="password" value="<?=htmlspecialchars($result['password'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="is_mx_based" <?=($result['is_mx_based']=="1") ? "checked" : null;?>> <?=$lang['edit']['lookup_mx'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">

View File

@ -192,7 +192,7 @@ function transport($_action, $_data = null) {
}
$destinations = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['destination']));
$active = intval($_data['active']);
$lookup_mx = intval($_data['lookup_mx']);
$is_mx_based = intval($_data['is_mx_based']);
$nexthop = trim($_data['nexthop']);
if (filter_var($nexthop, FILTER_VALIDATE_IP)) {
$nexthop = '[' . $nexthop . ']';
@ -238,7 +238,16 @@ function transport($_action, $_data = null) {
continue;
}
// ".domain" is a valid destination, "..domain" is not
if (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false)) {
if ($is_mx_based == 0 && (empty($dest) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $dest)) === false && $dest != '*' && filter_var($dest, FILTER_VALIDATE_EMAIL) === false))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_destination', $dest)
);
unset($destinations[$d_ix]);
continue;
}
if ($is_mx_based == 1 && (empty($dest) || @preg_match('/' . $dest . '/', null) === false)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
@ -275,14 +284,14 @@ function transport($_action, $_data = null) {
}
}
foreach ($destinations as $insert_dest) {
$stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `username` , `password`, `lookup_mx`, `active`)
VALUES (:nexthop, :destination, :username, :password, :lookup_mx, :active)");
$stmt = $pdo->prepare("INSERT INTO `transports` (`nexthop`, `destination`, `is_mx_based`, `username` , `password`, `active`)
VALUES (:nexthop, :destination, :is_mx_based, :username, :password, :active)");
$stmt->execute(array(
':nexthop' => $nexthop,
':destination' => $insert_dest,
':is_mx_based' => $is_mx_based,
':username' => $username,
':password' => str_replace(':', '\:', $password),
':lookup_mx' => $lookup_mx,
':active' => $active
));
}
@ -318,7 +327,7 @@ function transport($_action, $_data = null) {
$nexthop = (!empty($_data['nexthop'])) ? trim($_data['nexthop']) : $is_now['nexthop'];
$username = (isset($_data['username'])) ? trim($_data['username']) : $is_now['username'];
$password = (isset($_data['password'])) ? trim($_data['password']) : $is_now['password'];
$lookup_mx = (isset($_data['lookup_mx']) && $_data['lookup_mx'] != '') ? intval($_data['lookup_mx']) : $is_now['lookup_mx'];
$is_mx_based = (isset($_data['is_mx_based']) && $_data['is_mx_based'] != '') ? intval($_data['is_mx_based']) : $is_now['is_mx_based'];
$active = (isset($_data['active']) && $_data['active'] != '') ? intval($_data['active']) : $is_now['active'];
}
else {
@ -353,6 +362,22 @@ function transport($_action, $_data = null) {
}
}
}
if ($is_mx_based == 0 && (empty($destination) || (is_valid_domain_name(preg_replace('/^' . preg_quote('.', '/') . '/', '', $destination)) === false && $destination != '*' && filter_var($destination, FILTER_VALIDATE_EMAIL) === false))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_destination', $destination)
);
return false;
}
if ($is_mx_based == 1 && (empty($destination) || @preg_match('/' . $destination . '/', null) === false)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('invalid_destination', $destination)
);
return false;
}
if (isset($next_hop_matches[1])) {
if (in_array($next_hop_clean, $existing_nh)) {
$_SESSION['return'][] = array(
@ -381,19 +406,19 @@ function transport($_action, $_data = null) {
try {
$stmt = $pdo->prepare("UPDATE `transports` SET
`destination` = :destination,
`is_mx_based` = :is_mx_based,
`nexthop` = :nexthop,
`username` = :username,
`password` = :password,
`lookup_mx` = :lookup_mx,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':id' => $id,
':destination' => $destination,
':is_mx_based' => $is_mx_based,
':nexthop' => $nexthop,
':username' => $username,
':password' => $password,
':lookup_mx' => $lookup_mx,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `transports` SET
@ -456,7 +481,7 @@ function transport($_action, $_data = null) {
return false;
}
$transports = array();
$stmt = $pdo->query("SELECT `id`, `destination`, `nexthop`, `username` FROM `transports`");
$stmt = $pdo->query("SELECT `id`, `is_mx_based`, `destination`, `nexthop`, `username` FROM `transports`");
$transports = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $transports;
break;
@ -466,12 +491,12 @@ function transport($_action, $_data = null) {
}
$transportdata = array();
$stmt = $pdo->prepare("SELECT `id`,
`is_mx_based`,
`destination`,
`nexthop`,
`username`,
`password`,
`active`,
`lookup_mx`,
CONCAT(LEFT(`password`, 3), '...') AS `password_short`
FROM `transports`
WHERE `id` = :id");

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
$db_version = "25052021_0900";
$db_version = "27052021_2000";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -152,9 +152,9 @@ function init_db_schema() {
"id" => "INT NOT NULL AUTO_INCREMENT",
"destination" => "VARCHAR(255) NOT NULL",
"nexthop" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"lookup_mx" => "TINYINT(1) NOT NULL DEFAULT '1'",
"username" => "VARCHAR(255) NOT NULL DEFAULT ''",
"password" => "VARCHAR(255) NOT NULL DEFAULT ''",
"is_mx_based" => "TINYINT(1) NOT NULL DEFAULT '0'",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(

View File

@ -103,7 +103,7 @@ $(document).ready(function() {
$(".hibp-out").after(res);
}
});
$('[data-hibp]').after('<p class="small haveibeenpwned"> Check against haveibeenpwned.com</p><span class="hibp-out"></span>');
$('[data-hibp]').after('<p class="small haveibeenpwned"><i class="bi bi-shield-fill-exclamation"></i> Check against haveibeenpwned.com</p><span class="hibp-out"></span>');
$('[data-hibp]').on('input', function() {
out_field = $(this).next('.haveibeenpwned').next('.hibp-out').text('').attr('class', 'hibp-out');
});

View File

@ -187,9 +187,9 @@ jQuery(function($){
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
{"name":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"in_use_by","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
{"name":"in_use_by","title":lang.in_use_by,"style":{"min-width":"200px","width":"200px"}, "type": "text","breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
@ -213,11 +213,11 @@ jQuery(function($){
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
{"name":"destination","type":"text","title":lang.destination,"style":{"width":"250px"}},
{"name":"nexthop","type":"text","title":lang.nexthop,"style":{"width":"250px"}},
{"name":"destination","type":"text","title":lang.destination,"style":{"min-width":"300px","width":"300px"}},
{"name":"nexthop","type":"text","title":lang.nexthop,"style":{"min-width":"200px","width":"200px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active,"formatter": function(value){return 1==value?'<i class="bi bi-check-lg"></i>':0==value&&'<i class="bi bi-x-lg"></i>';}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","min-width":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
@ -233,7 +233,12 @@ jQuery(function($){
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
"toggleSelector": "table tbody span.footable-toggle",
"on": {
"ready.ft.table": function(e, ft){
$('.mx-info').tooltip();
}
}
});
}
function draw_queue() {
@ -288,8 +293,11 @@ jQuery(function($){
});
} else if (table == 'transportstable') {
$.each(data, function (i, item) {
if (item.is_mx_based) {
item.destination = '<i class="bi bi-info-circle-fill text-info mx-info" data-toggle="tooltip" title="' + lang.is_mx_based + '"></i> <code>' + item.destination + '</code>';
}
if (item.username) {
item.username = '<span style="border-left:3px solid #' + intToRGB(hashCode(item.nexthop)) + ';padding-left:5px;">' + item.username + '</span>';
item.username = '<i style="color:#' + intToRGB(hashCode(item.nexthop)) + ';" class="bi bi-square-fill"></i> ' + item.username;
}
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="transport-map" class="btn btn-xs btn-default"><i class="bi bi-caret-right-fill"></i> Test</a>' +

View File

@ -156,7 +156,7 @@
"change_logo": "Logo ändern",
"configuration": "Konfiguration",
"convert_html_to_text": "Konvertiere HTML zu reinem Text",
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Host.",
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Next Hop.",
"customer_id": "Kunde",
"customize": "UI-Anpassung",
"delete_queue": "Alle löschen",
@ -210,6 +210,7 @@
"html": "HTML",
"import": "Importieren",
"import_private_key": "Private Key importieren",
"is_mx_based": "MX-basiert",
"in_use_by": "Verwendet von",
"inactive": "Inaktiv",
"include_exclude": "Ein- und Ausschlüsse",
@ -220,7 +221,7 @@
"link": "Link",
"loading": "Bitte warten...",
"logo_info": "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Darstellung auf der Login-Maske beträgt die skalierte Breite maximal 250px. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.",
"lookup_mx": "Ziel gegen MX prüfen (etwa .outlook.com, um alle Ziele mit MX *.outlook.com zu routen)",
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
"main_name": "\"mailcow UI\" Name",
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
"message": "Nachricht",
@ -232,7 +233,7 @@
"no_record": "Kein Eintrag",
"oauth2_client_id": "Client ID",
"oauth2_client_secret": "Client Secret",
"oauth2_info": "Die OAuth2 Implementierung unterstützt den Grant Type \"Authorization Code\" mit Refresh Tokens.<br>\r\nDer Server wird automatisch einen neuen Refresh Token ausstellen, sobald ein vorheriger Token gegen einen Access Token eingetauscht wurde.<br><br>\r\n Der Standard Scope lautet <i>profile</i>. Nur Mailbox-Benutzer können sich gegen OAuth2 authentifizieren. Wird kein Scope angegeben, verwendet das System per Standard <i>profile</i>.<br>\r\n Der <i>state</i> Parameter wird im Zuge des Autorisierungsprozesses benötigt.<br><br>\r\nDie Pfade für die OAuth2 API lauten wie folgt: <br>\r\n<ul>\r\n <li>Authorization Endpoint: <code>/oauth/authorize</code></li>\r\n <li>Token Endpoint: <code>/oauth/token</code></li>\r\n <li>Resource Page: <code>/oauth/profile</code></li>\r\n</ul>\r\nDie Regenerierung des Client Secrets wird vorhandene Authorization Codes nicht invalidieren, dennoch wird der Renew des Access Tokens durch einen Refresh Token nicht mehr gelingen.<br><br>\r\nDas Entfernen aller Client Tokens verursacht die umgehende Terminierung aller aktiven OAuth2 Sessions. Clients müssen sich erneut gegen die OAuth2 Anwendung authentifizieren.",
"oauth2_info": "Die OAuth2 Implementierung unterstützt den Grant Type \"Authorization Code\" mit Refresh Tokens.<br>\r\nDer Server wird automatisch einen neuen Refresh Token ausstellen, sobald ein vorheriger Token gegen einen Access Token eingetauscht wurde.<br><br>\r\n&#8226; Der Standard Scope lautet <i>profile</i>. Nur Mailbox-Benutzer können sich gegen OAuth2 authentifizieren. Wird kein Scope angegeben, verwendet das System per Standard <i>profile</i>.<br>\r\n&#8226; Der <i>state</i> Parameter wird im Zuge des Autorisierungsprozesses benötigt.<br><br>\r\nDie Pfade für die OAuth2 API lauten wie folgt: <br>\r\n<ul>\r\n <li>Authorization Endpoint: <code>/oauth/authorize</code></li>\r\n <li>Token Endpoint: <code>/oauth/token</code></li>\r\n <li>Resource Page: <code>/oauth/profile</code></li>\r\n</ul>\r\nDie Regenerierung des Client Secrets wird vorhandene Authorization Codes nicht invalidieren, dennoch wird der Renew des Access Tokens durch einen Refresh Token nicht mehr gelingen.<br><br>\r\nDas Entfernen aller Client Tokens verursacht die umgehende Terminierung aller aktiven OAuth2 Sessions. Clients müssen sich erneut gegen die OAuth2 Anwendung authentifizieren.",
"oauth2_redirect_uri": "Redirect-URI",
"oauth2_renew_secret": "Neues Client Secret generieren",
"oauth2_revoke_tokens": "Alle Client Tokens entfernen",
@ -323,10 +324,10 @@
"title": "Title",
"title_name": "\"mailcow UI\" Webseiten Titel",
"to_top": "Nach oben",
"transport_dest_format": "Syntax: example.org, .example.org, *, box@example.org (mehrere Werte getrennt durch Komma einzugeben)",
"transport_dest_format": "Regex oder Syntax: example.org, .example.org, *, box@example.org (getrennt durch Komma einzugeben)",
"transport_maps": "Transport-Maps",
"transports_hint": "→ Transport-Maps <b>überwiegen</b> senderabhängige Transport Maps.<br>\r\n→ Transport-Maps ignorieren Mailbox-Einstellungen für ausgehende Verschlüsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.<br>\r\n Der Transport erfolgt immer via \"smtp:\", verwendet TLS wenn angeboten und unterstützt kein wrapped TLS (SMTPS).<br>\r\n Adressen, die mit \"/localhost$/\" übereinstimmen, werden immer via \"local:\" transportiert, daher sind sie von einer Zieldefinition \"*\" ausgeschlossen.<br>\r\n Die Authentifizierung wird anhand des \"Next hop\" Parameters ermittelt. Hierbei würde bei einem beispielhaften Wert \"[host]:25\" immer zuerst \"host\" abfragt und <b>erst im Anschluss</b> \"[host]:25\". Dieses Verhalten schließt die <b>gleichzeitige Verwendung</b> von Einträgen der Art \"host\" sowie \"[host]:25\" aus.",
"transport_test_rcpt_info": " Die Verwendung von null@hosted.mailcow.de testet das Relay gegen ein fremdes Ziel.",
"transports_hint": "&#8226; Transport-Maps <b>überwiegen</b> senderabhängige Transport Maps.<br>\r\n&#8226; MX-basierte Transporte werden bevorzugt.<br>\r\n&#8226; Transport-Maps ignorieren Mailbox-Einstellungen für ausgehende Verschlüsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.<br>\r\n&#8226; Der Transport erfolgt immer via \"smtp:\", verwendet TLS wenn angeboten und unterstützt kein wrapped TLS (SMTPS).<br>\r\n&#8226; Adressen, die mit \"/localhost$/\" übereinstimmen, werden immer via \"local:\" transportiert, daher sind sie von einer Zieldefinition \"*\" ausgeschlossen.<br>\r\n&#8226; Die Authentifizierung wird anhand des \"Next hop\" Parameters ermittelt. Hierbei würde bei einem beispielhaften Wert \"[host]:25\" immer zuerst \"host\" abfragt und <b>erst im Anschluss</b> \"[host]:25\". Dieses Verhalten schließt die <b>gleichzeitige Verwendung</b> von Einträgen der Art \"host\" sowie \"[host]:25\" aus.",
"transport_test_rcpt_info": "&#8226; Die Verwendung von null@hosted.mailcow.de testet das Relay gegen ein fremdes Ziel.",
"ui_footer": "Footer (HTML zulässig)",
"ui_header_announcement": "Ankündigungen",
"ui_header_announcement_active": "Ankündigung aktivieren",
@ -555,6 +556,7 @@
"hostname": "Servername",
"inactive": "Inaktiv",
"kind": "Art",
"lookup_mx": "Ziel mit MX vergleichen (Regex, etwa <code>.*google\\.com</code>, um alle Ziele mit MX *google.com zu routen)",
"mailbox": "Mailbox bearbeiten",
"mailbox_relayhost_info": "Wird auf eine Mailbox und direkte Alias-Adressen angewendet. Überschreibt die Einstellung einer Domain.",
"mailbox_quota_def": "Standard-Quota einer Mailbox",
@ -762,7 +764,7 @@
"running": "In Ausführung",
"set_postfilter": "Als Postfilter markieren",
"set_prefilter": "Als Prefilter markieren",
"sieve_info": "Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br>\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung <b>nicht</b>. Änderungen an globalen Sieve-Filtern bewirken einen Neustart von Dovecot.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sieve_info": "Es können mehrere Filter pro Benutzer existieren, aber nur ein Filter eines Typs (Pre-/Postfilter) kann gleichzeitig aktiv sein.<br>\r\nDie Ausführung erfolgt in nachstehender Reihenfolge. Ein fehlgeschlagenes Script sowie der Befehl \"keep;\" stoppen die weitere Verarbeitung <b>nicht</b>. Änderungen an globalen Sieve-Filtern bewirken einen Neustart von Dovecot.<br><br>Global sieve prefilter &#8226; Prefilter &#8226; User scripts &#8226; Postfilter &#8226; Global sieve postfilter",
"sieve_preset_1": "E-Mails mit potenziell gefährlichen Dateitypen abweisen",
"sieve_preset_2": "E-Mail eines bestimmten Absenders immer als gelesen markieren",
"sieve_preset_3": "Lautlos löschen, weitere Ausführung von Filtern verhindern",

View File

@ -154,7 +154,7 @@
"change_logo": "Change logo",
"configuration": "Configuration",
"convert_html_to_text": "Convert HTML to plain text",
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching nexthop column.",
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching next hop column.",
"customer_id": "Customer ID",
"customize": "Customize",
"delete_queue": "Delete all",
@ -208,6 +208,7 @@
"html": "HTML",
"import": "Import",
"import_private_key": "Import private key",
"is_mx_based": "MX based",
"in_use_by": "In use by",
"inactive": "Inactive",
"include_exclude": "Include/Exclude",
@ -218,7 +219,7 @@
"link": "Link",
"loading": "Please wait...",
"logo_info": "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.",
"lookup_mx": "Match destination against MX (.outlook.com to route all mail targeted to a MX *.outlook.com over this hop)",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"main_name": "\"mailcow UI\" name",
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
"message": "Message",
@ -230,7 +231,7 @@
"no_record": "No record",
"oauth2_client_id": "Client ID",
"oauth2_client_secret": "Client secret",
"oauth2_info": "The OAuth2 implementation supports the grant type \"Authorization Code\" and issues refresh tokens.<br>\r\nThe server also automatically issues new refresh tokens, after a refresh token has been used.<br><br>\r\n The default scope is <i>profile</i>. Only mailbox users can be authenticated against OAuth2. If the scope parameter is omitted, it falls back to <i>profile</i>.<br>\r\n The <i>state</i> parameter is required to be sent by the client as part of the authorize request.<br><br>\r\nPaths for requests to the OAuth2 API: <br>\r\n<ul>\r\n <li>Authorization endpoint: <code>/oauth/authorize</code></li>\r\n <li>Token endpoint: <code>/oauth/token</code></li>\r\n <li>Resource page: <code>/oauth/profile</code></li>\r\n</ul>\r\nRegenerating the client secret will not expire existing authorization codes, but they will fail to renew their token.<br><br>\r\nRevoking client tokens will cause immediate termination of all active sessions. All clients need to re-authenticate.",
"oauth2_info": "The OAuth2 implementation supports the grant type \"Authorization Code\" and issues refresh tokens.<br>\r\nThe server also automatically issues new refresh tokens, after a refresh token has been used.<br><br>\r\n&#8226; The default scope is <i>profile</i>. Only mailbox users can be authenticated against OAuth2. If the scope parameter is omitted, it falls back to <i>profile</i>.<br>\r\n&#8226; The <i>state</i> parameter is required to be sent by the client as part of the authorize request.<br><br>\r\nPaths for requests to the OAuth2 API: <br>\r\n<ul>\r\n <li>Authorization endpoint: <code>/oauth/authorize</code></li>\r\n <li>Token endpoint: <code>/oauth/token</code></li>\r\n <li>Resource page: <code>/oauth/profile</code></li>\r\n</ul>\r\nRegenerating the client secret will not expire existing authorization codes, but they will fail to renew their token.<br><br>\r\nRevoking client tokens will cause immediate termination of all active sessions. All clients need to re-authenticate.",
"oauth2_redirect_uri": "Redirect URI",
"oauth2_renew_secret": "Generate new client secret",
"oauth2_revoke_tokens": "Revoke all client tokens",
@ -321,10 +322,10 @@
"title": "Title",
"title_name": "\"mailcow UI\" website title",
"to_top": "Back to top",
"transport_dest_format": "Syntax: example.org, .example.org, *, box@example.org (multiple values can be comma-separated)",
"transport_dest_format": "Regex or syntax: example.org, .example.org, *, box@example.org (multiple values can be comma-separated)",
"transport_maps": "Transport Maps",
"transports_hint": " A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br>\r\n Outbound TLS policy settings per-user are ignored and can only be enforced by TLS policy map entries.<br>\r\n The transport service for defined transports is always \"smtp:\" and will therefore try TLS when offered. Wrapped TLS (SMTPS) is not supported.<br>\r\n Addresses matching \"/localhost$/\" will always be transported via \"local:\", therefore a \"*\" destination will not apply to those addresses.<br>\r\n To determine credentials for an exemplary next hop \"[host]:25\", Postfix <b>always</b> queries for \"host\" before searching for \"[host]:25\". This behavior makes it impossible to use \"host\" and \"[host]:25\" at the same time.",
"transport_test_rcpt_info": " Use null@hosted.mailcow.de to test relaying to a foreign destination.",
"transports_hint": "&#8226; A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br>\r\n&#8226; MX-based transports are preferably used.<br>\r\n&#8226; Outbound TLS policy settings per-user are ignored and can only be enforced by TLS policy map entries.<br>\r\n&#8226; The transport service for defined transports is always \"smtp:\" and will therefore try TLS when offered. Wrapped TLS (SMTPS) is not supported.<br>\r\n&#8226; Addresses matching \"/localhost$/\" will always be transported via \"local:\", therefore a \"*\" destination will not apply to those addresses.<br>\r\n&#8226; To determine credentials for an exemplary next hop \"[host]:25\", Postfix <b>always</b> queries for \"host\" before searching for \"[host]:25\". This behavior makes it impossible to use \"host\" and \"[host]:25\" at the same time.",
"transport_test_rcpt_info": "&#8226; Use null@hosted.mailcow.de to test relaying to a foreign destination.",
"ui_footer": "Footer (HTML allowed)",
"ui_header_announcement": "Announcements",
"ui_header_announcement_active": "Set announcement active",
@ -553,6 +554,7 @@
"hostname": "Hostname",
"inactive": "Inactive",
"kind": "Kind",
"lookup_mx": "Destination is a regular expression to match against MX name (<code>.*google\\.com</code> to route all mail targeted to a MX ending in google.com over this hop)",
"mailbox": "Edit mailbox",
"mailbox_quota_def": "Default mailbox quota",
"mailbox_relayhost_info": "Applied to the mailbox and direct aliases only, does override a domain relayhost.",
@ -760,7 +762,7 @@
"running": "Running",
"set_postfilter": "Mark as postfilter",
"set_prefilter": "Mark as prefilter",
"sieve_info": "You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts. Changes to global sieve scripts will trigger a restart of Dovecot.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sieve_info": "You can store multiple filters per user, but only one prefilter and one postfilter can be active at the same time.<br>\r\nEach filter will be processed in the described order. Neither a failed script nor an issued \"keep;\" will stop processing of further scripts. Changes to global sieve scripts will trigger a restart of Dovecot.<br><br>Global sieve prefilter &#8226; Prefilter &#8226; User scripts &#8226; Postfilter &#8226; Global sieve postfilter",
"sieve_preset_1": "Discard mail with probable dangerous file types",
"sieve_preset_2": "Always mark the e-mail of a specific sender as seen",
"sieve_preset_3": "Discard silently, stop all further sieve processing",

View File

@ -290,7 +290,7 @@ services:
- dovecot
postfix-mailcow:
image: mailcow/postfix:1.62
image: mailcow/postfix:1.63
depends_on:
- mysql-mailcow
volumes: