Page MenuHomeWMGMC Issues

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/data/web/admin.php b/data/web/admin.php
index 82d69b4c..8c5c606f 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -1,1258 +1,1317 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CACHE')) {
$_SESSION['gal'] = json_decode($license_cache, true);
}
?>
<div class="container">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#tab-access" aria-controls="tab-access" role="tab" data-toggle="tab"><?=$lang['admin']['access'];?></a></li>
<li role="presentation"><a href="#tab-config" aria-controls="tab-config" role="tab" data-toggle="tab"><?=$lang['admin']['configuration'];?></a></li>
<li role="presentation"><a href="#tab-routing" aria-controls="tab-routing" role="tab" data-toggle="tab"><?=$lang['admin']['routing'];?></a></li>
<li role="presentation"><a href="#tab-sys-mails" aria-controls="tab-sys-mails" role="tab" data-toggle="tab"><?=$lang['admin']['sys_mails'];?></a></li>
<li role="presentation"><a href="#tab-mailq" aria-controls="tab-mailq" role="tab" data-toggle="tab"><?=$lang['admin']['queue_manager'];?></a></li>
<li role="presentation"><a href="#tab-rspamdmaps" aria-controls="tab-rspamdmaps" role="tab" data-toggle="tab"><?=$lang['admin']['rspamd_global_filters'];?></a></li>
</ul>
<div class="row">
<div class="col-md-12">
<div class="tab-content" style="padding-top:20px">
<div role="tabpanel" class="tab-pane active" id="tab-access">
<div class="panel panel-danger">
<div class="panel-heading"><?=$lang['admin']['admin_details'];?></div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped table-condensed" id="adminstable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="admins" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="edit_selected" data-id="admins" data-api-url='edit/admin' data-api-attr='{"disable_tfa":"1"}' href="#"><?=$lang['tfa']['disable_tfa'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="admins" data-api-url='delete/admin' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" data-id="add_admin" data-toggle="modal" data-target="#addAdminModal" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add_admin'];?></a>
</div>
</div>
<legend style="margin-top:20px">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" style="margin-bottom: -5px;">
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"/>
</svg> <?=$lang['tfa']['tfa'];?></legend>
<div class="row">
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?>:</div>
<div class="col-sm-9 col-xs-7">
<p id="tfa_pretty"><?=$tfa_data['pretty'];?></p>
<div id="tfa_additional">
<?php if (!empty($tfa_data['additional'])):
foreach ($tfa_data['additional'] as $key_info): ?>
<form style="display:inline;" method="post">
<input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" />
<div style="padding:4px;margin:4px" class="label label-<?=($_SESSION['tfa_id'] == $key_info['id']) ? 'success' : 'default'; ?>">
<?=$key_info['key_id'];?>
<a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a>
</div>
</form>
<?php endforeach;
endif;?>
</div>
<br />
</div>
</div>
<div class="row">
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?>:</div>
<div class="col-sm-9 col-xs-7">
<select data-width="fit" id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
<option value="u2f"><?=$lang['tfa']['u2f'];?></option>
<option value="totp"><?=$lang['tfa']['totp'];?></option>
<option value="none"><?=$lang['tfa']['none'];?></option>
</select>
</div>
</div>
- <legend data-target="#license" class="arrow-toggle" unselectable="on" data-toggle="collapse">
- <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['guid_and_license'];?>
+ <legend style="cursor:pointer;" data-target="#license" class="arrow-toggle" unselectable="on" data-toggle="collapse">
+ <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-up"></span> <?=$lang['admin']['guid_and_license'];?>
</legend>
<div id="license" class="collapse in">
<form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
<div class="form-group">
<label class="control-label col-sm-3" for="guid"><?=$lang['admin']['guid'];?>:</label>
<div class="col-sm-9">
<div class="input-group">
<span class="input-group-addon">
<span class="glyphicon <?=(isset($_SESSION['gal']['valid']) && $_SESSION['gal']['valid'] === "true") ? 'glyphicon-heart text-danger' : 'glyphicon-remove';?>" aria-hidden="true"></span>
</span>
<input type="text" id="guid" class="form-control" value="<?=license('guid');?>" readonly>
</div>
<p class="help-block">
<?=$lang['admin']['customer_id'];?>: <?=(isset($_SESSION['gal']['c'])) ? $_SESSION['gal']['c'] : '?';?> -
<?=$lang['admin']['service_id'];?>: <?=(isset($_SESSION['gal']['s'])) ? $_SESSION['gal']['s'] : '?';?> -
<?=$lang['admin']['sal_level'];?>: <?=(isset($_SESSION['gal']['m'])) ? $_SESSION['gal']['m'] : '?';?>
</p>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<p class="help-block"><?=$lang['admin']['license_info'];?></p>
<div class="btn-group">
<button class="btn btn-sm btn-success" name="license_validate_now" type="submit" href="#"><?=$lang['admin']['validate_license_now'];?></button>
</div>
</div>
</div>
</form>
</div>
- <legend data-target="#api" class="arrow-toggle" unselectable="on" data-toggle="collapse">
- <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> API
+ <legend style="margin-top:20px">
+ <span style="font-size:12px" class="arrow rotate glyphicon glyphicon-wrench"></span> API
</legend>
<?php
- $api = admin_api('get');
+ $api_ro = admin_api('ro', 'get');
+ $api_rw = admin_api('rw', 'get');
?>
- <div id="api" class="collapse">
- <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
- <div class="form-group">
- <label class="control-label col-sm-3" for="allow_from"><?=$lang['admin']['api_allow_from'];?>:</label>
- <div class="col-sm-9">
- <textarea class="form-control" rows="5" name="allow_from" id="allow_from" <?=($api['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api['allow_from']);?></textarea>
- </div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-3 col-sm-9">
- <label>
- <input type="checkbox" id="skip_ip_check" name="skip_ip_check" <?=($api['skip_ip_check'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['api_skip_ip_check'];?>
- </label>
+ <div class="panel-group" id="accordion">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h4 class="panel-title">
+ <a data-toggle="collapse" data-parent="#accordion" href="#api-ro">
+ ⇇ Read-Only Access</a>
+ </h4>
</div>
- </div>
- <div class="form-group">
- <label class="control-label col-sm-3" for="admin_api_key"><?=$lang['admin']['api_key'];?>:</label>
- <div class="col-sm-9">
- <div class="input-group">
- <span class="input-group-addon">Read-Write</span>
- <input type="text" class="form-control" placeholder="-" value="<?=htmlspecialchars($api['api_key']);?>" readonly>
+ <div id="api-ro" class="panel-collapse collapse">
+ <div class="panel-body">
+ <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="allow_from_ro"><?=$lang['admin']['api_allow_from'];?>:</label>
+ <div class="col-sm-9">
+ <textarea class="form-control" rows="2" name="allow_from" id="allow_from_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_ro['allow_from']);?></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <label>
+ <input type="checkbox" name="skip_ip_check" id="skip_ip_check_ro" <?=($api_ro['skip_ip_check'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['api_skip_ip_check'];?>
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3"><?=$lang['admin']['api_key'];?>:</label>
+ <div class="col-sm-9">
+ <pre><?=(empty(htmlspecialchars($api_ro['api_key']))) ? '-' : htmlspecialchars($api_ro['api_key']);?></pre>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <label>
+ <input type="checkbox" name="active" <?=($api_ro['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <p class="help-block"><?=$lang['admin']['api_info'];?></p>
+ <div class="btn-group">
+ <button class="btn btn-sm btn-default" name="admin_api[ro]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+ <button class="btn btn-sm btn-primary" name="admin_api_regen_key[ro]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+ </div>
+ </div>
+ </div>
+ </form>
</div>
</div>
</div>
- <div class="form-group">
- <div class="col-sm-offset-3 col-sm-9">
- <label>
- <input type="checkbox" name="active" <?=($api['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
- </label>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h4 class="panel-title">
+ <a data-toggle="collapse" data-parent="#accordion" href="#api-rw">
+ ⇄ Read-Write Access</a>
+ </h4>
</div>
- </div>
- <div class="form-group">
- <div class="col-sm-offset-3 col-sm-9">
- <p class="help-block"><?=$lang['admin']['api_info'];?></p>
- <div class="btn-group">
- <button class="btn btn-default" name="admin_api" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
- <button class="btn btn-info" name="admin_api_regen_key" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+ <div id="api-rw" class="panel-collapse collapse">
+ <div class="panel-body">
+ <form class="form-horizontal" autocapitalize="none" autocorrect="off" role="form" method="post">
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="allow_from_rw"><?=$lang['admin']['api_allow_from'];?>:</label>
+ <div class="col-sm-9">
+ <textarea class="form-control" rows="2" name="allow_from" id="allow_from_rw" <?=($api_rw['skip_ip_check'] == 1) ? 'disabled' : null;?> required><?=htmlspecialchars($api_rw['allow_from']);?></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <label>
+ <input type="checkbox" name="skip_ip_check" id="skip_ip_check_rw" <?=($api_rw['skip_ip_check'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['api_skip_ip_check'];?>
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="control-label col-sm-3" for="admin_api_key"><?=$lang['admin']['api_key'];?>:</label>
+ <div class="col-sm-9">
+ <pre><?=(empty(htmlspecialchars($api_rw['api_key']))) ? '-' : htmlspecialchars($api_rw['api_key']);?></pre>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <label>
+ <input type="checkbox" name="active" <?=($api_rw['active'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['activate_api'];?>
+ </label>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-sm-offset-3 col-sm-9">
+ <p class="help-block"><?=$lang['admin']['api_info'];?></p>
+ <div class="btn-group">
+ <button class="btn btn-sm btn-default" name="admin_api[rw]" type="submit" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
+ <button class="btn btn-sm btn-primary" name="admin_api_regen_key[rw]" type="submit" href="#"><?=$lang['admin']['regen_api_key'];?></button>
+ </div>
+ </div>
+ </div>
+ </form>
</div>
</div>
</div>
- </form>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['domain_admins'];?></div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped table-condensed" id="domainadminstable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="domain_admins" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="edit_selected" data-id="domain_admins" data-api-url='edit/domain-admin' data-api-attr='{"disable_tfa":"1"}' href="#"><?=$lang['tfa']['disable_tfa'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="domain_admins" data-api-url='delete/domain-admin' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" data-id="add_domain_admin" data-toggle="modal" data-target="#addDomainAdminModal" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add_domain_admin'];?></a>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">OAuth2 Apps</div>
<div class="panel-body">
<p><?=$lang['admin']['oauth2_info'];?></p>
<div class="table-responsive">
<table class="table table-striped" id="oauth2clientstable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="oauth2_clients" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="delete_selected" data-id="oauth2_clients" data-api-url='delete/oauth2-client' href="#"><?=$lang['mailbox']['remove'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="edit_selected" data-id="oauth2_clients" data-api-url='edit/oauth2-client' data-api-attr='{"revoke_tokens":"1"}' href="#"><?=$lang['admin']['oauth2_revoke_tokens'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="edit_selected" data-id="oauth2_clients" data-api-url='edit/oauth2-client' data-api-attr='{"renew_secret":"1"}' href="#"><?=$lang['admin']['oauth2_renew_secret'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" data-id="add_oauth2_client" data-toggle="modal" data-target="#addOAuth2ClientModal" href="#"><span class="glyphicon glyphicon-plus"></span> Add OAuth2 client</a>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Rspamd UI</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-sm-9">
<form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post">
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<label>
<a href="/rspamd/" target="_blank"><span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> Rspamd UI</a>
</label>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="rspamd_ui_pass"><?=$lang['admin']['password'];?>:</label>
<div class="col-sm-9">
<input type="password" class="form-control" name="rspamd_ui_pass" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-3" for="rspamd_ui_pass2"><?=$lang['admin']['password_repeat'];?>:</label>
<div class="col-sm-9">
<input type="password" class="form-control" name="rspamd_ui_pass2" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default" id="rspamd_ui" name="rspamd_ui" href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</div>
</div>
</form>
</div>
<div class="col-sm-3">
<img class="img-responsive" src="/img/rspamd_logo.png" alt="Rspamd UI" />
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-routing">
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['relayhosts'];?></div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['relayhosts_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="relayhoststable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="rlyhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="rlyhosts" data-api-url='edit/relayhost' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="rlyhosts" data-api-url='delete/relayhost' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_relayhost'];?></legend>
<p class="help-block"><?=$lang['admin']['add_relayhost_hint'];?></p>
<div class="row">
<div class="col-md-6">
<form class="form" data-id="rlyhost" role="form" method="post">
<div class="form-group">
<label for="hostname"><?=$lang['admin']['host'];?></label>
<input class="form-control input-sm" 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="username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" name="username">
</div>
<div class="form-group">
<label for="password"><?=$lang['admin']['password'];?></label>
<input class="form-control input-sm" name="password">
</div>
<button class="btn btn-default" data-action="add_item" data-id="rlyhost" data-api-url='add/relayhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['transport_maps'];?></div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['transports_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="transportstable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="transports" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="transports" data-api-url='edit/transport' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="transports" data-api-url='delete/transport' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_transport'];?></legend>
<p class="help-block"><?=$lang['admin']['add_transports_hint'];?></p>
<div class="row">
<div class="col-md-6">
<form class="form" data-id="transport" role="form" method="post">
<div class="form-group">
<label for="destination"><?=$lang['admin']['destination'];?></label>
<input class="form-control input-sm" name="destination" placeholder='<?=$lang['admin']['transport_dest_format'];?>' required>
</div>
<div class="form-group">
<label for="nexthop"><?=$lang['admin']['nexthop'];?></label>
<input class="form-control input-sm" name="nexthop" placeholder='host:25, host, [host]:25, [0.0.0.0]:25' required>
</div>
<div class="form-group">
<label for="username"><?=$lang['admin']['username'];?></label>
<input class="form-control input-sm" name="username">
</div>
<div class="form-group">
<label for="password"><?=$lang['admin']['password'];?></label>
<input class="form-control" name="password">
</div>
<!-- <div class="form-group">
<label>
<input type="checkbox" name="lookup_mx" value="1"> <?=$lang['admin']['lookup_mx'];?>
</label>
</div> -->
<div class="form-group">
<label>
<input type="checkbox" name="active" value="1"> <?=$lang['admin']['active'];?>
</label>
</div>
<p class="help-block"><?=$lang['admin']['credentials_transport_warning'];?></p>
<button class="btn btn-default" data-action="add_item" data-id="transport" data-api-url='add/transport' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-config">
<div class="row">
<div id="sidebar-admin-config" class="col-sm-3 hidden-xs">
<div id="scrollbox-config" class="list-group">
<a href="#dkim" class="list-group-item"><?=$lang['admin']['dkim_keys'];?></a>
<a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
<a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
<a href="#quarantine" class="list-group-item"><?=$lang['admin']['quarantine'];?></a>
<a href="#quota" class="list-group-item"><?=$lang['admin']['quota_notifications'];?></a>
<a href="#rsettings" class="list-group-item"><?=$lang['admin']['rspamd_settings_map'];?></a>
<a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
<a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
</div>
</div>
<div class="col-sm-9">
<span class="anchor" id="dkim"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['dkim_keys'];?></div>
<div class="panel-body">
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="dkim" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<button type="button" data-action="delete_selected" name="delete_selected" data-id="dkim" data-api-url="delete/dkim" class="btn btn-danger"><?=$lang['admin']['remove'];?></button>
</div>
</div>
<?php
foreach(mailbox('get', 'domains') as $domain) {
if (!empty($dkim = dkim('details', $domain))) {
$dkim_domains[] = $domain;
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?>
<div class="row">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div>
<div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong>
<p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
<p class="dkim-label"><span class="label label-primary"><?=$lang['admin']['dkim_domains_selector'];?> '<?=$dkim['dkim_selector'];?>'</span></p>
<p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p>
</div>
<div class="col-md-8">
<pre><?=$dkim['dkim_txt'];?></pre>
<p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ <?=$lang['admin']['dkim_private_key'];?></small></p>
</div>
<hr class="visible-xs visible-sm">
</div>
<?php
}
else {
?>
<div class="row">
<div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" disabled /></div>
<div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($domain);?></strong><br /><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
</div>
<div class="col-md-8"><pre>-</pre></div>
<hr class="visible-xs visible-sm">
</div>
<?php
}
foreach(mailbox('get', 'alias_domains', $domain) as $alias_domain) {
if (!empty($dkim = dkim('details', $alias_domain))) {
$dkim_domains[] = $alias_domain;
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?>
<div class="row">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div>
<div class="col-md-2 col-md-offset-1">
<p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small>
<p class="dkim-label"><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
<p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
<p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p>
</div>
<div class="col-md-8">
<pre><?=$dkim['dkim_txt'];?></pre>
<p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
</div>
<hr class="visible-xs visible-sm">
</div>
<?php
}
else {
?>
<div class="row">
<div class="col-md-1"><input class="dkim_missing" type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" disabled /></div>
<div class="col-md-2 col-md-offset-1">
<p><small>↳ Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small><span class="label label-danger"><?=$lang['admin']['dkim_key_missing'];?></span></p>
</div>
<div class="col-md-8"><pre>-</pre></div>
<hr class="visible-xs visible-sm">
</div>
<?php
}
}
}
foreach(dkim('blind') as $blind) {
if (!empty($dkim = dkim('details', $blind))) {
$dkim_domains[] = $blind;
($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) ?: $dkim['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
?>
<div class="row">
<div class="col-md-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div>
<div class="col-md-3">
<p><?=$lang['admin']['domain'];?>: <strong><?=htmlspecialchars($blind);?></strong>
<p class="dkim-label"><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
<p class="dkim-label"><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
<p class="dkim-label"><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p>
</div>
<div class="col-md-8">
<pre><?=$dkim['dkim_txt'];?></pre>
<p data-toggle="modal" data-target="#showDKIMprivKey" id="dkim_priv" style="cursor:pointer;margin-top:-8pt" data-priv-key="<?=$dkim['privkey'];?>"><small>↪ Private key</small></p>
</div>
<hr class="visible-xs visible-sm">
</div>
<?php
}
}
?>
<legend style="margin-top:40px"><?=$lang['admin']['dkim_add_key'];?></legend>
<form class="form" data-id="dkim" role="form" method="post">
<div class="form-group">
<label for="domain"><?=$lang['admin']['domain_s'];?></label>
<input class="form-control input-sm" id="dkim_add_domains" name="domains" placeholder="example.org, example.com" required>
<small>↪ <a href="#" id="dkim_missing_keys"><?=$lang['admin']['dkim_domains_wo_keys'];?></a></small>
</div>
<div class="form-group">
<label for="domain"><?=$lang['admin']['dkim_domains_selector'];?></label>
<input class="form-control input-sm" name="dkim_selector" value="dkim" required>
</div>
<div class="form-group">
<select data-width="200px" data-style="btn btn-default btn-sm" class="form-control" id="key_size" name="key_size" title="<?=$lang['admin']['dkim_key_length'];?>" required>
<option data-subtext="bits">1024</option>
<option data-subtext="bits">2048</option>
</select>
</div>
<button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim" data-api-url='add/dkim' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
<legend data-target="#import_dkim" style="margin-top:40px;cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse">
<span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['import_private_key'];?>
</legend>
<div id="import_dkim" class="collapse">
<form class="form" data-id="dkim_import" role="form" method="post">
<div class="form-group">
<label for="domain"><?=$lang['admin']['domain'];?>:</label>
<input class="form-control input-sm" name="domain" placeholder="example.org" required>
</div>
<div class="form-group">
<label for="domain"><?=$lang['admin']['dkim_domains_selector'];?>:</label>
<input class="form-control input-sm" name="dkim_selector" value="dkim" required>
</div>
<div class="form-group">
<label for="private_key_file"><?=$lang['admin']['private_key'];?>: (RSA PKCS#8)</label>
<textarea class="form-control input-sm" rows="10" name="private_key_file" id="private_key_file" required placeholder="-----BEGIN RSA KEY-----"></textarea>
</div>
<button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button>
</form>
</div>
<legend data-target="#duplicate_dkim" style="margin-top:40px;cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse">
<span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['duplicate_dkim'];?>
</legend>
<div id="duplicate_dkim" class="collapse">
<form class="form-horizontal" data-id="dkim_duplicate" role="form" method="post">
<div class="form-group">
<label class="control-label col-sm-2" for="from_domain"><?=$lang['admin']['dkim_from'];?>:</label>
<div class="col-sm-10">
<select data-style="btn btn-default btn-sm"
data-live-search="true"
data-id="dkim_duplicate"
title="<?=$lang['admin']['dkim_from_title'];?>"
name="from_domain" id="from_domain" class="full-width-select form-control" required>
<?php
foreach ($dkim_domains as $dkim) {
?>
<option value="<?=$dkim;?>"><?=$dkim;?></option>
<?php
}
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="to_domain"><?=$lang['admin']['dkim_to'];?>:</label>
<div class="col-sm-10">
<select
data-live-search="true"
data-style="btn btn-default btn-sm"
data-id="dkim_duplicate"
title="<?=$lang['admin']['dkim_to_title'];?>"
name="to_domain" id="to_domain" class="full-width-select form-control" multiple required>
<?php
foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain) {
?>
<option value="<?=$domain;?>"><?=$domain;?></option>
<?php
}
?>
</select>
</div>
</div>
<button class="btn btn-sm btn-default" data-action="add_item" data-id="dkim_duplicate" data-api-url='add/dkim_duplicate' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-duplicate"></span> <?=$lang['admin']['duplicate'];?></button>
</form>
</div>
</div>
</div>
<span class="anchor" id="fwdhosts"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['forwarding_hosts'];?></div>
<div class="panel-body">
<p style="margin-bottom:40px"><?=$lang['admin']['forwarding_hosts_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="forwardinghoststable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group btn-group-sm">
<button type="button" id="toggle_multi_select_all" data-id="fwdhosts" class="btn btn-default"><?=$lang['mailbox']['toggle_all'];?></button>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"0"}' href="#">Enable spam filter</a></li>
<li><a data-action="edit_selected" data-id="fwdhosts" data-api-url='edit/fwdhost' data-api-attr='{"keep_spam":"1"}' href="#">Disable spam filter</a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="fwdhosts" data-api-url='delete/fwdhost' href="#"><?=$lang['admin']['remove'];?></a></li>
</ul>
</div>
</div>
<legend><?=$lang['admin']['add_forwarding_host'];?></legend>
<p class="help-block"><?=$lang['admin']['forwarding_hosts_add_hint'];?></p>
<form class="form" data-id="fwdhost" role="form" method="post">
<div class="form-group">
<label for="hostname"><?=$lang['admin']['host'];?></label>
<input class="form-control" name="hostname" placeholder="example.org" required>
</div>
<div class="form-group">
<select data-width="200px" class="form-control" id="filter_spam" name="filter_spam" title="<?=$lang['user']['spamfilter'];?>" required>
<option value="1"><?=$lang['admin']['active'];?></option>
<option value="0"><?=$lang['admin']['inactive'];?></option>
</select>
</div>
<button class="btn btn-default" data-action="add_item" data-id="fwdhost" data-api-url='add/fwdhost' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add'];?></button>
</form>
</div>
</div>
<span class="anchor" id="f2bparams"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['f2b_parameters'];?></div>
<div class="panel-body">
<?php
$f2b_data = fail2ban('get');
?>
<form class="form" data-id="f2b" role="form" method="post">
<div class="form-group">
<label for="ban_time"><?=$lang['admin']['f2b_ban_time'];?>:</label>
<input type="number" class="form-control" name="ban_time" value="<?=$f2b_data['ban_time'];?>" required>
</div>
<div class="form-group">
<label for="max_attempts"><?=$lang['admin']['f2b_max_attempts'];?>:</label>
<input type="number" class="form-control" name="max_attempts" value="<?=$f2b_data['max_attempts'];?>" required>
</div>
<div class="form-group">
<label for="retry_window"><?=$lang['admin']['f2b_retry_window'];?>:</label>
<input type="number" class="form-control" name="retry_window" value="<?=$f2b_data['retry_window'];?>" required>
</div>
<div class="form-group">
<label for="netban_ipv4"><?=$lang['admin']['f2b_netban_ipv4'];?>:</label>
<div class="input-group">
<span class="input-group-addon">/</span>
<input type="number" class="form-control" name="netban_ipv4" value="<?=$f2b_data['netban_ipv4'];?>" required>
</div>
</div>
<div class="form-group">
<label for="netban_ipv6"><?=$lang['admin']['f2b_netban_ipv6'];?>:</label>
<div class="input-group">
<span class="input-group-addon">/</span>
<input type="number" class="form-control" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required>
</div>
</div>
<p class="help-block"><?=$lang['admin']['f2b_list_info'];?></p>
<div class="form-group">
<label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label>
<textarea class="form-control" name="whitelist" rows="5"><?=$f2b_data['whitelist'];?></textarea>
</div>
<div class="form-group">
<label for="blacklist"><?=$lang['admin']['f2b_blacklist'];?>:</label>
<textarea class="form-control" name="blacklist" rows="5"><?=$f2b_data['blacklist'];?></textarea>
</div>
<div class="btn-group">
<button class="btn btn-default" data-action="edit_selected" data-item="self" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
<a href="#" role="button" class="btn btn-default" data-toggle="modal" data-container="netfilter-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_netfilter']; ?></a>
</div>
</form>
<hr>
<p class="help-block"><?=$lang['admin']['ban_list_info'];?></p>
<?php
if (empty($f2b_data['active_bans']) && empty($f2b_data['perm_bans'])):
?>
<i><?=$lang['admin']['no_active_bans'];?></i>
<?php
endif;
if (!empty($f2b_data['active_bans'])):
foreach ($f2b_data['active_bans'] as $active_bans):
?>
<p><span class="label label-info" style="padding:4px;font-size:85%;"><span class="glyphicon glyphicon-filter"></span> <?=$active_bans['network'];?> (<?=$active_bans['banned_until'];?>) -
<?php
if ($active_bans['queued_for_unban'] == 0):
?>
<a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"unban"}' href="#">[<?=$lang['admin']['queue_unban'];?>]</a>
<a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"whitelist"}' href="#">[whitelist]</a>
<a data-action="edit_selected" data-item="<?=$active_bans['network'];?>" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"blacklist"}' href="#">[blacklist (<b>needs restart</b>)]</a>
<?php
else:
?>
<i><?=$lang['admin']['unban_pending'];?></i>
<?php
endif;
?>
</span></p>
<?php
endforeach;
?>
<hr>
<?php
endif;
if (!empty($f2b_data['perm_bans'])):
foreach ($f2b_data['perm_bans'] as $perm_bans):
?>
<span class="label label-danger" style="padding: 0.1em 0.4em 0.1em;"><span class="glyphicon glyphicon-filter"></span> <?=$perm_bans?></span>
<?php
endforeach;
endif;
?>
</div>
</div>
<span class="anchor" id="quarantine"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['quarantine'];?></div>
<div class="panel-body">
<?php $q_data = quarantine('settings');
if (empty($q_data['retention_size']) || empty($q_data['max_size'])):
?>
<div class="panel-body"><div class="alert alert-info"><?=$lang['quarantine']['disabled_by_config'];?></div></div>
<?php
endif;
?>
<form class="form" data-id="quarantine" role="form" method="post">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="retention_size"><?=$lang['admin']['quarantine_retention_size'];?></label>
<input type="number" class="form-control" name="retention_size" value="<?=$q_data['retention_size'];?>" placeholder="0" required>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="max_size"><?=$lang['admin']['quarantine_max_size'];?></label>
<input type="number" class="form-control" name="max_size" value="<?=$q_data['max_size'];?>" placeholder="0" required>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="max_age"><?=$lang['admin']['quarantine_max_age'];?></label>
<input type="number" class="form-control" name="max_age" value="<?=$q_data['max_age'];?>" min="1" required>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="sender"><span class="glyphicon glyphicon-copy"></span> <?=$lang['admin']['quarantine_bcc'];?></label>
<input type="email" class="form-control" name="bcc" value="<?=htmlspecialchars($q_data['bcc']);?>" placeholder="">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="sender"><?=$lang['admin']['quarantine_notification_sender'];?>:</label>
<input type="email" class="form-control" name="sender" value="<?=htmlspecialchars($q_data['sender']);?>" placeholder="quarantine@localhost">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="subject"><?=$lang['admin']['quarantine_notification_subject'];?>:</label>
<input type="text" class="form-control" name="subject" value="<?=htmlspecialchars($q_data['subject']);?>" placeholder="Spam Quarantine Notification">
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-12">
<legend data-target="#quarantine_template" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse">
<span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['quarantine_notification_html'];?>
</legend>
<div id="quarantine_template" class="collapse" >
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="html_tmpl"><?=$q_data['html_tmpl'];?></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="release_format"><?=$lang['admin']['quarantine_release_format'];?>:</label>
<select data-width="100%" name="release_format" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
<option <?=($q_data['release_format'] == 'raw') ? 'selected' : null;?> value="raw"><?=$lang['admin']['quarantine_release_format_raw'];?></option>
<option <?=($q_data['release_format'] == 'attachment') ? 'selected' : null;?> value="attachment"><?=$lang['admin']['quarantine_release_format_att'];?></option>
</select>
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="exclude_domains"><?=$lang['admin']['quarantine_exclude_domains'];?>:</label><br />
<select data-width="100%" name="exclude_domains" class="selectpicker" title="<?=$lang['tfa']['select'];?>" multiple>
<?php
foreach (array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')) as $domain):
?>
<option <?=(in_array($domain, $q_data['exclude_domains'])) ? 'selected' : null;?>><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
?>
</select>
</div>
</div>
</div>
<button class="btn btn-default" data-action="edit_selected" data-item="self" data-id="quarantine" data-api-url='edit/quarantine' data-api-attr='{"action":"settings"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</form>
</div>
</div>
<span class="anchor" id="quota"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['quota_notifications'];?></div>
<div class="panel-body">
<p><?=$lang['admin']['quota_notifications_info'];?></p>
<?php $qw_data = quota_notification('get');?>
<form class="form" role="form" data-id="quota_notification" method="post">
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<label for="sender"><?=$lang['admin']['quarantine_notification_sender'];?>:</label>
<input type="email" class="form-control" name="sender" value="<?=htmlspecialchars($qw_data['sender']);?>" placeholder="quota-warning@localhost">
</div>
</div>
<div class="col-sm-6">
<div class="form-group">
<label for="subject"><?=$lang['admin']['quarantine_notification_subject'];?>:</label>
<input type="text" class="form-control" name="subject" value="<?=htmlspecialchars($qw_data['subject']);?>" placeholder="Quota warning">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<legend data-target="#quota_template" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse">
<span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['quarantine_notification_html'];?>
</legend>
<div id="quota_template" class="collapse" >
<!-- <small><?=$lang['admin']['quota_notifications_vars'];?></small><br><br>-->
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code collapse in" rows="20" name="html_tmpl"><?=$qw_data['html_tmpl'];?></textarea>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-10">
<div class="form-group">
<br>
<a type="button" class="btn btn-sm btn-success" data-action="edit_selected"
data-item="quota_notification"
data-id="quota_notification"
data-api-url='edit/quota_notification'
data-api-attr='{}'><?=$lang['user']['save_changes'];?></a>
</div>
</div>
</div>
</form>
</div>
</div>
<span class="anchor" id="rsettings"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['rspamd_settings_map'];?></div>
<div class="panel-body">
<legend data-target="#active_settings_map" style="cursor:pointer" class="arrow-toggle" unselectable="on" data-toggle="collapse">
<span style="font-size:12px" class="arrow rotate glyphicon glyphicon-menu-down"></span> <?=$lang['admin']['active_rspamd_settings_map'];?>
</legend>
<div id="active_settings_map" class="collapse" >
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
</div>
<br>
<?php $rsettings = rsettings('get'); ?>
<form class="form" data-id="rsettings" role="form" method="post">
<div class="row">
<div class="col-sm-3">
<div class="list-group">
<?php
if (empty($rsettings)):
?>
<span class="list-group-item"><em><?=$lang['admin']['rsetting_none'];?></em></span>
<?php
else:
foreach ($rsettings as $rsetting):
$rsetting_details = rsettings('details', $rsetting['id']);
?>
<a href="#<?=$rsetting_details['id'];?>" class="list-group-item list-group-item-<?=($rsetting_details['active_int'] == '1') ? 'success' : ''; ?>" data-dont-remember="1" data-toggle="tab"><?=$rsetting_details['desc'];?> (ID #<?=$rsetting['id'];?>)</a>
<?php
endforeach;
endif;
?>
<a href="#" class="list-group-item list-group-item-default"
data-id="add_domain_admin"
data-toggle="modal"
data-dont-remember="1"
data-target="#addRsettingModal"
data-toggle="tab"><?=$lang['admin']['rsetting_add_rule'];?></a>
</div>
</div>
<div class="col-sm-9">
<div class="tab-content">
<?php
if (empty($rsettings)):
?>
<div id="none" class="tab-pane active">
<p class="help-block"><?=$lang['admin']['rsetting_none'];?></p>
</div>
<?php
else:
?>
<div id="none" class="tab-pane active">
<p class="help-block"><?=$lang['admin']['rsetting_no_selection'];?></p>
</div>
<?php
foreach ($rsettings as $rsetting):
$rsetting_details = rsettings('details', $rsetting['id']);
?>
<div id="<?=$rsetting_details['id'];?>" class="tab-pane">
<form class="form" data-id="rsettings" role="form" method="post">
<input type="hidden" name="active" value="0">
<div class="form-group">
<label for="desc"><?=$lang['admin']['rsetting_desc'];?>:</label>
<input type="text" class="form-control" name="desc" value="<?=htmlspecialchars($rsetting_details['desc']);?>">
</div>
<div class="form-group">
<label for="content"><?=$lang['admin']['rsetting_content'];?>:</label>
<textarea class="form-control" name="content" rows="10"><?=htmlspecialchars($rsetting_details['content']);?></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="active" value="1" <?=($rsetting_details['active_int'] == 1) ? 'checked' : null;?>> <?=$lang['admin']['active'];?>
</label>
</div>
<button class="btn btn-default" data-action="edit_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url='edit/rsetting' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
<button class="btn btn-danger" data-action="delete_selected" data-item="<?=$rsetting_details['id'];?>" data-id="rsettings" data-api-url="delete/rsetting" data-api-attr='{}' href="#"><?=$lang['admin']['remove'];?></button>
</form>
</div>
<?php
endforeach;
endif;
?>
</div>
</div>
</div>
</form>
</div>
</div>
<span class="anchor" id="customize"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['customize'];?></div>
<div class="panel-body">
<legend><?=$lang['admin']['change_logo'];?></legend>
<p class="help-block"><?=$lang['admin']['logo_info'];?></p>
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
<p>
<input type="file" name="main_logo" class="filestyle" data-buttonName="btn-default" data-buttonText="Select" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml">
<button name="submit_main_logo" type="submit" class="btn btn-default"><span class="glyphicon glyphicon-cloud-upload"></span> <?=$lang['admin']['upload'];?></button>
</p>
</form>
<?php
if ($main_logo = customize('get', 'main_logo')):
$specs = customize('get', 'main_logo_specs');
?>
<div class="row">
<div class="col-sm-3">
<div class="thumbnail">
<img class="img-thumbnail" src="<?=$main_logo;?>" alt="mailcow logo">
<div class="caption">
<span class="label label-info"><?=$specs['geometry']['width'];?>x<?=$specs['geometry']['height'];?> px</span>
<span class="label label-info"><?=$specs['mimetype'];?></span>
<span class="label label-info"><?=$specs['fileSize'];?></span>
</div>
</div>
<hr>
<form class="form-inline" role="form" method="post">
<p><button name="reset_main_logo" type="submit" class="btn btn-xs btn-default"><?=$lang['admin']['reset_default'];?></button></p>
</form>
</div>
</div>
<?php
endif;
?>
<legend><?=$lang['admin']['app_links'];?></legend>
<p class="help-block"><?=$lang['admin']['merged_vars_hint'];?></p>
<form class="form-inline" data-id="app_links" role="form" method="post">
<table class="table table-condensed" style="white-space: nowrap;" id="app_link_table">
<tr>
<th><?=$lang['admin']['app_name'];?></th>
<th><?=$lang['admin']['link'];?></th>
<th>&nbsp;</th>
</tr>
<?php
$app_links = customize('get', 'app_links');
foreach ($app_links as $row) {
foreach ($row as $key => $val):
?>
<tr>
<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required value="<?=$key;?>"></td>
<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required value="<?=$val;?>"></td>
<td><a href="#" role="button" class="btn btn-xs btn-default" type="button"><?=$lang['admin']['remove_row'];?></a></td>
</tr>
<?php
endforeach;
}
foreach ($MAILCOW_APPS as $app):
?>
<tr>
<td><input class="input-sm form-control" value="<?=htmlspecialchars($app['name']);?>" disabled></td>
<td><input class="input-sm form-control" value="<?=htmlspecialchars($app['link']);?>" disabled></td>
<td>&nbsp;</td>
</tr>
<?php
endforeach;
?>
</table>
<p><div class="btn-group">
<button class="btn btn-sm btn-default" data-action="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
<button class="btn btn-sm btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
</div></p>
</form>
<legend data-target="#ui_texts" style="padding-top:20px" unselectable="on"><?=$lang['admin']['ui_texts'];?></legend>
<div id="ui_texts">
<?php
$ui_texts = customize('get', 'ui_texts');
?>
<form class="form" data-id="uitexts" role="form" method="post">
<div class="form-group">
<label for="title_name"><?=$lang['admin']['title_name'];?>:</label>
<input type="text" class="form-control" name="title_name" placeholder="mailcow UI" value="<?=$ui_texts['title_name'];?>">
</div>
<div class="form-group">
<label for="main_name"><?=$lang['admin']['main_name'];?>:</label>
<input type="text" class="form-control" name="main_name" placeholder="mailcow UI" value="<?=$ui_texts['main_name'];?>">
</div>
<div class="form-group">
<label for="apps_name"><?=$lang['admin']['apps_name'];?>:</label>
<input type="text" class="form-control" name="apps_name" placeholder="mailcow Apps" value="<?=$ui_texts['apps_name'];?>">
</div>
<div class="form-group">
<label for="help_text"><?=$lang['admin']['help_text'];?>:</label>
<textarea class="form-control" id="help_text" name="help_text" rows="7"><?=$ui_texts['help_text'];?></textarea>
</div>
<div class="form-group">
<label for="ui_footer"><?=$lang['admin']['ui_footer'];?>:</label>
<textarea class="form-control" id="ui_footer" name="ui_footer" rows="7"><?=$ui_texts['ui_footer'];?></textarea>
</div>
<button class="btn btn-default" data-action="edit_selected" data-item="ui" data-id="uitexts" data-api-url='edit/ui_texts' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-sys-mails">
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['sys_mails'];?></div>
<div class="panel-body">
<form class="form-horizontal" autocapitalize="none" data-id="admin" autocorrect="off" role="form" method="post">
<div class="form-group">
<label class="control-label col-sm-2" for="mass_from"><?=$lang['admin']['from'];?>:</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="mass_from" value="noreply@<?=getenv('MAILCOW_HOSTNAME');;?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mass_subject"><?=$lang['admin']['subject'];?>:</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="mass_subject" required>
</div>
</div>
<?php
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (!empty($domains)) {
foreach ($domains as $domain) {
foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) {
$mailboxes[] = $mailbox;
}
}
}
?>
<div class="form-group">
<label class="control-label col-sm-2" for="mass_subject"><?=$lang['admin']['include_exclude'];?>:
<p class="help-block"><?=$lang['admin']['include_exclude_info'];?></p>
</label>
<div class="col-sm-5">
<label class="control-label" for="mass_exclude"><?=$lang['admin']['excludes'];?>:</label>
<select id="mass_exclude" name="mass_exclude[]" data-live-search="true" data-width="100%" size="30" multiple>
<?php
if (!empty($mailboxes)) {
foreach (array_filter($mailboxes) as $mailbox):
?>
<option><?=htmlspecialchars($mailbox);?></option>
<?php
endforeach;
}
?>
</select>
</div>
<div class="col-sm-5">
<label class="control-label" for="mass_include"><?=$lang['admin']['includes'];?>:</label>
<select id="mass_include" name="mass_include[]" data-live-search="true" data-width="100%" size="30" multiple>
<?php
if (!empty($mailboxes)) {
foreach (array_filter($mailboxes) as $mailbox):
?>
<option><?=htmlspecialchars($mailbox);?></option>
<?php
endforeach;
}
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mass_text"><?=$lang['admin']['text'];?>:</label>
<div class="col-sm-10">
<textarea class="form-control" rows="10" name="mass_text" id="mass_text" required></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<label>
<input type="checkbox" id="mass_disarm"> <?=$lang['admin']['activate_send'];?>
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-default" type="submit" id="mass_send" name="mass_send" disabled><span class="glyphicon glyphicon-envelope"></span> <?=$lang['admin']['send'];?></button>
</div>
</div>
</form>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-mailq">
<div class="panel panel-default">
<div class="panel-heading">
<?=$lang['admin']['queue_manager'];?> <span class="badge badge-info table-lines"></span>
<div class="btn-group pull-right">
<button class="btn btn-xs btn-default refresh_table" data-draw="draw_queue" data-table="queuetable"><?=$lang['admin']['refresh'];?></button>
</div>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped table-condensed" id="queuetable"></table>
</div>
<div class="mass-actions-admin">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="mailqitems" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-toggle="tooltip" title="postqueue -i" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"deliver"}' href="#"><?=$lang['admin']['queue_deliver_mail'];?></a></li>
<li><a data-toggle="tooltip" title="postsuper -H" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"unhold"}' href="#"><?=$lang['admin']['queue_unhold_mail'];?></a></li>
<li><a data-toggle="tooltip" title="postsuper -h" data-action="edit_selected" data-id="mailqitems" data-api-url='edit/mailq' data-api-attr='{"action":"hold"}' href="#"><?=$lang['admin']['queue_hold_mail'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-toggle="tooltip" title="postsuper -d" data-action="delete_selected" data-id="mailqitems" data-api-url='delete/mailq' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-primary"
data-action="edit_selected"
data-item="mailqitems-all"
data-api-url='edit/mailq'
data-api-attr='{"action":"flush"}'
data-toggle="tooltip" title="postqueue -f"
href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['admin']['flush_queue'];?></a>
<a class="btn btn-sm btn-danger"
id="super_delete"
data-action="edit_selected"
data-item="mailqitems-all"
data-api-url='edit/mailq'
data-api-attr='{"action":"super_delete"}'
data-toggle="tooltip" title="postsuper -d ALL"
href="#"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?=$lang['admin']['delete_queue'];?></a>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tab-rspamdmaps">
<div class="row">
<div id="sidebar-admin-maps" class="col-sm-2 hidden-xs">
<div id="scrollbox-maps" class="list-group">
<a href="#regexmaps" class="list-group-item">Regex maps</a>
<!-- <a href="#standardmaps" class="list-group-item">Standard maps</a> -->
<a href="#top" class="list-group-item" style="border-top:1px dashed #dadada">↸ <?=$lang['admin']['to_top'];?></a>
</div>
</div>
<div class="col-sm-10">
<div class="panel panel-default">
<div class="panel-heading">
<?=$lang['admin']['rspamd_global_filters'];?>
</div>
<div class="panel-body">
<p><?=$lang['admin']['rspamd_global_filters_info'];?></p>
<div id="confirm_show_rspamd_global_filters" class="<?=($_SESSION['show_rspamd_global_filters'] === true) ? 'hidden' : '';?>">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<label>
<input type="checkbox" id="show_rspamd_global_filters"> <?=$lang['admin']['rspamd_global_filters_agree'];?>
</label>
</div>
</div>
</div>
<div id="rspamd_global_filters" class="<?=($_SESSION['show_rspamd_global_filters'] !== true) ? 'hidden' : '';?>">
<hr>
<span class="anchor" id="regexmaps"></span>
<h4>Regex Maps</h4>
<p><?=$lang['admin']['rspamd_global_filters_regex'];?></p>
<ul>
<?php
foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map):
?>
<li><a href="#<?=$rspamd_regex_map;?>"><?=$rspamd_regex_desc;?></a> (<small><?=$rspamd_regex_map;?></small>)</li>
<?php
endforeach;
?>
</ul>
<?php
foreach ($RSPAMD_MAPS['regex'] as $rspamd_regex_desc => $rspamd_regex_map):
?>
<hr>
<span class="anchor" id="<?=$rspamd_regex_map;?>"></span>
<form class="form-horizontal" data-id="<?=$rspamd_regex_map;?>" role="form" method="post">
<div class="form-group">
<label class="control-label col-sm-3" for="<?=$rspamd_regex_map;?>"><?=$rspamd_regex_desc;?><br><small><?=$rspamd_regex_map;?></small></label>
<div class="col-sm-9">
<textarea id="<?=$rspamd_regex_map;?>" spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="10" name="rspamd_map_data" required><?=file_get_contents('/rspamd_custom_maps/' . $rspamd_regex_map);?></textarea>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button class="btn btn-xs btn-default validate_rspamd_regex" data-regex-map="<?=$rspamd_regex_map;?>" href="#"><?=$lang['add']['validate'];?></button>
<button class="btn btn-xs btn-success submit_rspamd_regex" data-action="edit_selected" data-id="<?=$rspamd_regex_map;?>" data-item="<?=htmlspecialchars($rspamd_regex_map);?>" data-api-url='edit/rspamd-map' data-api-attr='{}' href="#" disabled><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
endforeach;
?>
</div>
</div>
</div>
</div>
</div>
</div> <!-- /tab-content -->
</div> <!-- /col-md-12 -->
</div> <!-- /row -->
</div> <!-- /container -->
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/admin.php';
?>
<script type='text/javascript'>
<?php
$lang_admin = json_encode($lang['admin']);
echo "var lang = ". $lang_admin . ";\n";
echo "var admin_username = '". $_SESSION['mailcow_cc_username'] . "';\n";
echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n";
?>
</script>
<?php
$js_minifier->add('/web/js/site/admin.js');
$js_minifier->add('/web/js/presets/rspamd.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
} else {
header('Location: /');
exit();
}
?>
diff --git a/data/web/edit.php b/data/web/edit.php
index b75a2317..3cc97916 100644
--- a/data/web/edit.php
+++ b/data/web/edit.php
@@ -1,1397 +1,1449 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
$AuthUsers = array("admin", "domainadmin", "user");
if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role'], $AuthUsers)) {
header('Location: /');
exit();
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
?>
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><?=$lang['edit']['title'];?></h3>
</div>
<div class="panel-body">
<?php
if (isset($_SESSION['mailcow_cc_role'])) {
if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin") {
if (isset($_GET["alias"]) &&
!empty($_GET["alias"])) {
$alias = html_entity_decode(rawurldecode($_GET["alias"]));
$result = mailbox('get', 'alias_details', $alias);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['alias'];?></h4>
<br>
<form class="form-horizontal" data-id="editalias" role="form" method="post">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="sogo_visible">
<div class="form-group">
<label class="control-label col-sm-2" for="address"><?=$lang['edit']['alias'];?></label>
<div class="col-sm-10">
<input class="form-control" type="text" name="address" value="<?=htmlspecialchars($result['address']);?>" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="goto"><?=$lang['edit']['target_address'];?></label>
<div class="col-sm-10">
<textarea id="textarea_alias_goto" class="form-control" autocapitalize="none" autocorrect="off" rows="10" id="goto" name="goto" required><?= (!preg_match('/^(null|ham|spam)@localhost$/i', $result['goto'])) ? str_replace(',', ', ', htmlspecialchars($result['goto'])) : null; ?></textarea>
<div class="checkbox">
<label><input class="goto_checkbox" type="checkbox" value="1" name="goto_null" <?= ($result['goto'] == "null@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_null'];?></label>
</div>
<div class="checkbox">
<label><input class="goto_checkbox" type="checkbox" value="1" name="goto_spam" <?= ($result['goto'] == "spam@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_spam'];?></label>
</div>
<div class="checkbox">
<label><input class="goto_checkbox" type="checkbox" value="1" name="goto_ham" <?= ($result['goto'] == "ham@localhost") ? "checked" : null; ?>> <?=$lang['add']['goto_ham'];?></label>
</div>
<hr>
<div class="checkbox">
<label><input type="checkbox" value="1" name="sogo_visible" <?php if (isset($result['sogo_visible_int']) && $result['sogo_visible_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['sogo_visible'];?></label>
</div>
<p class="help-block"><?=$lang['edit']['sogo_visible_info'];?></p>
</div>
</div>
<hr>
<div class="form-group">
<label class="control-label col-sm-2" for="private_"><?=$lang['edit']['private_comment'];?></label>
<div class="col-sm-10">
<input maxlength="160" class="form-control" type="text" name="private_comment" value="<?=htmlspecialchars($result['private_comment']);?>" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="public_comment"><?=$lang['edit']['public_comment'];?></label>
<div class="col-sm-10">
<input maxlength="160" class="form-control" type="text" name="public_comment" value="<?=htmlspecialchars($result['public_comment']);?>" />
</div>
</div>
<hr>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editalias" data-item="<?=htmlspecialchars($alias);?>" data-api-url='edit/alias' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['domainadmin'])) {
$domain_admin = $_GET["domainadmin"];
$result = domain_admin('details', $domain_admin);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['domain_admin'];?></h4>
<br>
<form class="form-horizontal" data-id="editdomainadmin" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
<input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($domain_admin);?>" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="domains"><?=$lang['edit']['domains'];?></label>
<div class="col-sm-10">
<select data-live-search="true" class="full-width-select" name="domains" multiple required>
<?php
foreach ($result['selected_domains'] as $domain):
?>
<option selected><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
foreach ($result['unselected_domains'] as $domain):
?>
<option><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
?>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?> (<a href="#" class="generate_password"><?=$lang['edit']['generate'];?></a>)</label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" class="form-control" name="password2">
</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="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</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="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-api-reload-location="/admin" data-id="editdomainadmin" data-item="<?=$domain_admin;?>" data-api-url='edit/domain-admin' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<form data-id="daacl" class="form-inline well" method="post">
<div class="row">
<div class="col-sm-1">
<p class="help-block">ACL</p>
</div>
<div class="col-sm-10">
<div class="form-group">
<select id="da_acl" name="da_acl" size="10" multiple>
<?php
$da_acls = acl('get', 'domainadmin', $domain_admin);
foreach ($da_acls as $acl => $val):
?>
<option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option>
<?php
endforeach;
?>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" data-action="edit_selected" data-id="daacl" data-item="<?=htmlspecialchars($domain_admin);?>" data-api-url='edit/da-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['admin'])) {
$admin = $_GET["admin"];
$result = admin('details', $admin);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['domain_admin'];?></h4>
<br>
<form class="form-horizontal" data-id="editadmin" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="username_new"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
<input class="form-control" type="text" name="username_new" value="<?=htmlspecialchars($admin);?>" />
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?> (<a href="#" class="generate_password"><?=$lang['edit']['generate'];?></a>)</label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" class="form-control" name="password2">
</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="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</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="disable_tfa"> <?=$lang['tfa']['disable_tfa'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-api-reload-location="/admin" data-id="editadmin" data-item="<?=$admin;?>" data-api-url='edit/admin' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['domain']) &&
is_valid_domain_name($_GET["domain"]) &&
!empty($_GET["domain"])) {
$domain = $_GET["domain"];
$result = mailbox('get', 'domain_details', $domain);
$rl = ratelimit('get', 'domain', $domain);
$rlyhosts = relayhost('get');
if (!empty($result)) {
?>
<h4><?=$lang['edit']['domain'];?></h4>
<form data-id="editdomain" class="form-horizontal" role="form" method="post">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="backupmx">
<input type="hidden" value="0" name="gal">
<input type="hidden" value="0" name="relay_all_recipients">
<input type="hidden" value="0" name="relay_unknown_only">
<div class="form-group">
<label class="control-label col-sm-2" for="description"><?=$lang['edit']['description'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" value="<?=htmlspecialchars($result['description']);?>">
</div>
</div>
<?php
if ($_SESSION['mailcow_cc_role'] == "admin") {
?>
<div class="form-group">
<label class="control-label col-sm-2" for="aliases"><?=$lang['edit']['max_aliases'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="aliases" value="<?=intval($result['max_num_aliases_for_domain']);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mailboxes"><?=$lang['edit']['max_mailboxes'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mailboxes" value="<?=intval($result['max_num_mboxes_for_domain']);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="defquota"><?=$lang['edit']['mailbox_quota_def'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="defquota" value="<?=intval($result['def_quota_for_mbox'] / 1048576);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="maxquota"><?=$lang['edit']['max_quota'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="maxquota" value="<?=intval($result['max_quota_for_mbox'] / 1048576);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['domain_quota'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="quota" value="<?=intval($result['max_quota_for_domain'] / 1048576);?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['relayhost'];?></label>
<div class="col-sm-10">
<select data-live-search="true" name="relayhost" class="form-control">
<?php
foreach ($rlyhosts as $rlyhost) {
?>
<option value="<?=$rlyhost['id'];?>" <?=($result['relayhost'] == $rlyhost['id']) ? 'selected' : null;?>>ID <?=$rlyhost['id'];?>: <?=$rlyhost['hostname'];?> (<?=$rlyhost['username'];?>)</option>
<?php
}
?>
<option value="" <?=($result['relayhost'] == "0") ? 'selected' : null;?>>None</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2"><?=$lang['edit']['backup_mx_options'];?></label>
<div class="col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="backupmx" <?=(isset($result['backupmx_int']) && $result['backupmx_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['relay_domain'];?></label>
<br>
<label><input type="checkbox" value="1" name="relay_all_recipients" <?=(isset($result['relay_all_recipients_int']) && $result['relay_all_recipients_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['relay_all'];?></label>
<p><?=$lang['edit']['relay_all_info'];?></p>
<label><input type="checkbox" value="1" name="relay_unknown_only" <?=(isset($result['relay_unknown_only_int']) && $result['relay_unknown_only_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['relay_unknown_only'];?></label>
<br>
<p><?=$lang['edit']['relay_transport_info'];?></p>
<hr style="margin:25px 0px 0px 0px">
</div>
</div>
</div>
<?php
}
?>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="gal" <?=(isset($result['gal_int']) && $result['gal_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['gal'];?></label>
<small class="help-block"><?=$lang['edit']['gal_info'];?></small>
</div>
</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="active" <?=(isset($result['active_int']) && $result['active_int']=="1") ? "checked" : null;?> <?=($_SESSION['mailcow_cc_role'] == "admin") ? null : "disabled";?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editdomain" data-item="<?=$domain;?>" data-api-url='edit/domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</form>
<?php
if (!empty($dkim = dkim('details', $domain))) {
?>
<hr>
<div class="row">
<div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($result['domain_name']);?></strong> (<?=$dkim['dkim_selector'];?>._domainkey)</p>
</div>
<div class="col-xs-10">
<pre><?=$dkim['dkim_txt'];?></pre>
</div>
</div>
<?php
}
?>
<hr>
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label"><?=$lang['acl']['ratelimit'];?></label>
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
<select name="rl_frame" class="form-control">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button data-acl="<?=$_SESSION['acl']['ratelimit'];?>" class="btn btn-default" data-action="edit_selected" data-id="domratelimit" data-item="<?=$domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</form>
<hr>
<div class="row">
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_wl'];?></h4>
<p><?=$lang['user']['spamfilter_wl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="wl_policy_domain_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_wl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</div>
</div>
<form class="form-inline" data-id="add_wl_policy_domain">
<div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" data-action="add_item" data-id="add_wl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"wl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_bl'];?></h4>
<p><?=$lang['user']['spamfilter_bl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="bl_policy_domain_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_domain" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_bl_domain" data-api-url='delete/domain-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</div>
</div>
<form class="form-inline" data-id="add_bl_policy_domain">
<div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" data-action="add_item" data-id="add_bl_policy_domain" data-api-url='add/domain-policy' data-api-attr='{"domain":"<?= $domain; ?>","object_list":"bl"}' href="#"><?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
</div>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['oauth2client']) &&
is_numeric($_GET["oauth2client"]) &&
!empty($_GET["oauth2client"])) {
$oauth2client = $_GET["oauth2client"];
$result = oauth2('details', 'client', $oauth2client);
if (!empty($result)) {
?>
<h4>OAuth2</h4>
<form data-id="oauth2client" class="form-horizontal" role="form" method="post">
<div class="form-group">
<label class="control-label col-sm-2" for="client_id"><?=$lang['edit']['client_id'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="client_id" id="client_id" value="<?=htmlspecialchars($result['client_id']);?>" disabled>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="client_secret"><?=$lang['edit']['client_secret'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="client_secret" id="client_secret" value="<?=htmlspecialchars($result['client_secret']);?>" disabled>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="scope"><?=$lang['edit']['scope'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="scope" id="scope" value="<?=htmlspecialchars($result['scope']);?>" disabled>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="redirect_uri"><?=$lang['edit']['redirect_uri'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="redirect_uri" id="redirect_uri" value="<?=htmlspecialchars($result['redirect_uri']);?>">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-default" data-action="edit_selected" data-id="oauth2client" data-item="<?=$oauth2client;?>" data-api-url='edit/oauth2-client' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['aliasdomain']) &&
is_valid_domain_name(html_entity_decode(rawurldecode($_GET["aliasdomain"]))) &&
!empty($_GET["aliasdomain"])) {
$alias_domain = html_entity_decode(rawurldecode($_GET["aliasdomain"]));
$result = mailbox('get', 'alias_domain_details', $alias_domain);
$rl = ratelimit('get', 'domain', $alias_domain);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['edit_alias_domain'];?></h4>
<form class="form-horizontal" data-id="editaliasdomain" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="target_domain"><?=$lang['edit']['target_domain'];?></label>
<div class="col-sm-10">
<select class="full-width-select" data-live-search="true" id="addSelectDomain" name="target_domain" required>
<?php
foreach (mailbox('get', 'domains') as $domain):
?>
<option <?=($result['target_domain'] != $domain) ?: 'selected';?>><?=htmlspecialchars($domain);?></option>
<?php
endforeach;
?>
</select>
</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="active" <?=(isset($result['active_int']) && $result['active_int']=="1") ? "checked" : null ?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editaliasdomain" data-item="<?=$alias_domain;?>" data-api-url='edit/alias-domain' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<hr>
<form data-id="domratelimit" class="form-inline well" method="post">
<div class="form-group">
<label class="control-label"><?=$lang['acl']['ratelimit'];?></label>
<input name="rl_value" type="number" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" autocomplete="off" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
<select name="rl_frame" class="form-control">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" data-action="edit_selected" data-id="domratelimit" data-item="<?=$alias_domain;?>" data-api-url='edit/rl-domain' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</form>
<?php
if (!empty($dkim = dkim('details', $alias_domain))) {
?>
<hr>
<div class="row">
<div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($result['alias_domain']);?></strong> (<?=$dkim['dkim_selector'];?>._domainkey)</p>
</div>
<div class="col-xs-10">
<pre><?=$dkim['dkim_txt'];?></pre>
</div>
</div>
<?php
}
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['mailbox']) && filter_var(html_entity_decode(rawurldecode($_GET["mailbox"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["mailbox"])) {
$mailbox = html_entity_decode(rawurldecode($_GET["mailbox"]));
$result = mailbox('get', 'mailbox_details', $mailbox);
$rl = ratelimit('get', 'mailbox', $mailbox);
+ $pushover_data = pushover('get', $mailbox);
$quarantine_notification = mailbox('get', 'quarantine_notification', $mailbox);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['mailbox'];?></h4>
<form class="form-horizontal" data-id="editmailbox" role="form" method="post">
<input type="hidden" value="default" name="sender_acl">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="force_pw_update">
<input type="hidden" value="0" name="sogo_access">
<div class="form-group">
<label class="control-label col-sm-2" for="name"><?=$lang['edit']['full_name'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="quota"><?=$lang['edit']['quota_mb'];?>
<br><span id="quotaBadge" class="badge">max. <?=intval($result['max_new_quota'] / 1048576)?> MiB</span>
</label>
<div class="col-sm-10">
<input type="number" name="quota" style="width:100%" min="0" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control">
<small class="help-block">0 = ∞</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="sender_acl"><?=$lang['edit']['sender_acl'];?></label>
<div class="col-sm-10">
<select data-live-search="true" data-width="100%" style="width:100%" id="editSelectSenderACL" name="sender_acl" size="10" multiple>
<?php
$sender_acl_handles = mailbox('get', 'sender_acl_handles', $mailbox);
foreach ($sender_acl_handles['sender_acl_domains']['ro'] as $domain):
?>
<option data-subtext="Admin" value="<?=htmlspecialchars($domain);?>" disabled selected><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['ro'] as $alias):
?>
<option data-subtext="Admin" disabled selected><?=htmlspecialchars($alias);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['fixed_sender_aliases'] as $alias):
?>
<option data-subtext="Alias" disabled selected><?=htmlspecialchars($alias);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_domains']['rw'] as $domain):
?>
<option value="<?=htmlspecialchars($domain);?>" selected><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_domains']['selectable'] as $domain):
?>
<option value="<?=htmlspecialchars($domain);?>"><?=htmlspecialchars(sprintf($lang['edit']['dont_check_sender_acl'], $domain));?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['rw'] as $address):
?>
<option selected><?=htmlspecialchars($address);?></option>
<?php
endforeach;
foreach ($sender_acl_handles['sender_acl_addresses']['selectable'] as $address):
?>
<option><?=htmlspecialchars($address);?></option>
<?php
endforeach;
// Generated here, but used in extended_sender_acl
if (!empty($sender_acl_handles['external_sender_aliases'])) {
$ext_sender_acl = implode(', ', $sender_acl_handles['external_sender_aliases']);
}
else {
$ext_sender_acl = '';
}
?>
</select>
<div style="display:none" id="sender_acl_disabled"><?=$lang['edit']['sender_acl_disabled'];?></div>
<small class="help-block"><?=$lang['edit']['sender_acl_info'];?></small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="sender_acl"><?=$lang['user']['quarantine_notification'];?></label>
<div class="col-sm-10">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($mailbox); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($mailbox); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($mailbox); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($mailbox); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
</div>
<div style="display:none" id="user_acl_q_notify_disabled"><?=$lang['edit']['user_acl_q_notify_disabled'];?></div>
<p class="help-block"><small><?=$lang['user']['quarantine_notification_info'];?></small></p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?> (<a href="#" class="generate_password"><?=$lang['edit']['generate'];?></a>)</label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="<?=$lang['edit']['unchanged_if_empty'];?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" class="form-control" name="password2">
</div>
</div>
<div data-acl="<?=$_SESSION['acl']['extend_sender_acl'];?>" class="form-group">
<label class="control-label col-sm-2" for="extended_sender_acl"><?=$lang['edit']['extended_sender_acl'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="extended_sender_acl" value="<?=empty($ext_sender_acl) ? '' : $ext_sender_acl; ?>" placeholder="user1@example.com, user2@example.org, @example.com, ...">
<small class="help-block"><?=$lang['edit']['extended_sender_acl_info'];?></small>
</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="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
</div>
</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="force_pw_update" <?=($result['attributes']['force_pw_update']=="1") ? "checked" : null;?>> <?=$lang['edit']['force_pw_update'];?></label>
<small class="help-block"><?=sprintf($lang['edit']['force_pw_update_info'], $UI_TEXTS['main_name']);?></small>
</div>
</div>
</div>
<div data-acl="<?=$_SESSION['acl']['sogo_access'];?>" class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="sogo_access" <?=($result['attributes']['sogo_access']=="1") ? "checked" : null;?>> <?=$lang['edit']['sogo_access'];?></label>
<small class="help-block"><?=$lang['edit']['sogo_access_info'];?></small>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editmailbox" data-item="<?=htmlspecialchars($result['username']);?>" data-api-url='edit/mailbox' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<hr>
+ <form data-id="pushover" class="form well" method="post">
+ <input type="hidden" value="0" name="active">
+ <div class="row">
+ <div class="col-sm-1">
+ <p class="help-block"><a href="https://pushover.net" target="_blank"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAACglBMVEUAAAAAAAEAAAAilecFGigAAAAAAAAAAAAAAAANj+c3n+Ypm+oeYI4KWI4MieAtkdQbleoJcLcjmeswmN4Rit4KgdMKUYQJKUAQSnILL0kMNlMSTngimOoNPF0hlOQBBgkNOlkRS3MHIjUhk+IPf8wKLUYsjM0AAAASTngAAAAAAAAPfckbdLIbdrYUWIgegsgce70knfEAAAAknfENOVkGHi8YaaIjnvEdgMUhkuAQSG8aca0hleQUh9YLjOM4nOEMgtMcbaYWa6YemO02ltkKhNktgLodYZEPXJEyi8kKesktfLUzj84cWYMiluckZ5YJXJYeW4Y0k9YKfs4yjs0pc6YHZaUviskLfMkqmugak+cqkNcViNcqeK4Iaq4XRmYGPmYMKDsFJTstgr0LdL0ti84CCQ4BCQ4Qgc8rlt8XjN8shcQsi8wZSGgEP2cRMEUDKkUAAAD///8dmvEamfExo/EXmPEWl/ERlvElnvEsofEjnfETl/Enn/Ezo/E4pvEvovEfm/E1pPEzpPEvofEOlfEpoPEamPEQlfEYmfE6p/EgnPEVlvEroPE3pfE2pfENk/Ern/E3pPEcmfEfmvEnnvBlufT6/P0soPAknPDd7/zs9vzo9PxBqfItofAqoPD9/f3B4/q43/mx2/l/xfZ6w/Vxv/VtvfVgt/RXtPNTsfNEq/L3+/31+v3a7fvR6vvH5fqs2vmc0/jx+P3v9/3h8fzW7PvV7PvL5/q13fmo1/mh1PiY0fiNy/aHyfZ2wfVou/Vdt/RPsPM3oeoQkuowmeAgjdgcgMQbeLrw9/3k8vy74Pm63/mX0PdYtfNNr/Ikm+4wnOchkuAVjOAfdrMVcrOdoJikAAAAcnRSTlMAIQ8IzzweFwf+/fvw8P79+/Xt7e3p6eji4d7U08y8qZyTiIWDgn53bWxqaWBKQ0JBOjUwMCkoJCEfHBkT/vz8/Pv7+vr69/b29PTy7ezm5ubm5N7e29vQ0M/Pv7+4uLW1pqaWloWDg3x7e21mUVFFRUXdPracAAAEbElEQVRIx4WUZbvaQBCFF+ru7u7u7u7u7t4mvVwSoBC0JIUCLRQolLq7u7vr/+nMLkmQyvlwyfPcd86e3ZldUqwyQ/p329J+XfutPQYOLUP+q55rFtQJRvY79+xxlZTUWbKpz7/xrrMr2+3BoNPpdLn2lJQ4HEeqLOr1d7z7XNkesQed4A848G63Oy4Gmg/6Mz542QvZbqe8C/Ig73CLYiYTrtLmT3zfqbIcAR7y4wIqH/B6M9Fo0+Ldb6sM9ph/v4ozPuz12mxRofaAAr7jCNkuoz/jNf9AGHibkBCm51fsGKvxsAGWx4H+jBcEi6V2birDpCL/9Klrd1KHbiSvPWP8V0tTnTfO03iXi57P6WNHOVUf44IFdFDRz6pV5fw8Zy5z3JVH5+R48OwxqDiGvKJIY9R+9JsCuJ5HPg74OVEMpz+nbdEPUHEWeEk6IDUnTC1l5r+f8uffc0cfxc8fS17kLso24SwUPFDA/6DE82xKDOPliJ7n/GGOOyWK9zD9CdjvOfg9Dv6AH+AX04LW9gj2i8W/APx1UbxwCAu+wPmcpgUKL/EHdvtq4uwaZwCuznPJVY5LHhED15G/isd5Hz4eKui/e/du02YoKFeD5mHzHIN/nxEDe25gQQwKorAid04CfyzwL4XutXvl1Pt1guMOwwKPkU8mYIFT8JHK+vv8prpDScUVL+j8s3lOctw1GIhbWHAS+HgKPk7xPM/4UtNAYmzizJkf6NgTb/gM8jePQLsewMdthS3g95tMpT1IhVm6v1s8fYmLeb13Odwp8Fh5KY048y/d14WUrwrb1e/X/rNp73nkD8kWS+wi/MZ4XuetG4mhKubJm3/WNEvi8SHwB56nPKjUam0LBdp9ARwupFemTYudvgN/L1+A/Ko/LGBuS8pPy+YR1fuCTWNKnUyoeUyYx2o2dyEVGmr5xTD42xzvkD16+Pb9WIIH6fmt1r3mbsTY7Bvw+n23naT8BUWh86bz6G/e259UXPUK3gfAxQDlo7Rpx3Geqb2e3wp83SGEdKpB7zvwYbzvT2n65xLwbH6YP+M9C8vA8E1wxLU8gkCbdhXGUyrMgwVrcbzLHonr78lzDvWM3q/C/HtDlXoSUIe3YkblhRPIX4E8Oo/9siLv8dRjV7SBlkdgTXvKS7nzsA/9AfeEuhKq9T8zWIDv1Sd6ETAP4D6/H/1V+1BojvruNa4SZXz4JhY84dV5MOF5agUvu5OsOo+KRpG30KalEnoeDccFlutPZYs38D5n3zcpr1/0fBhfb3DOY1z2tSAgLxWezz6zuoHhfUmOejf6blHQH/sFuJYfcMZX307ytKvRa3ifoV/586P5j+tICtS77BuJxzxYAPZsntX8k3eSIhlajK4p8b7iefCEKs03kD/I2LnxL9ovH+43y4fAv1YrI/mzDBsavAX/UppfzVOrZT/ydxk6lJ047MfLfVbcb6hS9ZEzWxekKQ5WrtPqZg3rV6tWrX6Tle3KQZj/q6KxQnmDoXwFY0VSrN9e8FRXBCTAvwAAAABJRU5ErkJggg==" class="img img-fluid"></a></p>
+ </div>
+ <div class="col-sm-10">
+ <div class="form-group">
+ <div class="row">
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="token">API Token/Key (Application)</label>
+ <input type="text" class="form-control" name="token" maxlength="30" value="<?=$pushover_data['token'];?>" required>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="key">User/Group Key</label>
+ <input type="text" class="form-control" name="key" maxlength="30" value="<?=$pushover_data['key'];?>" required>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="title"><?=$lang['admin']['pushover_title'];?></label>
+ <input type="text" class="form-control" name="title" value="<?=$pushover_data['title'];?>" placeholder="Mail">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="text"><?=$lang['admin']['pushover_text'];?></label>
+ <input type="text" class="form-control" name="text" value="<?=$pushover_data['text'];?>" placeholder="You've got mail 📧">
+ </div>
+ </div>
+ <div class="col-sm-12">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="active" <?=($pushover_data['active']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <p><?=sprintf($lang['admin']['pushover_info'], $mailbox);?></p>
+ <div class="btn-group" data-acl="<?=$_SESSION['acl']['pushover'];?>">
+ <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></a>
+ <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover-test" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover-test' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['admin']['pushover_verify'];?></a>
+ <a class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?=$lang['admin']['remove'];?></a>
+ </div>
+ </div>
+ </div>
+ </form>
+ <hr>
<form data-id="mboxratelimit" class="form-inline well" method="post">
<div class="row">
<div class="col-sm-1">
<p class="help-block"><?=$lang['acl']['ratelimit'];?></p>
</div>
<div class="col-sm-10">
<div class="form-group">
<input name="rl_value" type="number" autocomplete="off" value="<?=(!empty($rl['value'])) ? $rl['value'] : null;?>" class="form-control" placeholder="disabled">
</div>
<div class="form-group">
<select name="rl_frame" class="form-control">
<option value="s" <?=(isset($rl['frame']) && $rl['frame'] == 's') ? 'selected' : null;?>>msgs / second</option>
<option value="m" <?=(isset($rl['frame']) && $rl['frame'] == 'm') ? 'selected' : null;?>>msgs / minute</option>
<option value="h" <?=(isset($rl['frame']) && $rl['frame'] == 'h') ? 'selected' : null;?>>msgs / hour</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" data-action="edit_selected" data-id="mboxratelimit" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/rl-mbox' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
<p class="help-block"><?=$lang['edit']['mbox_rl_info'];?></p>
</div>
</div>
</form>
<form data-id="useracl" class="form-inline well" method="post">
<div class="row">
<div class="col-sm-1">
<p class="help-block">ACL</p>
</div>
<div class="col-sm-10">
<div class="form-group">
<select id="user_acl" name="user_acl" size="10" multiple>
<?php
$user_acls = acl('get', 'user', $mailbox);
foreach ($user_acls as $acl => $val):
?>
<option value="<?=$acl;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['acl'][$acl];?></option>
<?php
endforeach;
?>
</select>
</div>
<div class="form-group">
<button class="btn btn-default" data-action="edit_selected" data-id="useracl" data-item="<?=htmlspecialchars($mailbox);?>" data-api-url='edit/user-acl' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
</div>
</div>
</div>
</form>
<?php
}
}
elseif (isset($_GET['relayhost']) && is_numeric($_GET["relayhost"]) && !empty($_GET["relayhost"])) {
$relayhost = intval($_GET["relayhost"]);
$result = relayhost('details', $relayhost);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="editrelayhost">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="hostname"><?=$lang['add']['hostname'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="hostname" value="<?=htmlspecialchars($result['hostname'], ENT_QUOTES, 'UTF-8');?>" required>
<p class="help-block"><?=$lang['add']['relayhost_wrapped_tls_info'];?></p>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
<div class="col-sm-10">
<input type="password" 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="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editrelayhost" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/relayhost' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['transport']) && is_numeric($_GET["transport"]) && !empty($_GET["transport"])) {
$transport = intval($_GET["transport"]);
$result = transport('details', $transport);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="edittransport">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="destination"><?=$lang['add']['destination'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="destination" value="<?=htmlspecialchars($result['destination'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="nexthop"><?=$lang['edit']['nexthop'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="nexthop" placeholder='[0.0.0.0], [0.0.0.0]:25, host:25, host, [host]:25' value="<?=htmlspecialchars($result['nexthop'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="username"><?=$lang['add']['username'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="username" value="<?=htmlspecialchars($result['username'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['add']['password'];?></label>
<div class="col-sm-10">
<input type="password" 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="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="edittransport" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/transport' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['resource']) && filter_var(html_entity_decode(rawurldecode($_GET["resource"])), FILTER_VALIDATE_EMAIL) && !empty($_GET["resource"])) {
$resource = html_entity_decode(rawurldecode($_GET["resource"]));
$result = mailbox('get', 'resource_details', $resource);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['resource'];?></h4>
<form class="form-horizontal" role="form" method="post" data-id="editresource">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="description"><?=$lang['add']['description'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" value="<?=htmlspecialchars($result['description'], ENT_QUOTES, 'UTF-8');?>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="domain"><?=$lang['edit']['kind'];?></label>
<div class="col-sm-10">
<select name="kind" title="<?=$lang['edit']['select'];?>" required>
<option value="location" <?=($result['kind'] == "location") ? "selected" : null;?>>Location</option>
<option value="group" <?=($result['kind'] == "group") ? "selected" : null;?>>Group</option>
<option value="thing" <?=($result['kind'] == "thing") ? "selected" : null;?>>Thing</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="multiple_bookings_select"><?=$lang['add']['multiple_bookings'];?></label>
<div class="col-sm-10">
<select name="multiple_bookings_select" id="editSelectMultipleBookings" title="<?=$lang['add']['select'];?>" required>
<option value="0" <?=($result['multiple_bookings'] == 0) ? "selected" : null;?>><?=$lang['mailbox']['booking_0'];?></option>
<option value="-1" <?=($result['multiple_bookings'] == -1) ? "selected" : null;?>><?=$lang['mailbox']['booking_lt0'];?></option>
<option value="custom" <?=($result['multiple_bookings'] >= 1) ? "selected" : null;?>><?=$lang['mailbox']['booking_custom'];?></option>
</select>
<div style="display:none" id="multiple_bookings_custom_div">
<hr>
<input type="number" class="form-control" name="multiple_bookings_custom" id="multiple_bookings_custom" value="<?=($result['multiple_bookings'] >= 1) ? $result['multiple_bookings'] : null;?>">
</div>
<input type="hidden" name="multiple_bookings" id="multiple_bookings">
</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="active" <?=($result['active_int']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editresource" data-item="<?=htmlspecialchars($result['name']);?>" data-api-url='edit/resource' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['bcc']) && !empty($_GET["bcc"])) {
$bcc = intval($_GET["bcc"]);
$result = bcc('details', $bcc);
if (!empty($result)) {
?>
<h4><?=$lang['mailbox']['bcc_map'];?></h4>
<br>
<form class="form-horizontal" data-id="editbcc" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="bcc_dest"><?=$lang['mailbox']['bcc_destination'];?></label>
<div class="col-sm-10">
<input value="<?=$result['bcc_dest'];?>" type="text" class="form-control" name="bcc_dest" id="bcc_dest">
<small><?=$lang['edit']['bcc_dest_format'];?></small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="type"><?=$lang['mailbox']['bcc_map_type'];?></label>
<div class="col-sm-10">
<select id="addFilterType" name="type" id="type" required>
<option value="sender" <?=($result['type'] == 'sender') ? 'selected' : null;?>><?=$lang['mailbox']['bcc_sender_map'];?></option>
<option value="rcpt" <?=($result['type'] == 'rcpt') ? 'selected' : null;?>><?=$lang['mailbox']['bcc_rcpt_map'];?></option>
</select>
</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="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editbcc" data-item="<?=$bcc;?>" data-api-url='edit/bcc' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['recipient_map']) &&
!empty($_GET["recipient_map"]) &&
$_SESSION['mailcow_cc_role'] == "admin") {
$map = intval($_GET["recipient_map"]);
$result = recipient_map('details', $map);
if (substr($result['recipient_map_old'], 0, 1) == '@') {
$result['recipient_map_old'] = substr($result['recipient_map_old'], 1);
}
if (!empty($result)) {
?>
<h4><?=$lang['mailbox']['recipient_map']?>: <?=$result['recipient_map_old'];?></h4>
<br>
<form class="form-horizontal" data-id="edit_recipient_map" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="recipient_map_new"><?=$lang['mailbox']['recipient_map_old'];?></label>
<div class="col-sm-10">
<input value="<?=$result['recipient_map_old'];?>" type="text" class="form-control" name="recipient_map_old" id="recipient_map_old">
<small><?=$lang['mailbox']['recipient_map_old_info'];?></small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="recipient_map_new"><?=$lang['mailbox']['recipient_map_new'];?></label>
<div class="col-sm-10">
<input value="<?=$result['recipient_map_new'];?>" type="text" class="form-control" name="recipient_map_new" id="recipient_map_new">
<small><?=$lang['mailbox']['recipient_map_new_info'];?></small>
</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="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="edit_recipient_map" data-item="<?=$map;?>" data-api-url='edit/recipient_map' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['tls_policy_map']) &&
!empty($_GET["tls_policy_map"]) &&
$_SESSION['mailcow_cc_role'] == "admin") {
$map = intval($_GET["tls_policy_map"]);
$result = tls_policy_maps('details', $map);
if (!empty($result)) {
?>
<h4><?=$lang['mailbox']['tls_policy_maps']?>: <?=$result['dest'];?></h4>
<br>
<form class="form-horizontal" data-id="edit_tls_policy_maps" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="dest"><?=$lang['mailbox']['tls_map_dest'];?></label>
<div class="col-sm-10">
<input value="<?=$result['dest'];?>" type="text" class="form-control" name="dest" id="dest">
<small><?=$lang['mailbox']['tls_map_dest_info'];?></small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="policy"><?=$lang['mailbox']['tls_map_policy'];?></label>
<div class="col-sm-10">
<select class="full-width-select" name="policy" required>
<option value="none" <?=($result['policy'] != 'none') ?: 'selected';?>>none</option>
<option value="may" <?=($result['policy'] != 'may') ?: 'selected';?>>may</option>
<option value="encrypt" <?=($result['policy'] != 'encrypt') ?: 'selected';?>>encrypt</option>
<option value="dane" <?=($result['policy'] != 'dane') ?: 'selected';?>>dane</option>
<option value="dane-only" <?=($result['policy'] != 'dane-only') ?: 'selected';?>>dane-only</option>
<option value="fingerprint" <?=($result['policy'] != 'fingerprint') ?: 'selected';?>>fingerprint</option>
<option value="verify" <?=($result['policy'] != 'verify') ?: 'selected';?>>verify</option>
<option value="secure" <?=($result['policy'] != 'secure') ?: 'selected';?>>secure</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="parameters"><?=$lang['mailbox']['tls_map_parameters'];?></label>
<div class="col-sm-10">
<input value="<?=$result['parameters'];?>" type="text" class="form-control" name="parameters" id="parameters">
<small><?=$lang['mailbox']['tls_map_parameters_info'];?></small>
</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="active" <?php if (isset($result['active_int']) && $result['active_int']=="1") { echo "checked"; }; ?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="edit_tls_policy_maps" data-item="<?=$map;?>" data-api-url='edit/tls-policy-map' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
}
if ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin" || $_SESSION['mailcow_cc_role'] == "user") {
if (isset($_GET['syncjob']) &&
is_numeric($_GET['syncjob'])) {
$id = $_GET["syncjob"];
$result = mailbox('get', 'syncjob_details', $id);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['syncjob'];?></h4>
<form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
<input type="hidden" value="0" name="delete2duplicates">
<input type="hidden" value="0" name="delete1">
<input type="hidden" value="0" name="delete2">
<input type="hidden" value="0" name="automap">
<input type="hidden" value="0" name="skipcrossduplicates">
<input type="hidden" value="0" name="active">
<input type="hidden" value="0" name="subscribeall">
<div class="form-group">
<label class="control-label col-sm-2" for="host1"><?=$lang['edit']['hostname'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="host1" id="host1" value="<?=htmlspecialchars($result['host1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="port1">Port</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="<?=htmlspecialchars($result['port1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="user1"><?=$lang['edit']['username'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="user1" id="user1" value="<?=htmlspecialchars($result['user1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password1"><?=$lang['edit']['password'];?></label>
<div class="col-sm-10">
<input type="password" class="form-control" name="password1" id="password1" value="<?=htmlspecialchars($result['password1'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="enc1"><?=$lang['edit']['encryption'];?></label>
<div class="col-sm-10">
<select id="enc1" name="enc1">
<option <?=($result['enc1'] == "TLS") ? "selected" : null;?>>TLS</option>
<option <?=($result['enc1'] == "SSL") ? "selected" : null;?>>SSL</option>
<option <?=($result['enc1'] == "PLAIN") ? "selected" : null;?>>PLAIN</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="mins_interval"><?=$lang['edit']['mins_interval'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="mins_interval" min="1" max="3600" value="<?=htmlspecialchars($result['mins_interval'], ENT_QUOTES, 'UTF-8');?>" required>
<small class="help-block">1-3600</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="subfolder2"><?=$lang['edit']['subfolder2'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="<?=htmlspecialchars($result['subfolder2'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="maxage"><?=$lang['edit']['maxage'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="<?=htmlspecialchars($result['maxage'], ENT_QUOTES, 'UTF-8');?>">
<small class="help-block">0-32000</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="maxbytespersecond"><?=$lang['edit']['maxbytespersecond'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="<?=htmlspecialchars($result['maxbytespersecond'], ENT_QUOTES, 'UTF-8');?>">
<small class="help-block">0-125000000</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="timeout1"><?=$lang['add']['timeout1'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="<?=htmlspecialchars($result['timeout1'], ENT_QUOTES, 'UTF-8');?>">
<small class="help-block">1-32000</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="timeout2"><?=$lang['add']['timeout2'];?></label>
<div class="col-sm-10">
<input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="<?=htmlspecialchars($result['timeout2'], ENT_QUOTES, 'UTF-8');?>">
<small class="help-block">1-32000</small>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="exclude"><?=$lang['edit']['exclude'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="exclude" id="exclude" value="<?=htmlspecialchars($result['exclude'], ENT_QUOTES, 'UTF-8');?>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="custom_params"><?=$lang['add']['custom_params'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="custom_params" id="custom_params" value="<?=htmlspecialchars($result['custom_params'], ENT_QUOTES, 'UTF-8');?>" placeholder="--dry --some-param=xy --other-param=yx">
<small class="help-block"><?=$lang['add']['custom_params_hint'];?></small>
</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="delete2duplicates" <?=($result['delete2duplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete2duplicates'];?> (--delete2duplicates)</label>
</div>
</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="delete1" <?=($result['delete1']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete1'];?> (--delete1)</label>
</div>
</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="delete2" <?=($result['delete2']=="1") ? "checked" : "";?>> <?=$lang['edit']['delete2'];?> (--delete2)</label>
</div>
</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="automap" <?=($result['automap']=="1") ? "checked" : "";?>> <?=$lang['edit']['automap'];?> (--automap)</label>
</div>
</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="skipcrossduplicates" <?=($result['skipcrossduplicates']=="1") ? "checked" : "";?>> <?=$lang['edit']['skipcrossduplicates'];?> (--skipcrossduplicates)</label>
</div>
</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="subscribeall" <?=($result['subscribeall']=="1") ? "checked" : "";?>> <?=$lang['add']['subscribeall'];?> (--subscribeall)</label>
</div>
</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="active" <?=($result['active_int']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editsyncjob" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/syncjob' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['filter']) &&
is_numeric($_GET['filter'])) {
$id = $_GET["filter"];
$result = mailbox('get', 'filter_details', $id);
if (!empty($result)) {
?>
<h4>Filter</h4>
<form class="form-horizontal" data-id="editfilter" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="script_desc"><?=$lang['edit']['sieve_desc'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="script_desc" id="script_desc" value="<?=htmlspecialchars($result['script_desc'], ENT_QUOTES, 'UTF-8');?>" required maxlength="255">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="filter_type"><?=$lang['edit']['sieve_type'];?></label>
<div class="col-sm-10">
<select id="addFilterType" name="filter_type" id="filter_type" required>
<option value="prefilter" <?=($result['filter_type'] == 'prefilter') ? 'selected' : null;?>>Prefilter</option>
<option value="postfilter" <?=($result['filter_type'] == 'postfilter') ? 'selected' : null;?>>Postfilter</option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="script_data">Script:</label>
<div class="col-sm-10">
<textarea spellcheck="false" autocorrect="off" autocapitalize="none" class="form-control textarea-code" rows="20" id="script_data" name="script_data" required><?=$result['script_data'];?></textarea>
</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="active" <?=($result['active_int']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editfilter" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/filter' data-api-attr='{}' href="#"><?=$lang['edit']['validate_save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
elseif (isset($_GET['app-passwd']) &&
is_numeric($_GET['app-passwd'])) {
$id = $_GET["app-passwd"];
$result = app_passwd('details', $id);
if (!empty($result)) {
?>
<h4><?=$lang['edit']['app_passwd'];?></h4>
<form class="form-horizontal" data-pwgen-length="32" data-id="editapp" role="form" method="post">
<input type="hidden" value="0" name="active">
<div class="form-group">
<label class="control-label col-sm-2" for="app_name"><?=$lang['edit']['app_name'];?></label>
<div class="col-sm-10">
<input type="text" class="form-control" name="app_name" id="app_name" value="<?=htmlspecialchars($result['name'], ENT_QUOTES, 'UTF-8');?>" required maxlength="255">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?> (<a href="#" class="generate_password"><?=$lang['edit']['generate'];?></a>)</label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" data-hibp="true" class="form-control" name="password" placeholder="">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><?=$lang['edit']['password_repeat'];?></label>
<div class="col-sm-10">
<input type="password" data-pwgen-field="true" class="form-control" name="password2">
</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="active" <?=($result['active_int']=="1") ? "checked" : "";?>> <?=$lang['edit']['active'];?></label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button class="btn btn-success" data-action="edit_selected" data-id="editapp" data-item="<?=htmlspecialchars($result['id']);?>" data-api-url='edit/app-passwd' data-api-attr='{}' href="#"><?=$lang['edit']['save'];?></button>
</div>
</div>
</form>
<?php
}
else {
?>
<div class="alert alert-info" role="alert"><?=$lang['info']['no_action'];?></div>
<?php
}
}
}
}
else {
?>
<div class="alert alert-danger" role="alert"><?=$lang['danger']['access_denied'];?></div>
<?php
}
?>
</div>
</div>
</div>
</div>
<a href="<?=$_SESSION['return_to'];?>">&#8592; <?=$lang['edit']['previous'];?></a>
</div> <!-- /container -->
<script type='text/javascript'>
<?php
$lang_user = json_encode($lang['user']);
echo "var lang_user = ". $lang_user . ";\n";
echo "var table_for_domain = '". ((isset($domain)) ? $domain : null) . "';\n";
echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
?>
</script>
<?php
$js_minifier->add('/web/js/site/edit.js');
$js_minifier->add('/web/js/site/pwgen.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
?>
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 69fbb3bb..74a13391 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -1,1668 +1,1756 @@
<?php
function isset_has_content($var) {
if (isset($var) && $var != "") {
return true;
}
else {
return false;
}
}
function hash_password($password) {
$salt_str = bin2hex(openssl_random_pseudo_bytes(8));
return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
}
function last_login($user) {
global $pdo;
$stmt = $pdo->prepare('SELECT `remote`, `time` FROM `logs`
WHERE JSON_EXTRACT(`call`, "$[0]") = "check_login"
AND JSON_EXTRACT(`call`, "$[1]") = :user
AND `type` = "success" ORDER BY `time` DESC LIMIT 1');
$stmt->execute(array(':user' => $user));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
return $row;
}
else {
return false;
}
}
function flush_memcached() {
try {
$m = new Memcached();
$m->addServer('memcached', 11211);
$m->flush();
}
catch ( Exception $e ) {
// Dunno
}
}
function sys_mail($_data) {
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
return false;
}
$excludes = $_data['mass_exclude'];
$includes = $_data['mass_include'];
$mailboxes = array();
$mass_from = $_data['mass_from'];
$mass_text = $_data['mass_text'];
$mass_subject = $_data['mass_subject'];
if (!filter_var($mass_from, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'from_invalid'
);
return false;
}
if (empty($mass_subject)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'subject_empty'
);
return false;
}
if (empty($mass_text)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'text_empty'
);
return false;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
foreach ($domains as $domain) {
foreach (mailbox('get', 'mailboxes', $domain) as $mailbox) {
$mailboxes[] = $mailbox;
}
}
if (!empty($includes)) {
$rcpts = array_intersect($mailboxes, $includes);
}
elseif (!empty($excludes)) {
$rcpts = array_diff($mailboxes, $excludes);
}
else {
$rcpts = $mailboxes;
}
if (!empty($rcpts)) {
ini_set('max_execution_time', 0);
ini_set('max_input_time', 0);
$mail = new PHPMailer;
$mail->Timeout = 10;
$mail->SMTPOptions = array(
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
$mail->isSMTP();
$mail->Host = 'dovecot-mailcow';
$mail->SMTPAuth = false;
$mail->Port = 24;
$mail->setFrom($mass_from);
$mail->Subject = $mass_subject;
$mail->CharSet ="UTF-8";
$mail->Body = $mass_text;
$mail->XMailer = 'MooMassMail';
foreach ($rcpts as $rcpt) {
$mail->AddAddress($rcpt);
if (!$mail->send()) {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__),
'msg' => 'Mailer error (RCPT "' . htmlspecialchars($rcpt) . '"): ' . str_replace('https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting', '', $mail->ErrorInfo)
);
}
$mail->ClearAllRecipients();
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__),
'msg' => 'Mass mail job completed, sent ' . count($rcpts) . ' mails'
);
}
function logger($_data = false) {
/*
logger() will be called as last function
To manually log a message, logger needs to be called like below.
logger(array(
'return' => array(
array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => $err
)
)
));
These messages will not be printed as alert box.
To do so, push them to $_SESSION['return'] and do not call logger as they will be included automatically:
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => $err
);
*/
global $pdo;
if (!$_data) {
$_data = $_SESSION;
}
if (!empty($_data['return'])) {
$task = substr(strtoupper(md5(uniqid(rand(), true))), 0, 6);
foreach ($_data['return'] as $return) {
$type = $return['type'];
$msg = json_encode($return['msg'], JSON_UNESCAPED_UNICODE);
$call = json_encode($return['log'], JSON_UNESCAPED_UNICODE);
if (!empty($_SESSION["dual-login"]["username"])) {
$user = $_SESSION["dual-login"]["username"] . ' => ' . $_SESSION['mailcow_cc_username'];
$role = $_SESSION["dual-login"]["role"] . ' => ' . $_SESSION['mailcow_cc_role'];
}
elseif (!empty($_SESSION['mailcow_cc_username'])) {
$user = $_SESSION['mailcow_cc_username'];
$role = $_SESSION['mailcow_cc_role'];
}
else {
$user = 'unauthenticated';
$role = 'unauthenticated';
}
// We cannot log when logs is missing...
try {
$stmt = $pdo->prepare("INSERT INTO `logs` (`type`, `task`, `msg`, `call`, `user`, `role`, `remote`, `time`) VALUES
(:type, :task, :msg, :call, :user, :role, :remote, UNIX_TIMESTAMP())");
$stmt->execute(array(
':type' => $type,
':task' => $task,
':call' => $call,
':msg' => $msg,
':user' => $user,
':role' => $role,
':remote' => get_remote_ip()
));
}
catch (Exception $e) {
// Do nothing
}
}
}
else {
return true;
}
}
function hasDomainAccess($username, $role, $domain) {
global $pdo;
if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
return false;
}
if (empty($domain) || !is_valid_domain_name($domain)) {
return false;
}
if ($role != 'admin' && $role != 'domainadmin') {
return false;
}
if ($role == 'admin') {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `alias_domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
return true;
}
}
elseif ($role == 'domainadmin') {
$stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins`
WHERE (
`active`='1'
AND `username` = :username
AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2))
)");
$stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if (!empty($num_results)) {
return true;
}
}
return false;
}
function hasMailboxObjectAccess($username, $role, $object) {
global $pdo;
if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
return false;
}
if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
return false;
}
if ($username == $object) {
return true;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object");
$stmt->execute(array(':object' => $object));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
return true;
}
return false;
}
function hasAliasObjectAccess($username, $role, $object) {
global $pdo;
if (!filter_var(html_entity_decode(rawurldecode($username)), FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
return false;
}
if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
return false;
}
if ($username == $object) {
return true;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `alias` WHERE `address` = :object");
$stmt->execute(array(':object' => $object));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
return true;
}
return false;
}
function pem_to_der($pem_key) {
// Need to remove BEGIN/END PUBLIC KEY
$lines = explode("\n", trim($pem_key));
unset($lines[count($lines)-1]);
unset($lines[0]);
return base64_decode(implode('', $lines));
}
function generate_tlsa_digest($hostname, $port, $starttls = null) {
if (!is_valid_domain_name($hostname)) {
return "Not a valid hostname";
}
if (empty($starttls)) {
$context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)));
$stream = stream_socket_client('ssl://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context);
if (!$stream) {
$error_msg = isset($error_msg) ? $error_msg : '-';
return $error_nr . ': ' . $error_msg;
}
}
else {
$stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5);
if (!$stream) {
return $error_nr . ': ' . $error_msg;
}
$banner = fread($stream, 512 );
if (preg_match("/^220/i", $banner)) { // SMTP
fwrite($stream,"HELO tlsa.generator.local\r\n");
fread($stream, 512);
fwrite($stream,"STARTTLS\r\n");
fread($stream, 512);
}
elseif (preg_match("/imap.+starttls/i", $banner)) { // IMAP
fwrite($stream,"A1 STARTTLS\r\n");
fread($stream, 512);
}
elseif (preg_match("/^\+OK/", $banner)) { // POP3
fwrite($stream,"STLS\r\n");
fread($stream, 512);
}
elseif (preg_match("/^OK/m", $banner)) { // Sieve
fwrite($stream,"STARTTLS\r\n");
fread($stream, 512);
}
else {
return 'Unknown banner: "' . htmlspecialchars(trim($banner)) . '"';
}
// Upgrade connection
stream_set_blocking($stream, true);
stream_context_set_option($stream, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($stream, 'ssl', 'verify_peer', false);
stream_context_set_option($stream, 'ssl', 'verify_peer_name', false);
stream_context_set_option($stream, 'ssl', 'allow_self_signed', true);
stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
stream_set_blocking($stream, false);
}
$params = stream_context_get_params($stream);
if (!empty($params['options']['ssl']['peer_certificate'])) {
$key_resource = openssl_pkey_get_public($params['options']['ssl']['peer_certificate']);
// We cannot get ['rsa']['n'], the binary data would contain BEGIN/END PUBLIC KEY
$key_data = openssl_pkey_get_details($key_resource)['key'];
return '3 1 1 ' . openssl_digest(pem_to_der($key_data), 'sha256');
}
else {
return 'Error: Cannot read peer certificate';
}
}
function alertbox_log_parser($_data){
global $lang;
if (isset($_data['return'])) {
foreach ($_data['return'] as $return) {
// Get type
$type = $return['type'];
// If a lang[type][msg] string exists, use it as message
if (is_string($lang[$return['type']][$return['msg']])) {
$msg = $lang[$return['type']][$return['msg']];
}
// If msg is an array, use first element as language string and run printf on it with remaining array elements
elseif (is_array($return['msg'])) {
$msg = array_shift($return['msg']);
$msg = vsprintf(
$lang[$return['type']][$msg],
$return['msg']
);
}
// If none applies, use msg as returned message
else {
$msg = $return['msg'];
}
$log_array[] = array('msg' => json_encode($msg), 'type' => json_encode($type));
}
if (!empty($log_array)) {
return $log_array;
}
}
return false;
}
function verify_hash($hash, $password) {
if (preg_match('/^{SSHA256}/i', $hash)) {
// Remove tag if any
$hash = preg_replace('/^{SSHA256}/i', '', $hash);
// Decode hash
$dhash = base64_decode($hash);
// Get first 32 bytes of binary which equals a SHA256 hash
$ohash = substr($dhash, 0, 32);
// Remove SHA256 hash from decoded hash to get original salt string
$osalt = str_replace($ohash, '', $dhash);
// Check single salted SHA256 hash against extracted hash
if (hash_equals(hash('sha256', $password . $osalt, true), $ohash)) {
return true;
}
}
elseif (preg_match('/^{PLAIN-MD5}/i', $hash)) {
$hash = preg_replace('/^{PLAIN-MD5}/i', '', $hash);
if (md5($password) == $hash) {
return true;
}
}
elseif (preg_match('/^{SHA512-CRYPT}/i', $hash)) {
// Remove tag if any
$hash = preg_replace('/^{SHA512-CRYPT}/i', '', $hash);
// Decode hash
preg_match('/\\$6\\$(.*)\\$(.*)/i', $hash, $hash_array);
$osalt = $hash_array[1];
$ohash = $hash_array[2];
if (hash_equals(crypt($password, '$6$' . $osalt . '$'), $hash)) {
return true;
}
}
elseif (preg_match('/^{SSHA512}/i', $hash)) {
$hash = preg_replace('/^{SSHA512}/i', '', $hash);
// Decode hash
$dhash = base64_decode($hash);
// Get first 64 bytes of binary which equals a SHA512 hash
$ohash = substr($dhash, 0, 64);
// Remove SHA512 hash from decoded hash to get original salt string
$osalt = str_replace($ohash, '', $dhash);
// Check single salted SHA512 hash against extracted hash
if (hash_equals(hash('sha512', $password . $osalt, true), $ohash)) {
return true;
}
}
elseif (preg_match('/^{MD5-CRYPT}/i', $hash)) {
$hash = preg_replace('/^{MD5-CRYPT}/i', '', $hash);
if (password_verify($password, $hash)) {
return true;
}
}
return false;
}
function check_login($user, $pass) {
global $pdo;
global $redis;
global $imap_server;
if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'malformed_username'
);
return false;
}
$user = strtolower(trim($user));
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '1'
AND `active` = '1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_hash($row['password'], $pass)) {
if (get_tfa($user)['name'] != "none") {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "admin";
$_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "admin";
}
}
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `superadmin` = '0'
AND `active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_hash($row['password'], $pass) !== false) {
if (get_tfa($user)['name'] != "none") {
$_SESSION['pending_mailcow_cc_username'] = $user;
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
$_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'info',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'awaiting_tfa_confirmation'
);
return "pending";
}
else {
unset($_SESSION['ldelay']);
// Reactivate TFA if it was set to "deactivate TFA for next login"
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
$stmt->execute(array(':user' => $user));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "domainadmin";
}
}
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `active`='1'
AND `username` = :user");
$stmt->execute(array(':user' => $user));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
if (verify_hash($row['password'], $pass) !== false) {
unset($_SESSION['ldelay']);
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => array('logged_in_as', $user)
);
return "user";
}
}
if (!isset($_SESSION['ldelay'])) {
$_SESSION['ldelay'] = "0";
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
elseif (!isset($_SESSION['mailcow_cc_username'])) {
$_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $user, '*'),
'msg' => 'login_failed'
);
sleep($_SESSION['ldelay']);
return false;
}
function formatBytes($size, $precision = 2) {
if(!is_numeric($size)) {
return "0";
}
$base = log($size, 1024);
$suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB');
if ($size == "0") {
return "0";
}
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
function update_sogo_static_view() {
global $pdo;
global $lang;
$stmt = $pdo->query("SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'sogo_view'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings`)
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `ext_acl`, `kind`, `multiple_bookings` from sogo_view");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
}
flush_memcached();
}
function edit_user_account($_data) {
global $lang;
global $pdo;
$_data_log = $_data;
!isset($_data_log['user_new_pass']) ?: $_data_log['user_new_pass'] = '*';
!isset($_data_log['user_new_pass2']) ?: $_data_log['user_new_pass2'] = '*';
!isset($_data_log['user_old_pass']) ?: $_data_log['user_old_pass'] = '*';
$username = $_SESSION['mailcow_cc_username'];
$role = $_SESSION['mailcow_cc_role'];
$password_old = $_data['user_old_pass'];
if (filter_var($username, FILTER_VALIDATE_EMAIL === false) || $role != 'user') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['user_new_pass']) && isset($_data['user_new_pass2'])) {
$password_new = $_data['user_new_pass'];
$password_new2 = $_data['user_new_pass2'];
}
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $password_old)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (isset($password_new) && isset($password_new2)) {
if (!empty($password_new2) && !empty($password_new)) {
if ($password_new2 != $password_new) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'password_mismatch'
);
return false;
}
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'password_complexity'
);
return false;
}
$password_hashed = hash_password($password_new);
try {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed, `attributes` = JSON_SET(`attributes`, '$.force_pw_update', '0') WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
}
}
update_sogo_static_view();
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mailbox_modified', htmlspecialchars($username))
);
}
function user_get_alias_details($username) {
global $lang;
global $pdo;
$data['direct_aliases'] = false;
$data['shared_aliases'] = false;
if ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
return false;
}
$data['address'] = $username;
$stmt = $pdo->prepare("SELECT `address` AS `shared_aliases`, `public_comment` FROM `alias`
WHERE `goto` REGEXP :username_goto
AND `address` NOT LIKE '@%'
AND `goto` != :username_goto2
AND `address` != :username_address");
$stmt->execute(array(
':username_goto' => '(^|,)'.$username.'($|,)',
':username_goto2' => $username,
':username_address' => $username
));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['shared_aliases'][$row['shared_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']);
//$data['shared_aliases'][] = $row['shared_aliases'];
}
$stmt = $pdo->prepare("SELECT `address` AS `direct_aliases`, `public_comment` FROM `alias`
WHERE `goto` = :username_goto
AND `address` NOT LIKE '@%'
AND `address` != :username_address");
$stmt->execute(
array(
':username_goto' => $username,
':username_address' => $username
));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['direct_aliases'][$row['direct_aliases']]['public_comment'] = htmlspecialchars($row['public_comment']);
}
$stmt = $pdo->prepare("SELECT CONCAT(local_part, '@', alias_domain) AS `ad_alias`, `alias_domain` FROM `mailbox`
LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain`
WHERE `username` = :username ;");
$stmt->execute(array(':username' => $username));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
if (empty($row['ad_alias'])) {
continue;
}
$data['direct_aliases'][$row['ad_alias']]['public_comment'] = '↪ ' . $row['alias_domain'];
}
$stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
$stmt->execute(array(':username' => $username));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['aliases_also_send_as'] = $row['send_as'];
}
$stmt = $pdo->prepare("SELECT CONCAT_WS(', ', IFNULL(GROUP_CONCAT(DISTINCT `send_as` SEPARATOR ', '), '&#10008;'), GROUP_CONCAT(DISTINCT CONCAT('@',`alias_domain`) SEPARATOR ', ')) AS `send_as` FROM `sender_acl` LEFT JOIN `alias_domain` ON `alias_domain`.`target_domain` = TRIM(LEADING '@' FROM `send_as`) WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
$stmt->execute(array(':username' => $username));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['aliases_send_as_all'] = $row['send_as'];
}
$stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
$stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
$run = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($run)) {
$data['is_catch_all'] = $row['address'];
}
return $data;
}
function is_valid_domain_name($domain_name) {
if (empty($domain_name)) {
return false;
}
$domain_name = idn_to_ascii($domain_name, 0, INTL_IDNA_VARIANT_UTS46);
return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
&& preg_match("/^.{1,253}$/", $domain_name)
&& preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
}
function set_tfa($_data) {
global $lang;
global $pdo;
global $yubi;
global $u2f;
global $tfa;
$_data_log = $_data;
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
$username = $_SESSION['mailcow_cc_username'];
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
$_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
WHERE `username` = :user");
$stmt->execute(array(':user' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!verify_hash($row['password'], $_data["confirm_password"])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
switch ($_data["tfa_method"]) {
case "yubi_otp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
$yubico_id = $_data['yubico_id'];
$yubico_key = $_data['yubico_key'];
$yubi = new Auth_Yubico($yubico_id, $yubico_key);
if (!$yubi) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
if (!ctype_alnum($_data["otp_token"]) || strlen($_data["otp_token"]) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'tfa_token_invalid'
);
return false;
}
$yauth = $yubi->verify($_data["otp_token"]);
if (PEAR::isError($yauth)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
try {
// We could also do a modhex translation here
$yubico_modhex_id = substr($_data["otp_token"], 0, 12);
$stmt = $pdo->prepare("DELETE FROM `tfa`
WHERE `username` = :username
AND (`authmech` != 'yubi_otp')
OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
(:key_id, :username, 'yubi_otp', '1', :secret)");
$stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
case "u2f":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
try {
$reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($_data['token']));
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
$stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
$_SESSION['regReq'] = null;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
break;
case "totp":
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
try {
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')");
$stmt->execute(array($username, $key_id, $_POST['totp_secret']));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'totp_verification_failed'
);
}
break;
case "none":
try {
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', htmlspecialchars($username))
);
break;
}
}
function unset_tfa_key($_data) {
// Can only unset own keys
// Needs at least one key left
global $pdo;
global $lang;
$_data_log = $_data;
$id = intval($_data['unset_tfa_key']);
$username = $_SESSION['mailcow_cc_username'];
if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
$_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
try {
if (!is_numeric($id)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row['keys'] == "1") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => 'last_key'
);
return false;
}
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id");
$stmt->execute(array(':username' => $username, ':id' => $id));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('object_modified', $username)
);
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_data_log),
'msg' => array('mysql_error', $e)
);
return false;
}
}
function get_tfa($username = null) {
global $pdo;
if (isset($_SESSION['mailcow_cc_username'])) {
$username = $_SESSION['mailcow_cc_username'];
}
elseif (empty($username)) {
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
switch ($row["authmech"]) {
case "yubi_otp":
$data['name'] = "yubi_otp";
$data['pretty'] = "Yubico OTP";
$stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$data['additional'][] = $row;
}
return $data;
break;
case "u2f":
$data['name'] = "u2f";
$data['pretty'] = "Fido U2F";
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$data['additional'][] = $row;
}
return $data;
break;
case "hotp":
$data['name'] = "hotp";
$data['pretty'] = "HMAC-based OTP";
return $data;
break;
case "totp":
$data['name'] = "totp";
$data['pretty'] = "Time-based OTP";
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
$stmt->execute(array(
':username' => $username,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$data['additional'][] = $row;
}
return $data;
break;
default:
$data['name'] = 'none';
$data['pretty'] = "-";
return $data;
break;
}
}
function verify_tfa_login($username, $token) {
global $pdo;
global $lang;
global $yubi;
global $u2f;
global $tfa;
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
WHERE `username` = :username AND `active` = '1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
switch ($row["authmech"]) {
case "yubi_otp":
if (!ctype_alnum($token) || strlen($token) != 44) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'token length error')
);
return false;
}
$yubico_modhex_id = substr($token, 0, 12);
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'yubi_otp'
AND `active`='1'
AND `secret` LIKE :modhex");
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$yubico_auth = explode(':', $row['secret']);
$yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
$yauth = $yubi->verify($token);
if (PEAR::isError($yauth)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', $yauth->getMessage())
);
return false;
}
else {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_yotp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('yotp_verification_failed', 'unknown')
);
return false;
break;
case "u2f":
try {
$reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
$stmt = $pdo->prepare("SELECT `id` FROM `tfa` WHERE `keyHandle` = ?");
$stmt->execute(array($reg->keyHandle));
$row_key_id = $stmt->fetch(PDO::FETCH_ASSOC);
$_SESSION['tfa_id'] = $row_key_id['id'];
$_SESSION['authReq'] = null;
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_u2f_login'
);
return true;
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', $e->getMessage())
);
$_SESSION['regReq'] = null;
return false;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('u2f_verification_failed', 'unknown')
);
return false;
break;
case "hotp":
return false;
break;
case "totp":
try {
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
WHERE `username` = :username
AND `authmech` = 'totp'
AND `active`='1'");
$stmt->execute(array(':username' => $username));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {
$_SESSION['tfa_id'] = $row['id'];
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'verified_totp_login'
);
return true;
}
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'totp_verification_failed'
);
return false;
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => array('mysql_error', $e)
);
return false;
}
break;
default:
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $username, '*'),
'msg' => 'unknown_tfa_method'
);
return false;
break;
}
return false;
}
-function admin_api($action, $data = null) {
+function admin_api($access, $action, $data = null) {
global $pdo;
global $lang;
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
return false;
}
- switch ($action) {
- case "edit":
- $regen_key = $data['admin_api_regen_key'];
- $active = (isset($data['active'])) ? 1 : 0;
- $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
- $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
- foreach ($allow_from as $key => $val) {
- if (empty($val)) {
- continue;
- }
- if (!filter_var($val, FILTER_VALIDATE_IP)) {
- $_SESSION['return'][] = array(
- 'type' => 'warning',
- 'log' => array(__FUNCTION__, $data),
- 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
- );
- unset($allow_from[$key]);
- continue;
- }
- }
- $allow_from = implode(',', array_unique(array_filter($allow_from)));
- if (empty($allow_from) && $skip_ip_check == 0) {
- $_SESSION['return'][] = array(
- 'type' => 'danger',
- 'log' => array(__FUNCTION__, $data),
- 'msg' => 'ip_list_empty'
- );
- return false;
- }
- $api_key = implode('-', array(
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3)))
- ));
- $stmt = $pdo->query("SELECT `api_key` FROM `api`");
- $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
- if (empty($num_results)) {
- $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`)
- VALUES (:api_key, :skip_ip_check, :active, :allow_from);");
- $stmt->execute(array(
- ':api_key' => $api_key,
- ':skip_ip_check' => $skip_ip_check,
- ':active' => $active,
- ':allow_from' => $allow_from
- ));
- }
- else {
- if ($skip_ip_check == 0) {
- $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active, `allow_from` = :allow_from ;");
+ switch ($access) {
+ case "rw":
+ switch ($action) {
+ case "edit":
+ $active = (isset($data['active'])) ? 1 : 0;
+ $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
+ $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+ foreach ($allow_from as $key => $val) {
+ if (empty($val)) {
+ continue;
+ }
+ if (!filter_var($val, FILTER_VALIDATE_IP)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'warning',
+ 'log' => array(__FUNCTION__, $data),
+ 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
+ );
+ unset($allow_from[$key]);
+ continue;
+ }
+ }
+ $allow_from = implode(',', array_unique(array_filter($allow_from)));
+ if (empty($allow_from) && $skip_ip_check == 0) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $data),
+ 'msg' => 'ip_list_empty'
+ );
+ return false;
+ }
+ $api_key = implode('-', array(
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3)))
+ ));
+ $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = 'rw'");
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ if (empty($num_results)) {
+ $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
+ VALUES (:api_key, :skip_ip_check, :active, :allow_from, 'rw');");
+ $stmt->execute(array(
+ ':api_key' => $api_key,
+ ':skip_ip_check' => $skip_ip_check,
+ ':active' => $active,
+ ':allow_from' => $allow_from
+ ));
+ }
+ else {
+ if ($skip_ip_check == 0) {
+ $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active, `allow_from` = :allow_from WHERE `access` = 'rw';");
+ $stmt->execute(array(
+ ':active' => $active,
+ ':skip_ip_check' => $skip_ip_check,
+ ':allow_from' => $allow_from
+ ));
+ }
+ else {
+ $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active WHERE `access` = 'rw';");
+ $stmt->execute(array(
+ ':active' => $active,
+ ':skip_ip_check' => $skip_ip_check
+ ));
+ }
+ }
+ break;
+ case "regen_key":
+ $api_key = implode('-', array(
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3)))
+ ));
+ $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = 'rw'");
$stmt->execute(array(
- ':active' => $active,
- ':skip_ip_check' => $skip_ip_check,
- ':allow_from' => $allow_from
+ ':api_key' => $api_key
));
- }
- else {
- $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active ;");
+ break;
+ case "get":
+ $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = 'rw'");
+ $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
+ return $apidata;
+ break;
+ }
+ case "ro":
+ switch ($action) {
+ case "edit":
+ $active = (isset($data['active'])) ? 1 : 0;
+ $skip_ip_check = (isset($data['skip_ip_check'])) ? 1 : 0;
+ $allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $data['allow_from']));
+ foreach ($allow_from as $key => $val) {
+ if (empty($val)) {
+ continue;
+ }
+ if (!filter_var($val, FILTER_VALIDATE_IP)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'warning',
+ 'log' => array(__FUNCTION__, $data),
+ 'msg' => array('ip_invalid', htmlspecialchars($allow_from[$key]))
+ );
+ unset($allow_from[$key]);
+ continue;
+ }
+ }
+ $allow_from = implode(',', array_unique(array_filter($allow_from)));
+ if (empty($allow_from) && $skip_ip_check == 0) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $data),
+ 'msg' => 'ip_list_empty'
+ );
+ return false;
+ }
+ $api_key = implode('-', array(
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3)))
+ ));
+ $stmt = $pdo->query("SELECT `api_key` FROM `api` WHERE `access` = 'ro'");
+ $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
+ if (empty($num_results)) {
+ $stmt = $pdo->prepare("INSERT INTO `api` (`api_key`, `skip_ip_check`, `active`, `allow_from`, `access`)
+ VALUES (:api_key, :skip_ip_check, :active, :allow_from, 'ro');");
+ $stmt->execute(array(
+ ':api_key' => $api_key,
+ ':skip_ip_check' => $skip_ip_check,
+ ':active' => $active,
+ ':allow_from' => $allow_from
+ ));
+ }
+ else {
+ if ($skip_ip_check == 0) {
+ $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active, `allow_from` = :allow_from WHERE `access` = 'ro';");
+ $stmt->execute(array(
+ ':active' => $active,
+ ':skip_ip_check' => $skip_ip_check,
+ ':allow_from' => $allow_from
+ ));
+ }
+ else {
+ $stmt = $pdo->prepare("UPDATE `api` SET `skip_ip_check` = :skip_ip_check, `active` = :active WHERE `access` = 'ro';");
+ $stmt->execute(array(
+ ':active' => $active,
+ ':skip_ip_check' => $skip_ip_check
+ ));
+ }
+ }
+ break;
+ case "regen_key":
+ $api_key = implode('-', array(
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3))),
+ strtoupper(bin2hex(random_bytes(3)))
+ ));
+ $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key WHERE `access` = 'ro'");
$stmt->execute(array(
- ':active' => $active,
- ':skip_ip_check' => $skip_ip_check
+ ':api_key' => $api_key
));
- }
+ break;
+ case "get":
+ $stmt = $pdo->query("SELECT * FROM `api` WHERE `access` = 'ro'");
+ $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
+ return $apidata;
+ break;
}
break;
- case "regen_key":
- $api_key = implode('-', array(
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3))),
- strtoupper(bin2hex(random_bytes(3)))
- ));
- $stmt = $pdo->prepare("UPDATE `api` SET `api_key` = :api_key");
- $stmt->execute(array(
- ':api_key' => $api_key
- ));
- break;
- case "get":
- $stmt = $pdo->query("SELECT * FROM `api`");
- $apidata = $stmt->fetch(PDO::FETCH_ASSOC);
- return $apidata;
- break;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $data),
'msg' => 'admin_api_modified'
);
}
function license($action, $data = null) {
global $pdo;
global $redis;
global $lang;
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
return false;
}
switch ($action) {
case "verify":
// Keep result until revalidate button is pressed or session expired
$stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'");
$versions = $stmt->fetch(PDO::FETCH_ASSOC);
$post = array('guid' => $versions['version']);
$curl = curl_init('https://verify.mailcow.email');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
$response = curl_exec($curl);
curl_close($curl);
$json_return = json_decode($response, true);
if ($response && $json_return) {
if ($json_return['response'] === "ok") {
$_SESSION['gal']['valid'] = "true";
$_SESSION['gal']['c'] = $json_return['c'];
$_SESSION['gal']['s'] = $json_return['s'];
if ($json_return['m'] == 'NoMoore') {
$_SESSION['gal']['m'] = '🐄';
}
else {
$_SESSION['gal']['m'] = str_repeat('🐄', substr_count($json_return['m'], 'o'));
}
}
elseif ($json_return['response'] === "invalid") {
$_SESSION['gal']['valid'] = "false";
$_SESSION['gal']['c'] = $lang['mailbox']['no'];
$_SESSION['gal']['s'] = $lang['mailbox']['no'];
$_SESSION['gal']['m'] = $lang['mailbox']['no'];
}
}
else {
$_SESSION['gal']['valid'] = "false";
$_SESSION['gal']['c'] = $lang['danger']['temp_error'];
$_SESSION['gal']['s'] = $lang['danger']['temp_error'];
$_SESSION['gal']['m'] = $lang['danger']['temp_error'];
}
try {
// json_encode needs "true"/"false" instead of true/false, to not encode it to 0 or 1
$redis->Set('LICENSE_STATUS_CACHE', json_encode($_SESSION['gal']));
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
return $_SESSION['gal']['valid'];
break;
case "guid":
$stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'GUID'");
$versions = $stmt->fetch(PDO::FETCH_ASSOC);
return $versions['version'];
break;
}
}
function rspamd_ui($action, $data = null) {
global $lang;
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'access_denied'
);
return false;
}
switch ($action) {
case "edit":
$rspamd_ui_pass = $data['rspamd_ui_pass'];
$rspamd_ui_pass2 = $data['rspamd_ui_pass2'];
if (empty($rspamd_ui_pass) || empty($rspamd_ui_pass2)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'password_empty'
);
return false;
}
if ($rspamd_ui_pass != $rspamd_ui_pass2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'password_mismatch'
);
return false;
}
if (strlen($rspamd_ui_pass) < 6) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'rspamd_ui_pw_length'
);
return false;
}
$docker_return = docker('post', 'rspamd-mailcow', 'exec', array('cmd' => 'rspamd', 'task' => 'worker_password', 'raw' => $rspamd_ui_pass), array('Content-Type: application/json'));
if ($docker_return_array = json_decode($docker_return, true)) {
if ($docker_return_array['type'] == 'success') {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'rspamd_ui_pw_set'
);
return true;
}
else {
$_SESSION['return'][] = array(
'type' => $docker_return_array['type'],
'log' => array(__FUNCTION__, '*', '*'),
'msg' => $docker_return_array['msg']
);
return false;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, '*', '*'),
'msg' => 'unknown'
);
return false;
}
break;
}
}
function get_u2f_registrations($username) {
global $pdo;
$sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
$sel->execute(array($username));
return $sel->fetchAll(PDO::FETCH_OBJ);
}
function get_logs($application, $lines = false) {
if ($lines === false) {
$lines = $GLOBALS['LOG_LINES'] - 1;
}
elseif(is_numeric($lines) && $lines >= 1) {
$lines = abs(intval($lines) - 1);
}
else {
list ($from, $to) = explode('-', $lines);
$from = intval($from);
$to = intval($to);
if ($from < 1 || $to < $from) { return false; }
}
global $lang;
global $redis;
global $pdo;
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
// SQL
if ($application == "mailcow-ui") {
if (isset($from) && isset($to)) {
$stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :from, :to");
$stmt->execute(array(
':from' => $from - 1,
':to' => $to
));
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
else {
$stmt = $pdo->prepare("SELECT * FROM `logs` ORDER BY `id` DESC LIMIT :lines");
$stmt->execute(array(
':lines' => $lines + 1,
));
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
}
if (is_array($data)) {
return $data;
}
}
// Redis
if ($application == "dovecot-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('DOVECOT_MAILLOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "postfix-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('POSTFIX_MAILLOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "sogo-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('SOGO_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('SOGO_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "watchdog-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('WATCHDOG_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('WATCHDOG_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "acme-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('ACME_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('ACME_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "ratelimited") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('RL_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('RL_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "api-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('API_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('API_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "netfilter-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('NETFILTER_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('NETFILTER_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "autodiscover-mailcow") {
if (isset($from) && isset($to)) {
$data = $redis->lRange('AUTODISCOVER_LOG', $from - 1, $to - 1);
}
else {
$data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines);
}
if ($data) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($application == "rspamd-history") {
$curl = curl_init();
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
if (!is_numeric($lines)) {
list ($from, $to) = explode('-', $lines);
curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?from=" . intval($from) . "&to=" . intval($to));
}
else {
curl_setopt($curl, CURLOPT_URL,"http://rspamd/history?to=" . intval($lines));
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$history = curl_exec($curl);
if (!curl_errno($curl)) {
$data_array = json_decode($history, true);
curl_close($curl);
return $data_array['rows'];
}
curl_close($curl);
return false;
}
return false;
}
function getGUID() {
if (function_exists('com_create_guid')) {
return com_create_guid();
}
mt_srand((double)microtime()*10000);//optional for php 4.2.0 and up.
$charid = strtoupper(md5(uniqid(rand(), true)));
$hyphen = chr(45);// "-"
return substr($charid, 0, 8).$hyphen
.substr($charid, 8, 4).$hyphen
.substr($charid,12, 4).$hyphen
.substr($charid,16, 4).$hyphen
.substr($charid,20,12);
}
function solr_status() {
$curl = curl_init();
$endpoint = 'http://solr:8983/solr/admin/cores';
$params = array(
'action' => 'STATUS',
'core' => 'dovecot-fts',
'indexInfo' => 'true'
);
$url = $endpoint . '?' . http_build_query($params);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
$response_core = curl_exec($curl);
if ($response_core === false) {
$err = curl_error($curl);
curl_close($curl);
return false;
}
else {
curl_close($curl);
$curl = curl_init();
$status_core = json_decode($response_core, true);
$url = 'http://solr:8983/solr/admin/info/system';
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
$response_sysinfo = curl_exec($curl);
if ($response_sysinfo === false) {
$err = curl_error($curl);
curl_close($curl);
return false;
}
else {
curl_close($curl);
$status_sysinfo = json_decode($response_sysinfo, true);
$status = array_merge($status_core, $status_sysinfo);
return (!empty($status['status']['dovecot-fts']) && !empty($status['jvm']['memory'])) ? $status : false;
}
return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false;
}
return false;
}
function cleanupJS($ignore = '', $folder = '/tmp/*.js') {
$now = time();
foreach (glob($folder) as $filename) {
if(strpos($filename, $ignore) !== false) {
continue;
}
if (is_file($filename)) {
if ($now - filemtime($filename) >= 60 * 60) {
unlink($filename);
}
}
}
}
function cleanupCSS($ignore = '', $folder = '/tmp/*.css') {
$now = time();
foreach (glob($folder) as $filename) {
if(strpos($filename, $ignore) !== false) {
continue;
}
if (is_file($filename)) {
if ($now - filemtime($filename) >= 60 * 60) {
unlink($filename);
}
}
}
}
?>
diff --git a/data/web/inc/functions.mailbox.inc.php b/data/web/inc/functions.mailbox.inc.php
index 8238db31..977c1969 100644
--- a/data/web/inc/functions.mailbox.inc.php
+++ b/data/web/inc/functions.mailbox.inc.php
@@ -1,4105 +1,4109 @@
<?php
function mailbox($_action, $_type, $_data = null, $_extra = null) {
global $pdo;
global $redis;
global $lang;
global $MAILBOX_DEFAULT_ATTRIBUTES;
$_data_log = $_data;
!isset($_data_log['password']) ?: $_data_log['password'] = '*';
!isset($_data_log['password2']) ?: $_data_log['password2'] = '*';
switch ($_action) {
case 'add':
switch ($_type) {
case 'time_limited_alias':
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
else {
$username = $_data['username'];
}
}
else {
$username = $_SESSION['mailcow_cc_username'];
}
if (!is_numeric($_data["validity"]) || $_data["validity"] > 672) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'validity_missing'
);
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $_SESSION['mailcow_cc_username']));
$domain = $stmt->fetch(PDO::FETCH_ASSOC)['domain'];
$validity = strtotime("+".$_data["validity"]." hour");
$letters = 'abcefghijklmnopqrstuvwxyz1234567890';
$random_name = substr(str_shuffle($letters), 0, 24);
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `goto`, `validity`) VALUES
(:address, :goto, :validity)");
$stmt->execute(array(
':address' => $random_name . '@' . $domain,
':goto' => $username,
':validity' => $validity
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', htmlspecialchars($_SESSION['mailcow_cc_username']))
);
break;
case 'global_filter':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$sieve = new Sieve\SieveParser();
$script_data = $_data['script_data'];
$script_data = str_replace("\r\n", "\n", $script_data); // windows -> unix
$script_data = str_replace("\r", "\n", $script_data); // remaining -> unix
$filter_type = $_data['filter_type'];
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sieve_error', $e->getMessage())
);
return false;
}
if ($filter_type == 'prefilter') {
try {
if (file_exists('/global_sieve/before')) {
$filter_handle = fopen('/global_sieve/before', 'w');
if (!$filter_handle) {
throw new Exception($lang['danger']['file_open_error']);
}
fwrite($filter_handle, $script_data);
fclose($filter_handle);
}
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage()))
);
return false;
}
}
elseif ($filter_type == 'postfilter') {
try {
if (file_exists('/global_sieve/after')) {
$filter_handle = fopen('/global_sieve/after', 'w');
if (!$filter_handle) {
throw new Exception($lang['danger']['file_open_error']);
}
fwrite($filter_handle, $script_data);
fclose($filter_handle);
}
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('global_filter_write_error', htmlspecialchars($e->getMessage()))
);
return false;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'invalid_filter_type'
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'global_filter_written'
);
return true;
case 'filter':
$sieve = new Sieve\SieveParser();
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
else {
$username = $_data['username'];
}
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'no_user_defined'
);
return false;
}
$active = intval($_data['active']);
$script_data = $_data['script_data'];
$script_desc = $_data['script_desc'];
$filter_type = $_data['filter_type'];
if (empty($script_data)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'script_empty'
);
return false;
}
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sieve_error', $e->getMessage())
);
return false;
}
if (empty($script_data) || empty($script_desc)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'value_missing'
);
return false;
}
if ($filter_type != 'postfilter' && $filter_type != 'prefilter') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'filter_type'
);
return false;
}
if (!empty($active)) {
$script_name = 'active';
$stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_name` = 'inactive' WHERE `username` = :username AND `filter_type` = :filter_type");
$stmt->execute(array(
':username' => $username,
':filter_type' => $filter_type
));
}
else {
$script_name = 'inactive';
}
$stmt = $pdo->prepare("INSERT INTO `sieve_filters` (`username`, `script_data`, `script_desc`, `script_name`, `filter_type`)
VALUES (:username, :script_data, :script_desc, :script_name, :filter_type)");
$stmt->execute(array(
':username' => $username,
':script_data' => $script_data,
':script_desc' => $script_desc,
':script_name' => $script_name,
':filter_type' => $filter_type
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
break;
case 'syncjob':
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (isset($_data['username']) && filter_var($_data['username'], FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data['username'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
else {
$username = $_data['username'];
}
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
$username = $_SESSION['mailcow_cc_username'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'no_user_defined'
);
return false;
}
$active = intval($_data['active']);
$subscribeall = intval($_data['subscribeall']);
$delete2duplicates = intval($_data['delete2duplicates']);
$delete1 = intval($_data['delete1']);
$delete2 = intval($_data['delete2']);
$timeout1 = intval($_data['timeout1']);
$timeout2 = intval($_data['timeout2']);
$skipcrossduplicates = intval($_data['skipcrossduplicates']);
$automap = intval($_data['automap']);
$port1 = $_data['port1'];
$host1 = strtolower($_data['host1']);
$password1 = $_data['password1'];
$exclude = $_data['exclude'];
$maxage = $_data['maxage'];
$maxbytespersecond = $_data['maxbytespersecond'];
$subfolder2 = $_data['subfolder2'];
$user1 = $_data['user1'];
$mins_interval = $_data['mins_interval'];
$enc1 = $_data['enc1'];
$custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']);
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$maxage = "0";
}
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout1 = "600";
}
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout2 = "600";
}
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
$maxbytespersecond = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 3600)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
// if (!is_valid_domain_name($host1)) {
// $_SESSION['return'][] = array(
// 'type' => 'danger',
// 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
// 'msg' => 'access_denied'
// );
// return false;
// }
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT '1' FROM `imapsync`
WHERE `user2` = :user2 AND `user1` = :user1 AND `host1` = :host1");
$stmt->execute(array(':user1' => $user1, ':user2' => $username, ':host1' => $host1));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('object_exists', htmlspecialchars($host1 . ' / ' . $user1))
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `imapsync` (`user2`, `exclude`, `delete1`, `delete2`, `timeout1`, `timeout2`, `automap`, `skipcrossduplicates`, `maxbytespersecond`, `subscribeall`, `maxage`, `subfolder2`, `host1`, `authmech1`, `user1`, `password1`, `mins_interval`, `port1`, `enc1`, `delete2duplicates`, `custom_params`, `active`)
VALUES (:user2, :exclude, :delete1, :delete2, :timeout1, :timeout2, :automap, :skipcrossduplicates, :maxbytespersecond, :subscribeall, :maxage, :subfolder2, :host1, :authmech1, :user1, :password1, :mins_interval, :port1, :enc1, :delete2duplicates, :custom_params, :active)");
$stmt->execute(array(
':user2' => $username,
':custom_params' => $custom_params,
':exclude' => $exclude,
':maxage' => $maxage,
':delete1' => $delete1,
':delete2' => $delete2,
':timeout1' => $timeout1,
':timeout2' => $timeout2,
':automap' => $automap,
':skipcrossduplicates' => $skipcrossduplicates,
':maxbytespersecond' => $maxbytespersecond,
':subscribeall' => $subscribeall,
':subfolder2' => $subfolder2,
':host1' => $host1,
':authmech1' => 'PLAIN',
':user1' => $user1,
':password1' => $password1,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':active' => $active,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
break;
case 'domain':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$description = $_data['description'];
if (empty($description)) {
$description = $domain;
}
$aliases = $_data['aliases'];
$mailboxes = $_data['mailboxes'];
$defquota = $_data['defquota'];
$maxquota = $_data['maxquota'];
$restart_sogo = $_data['restart_sogo'];
$quota = $_data['quota'];
if ($defquota > $maxquota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota'
);
return false;
}
if ($maxquota > $quota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_quota_exceeds_domain_quota'
);
return false;
}
if ($defquota == "0" || empty($defquota)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'defquota_empty'
);
return false;
}
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'maxquota_empty'
);
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']);
if ($relay_all_recipients == 1) {
$backupmx = '1';
}
if ($relay_unknown_only == 1) {
$backupmx = 1;
$relay_all_recipients = 1;
}
if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
return false;
}
foreach (array($quota, $maxquota, $mailboxes, $aliases) as $data) {
if (!is_numeric($data)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('object_is_not_numeric', htmlspecialchars($data))
);
return false;
}
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain`
WHERE `alias_domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = $num_results + count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_exists', htmlspecialchars($domain))
);
return false;
}
if ($domain == getenv('MAILCOW_HOSTNAME')) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_cannot_match_hostname'
);
return false;
}
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain");
$stmt->execute(array(
':domain' => '%@' . $domain
));
$stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`)
VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)");
$stmt->execute(array(
':domain' => $domain,
':description' => $description,
':aliases' => $aliases,
':mailboxes' => $mailboxes,
':defquota' => $defquota,
':maxquota' => $maxquota,
':quota' => $quota,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':relay_unknown_only' => $relay_unknown_only,
':relay_all_recipients' => $relay_all_recipients
));
try {
$redis->hSet('DOMAIN_MAP', $domain, 1);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
return false;
}
if (!empty(intval($_data['rl_value']))) {
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $domain));
}
if (!empty($restart_sogo)) {
$restart_reponse = json_decode(docker('post', 'sogo-mailcow', 'restart'), true);
if ($restart_reponse['type'] == "success") {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_added', htmlspecialchars($domain))
);
return true;
}
else {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_added_sogo_failed'
);
return false;
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_added', htmlspecialchars($domain))
);
return true;
break;
case 'alias':
$addresses = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['address']));
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['goto']));
$active = intval($_data['active']);
$sogo_visible = intval($_data['sogo_visible']);
$goto_null = intval($_data['goto_null']);
$goto_spam = intval($_data['goto_spam']);
$goto_ham = intval($_data['goto_ham']);
$private_comment = $_data['private_comment'];
$public_comment = $_data['public_comment'];
if (strlen($private_comment) > 160 | strlen($public_comment) > 160){
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'comment_too_long'
);
return false;
}
if (empty($addresses[0])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'alias_empty'
);
return false;
}
if (empty($gotos[0]) && ($goto_null + $goto_spam + $goto_ham == 0)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'goto_empty'
);
return false;
}
if ($goto_null == "1") {
$goto = "null@localhost";
}
elseif ($goto_spam == "1") {
$goto = "spam@localhost";
}
elseif ($goto_ham == "1") {
$goto = "ham@localhost";
}
else {
foreach ($gotos as $i => &$goto) {
if (empty($goto)) {
continue;
}
$goto_domain = idn_to_ascii(substr(strstr($goto, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$goto_local_part = strstr($goto, '@', true);
$goto = $goto_local_part.'@'.$goto_domain;
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `kind` REGEXP 'location|thing|group'
AND `username`= :goto");
$stmt->execute(array(':goto' => $goto));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('goto_invalid', htmlspecialchars($goto))
);
unset($gotos[$i]);
continue;
}
if (!filter_var($goto, FILTER_VALIDATE_EMAIL) === true) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('goto_invalid', htmlspecialchars($goto))
);
unset($gotos[$i]);
continue;
}
}
$gotos = array_filter($gotos);
if (empty($gotos)) { return false; }
$goto = implode(",", $gotos);
}
foreach ($addresses as $address) {
if (empty($address)) {
continue;
}
if (in_array($address, $gotos)) {
continue;
}
$domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
$local_part = strstr($address, '@', true);
$address = $local_part.'@'.$domain;
$domaindata = mailbox('get', 'domain_details', $domain);
if (is_array($domaindata) && $domaindata['aliases_left'] == "0") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'max_alias_exceeded'
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `address`= :address OR `address` IN (
SELECT `username` FROM `mailbox`, `alias_domain`
WHERE (
`alias_domain`.`alias_domain` = :address_d
AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))");
$stmt->execute(array(
':address' => $address,
':address_l' => $local_part,
':address_d' => $domain
));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_alias_or_mailbox', htmlspecialchars($address))
);
continue;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
$stmt->execute(array(':domain1' => $domain, ':domain2' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_found', htmlspecialchars($domain))
);
continue;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_spam_alias', htmlspecialchars($address))
);
continue;
}
if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_invalid', $address)
);
continue;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `sogo_visible`, `active`)
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :sogo_visible, :active)");
if (!filter_var($address, FILTER_VALIDATE_EMAIL) === true) {
$stmt->execute(array(
':address' => '@'.$domain,
':public_comment' => $public_comment,
':private_comment' => $private_comment,
':address' => '@'.$domain,
':goto' => $goto,
':domain' => $domain,
':sogo_visible' => $sogo_visible,
':active' => $active
));
}
else {
$stmt->execute(array(
':address' => $address,
':public_comment' => $public_comment,
':private_comment' => $private_comment,
':goto' => $goto,
':domain' => $domain,
':sogo_visible' => $sogo_visible,
':active' => $active
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_added', $address)
);
}
break;
case 'alias_domain':
$active = intval($_data['active']);
$alias_domains = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['alias_domain']));
$alias_domains = array_filter($alias_domains);
$target_domain = idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46);
if (!isset($_SESSION['acl']['alias_domains']) || $_SESSION['acl']['alias_domains'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_valid_domain_name($target_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'target_domain_invalid'
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($alias_domains as $i => $alias_domain) {
$alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46);
if (!is_valid_domain_name($alias_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_domain_invalid', htmlspecialchars(alias_domain))
);
continue;
}
if ($alias_domain == $target_domain) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('aliasd_targetd_identical', htmlspecialchars($target_domain))
);
continue;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :target_domain");
$stmt->execute(array(':target_domain' => $target_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('targetd_not_found', htmlspecialchars($target_domain))
);
continue;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :target_domain AND `backupmx` = '1'");
$stmt->execute(array(':target_domain' => $target_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 1) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('targetd_relay_domain', htmlspecialchars($target_domain))
);
continue;
}
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `alias_domain`= :alias_domain
UNION
SELECT `domain` FROM `domain` WHERE `domain`= :alias_domain_in_domain");
$stmt->execute(array(':alias_domain' => $alias_domain, ':alias_domain_in_domain' => $alias_domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_domain_invalid', $alias_domain)
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `send_as` LIKE :domain");
$stmt->execute(array(
':domain' => '%@' . $domain
));
$stmt = $pdo->prepare("INSERT INTO `alias_domain` (`alias_domain`, `target_domain`, `active`)
VALUES (:alias_domain, :target_domain, :active)");
$stmt->execute(array(
':alias_domain' => $alias_domain,
':target_domain' => $target_domain,
':active' => $active
));
try {
$redis->hSet('DOMAIN_MAP', $alias_domain, 1);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
return false;
}
if (!empty(intval($_data['rl_value']))) {
ratelimit('edit', 'domain', array('rl_value' => $_data['rl_value'], 'rl_frame' => $_data['rl_frame'], 'object' => $alias_domain));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('aliasd_added', htmlspecialchars($alias_domain))
);
}
break;
case 'mailbox':
$local_part = strtolower(trim($_data['local_part']));
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$username = $local_part . '@' . $domain;
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_invalid'
);
return false;
}
if (empty($_data['local_part'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_invalid'
);
return false;
}
$password = $_data['password'];
$password2 = $_data['password2'];
$name = ltrim(rtrim($_data['name'], '>'), '<');
$quota_m = intval($_data['quota']);
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && $quota_m === 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'unlimited_quota_acl'
);
return false;
}
if (empty($name)) {
$name = $local_part;
}
$active = intval($_data['active']);
$quota_b = ($quota_m * 1048576);
$mailbox_attrs = json_encode(
array(
'force_pw_update' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'])),
'tls_enforce_in' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'])),
'tls_enforce_out' => strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'])),
'sogo_access' => (!isset($_SESSION['acl']['sogo_access']) || $_SESSION['acl']['sogo_access'] != "1") ? 0 : strval(intval($MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'])),
'mailbox_format' => strval($MAILBOX_DEFAULT_ATTRIBUTES['mailbox_format']),
'quarantine_notification' => strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'])
)
);
if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT `mailboxes`, `maxquota`, `quota` FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT
COUNT(*) as count,
COALESCE(ROUND(SUM(`quota`)/1048576), 0) as `quota`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `local_part` FROM `mailbox` WHERE `local_part` = :local_part and `domain`= :domain");
$stmt->execute(array(':local_part' => $local_part, ':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('object_exists', htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :username");
$stmt->execute(array(':username' => $username));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_alias', htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :username");
$stmt->execute(array(':username' => $username));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_spam_alias', htmlspecialchars($username))
);
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_found', htmlspecialchars($domain))
);
return false;
}
if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_complexity'
);
return false;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_mismatch'
);
return false;
}
$password_hashed = hash_password($password);
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_empty'
);
return false;
}
if ($MailboxData['count'] >= $DomainData['mailboxes']) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('max_mailbox_exceeded', $MailboxData['count'], $DomainData['mailboxes'])
);
return false;
}
if ($quota_m > $DomainData['maxquota']) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota'])
);
return false;
}
if (($MailboxData['quota'] + $quota_m) > $DomainData['quota']) {
$quota_left_m = ($DomainData['quota'] - $MailboxData['quota']);
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_quota_left_exceeded', $quota_left_m)
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `attributes`, `active`)
VALUES (:username, :password_hashed, :name, :quota_b, :local_part, :domain, :mailbox_attrs, :active)");
$stmt->execute(array(
':username' => $username,
':password_hashed' => $password_hashed,
':name' => $name,
':quota_b' => $quota_b,
':local_part' => $local_part,
':domain' => $domain,
':mailbox_attrs' => $mailbox_attrs,
':active' => $active
));
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `quota2replica` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
$stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`)
VALUES (:username1, :username2, :domain, :active)");
$stmt->execute(array(
':username1' => $username,
':username2' => $username,
':domain' => $domain,
':active' => $active
));
$stmt = $pdo->prepare("INSERT INTO `user_acl` (`username`) VALUES (:username)");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_added', htmlspecialchars($username))
);
break;
case 'resource':
$domain = idn_to_ascii(strtolower(trim($_data['domain'])), 0, INTL_IDNA_VARIANT_UTS46);
$description = $_data['description'];
$local_part = preg_replace('/[^\da-z]/i', '', preg_quote($description, '/'));
$name = $local_part . '@' . $domain;
$kind = $_data['kind'];
$multiple_bookings = intval($_data['multiple_bookings']);
$active = intval($_data['active']);
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'resource_invalid'
);
return false;
}
if (empty($description)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'description_invalid'
);
return false;
}
if (!isset($multiple_bookings) || $multiple_bookings < -1) {
$multiple_bookings = -1;
}
if ($kind != 'location' && $kind != 'group' && $kind != 'thing') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'resource_invalid'
);
return false;
}
if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
return false;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('object_exists', htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE address= :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_alias', htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias` WHERE `address`= :name");
$stmt->execute(array(':name' => $name));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_spam_alias', htmlspecialchars($name))
);
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_found', htmlspecialchars($domain))
);
return false;
}
$stmt = $pdo->prepare("INSERT INTO `mailbox` (`username`, `password`, `name`, `quota`, `local_part`, `domain`, `active`, `multiple_bookings`, `kind`)
VALUES (:name, 'RESOURCE', :description, 0, :local_part, :domain, :active, :multiple_bookings, :kind)");
$stmt->execute(array(
':name' => $name,
':description' => $description,
':local_part' => $local_part,
':domain' => $domain,
':active' => $active,
':kind' => $kind,
':multiple_bookings' => $multiple_bookings
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_added', htmlspecialchars($name))
);
break;
}
break;
case 'edit':
switch ($_type) {
case 'alias_domain':
$alias_domains = (array)$_data['alias_domain'];
foreach ($alias_domains as $alias_domain) {
$alias_domain = idn_to_ascii(strtolower(trim($alias_domain)), 0, INTL_IDNA_VARIANT_UTS46);
$is_now = mailbox('get', 'alias_domain_details', $alias_domain);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$target_domain = (!empty($_data['target_domain'])) ? idn_to_ascii(strtolower(trim($_data['target_domain'])), 0, INTL_IDNA_VARIANT_UTS46) : $is_now['target_domain'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_domain_invalid', htmlspecialchars($alias_domain))
);
continue;
}
if (!is_valid_domain_name($target_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('target_domain_invalid', htmlspecialchars($target_domain))
);
continue;
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $target_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (empty(mailbox('get', 'domain_details', $target_domain)) || !empty(mailbox('get', 'alias_domain_details', $target_domain))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('target_domain_invalid', htmlspecialchars($target_domain))
);
continue;
}
$stmt = $pdo->prepare("UPDATE `alias_domain` SET
`target_domain` = :target_domain,
`active` = :active
WHERE `alias_domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
':target_domain' => $target_domain,
':active' => $active
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('aliasd_modified', htmlspecialchars($alias_domain))
);
}
break;
case 'tls_policy':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['tls_policy']) || $_SESSION['acl']['tls_policy'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$is_now = mailbox('get', 'tls_policy', $username);
if (!empty($is_now)) {
$tls_enforce_in = (isset($_data['tls_enforce_in'])) ? intval($_data['tls_enforce_in']) : $is_now['tls_enforce_in'];
$tls_enforce_out = (isset($_data['tls_enforce_out'])) ? intval($_data['tls_enforce_out']) : $is_now['tls_enforce_out'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `mailbox`
SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_out),
`attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_in)
WHERE `username` = :username");
$stmt->execute(array(
':tls_out' => intval($tls_enforce_out),
':tls_in' => intval($tls_enforce_in),
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'quarantine_notification':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['quarantine_notification']) || $_SESSION['acl']['quarantine_notification'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$is_now = mailbox('get', 'quarantine_notification', $username);
if (!empty($is_now)) {
$quarantine_notification = (isset($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!in_array($quarantine_notification, array('never', 'hourly', 'daily', 'weekly'))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `mailbox`
SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', :quarantine_notification)
WHERE `username` = :username");
$stmt->execute(array(
':quarantine_notification' => $quarantine_notification,
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'spam_score':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['spam_score']) || $_SESSION['acl']['spam_score'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if ($_data['spam_score'] == "default") {
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
continue;
}
$lowspamlevel = explode(',', $_data['spam_score'])[0];
$highspamlevel = explode(',', $_data['spam_score'])[1];
if (!is_numeric($lowspamlevel) || !is_numeric($highspamlevel)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Invalid spam score, format must be "1,2" where first is low and second is high spam value.'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username
AND (`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`)
VALUES (:username, 'highspamlevel', :highspamlevel)");
$stmt->execute(array(
':username' => $username,
':highspamlevel' => $highspamlevel
));
$stmt = $pdo->prepare("INSERT INTO `filterconf` (`object`, `option`, `value`)
VALUES (:username, 'lowspamlevel', :lowspamlevel)");
$stmt->execute(array(
':username' => $username,
':lowspamlevel' => $lowspamlevel
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'time_limited_alias':
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
foreach ($addresses as $address) {
$stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address");
$stmt->execute(array(':address' => $address));
$goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (empty($_data['validity'])) {
continue;
}
$validity = round((int)time() + ($_data['validity'] * 3600));
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE
`address` = :address");
$stmt->execute(array(
':address' => $address,
':validity' => $validity
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', htmlspecialchars(implode(', ', $usernames)))
);
}
break;
case 'delimiter_action':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['delimiter_action']) || $_SESSION['acl']['delimiter_action'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL) || !hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subject") {
try {
$redis->hSet('RCPT_WANTS_SUBJECT_TAG', $username, 1);
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
}
else if (isset($_data['tagged_mail_handler']) && $_data['tagged_mail_handler'] == "subfolder") {
try {
$redis->hSet('RCPT_WANTS_SUBFOLDER_TAG', $username, 1);
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
}
else {
try {
$redis->hDel('RCPT_WANTS_SUBJECT_TAG', $username);
$redis->hDel('RCPT_WANTS_SUBFOLDER_TAG', $username);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'syncjob_details', $id, array('with_password'));
if (!empty($is_now)) {
$username = $is_now['user2'];
$user1 = (!empty($_data['user1'])) ? $_data['user1'] : $is_now['user1'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$last_run = (isset($_data['last_run'])) ? NULL : $is_now['last_run'];
$delete2duplicates = (isset($_data['delete2duplicates'])) ? intval($_data['delete2duplicates']) : $is_now['delete2duplicates'];
$subscribeall = (isset($_data['subscribeall'])) ? intval($_data['subscribeall']) : $is_now['subscribeall'];
$delete1 = (isset($_data['delete1'])) ? intval($_data['delete1']) : $is_now['delete1'];
$delete2 = (isset($_data['delete2'])) ? intval($_data['delete2']) : $is_now['delete2'];
$automap = (isset($_data['automap'])) ? intval($_data['automap']) : $is_now['automap'];
$skipcrossduplicates = (isset($_data['skipcrossduplicates'])) ? intval($_data['skipcrossduplicates']) : $is_now['skipcrossduplicates'];
$port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
$password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
$host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
$subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
$enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
$mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
$exclude = (isset($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude'];
$custom_params = (isset($_data['custom_params'])) ? $_data['custom_params'] : $is_now['custom_params'];
$maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
$maxbytespersecond = (isset($_data['maxbytespersecond']) && $_data['maxbytespersecond'] != "") ? intval($_data['maxbytespersecond']) : $is_now['maxbytespersecond'];
$timeout1 = (isset($_data['timeout1']) && $_data['timeout1'] != "") ? intval($_data['timeout1']) : $is_now['timeout1'];
$timeout2 = (isset($_data['timeout2']) && $_data['timeout2'] != "") ? intval($_data['timeout2']) : $is_now['timeout2'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (empty($subfolder2)) {
$subfolder2 = "";
}
if (!isset($maxage) || !filter_var($maxage, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$maxage = "0";
}
if (!isset($timeout1) || !filter_var($timeout1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout1 = "600";
}
if (!isset($timeout2) || !filter_var($timeout2, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 32000)))) {
$timeout2 = "600";
}
if (!isset($maxbytespersecond) || !filter_var($maxbytespersecond, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 125000000)))) {
$maxbytespersecond = "0";
}
if (!filter_var($port1, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 65535)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!filter_var($mins_interval, FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 3600)))) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!is_valid_domain_name($host1)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if ($enc1 != "TLS" && $enc1 != "SSL" && $enc1 != "PLAIN") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (@preg_match("/" . $exclude . "/", null) === false) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `imapsync` SET `delete1` = :delete1,
`delete2` = :delete2,
`automap` = :automap,
`skipcrossduplicates` = :skipcrossduplicates,
`maxage` = :maxage,
`maxbytespersecond` = :maxbytespersecond,
`subfolder2` = :subfolder2,
`exclude` = :exclude,
`host1` = :host1,
`last_run` = :last_run,
`user1` = :user1,
`password1` = :password1,
`mins_interval` = :mins_interval,
`port1` = :port1,
`enc1` = :enc1,
`delete2duplicates` = :delete2duplicates,
`custom_params` = :custom_params,
`timeout1` = :timeout1,
`timeout2` = :timeout2,
`subscribeall` = :subscribeall,
`active` = :active
WHERE `id` = :id");
$stmt->execute(array(
':delete1' => $delete1,
':delete2' => $delete2,
':automap' => $automap,
':skipcrossduplicates' => $skipcrossduplicates,
':id' => $id,
':exclude' => $exclude,
':maxage' => $maxage,
':maxbytespersecond' => $maxbytespersecond,
':subfolder2' => $subfolder2,
':host1' => $host1,
':user1' => $user1,
':password1' => $password1,
':last_run' => $last_run,
':mins_interval' => $mins_interval,
':port1' => $port1,
':enc1' => $enc1,
':delete2duplicates' => $delete2duplicates,
':custom_params' => $custom_params,
':timeout1' => $timeout1,
':timeout2' => $timeout2,
':subscribeall' => $subscribeall,
':active' => $active,
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'filter':
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$sieve = new Sieve\SieveParser();
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'filter_details', $id);
if (!empty($is_now)) {
$username = $is_now['username'];
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$script_desc = (!empty($_data['script_desc'])) ? $_data['script_desc'] : $is_now['script_desc'];
$script_data = (!empty($_data['script_data'])) ? $_data['script_data'] : $is_now['script_data'];
$filter_type = (!empty($_data['filter_type'])) ? $_data['filter_type'] : $is_now['filter_type'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
try {
$sieve->parse($script_data);
}
catch (Exception $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sieve_error', $e->getMessage())
);
continue;
}
if ($filter_type != 'postfilter' && $filter_type != 'prefilter') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'filter_type'
);
continue;
}
if ($active == '1') {
$script_name = 'active';
$stmt = $pdo->prepare("UPDATE `sieve_filters`
SET `script_name` = 'inactive'
WHERE `username` = :username
AND `filter_type` = :filter_type");
$stmt->execute(array(
':username' => $username,
':filter_type' => $filter_type
));
}
else {
$script_name = 'inactive';
}
$stmt = $pdo->prepare("UPDATE `sieve_filters` SET `script_desc` = :script_desc, `script_data` = :script_data, `script_name` = :script_name, `filter_type` = :filter_type
WHERE `id` = :id");
$stmt->execute(array(
':script_desc' => $script_desc,
':script_data' => $script_data,
':script_name' => $script_name,
':filter_type' => $filter_type,
':id' => $id
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'alias':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
foreach ($ids as $id) {
$is_now = mailbox('get', 'alias_details', $id);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$sogo_visible = (isset($_data['sogo_visible'])) ? intval($_data['sogo_visible']) : $is_now['sogo_visible_int'];
$goto_null = (isset($_data['goto_null'])) ? intval($_data['goto_null']) : 0;
$goto_spam = (isset($_data['goto_spam'])) ? intval($_data['goto_spam']) : 0;
$goto_ham = (isset($_data['goto_ham'])) ? intval($_data['goto_ham']) : 0;
$public_comment = (isset($_data['public_comment'])) ? $_data['public_comment'] : $is_now['public_comment'];
$private_comment = (isset($_data['private_comment'])) ? $_data['private_comment'] : $is_now['private_comment'];
$goto = (!empty($_data['goto'])) ? $_data['goto'] : $is_now['goto'];
$address = (!empty($_data['address'])) ? $_data['address'] : $is_now['address'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_invalid', $address)
);
continue;
}
$domain = idn_to_ascii(substr(strstr($address, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if ($is_now['address'] != $address) {
$local_part = strstr($address, '@', true);
$address = $local_part.'@'.$domain;
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_invalid', $address)
);
continue;
}
if (strtolower($is_now['address']) != strtolower($address)) {
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `address`= :address OR `address` IN (
SELECT `username` FROM `mailbox`, `alias_domain`
WHERE (
`alias_domain`.`alias_domain` = :address_d
AND `mailbox`.`username` = CONCAT(:address_l, '@', alias_domain.target_domain)))");
$stmt->execute(array(
':address' => $address,
':address_l' => $local_part,
':address_d' => $domain
));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_alias_or_mailbox', htmlspecialchars($address))
);
continue;
}
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
$stmt->execute(array(':domain1' => $domain, ':domain2' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_found', htmlspecialchars($domain))
);
continue;
}
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias`
WHERE `address`= :address");
$stmt->execute(array(':address' => $address));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_spam_alias', htmlspecialchars($address))
);
continue;
}
}
if ($goto_null == "1") {
$goto = "null@localhost";
}
elseif ($goto_spam == "1") {
$goto = "spam@localhost";
}
elseif ($goto_ham == "1") {
$goto = "ham@localhost";
}
else {
$gotos = array_map('trim', preg_split( "/( |,|;|\n)/", $goto));
foreach ($gotos as $i => &$goto) {
if (empty($goto)) {
continue;
}
if (!filter_var($goto, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('goto_invalid', $goto)
);
unset($gotos[$i]);
continue;
}
if ($goto == $address) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'alias_goto_identical'
);
unset($gotos[$i]);
continue;
}
// Delete from sender_acl to prevent duplicates
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE
`logged_in_as` = :goto AND
`send_as` = :address");
$stmt->execute(array(
':goto' => $goto,
':address' => $address
));
}
$gotos = array_filter($gotos);
$goto = implode(",", $gotos);
}
if (!empty($goto)) {
$stmt = $pdo->prepare("UPDATE `alias` SET
`address` = :address,
`public_comment` = :public_comment,
`private_comment` = :private_comment,
`domain` = :domain,
`goto` = :goto,
`sogo_visible`= :sogo_visible,
`active`= :active
WHERE `id` = :id");
$stmt->execute(array(
':address' => $address,
':public_comment' => $public_comment,
':private_comment' => $private_comment,
':domain' => $domain,
':goto' => $goto,
':sogo_visible' => $sogo_visible,
':active' => $active,
':id' => $id
));
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_modified', htmlspecialchars($address))
);
}
break;
case 'domain':
if (!is_array($_data['domain'])) {
$domains = array();
$domains[] = $_data['domain'];
}
else {
$domains = $_data['domain'];
}
foreach ($domains as $domain) {
$domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
continue;
}
if ($_SESSION['mailcow_cc_role'] == "domainadmin" &&
hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$is_now = mailbox('get', 'domain_details', $domain);
if (!empty($is_now)) {
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal_int'];
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `domain` SET
`description` = :description,
`gal` = :gal
WHERE `domain` = :domain");
$stmt->execute(array(
':description' => $description,
':gal' => $gal,
':domain' => $domain
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_modified', htmlspecialchars($domain))
);
}
elseif ($_SESSION['mailcow_cc_role'] == "admin") {
$is_now = mailbox('get', 'domain_details', $domain);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx_int'];
$gal = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal_int'];
$relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients_int'];
$relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only_int'];
$relayhost = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost'];
$aliases = (!empty($_data['aliases'])) ? $_data['aliases'] : $is_now['max_num_aliases_for_domain'];
$mailboxes = (isset($_data['mailboxes']) && $_data['mailboxes'] != '') ? intval($_data['mailboxes']) : $is_now['max_num_mboxes_for_domain'];
$defquota = (isset($_data['defquota']) && $_data['defquota'] != '') ? intval($_data['defquota']) : ($is_now['def_quota_for_mbox'] / 1048576);
$maxquota = (!empty($_data['maxquota'])) ? $_data['maxquota'] : ($is_now['max_quota_for_mbox'] / 1048576);
$quota = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['max_quota_for_domain'] / 1048576);
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
if ($relay_all_recipients == '1') {
$backupmx = '1';
}
if ($relay_unknown_only == '1') {
$backupmx = '1';
$relay_all_recipients = '1';
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
continue;
}
// todo: should be using api here
$stmt = $pdo->prepare("SELECT
COUNT(*) AS count,
MAX(COALESCE(ROUND(`quota`/1048576), 0)) AS `biggest_mailbox`,
COALESCE(ROUND(SUM(`quota`)/1048576), 0) AS `quota_all`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND domain = :domain");
$stmt->execute(array(':domain' => $domain));
$MailboxData = $stmt->fetch(PDO::FETCH_ASSOC);
// todo: should be using api here
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count` FROM `alias`
WHERE domain = :domain
AND address NOT IN (
SELECT `username` FROM `mailbox`
)");
$stmt->execute(array(':domain' => $domain));
$AliasData = $stmt->fetch(PDO::FETCH_ASSOC);
if ($defquota > $maxquota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_defquota_exceeds_mailbox_maxquota'
);
continue;
}
if ($defquota == "0" || empty($defquota)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'defquota_empty'
);
continue;
}
if ($maxquota > $quota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'mailbox_quota_exceeds_domain_quota'
);
continue;
}
if ($maxquota == "0" || empty($maxquota)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'maxquota_empty'
);
continue;
}
if ($MailboxData['biggest_mailbox'] > $maxquota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('max_quota_in_use', $MailboxData['biggest_mailbox'])
);
continue;
}
if ($MailboxData['quota_all'] > $quota) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_quota_m_in_use', $MailboxData['quota_all'])
);
continue;
}
if ($MailboxData['count'] > $mailboxes) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailboxes_in_use', $MailboxData['count'])
);
continue;
}
if ($AliasData['count'] > $aliases) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('aliases_in_use', $AliasData['count'])
);
continue;
}
$stmt = $pdo->prepare("UPDATE `domain` SET
`relay_all_recipients` = :relay_all_recipients,
`relay_unknown_only` = :relay_unknown_only,
`backupmx` = :backupmx,
`gal` = :gal,
`active` = :active,
`quota` = :quota,
`defquota` = :defquota,
`maxquota` = :maxquota,
`relayhost` = :relayhost,
`mailboxes` = :mailboxes,
`aliases` = :aliases,
`description` = :description
WHERE `domain` = :domain");
$stmt->execute(array(
':relay_all_recipients' => $relay_all_recipients,
':relay_unknown_only' => $relay_unknown_only,
':backupmx' => $backupmx,
':gal' => $gal,
':active' => $active,
':quota' => $quota,
':defquota' => $defquota,
':maxquota' => $maxquota,
':relayhost' => $relayhost,
':mailboxes' => $mailboxes,
':aliases' => $aliases,
':description' => $description,
':domain' => $domain
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_modified', htmlspecialchars($domain))
);
}
}
break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('username_invalid', $username)
);
continue;
}
$is_now = mailbox('get', 'mailbox_details', $username);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access']) && isset($_SESSION['acl']['sogo_access']) && $_SESSION['acl']['sogo_access'] == "1") ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
(int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
$domain = $is_now['domain'];
$quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
// if already 0 == ok
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'unlimited_quota_acl'
);
return false;
}
$stmt = $pdo->prepare("SELECT `quota`, `maxquota`
FROM `domain`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if ($quota_m > $DomainData['maxquota']) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_quota_exceeded', $DomainData['maxquota'])
);
continue;
}
if (((($is_now['quota_used'] / 1048576) - $quota_m) + $quota_m) > $DomainData['quota']) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_quota_left_exceeded', ($is_now['max_new_quota'] / 1048576))
);
continue;
}
$extra_acls = array();
if (isset($_data['extended_sender_acl'])) {
if (!isset($_SESSION['acl']['extend_sender_acl']) || $_SESSION['acl']['extend_sender_acl'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
$extra_acls = array_map('trim', preg_split( "/( |,|;|\n)/", $_data['extended_sender_acl']));
foreach ($extra_acls as $i => &$extra_acl) {
if (empty($extra_acl)) {
continue;
}
if (substr($extra_acl, 0, 1) === "@") {
$extra_acl = ltrim($extra_acl, '@');
}
if (!filter_var($extra_acl, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name($extra_acl)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid', htmlspecialchars($extra_acl))
);
unset($extra_acls[$i]);
continue;
}
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (filter_var($extra_acl, FILTER_VALIDATE_EMAIL)) {
$extra_acl_domain = idn_to_ascii(substr(strstr($extra_acl, '@'), 1), 0, INTL_IDNA_VARIANT_UTS46);
if (in_array($extra_acl_domain, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
}
else {
if (in_array($extra_acl, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('extra_acl_invalid_domain', $extra_acl_domain)
);
unset($extra_acls[$i]);
continue;
}
$extra_acl = '@' . $extra_acl;
}
}
$extra_acls = array_filter($extra_acls);
$extra_acls = array_values($extra_acls);
$extra_acls = array_unique($extra_acls);
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 1 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
foreach ($extra_acls as $sender_acl_external) {
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`, `external`)
VALUES (:sender_acl, :username, 1)");
$stmt->execute(array(
':sender_acl' => $sender_acl_external,
':username' => $username
));
}
}
if (isset($_data['sender_acl'])) {
// Get sender_acl items set by admin
$sender_acl_admin = array_merge(
mailbox('get', 'sender_acl_handles', $username)['sender_acl_domains']['ro'],
mailbox('get', 'sender_acl_handles', $username)['sender_acl_addresses']['ro']
);
// Get sender_acl items from POST array
// Set sender_acl_domain_admin to empty array if sender_acl contains "default" to trigger a reset
// Delete records from sender_acl if sender_acl contains "*" and set to array("*")
$_data['sender_acl'] = (array)$_data['sender_acl'];
if (in_array("*", $_data['sender_acl'])) {
$sender_acl_domain_admin = array('*');
}
elseif (array("default") === $_data['sender_acl']) {
$sender_acl_domain_admin = array();
}
else {
if (array_search('default', $_data['sender_acl']) !== false){
unset($_data['sender_acl'][array_search('default', $_data['sender_acl'])]);
}
$sender_acl_domain_admin = $_data['sender_acl'];
}
if (!empty($sender_acl_domain_admin) || !empty($sender_acl_admin)) {
// Check items in POST array and skip invalid
foreach ($sender_acl_domain_admin as $key => $val) {
// Check for invalid domain or email format or not *
if (!filter_var($val, FILTER_VALIDATE_EMAIL) && !is_valid_domain_name(ltrim($val, '@')) && $val != '*') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key])
);
unset($sender_acl_domain_admin[$key]);
continue;
}
// Check if user has domain access (if object is domain)
$domain = ltrim($sender_acl_domain_admin[$key], '@');
if (is_valid_domain_name($domain)) {
// Check for- and skip non-mailcow domains
$domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
if (!empty($domains)) {
if (!in_array($domain, $domains)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key])
);
unset($sender_acl_domain_admin[$key]);
continue;
}
}
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key])
);
unset($sender_acl_domain_admin[$key]);
continue;
}
}
// Wildcard can only be used if role == admin
if ($val == '*' && $_SESSION['mailcow_cc_role'] != 'admin') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key])
);
unset($sender_acl_domain_admin[$key]);
continue;
}
// Check if user has alias access (if object is email)
if (filter_var($val, FILTER_VALIDATE_EMAIL)) {
if (!hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $val)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sender_acl_invalid', $sender_acl_domain_admin[$key])
);
unset($sender_acl_domain_admin[$key]);
continue;
}
}
}
// Merge both arrays
$sender_acl_merged = array_merge($sender_acl_domain_admin, $sender_acl_admin);
// If merged array still contains "*", set it as only value
!in_array('*', $sender_acl_merged) ?: $sender_acl_merged = array('*');
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
$fixed_sender_aliases = mailbox('get', 'sender_acl_handles', $username)['fixed_sender_aliases'];
foreach ($sender_acl_merged as $sender_acl) {
$domain = ltrim($sender_acl, '@');
if (is_valid_domain_name($domain)) {
$sender_acl = '@' . $domain;
}
// Don't add if allowed by alias
if (in_array($sender_acl, $fixed_sender_aliases)) {
continue;
}
$stmt = $pdo->prepare("INSERT INTO `sender_acl` (`send_as`, `logged_in_as`)
VALUES (:sender_acl, :username)");
$stmt->execute(array(
':sender_acl' => $sender_acl,
':username' => $username
));
}
}
else {
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `external` = 0 AND `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
}
}
if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_complexity'
);
continue;
}
if ($password != $password2) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'password_mismatch'
);
continue;
}
$password_hashed = hash_password($password);
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`password` = :password_hashed
WHERE `username` = :username");
$stmt->execute(array(
':password_hashed' => $password_hashed,
':username' => $username
));
}
$stmt = $pdo->prepare("UPDATE `alias` SET
`active` = :active
WHERE `address` = :address");
$stmt->execute(array(
':address' => $username,
':active' => $active
));
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :name,
`quota` = :quota_b,
`attributes` = JSON_SET(`attributes`, '$.force_pw_update', :force_pw_update),
`attributes` = JSON_SET(`attributes`, '$.sogo_access', :sogo_access)
WHERE `username` = :username");
$stmt->execute(array(
':active' => $active,
':name' => $name,
':quota_b' => $quota_b,
':force_pw_update' => $force_pw_update,
':sogo_access' => $sogo_access,
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', $username)
);
}
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
$names[] = $_data['name'];
}
else {
$names = $_data['name'];
}
foreach ($names as $name) {
$is_now = mailbox('get', 'resource_details', $name);
if (!empty($is_now)) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
$multiple_bookings = (isset($_data['multiple_bookings'])) ? intval($_data['multiple_bookings']) : $is_now['multiple_bookings'];
$description = (!empty($_data['description'])) ? $_data['description'] : $is_now['description'];
$kind = (!empty($_data['kind'])) ? $_data['kind'] : $is_now['kind'];
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_invalid', htmlspecialchars($name))
);
continue;
}
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_invalid', htmlspecialchars($name))
);
continue;
}
if (!isset($multiple_bookings) || $multiple_bookings < -1) {
$multiple_bookings = -1;
}
if (empty($description)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('description_invalid', htmlspecialchars($name))
);
continue;
}
if ($kind != 'location' && $kind != 'group' && $kind != 'thing') {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_invalid', htmlspecialchars($name))
);
continue;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("UPDATE `mailbox` SET
`active` = :active,
`name`= :description,
`kind`= :kind,
`multiple_bookings`= :multiple_bookings
WHERE `username` = :name");
$stmt->execute(array(
':active' => $active,
':description' => $description,
':multiple_bookings' => $multiple_bookings,
':kind' => $kind,
':name' => $name
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_modified', htmlspecialchars($name))
);
}
break;
}
break;
case 'get':
switch ($_type) {
case 'sender_acl_handles':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$data['sender_acl_domains']['ro'] = array();
$data['sender_acl_domains']['rw'] = array();
$data['sender_acl_domains']['selectable'] = array();
$data['sender_acl_addresses']['ro'] = array();
$data['sender_acl_addresses']['rw'] = array();
$data['sender_acl_addresses']['selectable'] = array();
$data['fixed_sender_aliases'] = array();
$data['external_sender_aliases'] = array();
// Fixed addresses
$stmt = $pdo->prepare("SELECT `address` FROM `alias` WHERE `goto` REGEXP :goto AND `address` NOT LIKE '@%'");
$stmt->execute(array(':goto' => '(^|,)'.$_data.'($|,)'));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$data['fixed_sender_aliases'][] = $row['address'];
}
$stmt = $pdo->prepare("SELECT CONCAT(`local_part`, '@', `alias_domain`.`alias_domain`) AS `alias_domain_alias` FROM `mailbox`, `alias_domain`
WHERE `alias_domain`.`target_domain` = `mailbox`.`domain`
AND `mailbox`.`username` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
if (!empty($row['alias_domain_alias'])) {
$data['fixed_sender_aliases'][] = $row['alias_domain_alias'];
}
}
// External addresses
$stmt = $pdo->prepare("SELECT `send_as` as `send_as_external` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '1'");
$stmt->execute(array(':logged_in_as' => $_data));
$exernal_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($exernal_rows)) {
if (!empty($row['send_as_external'])) {
$data['external_sender_aliases'][] = $row['send_as_external'];
}
}
// Return array $data['sender_acl_domains/addresses']['ro'] with read-only objects
// Return array $data['sender_acl_domains/addresses']['rw'] with read-write objects (can be deleted)
$stmt = $pdo->prepare("SELECT REPLACE(`send_as`, '@', '') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` LIKE '@%' OR `send_as` = '*')");
$stmt->execute(array(':logged_in_as' => $_data));
$domain_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($domain_row = array_shift($domain_rows)) {
if (is_valid_domain_name($domain_row['send_as']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) {
$data['sender_acl_domains']['ro'][] = $domain_row['send_as'];
continue;
}
if (is_valid_domain_name($domain_row['send_as']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain_row['send_as'])) {
$data['sender_acl_domains']['rw'][] = $domain_row['send_as'];
continue;
}
if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] != 'admin') {
$data['sender_acl_domains']['ro'][] = $domain_row['send_as'];
}
if ($domain_row['send_as'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') {
$data['sender_acl_domains']['rw'][] = $domain_row['send_as'];
}
}
$stmt = $pdo->prepare("SELECT `send_as` FROM `sender_acl` WHERE `logged_in_as` = :logged_in_as AND `external` = '0' AND (`send_as` NOT LIKE '@%' AND `send_as` != '*')");
$stmt->execute(array(':logged_in_as' => $_data));
$address_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($address_row = array_shift($address_rows)) {
if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && !hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) {
$data['sender_acl_addresses']['ro'][] = $address_row['send_as'];
continue;
}
if (filter_var($address_row['send_as'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $address_row['send_as'])) {
$data['sender_acl_addresses']['rw'][] = $address_row['send_as'];
continue;
}
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain` NOT IN (
SELECT REPLACE(`send_as`, '@', '') FROM `sender_acl`
WHERE `logged_in_as` = :logged_in_as1
AND `external` = '0'
AND `send_as` LIKE '@%')
UNION
SELECT '*' FROM `domain`
WHERE '*' NOT IN (
SELECT `send_as` FROM `sender_acl`
WHERE `logged_in_as` = :logged_in_as2
AND `external` = '0'
)");
$stmt->execute(array(
':logged_in_as1' => $_data,
':logged_in_as2' => $_data
));
$rows_domain = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row_domain = array_shift($rows_domain)) {
if (is_valid_domain_name($row_domain['domain']) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row_domain['domain'])) {
$data['sender_acl_domains']['selectable'][] = $row_domain['domain'];
continue;
}
if ($row_domain['domain'] == '*' && $_SESSION['mailcow_cc_role'] == 'admin') {
$data['sender_acl_domains']['selectable'][] = $row_domain['domain'];
continue;
}
}
$stmt = $pdo->prepare("SELECT `address` FROM `alias`
WHERE `goto` != :goto
AND `address` NOT IN (
SELECT `send_as` FROM `sender_acl`
WHERE `logged_in_as` = :logged_in_as
AND `external` = '0'
AND `send_as` NOT LIKE '@%')");
$stmt->execute(array(
':logged_in_as' => $_data,
':goto' => $_data
));
$rows_mbox = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows_mbox)) {
// Aliases are not selectable
if (in_array($row['address'], $data['fixed_sender_aliases'])) {
continue;
}
if (filter_var($row['address'], FILTER_VALIDATE_EMAIL) && hasAliasObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $row['address'])) {
$data['sender_acl_addresses']['selectable'][] = $row['address'];
}
}
return $data;
break;
case 'mailboxes':
$mailboxes = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$mailboxes[] = $row['username'];
}
}
else {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND (`domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role)");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$mailboxes[] = $row['username'];
}
}
return $mailboxes;
break;
case 'tls_policy':
$attrs = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $_data));
$attrs = $stmt->fetch(PDO::FETCH_ASSOC);
$attrs = json_decode($attrs['attributes'], true);
return array(
'tls_enforce_in' => $attrs['tls_enforce_in'],
'tls_enforce_out' => $attrs['tls_enforce_out']
);
break;
case 'quarantine_notification':
$attrs = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `attributes` FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(':username' => $_data));
$attrs = $stmt->fetch(PDO::FETCH_ASSOC);
$attrs = json_decode($attrs['attributes'], true);
return $attrs['quarantine_notification'];
break;
case 'filters':
$filters = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `id` FROM `sieve_filters` WHERE `username` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$filters[] = $row['id'];
}
return $filters;
break;
case 'global_filter_details':
$global_filters = array();
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$global_filters['prefilter'] = file_get_contents('/global_sieve/before');
$global_filters['postfilter'] = file_get_contents('/global_sieve/after');
return $global_filters;
break;
case 'filter_details':
$filter_details = array();
if (!is_numeric($_data)) {
return false;
}
$stmt = $pdo->prepare("SELECT CASE `script_name` WHEN 'active' THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
CASE `script_name` WHEN 'active' THEN 1 ELSE 0 END AS `active_int`,
id,
username,
filter_type,
script_data,
script_desc
FROM `sieve_filters`
WHERE `id` = :id");
$stmt->execute(array(':id' => $_data));
$filter_details = $stmt->fetch(PDO::FETCH_ASSOC);
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $filter_details['username'])) {
return false;
}
return $filter_details;
break;
case 'active_user_sieve':
$filter_details = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$exec_fields = array(
'cmd' => 'sieve',
'task' => 'list',
'username' => $_data
);
$filters = docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
$filters = array_filter(preg_split("/(\r\n|\n|\r)/",$filters));
foreach ($filters as $filter) {
if (preg_match('/.+ ACTIVE/i', $filter)) {
$exec_fields = array(
'cmd' => 'sieve',
'task' => 'print',
'script_name' => substr($filter, 0, -7),
'username' => $_data
);
$script = docker('post', 'dovecot-mailcow', 'exec', $exec_fields);
// Remove first line
return preg_replace('/^.+\n/', '', $script);
}
}
return false;
break;
case 'syncjob_details':
$syncjobdetails = array();
if (!is_numeric($_data)) {
return false;
}
if (isset($_extra) && in_array('no_log', $_extra)) {
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("returned_text", "password1")');
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
while($field = array_shift($fields)) {
$shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
elseif (isset($_extra) && in_array('with_password', $_extra)) {
$stmt = $pdo->prepare("SELECT *,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
else {
$field_query = $pdo->query('SHOW FIELDS FROM `imapsync` WHERE FIELD NOT IN ("password1")');
$fields = $field_query->fetchAll(PDO::FETCH_ASSOC);
while($field = array_shift($fields)) {
$shown_fields[] = $field['Field'];
}
$stmt = $pdo->prepare("SELECT " . implode(',', $shown_fields) . ",
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `imapsync` WHERE id = :id");
}
$stmt->execute(array(':id' => $_data));
$syncjobdetails = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($syncjobdetails['returned_text'])) {
$syncjobdetails['log'] = $syncjobdetails['returned_text'];
}
else {
$syncjobdetails['log'] = '';
}
unset($syncjobdetails['returned_text']);
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $syncjobdetails['user2'])) {
return false;
}
return $syncjobdetails;
break;
case 'syncjobs':
$syncjobdata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `id` FROM `imapsync` WHERE `user2` = :username");
$stmt->execute(array(':username' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$syncjobdata[] = $row['id'];
}
return $syncjobdata;
break;
case 'spam_score':
$curl = curl_init();
curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
curl_setopt($curl, CURLOPT_URL,"http://rspamd/actions");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$default_actions = curl_exec($curl);
if (!curl_errno($curl)) {
$data_array = json_decode($default_actions, true);
curl_close($curl);
foreach ($data_array as $data) {
if ($data['action'] == 'reject') {
$reject = $data['value'];
continue;
}
elseif ($data['action'] == 'add header') {
$add_header = $data['value'];
continue;
}
}
if (empty($add_header) || empty($reject)) {
// Assume default, set warning
$default = "5, 15";
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not determine servers default spam score, assuming default'
);
}
else {
$default = $add_header . ', ' . $reject;
}
}
else {
// Assume default, set warning
$default = "5, 15";
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not determine servers default spam score, assuming default'
);
}
curl_close($curl);
$policydata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `object` = :username AND
(`option` = 'lowspamlevel' OR `option` = 'highspamlevel')");
$stmt->execute(array(':username' => $_data));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if (empty($num_results)) {
return $default;
}
else {
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'highspamlevel' AND `object` = :username");
$stmt->execute(array(':username' => $_data));
$highspamlevel = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` WHERE `option` = 'lowspamlevel' AND `object` = :username");
$stmt->execute(array(':username' => $_data));
$lowspamlevel = $stmt->fetch(PDO::FETCH_ASSOC);
return $lowspamlevel['value'].', '.$highspamlevel['value'];
}
break;
case 'time_limited_aliases':
$tladata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
$stmt = $pdo->prepare("SELECT `address`,
`goto`,
`validity`
FROM `spamalias`
WHERE `goto` = :username
AND `validity` >= :unixnow");
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $tladata;
break;
case 'delimiter_action':
$policydata = array();
if (isset($_data) && filter_var($_data, FILTER_VALIDATE_EMAIL)) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
}
else {
$_data = $_SESSION['mailcow_cc_username'];
}
try {
if ($redis->hGet('RCPT_WANTS_SUBJECT_TAG', $_data)) {
return "subject";
}
elseif ($redis->hGet('RCPT_WANTS_SUBFOLDER_TAG', $_data)) {
return "subfolder";
}
else {
return "none";
}
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
return false;
}
break;
case 'resources':
$resources = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$resources[] = $row['username'];
}
}
else {
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$resources[] = $row['username'];
}
}
return $resources;
break;
case 'alias_domains':
$aliasdomains = array();
if (isset($_data) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
elseif (isset($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliasdomains[] = $row['alias_domain'];
}
}
else {
$stmt = $pdo->prepare("SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` IN (SELECT `domain` FROM `domain_admins` WHERE `active` = '1' AND `username` = :username) OR 'admin' = :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliasdomains[] = $row['alias_domain'];
}
}
return $aliasdomains;
break;
case 'aliases':
$aliases = array();
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
$stmt = $pdo->prepare("SELECT `id` FROM `alias` WHERE `address` != `goto` AND `domain` = :domain");
$stmt->execute(array(
':domain' => $_data,
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$aliases[] = $row['id'];
}
return $aliases;
break;
case 'alias_details':
$aliasdata = array();
$stmt = $pdo->prepare("SELECT
`id`,
`domain`,
`goto`,
`address`,
`public_comment`,
`private_comment`,
`active` as `active_int`,
`sogo_visible` as `sogo_visible_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
CASE `sogo_visible` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `sogo_visible`,
`created`,
`modified`
FROM `alias`
WHERE `id` = :id AND `address` != `goto`");
$stmt->execute(array(
':id' => intval($_data),
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
$stmt->execute(array(
':domain' => $row['domain'],
));
$row_alias_domain = $stmt->fetch(PDO::FETCH_ASSOC);
if (isset($row_alias_domain['target_domain']) && !empty($row_alias_domain['target_domain'])) {
$aliasdata['in_primary_domain'] = $row_alias_domain['target_domain'];
}
else {
$aliasdata['in_primary_domain'] = "";
}
$aliasdata['id'] = $row['id'];
$aliasdata['domain'] = $row['domain'];
$aliasdata['public_comment'] = $row['public_comment'];
$aliasdata['private_comment'] = $row['private_comment'];
$aliasdata['domain'] = $row['domain'];
$aliasdata['goto'] = $row['goto'];
$aliasdata['address'] = $row['address'];
(!filter_var($aliasdata['address'], FILTER_VALIDATE_EMAIL)) ? $aliasdata['is_catch_all'] = 1 : $aliasdata['is_catch_all'] = 0;
$aliasdata['active'] = $row['active'];
$aliasdata['sogo_visible'] = $row['sogo_visible'];
$aliasdata['active_int'] = $row['active_int'];
$aliasdata['sogo_visible_int'] = $row['sogo_visible_int'];
$aliasdata['created'] = $row['created'];
$aliasdata['modified'] = $row['modified'];
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdata['domain'])) {
return false;
}
return $aliasdata;
break;
case 'alias_domain_details':
$aliasdomaindata = array();
$rl = ratelimit('get', 'domain', $_data);
$stmt = $pdo->prepare("SELECT
`alias_domain`,
`target_domain`,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`created`,
`modified`
FROM `alias_domain`
WHERE `alias_domain` = :aliasdomain");
$stmt->execute(array(
':aliasdomain' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `backupmx` FROM `domain` WHERE `domain` = :target_domain");
$stmt->execute(array(
':target_domain' => $row['target_domain']
));
$row_parent = $stmt->fetch(PDO::FETCH_ASSOC);
$aliasdomaindata['alias_domain'] = $row['alias_domain'];
$aliasdomaindata['parent_is_backupmx'] = $row_parent['backupmx'];
$aliasdomaindata['target_domain'] = $row['target_domain'];
$aliasdomaindata['active'] = $row['active'];
$aliasdomaindata['rl'] = $rl;
$aliasdomaindata['active_int'] = $row['active_int'];
$aliasdomaindata['created'] = $row['created'];
$aliasdomaindata['modified'] = $row['modified'];
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $aliasdomaindata['target_domain'])) {
return false;
}
return $aliasdomaindata;
break;
case 'domains':
$domains = array();
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
$stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE (`domain` IN (
SELECT `domain` from `domain_admins`
WHERE (`active`='1' AND `username` = :username))
)
OR 'admin'= :role");
$stmt->execute(array(
':username' => $_SESSION['mailcow_cc_username'],
':role' => $_SESSION['mailcow_cc_role'],
));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$domains[] = $row['domain'];
}
return $domains;
break;
case 'domain_details':
$domaindata = array();
$_data = idn_to_ascii(strtolower(trim($_data)), 0, INTL_IDNA_VARIANT_UTS46);
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
$stmt->execute(array(
':domain' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($row)) {
$_data = $row['target_domain'];
}
$stmt = $pdo->prepare("SELECT
`domain`,
`description`,
`aliases`,
`mailboxes`,
`defquota`,
`maxquota`,
`quota`,
`relayhost`,
`relay_all_recipients` as `relay_all_recipients_int`,
`relay_unknown_only` as `relay_unknown_only_int`,
`backupmx` as `backupmx_int`,
`gal` as `gal_int`,
`active` as `active_int`,
CASE `relay_all_recipients` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_all_recipients`,
CASE `relay_unknown_only` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `relay_unknown_only`,
CASE `backupmx` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `backupmx`,
CASE `gal` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `gal`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
FROM `domain` WHERE `domain`= :domain");
$stmt->execute(array(
':domain' => $_data
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (empty($row)) {
return false;
}
$stmt = $pdo->prepare("SELECT COUNT(*) AS `count`,
COALESCE(SUM(`quota`), 0) AS `in_use`
FROM `mailbox`
WHERE `kind` NOT REGEXP 'location|thing|group'
AND `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain']));
$MailboxDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT SUM(bytes) AS `bytes_total`, SUM(messages) AS `msgs_total` FROM `quota2`
WHERE `username` IN (
SELECT `username` FROM `mailbox`
WHERE `domain` = :domain
);");
$stmt->execute(array(':domain' => $row['domain']));
$SumQuotaInUse = $stmt->fetch(PDO::FETCH_ASSOC);
$rl = ratelimit('get', 'domain', $_data);
$domaindata['max_new_mailbox_quota'] = ($row['quota'] * 1048576) - $MailboxDataDomain['in_use'];
if ($domaindata['max_new_mailbox_quota'] > ($row['maxquota'] * 1048576)) {
$domaindata['max_new_mailbox_quota'] = ($row['maxquota'] * 1048576);
}
$domaindata['def_new_mailbox_quota'] = $domaindata['max_new_mailbox_quota'];
if ($domaindata['def_new_mailbox_quota'] > ($row['defquota'] * 1048576)) {
$domaindata['def_new_mailbox_quota'] = ($row['defquota'] * 1048576);
}
$domaindata['quota_used_in_domain'] = $MailboxDataDomain['in_use'];
if (!empty($SumQuotaInUse['bytes_total'])) {
$domaindata['bytes_total'] = $SumQuotaInUse['bytes_total'];
}
else {
$domaindata['bytes_total'] = 0;
}
if (!empty($SumQuotaInUse['msgs_total'])) {
$domaindata['msgs_total'] = $SumQuotaInUse['msgs_total'];
}
else {
$domaindata['msgs_total'] = 0;
}
$domaindata['mboxes_in_domain'] = $MailboxDataDomain['count'];
$domaindata['mboxes_left'] = $row['mailboxes'] - $MailboxDataDomain['count'];
$domaindata['domain_name'] = $row['domain'];
$domaindata['description'] = $row['description'];
$domaindata['max_num_aliases_for_domain'] = $row['aliases'];
$domaindata['max_num_mboxes_for_domain'] = $row['mailboxes'];
$domaindata['def_quota_for_mbox'] = $row['defquota'] * 1048576;
$domaindata['max_quota_for_mbox'] = $row['maxquota'] * 1048576;
$domaindata['max_quota_for_domain'] = $row['quota'] * 1048576;
$domaindata['relayhost'] = $row['relayhost'];
$domaindata['backupmx'] = $row['backupmx'];
$domaindata['gal'] = $row['gal'];
$domaindata['backupmx_int'] = $row['backupmx_int'];
$domaindata['gal_int'] = $row['gal_int'];
$domaindata['rl'] = $rl;
$domaindata['active'] = $row['active'];
$domaindata['active_int'] = $row['active_int'];
$domaindata['relay_all_recipients'] = $row['relay_all_recipients'];
$domaindata['relay_unknown_only'] = $row['relay_unknown_only'];
$domaindata['relay_all_recipients_int'] = $row['relay_all_recipients_int'];
$domaindata['relay_unknown_only_int'] = $row['relay_unknown_only_int'];
$stmt = $pdo->prepare("SELECT COUNT(*) AS `alias_count` FROM `alias`
WHERE (`domain`= :domain OR `domain` IN (SELECT `alias_domain` FROM `alias_domain` WHERE `target_domain` = :domain2))
AND `address` NOT IN (
SELECT `username` FROM `mailbox`
)");
$stmt->execute(array(
':domain' => $_data,
':domain2' => $_data
));
$AliasDataDomain = $stmt->fetch(PDO::FETCH_ASSOC);
(isset($AliasDataDomain['alias_count'])) ? $domaindata['aliases_in_domain'] = $AliasDataDomain['alias_count'] : $domaindata['aliases_in_domain'] = "0";
$domaindata['aliases_left'] = $row['aliases'] - $AliasDataDomain['alias_count'];
if ($_SESSION['mailcow_cc_role'] == "admin")
{
$stmt = $pdo->prepare("SELECT GROUP_CONCAT(`username` SEPARATOR ', ') AS domain_admins FROM `domain_admins` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $_data
));
$domain_admins = $stmt->fetch(PDO::FETCH_ASSOC);
(isset($domain_admins['domain_admins'])) ? $domaindata['domain_admins'] = $domain_admins['domain_admins'] : $domaindata['domain_admins'] = "-";
}
return $domaindata;
break;
case 'mailbox_details':
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
$mailboxdata = array();
$rl = ratelimit('get', 'mailbox', $_data);
$last_mail_login = $redis->Get('last-login/' . $_data);
if ($last_mail_login === false) {
$last_mail_login = '';
}
if (preg_match('/y|yes/i', getenv('MASTER'))) {
$stmt = $pdo->prepare("SELECT
`domain`.`backupmx`,
`mailbox`.`username`,
`mailbox`.`name`,
`mailbox`.`active` AS `active_int`,
CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`mailbox`.`domain`,
`mailbox`.`local_part`,
`mailbox`.`quota`,
`quota2`.`bytes`,
`attributes`,
`quota2`.`messages`
FROM `mailbox`, `quota2`, `domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");
}
else {
$stmt = $pdo->prepare("SELECT
`domain`.`backupmx`,
`mailbox`.`username`,
`mailbox`.`name`,
`mailbox`.`active` AS `active_int`,
CASE `mailbox`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`mailbox`.`domain`,
`mailbox`.`local_part`,
`mailbox`.`quota`,
`quota2replica`.`bytes`,
`attributes`,
`quota2replica`.`messages`
FROM `mailbox`, `quota2replica`, `domain`
WHERE `mailbox`.`kind` NOT REGEXP 'location|thing|group' AND `mailbox`.`username` = `quota2replica`.`username` AND `domain`.`domain` = `mailbox`.`domain` AND `mailbox`.`username` = :mailbox");
}
$stmt->execute(array(
':mailbox' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT `maxquota`, `quota` FROM `domain` WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $row['domain']));
$DomainQuota = $stmt->fetch(PDO::FETCH_ASSOC);
+ $stmt = $pdo->prepare("SELECT IFNULL(COUNT(`active`), 0) AS `pushover_active` FROM `pushover` WHERE `username` = :username AND `active` = 1");
+ $stmt->execute(array(':username' => $_data));
+ $PushoverActive = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE `kind` NOT REGEXP 'location|thing|group' AND `domain` = :domain AND `username` != :username");
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
if ($mailboxdata['max_new_quota'] > ($DomainQuota['maxquota'] * 1048576)) {
$mailboxdata['max_new_quota'] = ($DomainQuota['maxquota'] * 1048576);
}
$mailboxdata['username'] = $row['username'];
if (!empty($rl)) {
$mailboxdata['rl'] = $rl;
$mailboxdata['rl_scope'] = 'mailbox';
}
else {
$mailboxdata['rl'] = ratelimit('get', 'domain', $row['domain']);
$mailboxdata['rl_scope'] = 'domain';
}
$mailboxdata['is_relayed'] = $row['backupmx'];
$mailboxdata['name'] = $row['name'];
$mailboxdata['last_mail_login'] = $last_mail_login;
$mailboxdata['active'] = $row['active'];
$mailboxdata['active_int'] = $row['active_int'];
$mailboxdata['domain'] = $row['domain'];
$mailboxdata['local_part'] = $row['local_part'];
$mailboxdata['quota'] = $row['quota'];
$mailboxdata['attributes'] = json_decode($row['attributes'], true);
$mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['messages'] = $row['messages'];
$mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];
+ $mailboxdata['pushover_active'] = ($PushoverActive['pushover_active'] == 1) ? $lang['mailbox']['yes'] : $lang['mailbox']['no'];
if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "info";
}
elseif ($mailboxdata['percent_in_use'] >= 90) {
$mailboxdata['percent_class'] = "danger";
}
elseif ($mailboxdata['percent_in_use'] >= 75) {
$mailboxdata['percent_class'] = "warning";
}
else {
$mailboxdata['percent_class'] = "success";
}
return $mailboxdata;
break;
case 'resource_details':
$resourcedata = array();
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
return false;
}
$stmt = $pdo->prepare("SELECT
`username`,
`name`,
`kind`,
`multiple_bookings`,
`local_part`,
`active` AS `active_int`,
CASE `active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`,
`domain`
FROM `mailbox` WHERE `kind` REGEXP 'location|thing|group' AND `username` = :resource");
$stmt->execute(array(
':resource' => $_data,
));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$resourcedata['name'] = $row['username'];
$resourcedata['kind'] = $row['kind'];
$resourcedata['multiple_bookings'] = $row['multiple_bookings'];
$resourcedata['description'] = $row['name'];
$resourcedata['active'] = $row['active'];
$resourcedata['active_int'] = $row['active_int'];
$resourcedata['domain'] = $row['domain'];
$resourcedata['local_part'] = $row['local_part'];
if (!isset($resourcedata['domain']) ||
(isset($resourcedata['domain']) && !hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $resourcedata['domain']))) {
return false;
}
return $resourcedata;
break;
}
break;
case 'delete':
switch ($_type) {
case 'syncjob':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['syncjobs']) || $_SESSION['acl']['syncjobs'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($ids as $id) {
if (!is_numeric($id)) {
return false;
}
$stmt = $pdo->prepare("SELECT `user2` FROM `imapsync` WHERE id = :id");
$stmt->execute(array(':id' => $id));
$user2 = $stmt->fetch(PDO::FETCH_ASSOC)['user2'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $user2)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('deleted_syncjob', $id)
);
}
break;
case 'filter':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
if (!isset($_SESSION['acl']['filters']) || $_SESSION['acl']['filters'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($ids as $id) {
if (!is_numeric($id)) {
continue;
}
$stmt = $pdo->prepare("SELECT `username` FROM `sieve_filters` WHERE id = :id");
$stmt->execute(array(':id' => $id));
$usr = $stmt->fetch(PDO::FETCH_ASSOC)['username'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $usr)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `sieve_filters` WHERE `id`= :id");
$stmt->execute(array(':id' => $id));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('delete_filter', $id)
);
}
break;
case 'time_limited_alias':
if (!is_array($_data['address'])) {
$addresses = array();
$addresses[] = $_data['address'];
}
else {
$addresses = $_data['address'];
}
if (!isset($_SESSION['acl']['spam_alias']) || $_SESSION['acl']['spam_alias'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($addresses as $address) {
$stmt = $pdo->prepare("SELECT `goto` FROM `spamalias` WHERE `address` = :address");
$stmt->execute(array(':address' => $address));
$goto = $stmt->fetch(PDO::FETCH_ASSOC)['goto'];
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $goto)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username AND `address` = :item");
$stmt->execute(array(
':username' => $goto,
':item' => $address
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_modified', htmlspecialchars($goto))
);
}
break;
case 'eas_cache':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['eas_reset']) || $_SESSION['acl']['eas_reset'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('eas_reset', htmlspecialchars($username))
);
}
break;
case 'sogo_profile':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
if (!isset($_SESSION['acl']['sogo_profile_reset']) || $_SESSION['acl']['sogo_profile_reset'] != "1" ) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($usernames as $username) {
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $username . "/%' OR `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
$stmt->execute(array(
':username' => $username
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('sogo_profile_reset', htmlspecialchars($username))
);
}
break;
case 'domain':
if (!is_array($_data['domain'])) {
$domains = array();
$domains[] = $_data['domain'];
}
else {
$domains = $_data['domain'];
}
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
return false;
}
foreach ($domains as $domain) {
if (!is_valid_domain_name($domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
continue;
}
$domain = idn_to_ascii(strtolower(trim($domain)), 0, INTL_IDNA_VARIANT_UTS46);
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $domain));
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0 || !empty($num_results)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_empty', $domain)
);
continue;
}
$exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $domain);
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
if ($maildir_gc['type'] != 'success') {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not move mail storage to garbage collector: ' . $maildir_gc['msg']
);
}
$stmt = $pdo->prepare("DELETE FROM `domain` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `target_domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `domain` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` LIKE :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` LIKE :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` LIKE :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `address` LIKE :domain");
$stmt->execute(array(
':domain' => '%@'.$domain,
));
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :domain");
$stmt->execute(array(
':domain' => $domain,
));
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
try {
$redis->hDel('DOMAIN_MAP', $domain);
$redis->hDel('RL_VALUE', $domain);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_removed', htmlspecialchars($domain))
);
}
break;
case 'alias':
if (!is_array($_data['id'])) {
$ids = array();
$ids[] = $_data['id'];
}
else {
$ids = $_data['id'];
}
foreach ($ids as $id) {
$alias_data = mailbox('get', 'alias_details', $id);
if (empty($alias_data)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `id` = :id");
$stmt->execute(array(
':id' => $id
));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `send_as` = :alias_address");
$stmt->execute(array(
':alias_address' => $alias_data['address']
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_removed', htmlspecialchars($alias_data['address']))
);
}
break;
case 'alias_domain':
if (!is_array($_data['alias_domain'])) {
$alias_domains = array();
$alias_domains[] = $_data['alias_domain'];
}
else {
$alias_domains = $_data['alias_domain'];
}
foreach ($alias_domains as $alias_domain) {
if (!is_valid_domain_name($alias_domain)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'domain_invalid'
);
continue;
}
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain`
WHERE `alias_domain`= :alias_domain");
$stmt->execute(array(':alias_domain' => $alias_domain));
$DomainData = $stmt->fetch(PDO::FETCH_ASSOC);
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $DomainData['target_domain'])) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `alias_domain` WHERE `alias_domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
));
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `domain` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
));
$stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :alias_domain");
$stmt->execute(array(
':alias_domain' => $alias_domain,
));
try {
$redis->hDel('DOMAIN_MAP', $alias_domain);
$redis->hDel('RL_VALUE', $domain);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('alias_domain_removed', htmlspecialchars($alias_domain))
);
}
break;
case 'mailbox':
if (!is_array($_data['username'])) {
$usernames = array();
$usernames[] = $_data['username'];
}
else {
$usernames = $_data['username'];
}
foreach ($usernames as $username) {
if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$mailbox_details = mailbox('get', 'mailbox_details', $username);
if (!empty($mailbox_details['domain']) && !empty($mailbox_details['local_part'])) {
$maildir = $mailbox_details['domain'] . '/' . $mailbox_details['local_part'];
$exec_fields = array('cmd' => 'maildir', 'task' => 'cleanup', 'maildir' => $maildir);
$maildir_gc = json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields), true);
if ($maildir_gc['type'] != 'success') {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not move maildir to garbage collector: ' . $maildir_gc['msg']
);
}
}
else {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not move maildir to garbage collector: variables local_part and/or domain empty'
);
}
if (strtolower(getenv('SKIP_SOLR')) == 'n') {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true');
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, '<delete><query>user:' . $username . '</query></delete>');
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'Could not remove Solr index: ' . print_r($err, true)
);
}
curl_close($curl);
}
$stmt = $pdo->prepare("DELETE FROM `alias` WHERE `goto` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `quarantine` WHERE `rcpt` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `quota2` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `quota2replica` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sender_acl` WHERE `logged_in_as` = :username");
$stmt->execute(array(
':username' => $username
));
// fk, better safe than sorry
$stmt = $pdo->prepare("DELETE FROM `user_acl` WHERE `username` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `spamalias` WHERE `goto` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `imapsync` WHERE `user2` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `filterconf` WHERE `object` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . str_replace('%', '\%', $username) . "/%' OR `c_uid` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `bcc_maps` WHERE `local_dest` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `oauth_access_tokens` WHERE `user_id` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `oauth_refresh_tokens` WHERE `user_id` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("DELETE FROM `oauth_authorization_codes` WHERE `user_id` = :username");
$stmt->execute(array(
':username' => $username
));
$stmt = $pdo->prepare("SELECT `address`, `goto` FROM `alias`
WHERE `goto` REGEXP :username");
$stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
$GotoData = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($GotoData as $gotos) {
$goto_exploded = explode(',', $gotos['goto']);
if (($key = array_search($username, $goto_exploded)) !== false) {
unset($goto_exploded[$key]);
}
$gotos_rebuild = implode(',', $goto_exploded);
$stmt = $pdo->prepare("UPDATE `alias` SET
`goto` = :goto
WHERE `address` = :address");
$stmt->execute(array(
':goto' => $gotos_rebuild,
':address' => $gotos['address']
));
}
try {
$redis->hDel('RL_VALUE', $username);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('redis_error', $e)
);
continue;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('mailbox_removed', htmlspecialchars($username))
);
}
break;
case 'resource':
if (!is_array($_data['name'])) {
$names = array();
$names[] = $_data['name'];
}
else {
$names = $_data['name'];
}
foreach ($names as $name) {
if (!filter_var($name, FILTER_VALIDATE_EMAIL)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $name)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied'
);
continue;
}
$stmt = $pdo->prepare("DELETE FROM `mailbox` WHERE `username` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_user_profile` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_cache_folder` WHERE `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_acl` WHERE `c_object` LIKE '%/" . $name . "/%' OR `c_uid` = :username");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_store` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_contact` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_quick_appointment` WHERE `c_folder_id` IN (SELECT `c_folder_id` FROM `sogo_folder_info` WHERE `c_path2` = :username)");
$stmt->execute(array(
':username' => $name
));
$stmt = $pdo->prepare("DELETE FROM `sogo_folder_info` WHERE `c_path2` = :username");
$stmt->execute(array(
':username' => $name
));
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('resource_removed', htmlspecialchars($name))
);
}
break;
}
break;
}
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) {
update_sogo_static_view();
}
}
diff --git a/data/web/inc/functions.pushover.inc.php b/data/web/inc/functions.pushover.inc.php
new file mode 100644
index 00000000..f0cca30a
--- /dev/null
+++ b/data/web/inc/functions.pushover.inc.php
@@ -0,0 +1,175 @@
+<?php
+function pushover($_action, $_data = null) {
+ global $pdo;
+ global $lang;
+ switch ($_action) {
+ case 'edit':
+ if (!isset($_SESSION['acl']['pushover']) || $_SESSION['acl']['pushover'] != "1" ) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['username'])) {
+ $usernames = array();
+ $usernames[] = $_data['username'];
+ }
+ else {
+ $usernames = $_data['username'];
+ }
+ foreach ($usernames as $username) {
+ if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ continue;
+ }
+ $delete = $_data['delete'];
+ if ($delete == "true") {
+ $stmt = $pdo->prepare("DELETE FROM `pushover` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username
+ ));
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_data, $_data),
+ 'msg' => 'pushover_settings_edited'
+ );
+ continue;
+ }
+ $key = $_data['key'];
+ $token = $_data['token'];
+ if (!ctype_alnum($key) || strlen($key) != 30) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data, $_data),
+ 'msg' => 'pushover_key'
+ );
+ continue;
+ }
+ if (!ctype_alnum($token) || strlen($token) != 30) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data, $_data),
+ 'msg' => 'pushover_token'
+ );
+ continue;
+ }
+ $title = $_data['title'];
+ $text = $_data['text'];
+ $active = intval($_data['active']);
+ $stmt = $pdo->prepare("REPLACE INTO `pushover` (`username`, `key`, `token`, `title`, `text`, `active`)
+ VALUES (:username, :key, :token, :title, :text, :active)");
+ $stmt->execute(array(
+ ':username' => $username,
+ ':key' => $key,
+ ':token' => $token,
+ ':title' => $title,
+ ':text' => $text,
+ ':active' => $active
+ ));
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_data, $_data),
+ 'msg' => 'pushover_settings_edited'
+ );
+ }
+ break;
+ case 'get':
+ if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ $stmt = $pdo->prepare("SELECT * FROM `pushover` WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $_data
+ ));
+ $data = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (empty($data)) {
+ return false;
+ }
+ else {
+ return $data;
+ }
+ break;
+ case 'test':
+ if (!isset($_SESSION['acl']['pushover']) || $_SESSION['acl']['pushover'] != "1" ) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ return false;
+ }
+ if (!is_array($_data['username'])) {
+ $usernames = array();
+ $usernames[] = $_data['username'];
+ }
+ else {
+ $usernames = $_data['username'];
+ }
+ foreach ($usernames as $username) {
+ if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $username)) {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'access_denied'
+ );
+ continue;
+ }
+ $stmt = $pdo->prepare("SELECT * FROM `pushover`
+ WHERE `username` = :username");
+ $stmt->execute(array(
+ ':username' => $username
+ ));
+ $api_data = $stmt->fetch(PDO::FETCH_ASSOC);
+ if (!empty($api_data)) {
+ $title = (!empty($api_data['title'])) ? $api_data['title'] : 'Mail';
+ $text = (!empty($api_data['text'])) ? $api_data['text'] : 'You\'ve got mail 📧';
+ curl_setopt_array($ch = curl_init(), array(
+ CURLOPT_URL => "https://api.pushover.net/1/users/validate.json",
+ CURLOPT_POSTFIELDS => array(
+ "token" => $api_data['token'],
+ "user" => $api_data['key']
+ ),
+ CURLOPT_SAFE_UPLOAD => true,
+ CURLOPT_RETURNTRANSFER => true,
+ ));
+ $result = curl_exec($ch);
+ $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ if ($httpcode == 200) {
+ $_SESSION['return'][] = array(
+ 'type' => 'success',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => sprintf('Pushover API OK (%d): %s', $httpcode, $result)
+ );
+ }
+ else {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => sprintf('Pushover API ERR (%d): %s', $httpcode, $result)
+ );
+ }
+ }
+ else {
+ $_SESSION['return'][] = array(
+ 'type' => 'danger',
+ 'log' => array(__FUNCTION__, $_action, $_data),
+ 'msg' => 'pushover_credentials_missing'
+ );
+ return false;
+ }
+ }
+ break;
+ }
+}
\ No newline at end of file
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index f122b220..fc50971f 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -1,1210 +1,1228 @@
<?php
function init_db_schema() {
try {
global $pdo;
- $db_version = "03042020_0915";
+ $db_version = "09042020_1403";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->query("SELECT `version` FROM `versions` WHERE `application` = 'db_schema'");
if ($stmt->fetch(PDO::FETCH_ASSOC)['version'] == $db_version) {
return true;
}
if (!preg_match('/y|yes/i', getenv('MASTER'))) {
$_SESSION['return'][] = array(
'type' => 'warning',
'log' => array(__FUNCTION__),
'msg' => 'Database not initialized: not running db_init on slave.'
);
return true;
}
}
$views = array(
"grouped_mail_aliases" => "CREATE VIEW grouped_mail_aliases (username, aliases) AS
SELECT goto, IFNULL(GROUP_CONCAT(address ORDER BY address SEPARATOR ' '), '') AS address FROM alias
WHERE address!=goto
AND active = '1'
AND sogo_visible = '1'
AND address NOT LIKE '@%'
GROUP BY goto;",
// START
// Unused at the moment - we cannot allow to show a foreign mailbox as sender address in SOGo, as SOGo does not like this
// We need to create delegation in SOGo AND set a sender_acl in mailcow to allow to send as user X
"grouped_sender_acl" => "CREATE VIEW grouped_sender_acl (username, send_as_acl) AS
SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl
WHERE send_as NOT LIKE '@%'
GROUP BY logged_in_as;",
// END
"grouped_sender_acl_external" => "CREATE VIEW grouped_sender_acl_external (username, send_as_acl) AS
SELECT logged_in_as, IFNULL(GROUP_CONCAT(send_as SEPARATOR ' '), '') AS send_as_acl FROM sender_acl
WHERE send_as NOT LIKE '@%' AND external = '1'
GROUP BY logged_in_as;",
"grouped_domain_alias_address" => "CREATE VIEW grouped_domain_alias_address (username, ad_alias) AS
SELECT username, IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ' '), '') AS ad_alias FROM mailbox
LEFT OUTER JOIN alias_domain ON target_domain=domain
GROUP BY username;",
"sieve_before" => "CREATE VIEW sieve_before (id, username, script_name, script_data) AS
SELECT md5(script_data), username, script_name, script_data FROM sieve_filters
WHERE filter_type = 'prefilter';",
"sieve_after" => "CREATE VIEW sieve_after (id, username, script_name, script_data) AS
SELECT md5(script_data), username, script_name, script_data FROM sieve_filters
WHERE filter_type = 'postfilter';"
);
$tables = array(
"versions" => array(
"cols" => array(
"application" => "VARCHAR(255) NOT NULL",
"version" => "VARCHAR(100) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
),
"keys" => array(
"primary" => array(
"" => array("application")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"admin" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"superadmin" => "TINYINT(1) NOT NULL DEFAULT '0'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE NOW(0)",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("username")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"_sogo_static_view" => array(
"cols" => array(
"c_uid" => "VARCHAR(255) NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"c_name" => "VARCHAR(255) NOT NULL",
"c_password" => "VARCHAR(255) NOT NULL DEFAULT ''",
"c_cn" => "VARCHAR(255)",
"mail" => "VARCHAR(255) NOT NULL",
// TODO -> use TEXT and check if SOGo login breaks on empty aliases
"aliases" => "TEXT NOT NULL",
"ad_aliases" => "VARCHAR(6144) NOT NULL DEFAULT ''",
"ext_acl" => "VARCHAR(6144) NOT NULL DEFAULT ''",
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
"multiple_bookings" => "INT NOT NULL DEFAULT -1"
),
"keys" => array(
"primary" => array(
"" => array("c_uid")
),
"key" => array(
"domain" => array("domain")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"relayhosts" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"hostname" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"hostname" => array("hostname")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"transports" => array(
"cols" => array(
"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'",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"destination" => array("destination"),
"nexthop" => array("nexthop"),
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"alias" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"address" => "VARCHAR(255) NOT NULL",
"goto" => "TEXT NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"private_comment" => "TEXT",
"public_comment" => "TEXT",
"sogo_visible" => "TINYINT(1) NOT NULL DEFAULT '1'",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"unique" => array(
"address" => array("address")
),
"key" => array(
"domain" => array("domain")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"api" => array(
"cols" => array(
"api_key" => "VARCHAR(255) NOT NULL",
"allow_from" => "VARCHAR(512) NOT NULL",
"skip_ip_check" => "TINYINT(1) NOT NULL DEFAULT '0'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE NOW(0)",
"access" => "ENUM('ro', 'rw') NOT NULL DEFAULT 'rw'",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("api_key")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sender_acl" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"logged_in_as" => "VARCHAR(255) NOT NULL",
"send_as" => "VARCHAR(255) NOT NULL",
"external" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"domain" => array(
// Todo: Move some attributes to json
"cols" => array(
"domain" => "VARCHAR(255) NOT NULL",
"description" => "VARCHAR(255)",
"aliases" => "INT(10) NOT NULL DEFAULT '0'",
"mailboxes" => "INT(10) NOT NULL DEFAULT '0'",
"defquota" => "BIGINT(20) NOT NULL DEFAULT '3072'",
"maxquota" => "BIGINT(20) NOT NULL DEFAULT '102400'",
"quota" => "BIGINT(20) NOT NULL DEFAULT '102400'",
"relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'",
"backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'",
"gal" => "TINYINT(1) NOT NULL DEFAULT '1'",
"relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'",
"relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("domain")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"tls_policy_override" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"dest" => "VARCHAR(255) NOT NULL",
"policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL",
"parameters" => "VARCHAR(255) DEFAULT ''",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"unique" => array(
"dest" => array("dest")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"quarantine" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"qid" => "VARCHAR(30) NOT NULL",
"subject" => "VARCHAR(500)",
"score" => "FLOAT(8,2)",
"ip" => "VARCHAR(50)",
"action" => "CHAR(20) NOT NULL DEFAULT 'unknown'",
"symbols" => "JSON",
"sender" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
"rcpt" => "VARCHAR(255)",
"msg" => "LONGTEXT",
"domain" => "VARCHAR(255)",
"notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"mailbox" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"name" => "VARCHAR(255)",
// mailbox_path_prefix is followed by domain/local_part/
"mailbox_path_prefix" => "VARCHAR(150) DEFAULT '/var/vmail/'",
"quota" => "BIGINT(20) NOT NULL DEFAULT '102400'",
"local_part" => "VARCHAR(255) NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"attributes" => "JSON",
"kind" => "VARCHAR(100) NOT NULL DEFAULT ''",
"multiple_bookings" => "INT NOT NULL DEFAULT -1",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("username")
),
"key" => array(
"domain" => array("domain")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sieve_filters" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"username" => "VARCHAR(255) NOT NULL",
"script_desc" => "VARCHAR(255) NOT NULL",
"script_name" => "ENUM('active','inactive')",
"script_data" => "TEXT NOT NULL",
"filter_type" => "ENUM('postfilter','prefilter')",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"username" => array("username"),
"script_desc" => array("script_desc")
),
"fkey" => array(
"fk_username_sieve_global_before" => array(
"col" => "username",
"ref" => "mailbox.username",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"app_passwd" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"name" => "VARCHAR(255) NOT NULL",
"mailbox" => "VARCHAR(255) NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"password" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"mailbox" => array("mailbox"),
"password" => array("password"),
"domain" => array("domain"),
),
"fkey" => array(
"fk_username_app_passwd" => array(
"col" => "mailbox",
"ref" => "mailbox.username",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"user_acl" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"spam_alias" => "TINYINT(1) NOT NULL DEFAULT '1'",
"tls_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
"spam_score" => "TINYINT(1) NOT NULL DEFAULT '1'",
"spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
"delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"eas_reset" => "TINYINT(1) NOT NULL DEFAULT '0'",
"sogo_profile_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'",
// quarantine is for quarantine actions, todo: rename
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_attachments" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine_notification" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
),
"keys" => array(
"primary" => array(
"" => array("username")
),
"fkey" => array(
"fk_username" => array(
"col" => "username",
"ref" => "mailbox.username",
"delete" => "CASCADE",
"update" => "NO ACTION"
)
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"alias_domain" => array(
"cols" => array(
"alias_domain" => "VARCHAR(255) NOT NULL",
"target_domain" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("alias_domain")
),
"key" => array(
"active" => array("active"),
"target_domain" => array("target_domain")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"spamalias" => array(
"cols" => array(
"address" => "VARCHAR(255) NOT NULL",
"goto" => "TEXT NOT NULL",
"validity" => "INT(11) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("address")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"filterconf" => array(
"cols" => array(
"object" => "VARCHAR(255) NOT NULL DEFAULT ''",
"option" => "VARCHAR(50) NOT NULL DEFAULT ''",
"value" => "VARCHAR(100) NOT NULL DEFAULT ''",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"prefid" => "INT(11) NOT NULL AUTO_INCREMENT"
),
"keys" => array(
"primary" => array(
"" => array("prefid")
),
"key" => array(
"object" => array("object")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"settingsmap" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"desc" => "VARCHAR(255) NOT NULL",
"content" => "LONGTEXT NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"logs" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"task" => "CHAR(32) NOT NULL DEFAULT '000000'",
"type" => "VARCHAR(32) DEFAULT ''",
"msg" => "TEXT",
"call" => "TEXT",
"user" => "VARCHAR(64) NOT NULL",
"role" => "VARCHAR(32) NOT NULL",
"remote" => "VARCHAR(39) NOT NULL",
"time" => "INT(11) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"quota2" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"bytes" => "BIGINT(20) NOT NULL DEFAULT '0'",
"messages" => "BIGINT(20) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("username")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"quota2replica" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"bytes" => "BIGINT(20) NOT NULL DEFAULT '0'",
"messages" => "BIGINT(20) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("username")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"domain_admins" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"username" => "VARCHAR(255) NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"active" => "TINYINT(1) NOT NULL DEFAULT '1'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"username" => array("username")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"da_acl" => array(
"cols" => array(
"username" => "VARCHAR(255) NOT NULL",
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
"login_as" => "TINYINT(1) NOT NULL DEFAULT '1'",
"sogo_access" => "TINYINT(1) NOT NULL DEFAULT '1'",
"app_passwds" => "TINYINT(1) NOT NULL DEFAULT '1'",
"bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "pushover" => "TINYINT(1) NOT NULL DEFAULT '0'",
"filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
"ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'",
"spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
"extend_sender_acl" => "TINYINT(1) NOT NULL DEFAULT '0'",
"unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'",
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
),
"keys" => array(
"primary" => array(
"" => array("username")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"imapsync" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"user2" => "VARCHAR(255) NOT NULL",
"host1" => "VARCHAR(255) NOT NULL",
"authmech1" => "ENUM('PLAIN','LOGIN','CRAM-MD5') DEFAULT 'PLAIN'",
"regextrans2" => "VARCHAR(255) DEFAULT ''",
"authmd51" => "TINYINT(1) NOT NULL DEFAULT 0",
"domain2" => "VARCHAR(255) NOT NULL DEFAULT ''",
"subfolder2" => "VARCHAR(255) NOT NULL DEFAULT ''",
"user1" => "VARCHAR(255) NOT NULL",
"password1" => "VARCHAR(255) NOT NULL",
"exclude" => "VARCHAR(500) NOT NULL DEFAULT ''",
"maxage" => "SMALLINT NOT NULL DEFAULT '0'",
"mins_interval" => "VARCHAR(50) NOT NULL DEFAULT '0'",
"maxbytespersecond" => "VARCHAR(50) NOT NULL DEFAULT '0'",
"port1" => "SMALLINT UNSIGNED NOT NULL",
"enc1" => "ENUM('TLS','SSL','PLAIN') DEFAULT 'TLS'",
"delete2duplicates" => "TINYINT(1) NOT NULL DEFAULT '1'",
"delete1" => "TINYINT(1) NOT NULL DEFAULT '0'",
"delete2" => "TINYINT(1) NOT NULL DEFAULT '0'",
"automap" => "TINYINT(1) NOT NULL DEFAULT '0'",
"skipcrossduplicates" => "TINYINT(1) NOT NULL DEFAULT '0'",
"custom_params" => "VARCHAR(512) NOT NULL DEFAULT ''",
"timeout1" => "SMALLINT NOT NULL DEFAULT '600'",
"timeout2" => "SMALLINT NOT NULL DEFAULT '600'",
"subscribeall" => "TINYINT(1) NOT NULL DEFAULT '1'",
"is_running" => "TINYINT(1) NOT NULL DEFAULT '0'",
"returned_text" => "LONGTEXT",
"last_run" => "TIMESTAMP NULL DEFAULT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"bcc_maps" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"local_dest" => "VARCHAR(255) NOT NULL",
"bcc_dest" => "VARCHAR(255) NOT NULL",
"domain" => "VARCHAR(255) NOT NULL",
"type" => "ENUM('sender','rcpt')",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"local_dest" => array("local_dest"),
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"recipient_maps" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"old_dest" => "VARCHAR(255) NOT NULL",
"new_dest" => "VARCHAR(255) NOT NULL",
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"local_dest" => array("old_dest"),
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"tfa" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"key_id" => "VARCHAR(255) NOT NULL",
"username" => "VARCHAR(255) NOT NULL",
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp')",
"secret" => "VARCHAR(255) DEFAULT NULL",
"keyHandle" => "VARCHAR(255) DEFAULT NULL",
"publicKey" => "VARCHAR(255) DEFAULT NULL",
"counter" => "INT NOT NULL DEFAULT '0'",
"certificate" => "TEXT",
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"forwarding_hosts" => array(
"cols" => array(
"host" => "VARCHAR(255) NOT NULL",
"source" => "VARCHAR(255) NOT NULL",
"filter_spam" => "TINYINT(1) NOT NULL DEFAULT '0'"
),
"keys" => array(
"primary" => array(
"" => array("host")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_acl" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"c_folder_id" => "INT NOT NULL",
"c_object" => "VARCHAR(255) NOT NULL",
"c_uid" => "VARCHAR(255) NOT NULL",
"c_role" => "VARCHAR(80) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("id")
),
"key" => array(
"sogo_acl_c_folder_id_idx" => array("c_folder_id"),
"sogo_acl_c_uid_idx" => array("c_uid")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_alarms_folder" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"c_path" => "VARCHAR(255) NOT NULL",
"c_name" => "VARCHAR(255) NOT NULL",
"c_uid" => "VARCHAR(255) NOT NULL",
"c_recurrence_id" => "INT(11) DEFAULT NULL",
"c_alarm_number" => "INT(11) NOT NULL",
"c_alarm_date" => "INT(11) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_cache_folder" => array(
"cols" => array(
"c_uid" => "VARCHAR(255) NOT NULL",
"c_path" => "VARCHAR(255) NOT NULL",
"c_parent_path" => "VARCHAR(255) DEFAULT NULL",
"c_type" => "TINYINT(3) unsigned NOT NULL",
"c_creationdate" => "INT(11) NOT NULL",
"c_lastmodified" => "INT(11) NOT NULL",
"c_version" => "INT(11) NOT NULL DEFAULT '0'",
"c_deleted" => "TINYINT(4) NOT NULL DEFAULT '0'",
"c_content" => "LONGTEXT"
),
"keys" => array(
"primary" => array(
"" => array("c_uid", "c_path")
),
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_folder_info" => array(
"cols" => array(
"c_folder_id" => "BIGINT(20) unsigned NOT NULL AUTO_INCREMENT",
"c_path" => "VARCHAR(255) NOT NULL",
"c_path1" => "VARCHAR(255) NOT NULL",
"c_path2" => "VARCHAR(255) DEFAULT NULL",
"c_path3" => "VARCHAR(255) DEFAULT NULL",
"c_path4" => "VARCHAR(255) DEFAULT NULL",
"c_foldername" => "VARCHAR(255) NOT NULL",
"c_location" => "VARCHAR(2048) DEFAULT NULL",
"c_quick_location" => "VARCHAR(2048) DEFAULT NULL",
"c_acl_location" => "VARCHAR(2048) DEFAULT NULL",
"c_folder_type" => "VARCHAR(255) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("c_path")
),
"unique" => array(
"c_folder_id" => array("c_folder_id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_quick_appointment" => array(
"cols" => array(
"c_folder_id" => "INT NOT NULL",
"c_name" => "VARCHAR(255) NOT NULL",
"c_uid" => "VARCHAR(255) NOT NULL",
"c_startdate" => "INT",
"c_enddate" => "INT",
"c_cycleenddate" => "INT",
"c_title" => "VARCHAR(1000) NOT NULL",
"c_participants" => "TEXT",
"c_isallday" => "INT",
"c_iscycle" => "INT",
"c_cycleinfo" => "TEXT",
"c_classification" => "INT NOT NULL",
"c_isopaque" => "INT NOT NULL",
"c_status" => "INT NOT NULL",
"c_priority" => "INT",
"c_location" => "VARCHAR(255)",
"c_orgmail" => "VARCHAR(255)",
"c_partmails" => "TEXT",
"c_partstates" => "TEXT",
"c_category" => "VARCHAR(255)",
"c_sequence" => "INT",
"c_component" => "VARCHAR(10) NOT NULL",
"c_nextalarm" => "INT",
"c_description" => "TEXT"
),
"keys" => array(
"primary" => array(
"" => array("c_folder_id", "c_name")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_quick_contact" => array(
"cols" => array(
"c_folder_id" => "INT NOT NULL",
"c_name" => "VARCHAR(255) NOT NULL",
"c_givenname" => "VARCHAR(255)",
"c_cn" => "VARCHAR(255)",
"c_sn" => "VARCHAR(255)",
"c_screenname" => "VARCHAR(255)",
"c_l" => "VARCHAR(255)",
"c_mail" => "TEXT",
"c_o" => "VARCHAR(255)",
"c_ou" => "VARCHAR(255)",
"c_telephonenumber" => "VARCHAR(255)",
"c_categories" => "VARCHAR(255)",
"c_component" => "VARCHAR(10) NOT NULL",
"c_hascertificate" => "INT4 DEFAULT 0"
),
"keys" => array(
"primary" => array(
"" => array("c_folder_id", "c_name")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_sessions_folder" => array(
"cols" => array(
"c_id" => "VARCHAR(255) NOT NULL",
"c_value" => "VARCHAR(255) NOT NULL",
"c_creationdate" => "INT(11) NOT NULL",
"c_lastseen" => "INT(11) NOT NULL"
),
"keys" => array(
"primary" => array(
"" => array("c_id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"sogo_store" => array(
"cols" => array(
"c_folder_id" => "INT NOT NULL",
"c_name" => "VARCHAR(255) NOT NULL",
"c_content" => "MEDIUMTEXT NOT NULL",
"c_creationdate" => "INT NOT NULL",
"c_lastmodified" => "INT NOT NULL",
"c_version" => "INT NOT NULL",
"c_deleted" => "INT"
),
"keys" => array(
"primary" => array(
"" => array("c_folder_id", "c_name")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
+ "pushover" => array(
+ "cols" => array(
+ "username" => "VARCHAR(255) NOT NULL",
+ "key" => "VARCHAR(255) NOT NULL",
+ "token" => "VARCHAR(255) NOT NULL",
+ "title" => "TEXT",
+ "text" => "TEXT",
+ "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
+ ),
+ "keys" => array(
+ "primary" => array(
+ "" => array("username")
+ )
+ ),
+ "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+ ),
"sogo_user_profile" => array(
"cols" => array(
"c_uid" => "VARCHAR(255) NOT NULL",
"c_defaults" => "LONGTEXT",
"c_settings" => "LONGTEXT"
),
"keys" => array(
"primary" => array(
"" => array("c_uid")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"oauth_clients" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
"client_id" => "VARCHAR(80) NOT NULL",
"client_secret" => "VARCHAR(80)",
"redirect_uri" => "VARCHAR(2000)",
"grant_types" => "VARCHAR(80)",
"scope" => "VARCHAR(4000)",
"user_id" => "VARCHAR(80)"
),
"keys" => array(
"primary" => array(
"" => array("client_id")
),
"unique" => array(
"id" => array("id")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"oauth_access_tokens" => array(
"cols" => array(
"access_token" => "VARCHAR(40) NOT NULL",
"client_id" => "VARCHAR(80) NOT NULL",
"user_id" => "VARCHAR(80)",
"expires" => "TIMESTAMP NOT NULL",
"scope" => "VARCHAR(4000)"
),
"keys" => array(
"primary" => array(
"" => array("access_token")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"oauth_authorization_codes" => array(
"cols" => array(
"authorization_code" => "VARCHAR(40) NOT NULL",
"client_id" => "VARCHAR(80) NOT NULL",
"user_id" => "VARCHAR(80)",
"redirect_uri" => "VARCHAR(2000)",
"expires" => "TIMESTAMP NOT NULL",
"scope" => "VARCHAR(4000)",
"id_token" => "VARCHAR(1000)"
),
"keys" => array(
"primary" => array(
"" => array("authorization_code")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
"oauth_refresh_tokens" => array(
"cols" => array(
"refresh_token" => "VARCHAR(40) NOT NULL",
"client_id" => "VARCHAR(80) NOT NULL",
"user_id" => "VARCHAR(80)",
"expires" => "TIMESTAMP NOT NULL",
"scope" => "VARCHAR(4000)"
),
"keys" => array(
"primary" => array(
"" => array("refresh_token")
)
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
)
);
foreach ($tables as $table => $properties) {
// Migrate to quarantine
if ($table == 'quarantine') {
$stmt = $pdo->query("SHOW TABLES LIKE 'quarantaine'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->query("SHOW TABLES LIKE 'quarantine'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$pdo->query("RENAME TABLE `quarantaine` TO `quarantine`");
}
}
}
// Migrate tls_enforce_* options
if ($table == 'mailbox') {
$stmt = $pdo->query("SHOW TABLES LIKE 'mailbox'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->query("SHOW COLUMNS FROM `mailbox` LIKE '%tls_enforce%'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->query("SELECT `username`, `tls_enforce_in`, `tls_enforce_out` FROM `mailbox`");
$tls_options_rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($tls_options_rows)) {
$tls_options[$row['username']] = array('tls_enforce_in' => $row['tls_enforce_in'], 'tls_enforce_out' => $row['tls_enforce_out']);
}
}
}
}
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints`
WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;");
$stmt->execute(array(':table' => $table));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$pdo->query($row['FKEY_DROP']);
}
foreach($properties['cols'] as $column => $type) {
$stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
if (strpos($type, 'AUTO_INCREMENT') !== false) {
$type = $type . ' PRIMARY KEY ';
// Adding an AUTO_INCREMENT key, need to drop primary keys first, if exists
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY");
}
}
$pdo->query("ALTER TABLE `" . $table . "` ADD `" . $column . "` " . $type);
}
else {
$pdo->query("ALTER TABLE `" . $table . "` MODIFY COLUMN `" . $column . "` " . $type);
}
}
foreach($properties['keys'] as $key_type => $key_content) {
if (strtolower($key_type) == 'primary') {
foreach ($key_content as $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$is_drop = ($num_results != 0) ? "DROP PRIMARY KEY, " : "";
$pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD PRIMARY KEY (" . $fields . ")");
}
}
if (strtolower($key_type) == 'key') {
foreach ($key_content as $key_name => $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : "";
$pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD KEY `" . $key_name . "` (" . $fields . ")");
}
}
if (strtolower($key_type) == 'unique') {
foreach ($key_content as $key_name => $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
$is_drop = ($num_results != 0) ? "DROP INDEX `" . $key_name . "`, " : "";
$pdo->query("ALTER TABLE `" . $table . "` " . $is_drop . "ADD UNIQUE KEY `" . $key_name . "` (" . $fields . ")");
}
}
if (strtolower($key_type) == 'fkey') {
foreach ($key_content as $key_name => $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`");
}
@list($table_ref, $field_ref) = explode('.', $key_values['ref']);
$pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`)
ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update']);
}
}
}
// Drop all vanished columns
$stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "`");
$cols_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($cols_in_table)) {
if (!array_key_exists($row['Field'], $properties['cols'])) {
$pdo->query("ALTER TABLE `" . $table . "` DROP COLUMN `" . $row['Field'] . "`;");
}
}
// Step 1: Get all non-primary keys, that currently exist and those that should exist
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE `Key_name` != 'PRIMARY'");
$keys_in_table = $stmt->fetchAll(PDO::FETCH_ASSOC);
$keys_to_exist = array();
if (isset($properties['keys']['unique']) && is_array($properties['keys']['unique'])) {
foreach ($properties['keys']['unique'] as $key_name => $key_values) {
$keys_to_exist[] = $key_name;
}
}
if (isset($properties['keys']['key']) && is_array($properties['keys']['key'])) {
foreach ($properties['keys']['key'] as $key_name => $key_values) {
$keys_to_exist[] = $key_name;
}
}
// Index for foreign key must exist
if (isset($properties['keys']['fkey']) && is_array($properties['keys']['fkey'])) {
foreach ($properties['keys']['fkey'] as $key_name => $key_values) {
$keys_to_exist[] = $key_name;
}
}
// Step 2: Drop all vanished indexes
while ($row = array_shift($keys_in_table)) {
if (!in_array($row['Key_name'], $keys_to_exist)) {
$pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`");
}
}
// Step 3: Drop all vanished primary keys
if (!isset($properties['keys']['primary'])) {
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = 'PRIMARY'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) {
$pdo->query("ALTER TABLE `" . $table . "` DROP PRIMARY KEY");
}
}
}
else {
// Create table if it is missing
$sql = "CREATE TABLE IF NOT EXISTS `" . $table . "` (";
foreach($properties['cols'] as $column => $type) {
$sql .= "`" . $column . "` " . $type . ",";
}
foreach($properties['keys'] as $key_type => $key_content) {
if (strtolower($key_type) == 'primary') {
foreach ($key_content as $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$sql .= "PRIMARY KEY (" . $fields . ")" . ",";
}
}
elseif (strtolower($key_type) == 'key') {
foreach ($key_content as $key_name => $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$sql .= "KEY `" . $key_name . "` (" . $fields . ")" . ",";
}
}
elseif (strtolower($key_type) == 'unique') {
foreach ($key_content as $key_name => $key_values) {
$fields = "`" . implode("`, `", $key_values) . "`";
$sql .= "UNIQUE KEY `" . $key_name . "` (" . $fields . ")" . ",";
}
}
elseif (strtolower($key_type) == 'fkey') {
foreach ($key_content as $key_name => $key_values) {
@list($table_ref, $field_ref) = explode('.', $key_values['ref']);
$fields = "`" . implode("`, `", $key_values) . "`";
$sql .= "FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`)
ON DELETE " . $key_values['delete'] . " ON UPDATE " . $key_values['update'] . ",";
}
}
}
$sql = rtrim($sql, ",");
$sql .= ") " . $properties['attr'];
$pdo->query($sql);
}
// Reset table attributes
$pdo->query("ALTER TABLE `" . $table . "` " . $properties['attr'] . ";");
}
// Recreate SQL views
foreach ($views as $view => $create) {
$pdo->query("DROP VIEW IF EXISTS `" . $view . "`;");
$pdo->query($create);
}
// Inject admin if not exists
$stmt = $pdo->query("SELECT NULL FROM `admin`");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results == 0) {
$stmt = $pdo->query("INSERT INTO `admin` (`username`, `password`, `superadmin`, `created`, `modified`, `active`)
VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1)");
$stmt = $pdo->query("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
SELECT `username`, 'ALL', NOW(), 1 FROM `admin`
WHERE superadmin='1' AND `username` NOT IN (SELECT `username` FROM `domain_admins`);");
$stmt = $pdo->query("DELETE FROM `admin` WHERE `username` NOT IN (SELECT `username` FROM `domain_admins`);");
}
// Insert new DB schema version
$stmt = $pdo->query("REPLACE INTO `versions` (`application`, `version`) VALUES ('db_schema', '" . $db_version . "');");
// Fix dangling domain admins
$stmt = $pdo->query("DELETE FROM `admin` WHERE `superadmin` = 0 AND `username` NOT IN (SELECT `username`FROM `domain_admins`);");
$stmt = $pdo->query("DELETE FROM `da_acl` WHERE `username` NOT IN (SELECT `username`FROM `domain_admins`);");
// Migrate attributes
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = '{}' WHERE `attributes` = '' OR `attributes` IS NULL;");
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.force_pw_update', \"0\") WHERE JSON_EXTRACT(`attributes`, '$.force_pw_update') IS NULL;");
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.sogo_access', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.sogo_access') IS NULL;");
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.mailbox_format', \"maildir:\") WHERE JSON_EXTRACT(`attributes`, '$.mailbox_format') IS NULL;");
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.quarantine_notification', \"never\") WHERE JSON_EXTRACT(`attributes`, '$.quarantine_notification') IS NULL;");
foreach($tls_options as $tls_user => $tls_options) {
$stmt = $pdo->prepare("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', :tls_enforce_in),
`attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', :tls_enforce_out)
WHERE `username` = :username");
$stmt->execute(array(':tls_enforce_in' => $tls_options['tls_enforce_in'], ':tls_enforce_out' => $tls_options['tls_enforce_out'], ':username' => $tls_user));
}
// Set tls_enforce_* if still missing (due to deleted attrs for example)
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_out', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_out') IS NULL;");
$stmt = $pdo->query("UPDATE `mailbox` SET `attributes` = JSON_SET(`attributes`, '$.tls_enforce_in', \"1\") WHERE JSON_EXTRACT(`attributes`, '$.tls_enforce_in') IS NULL;");
// Fix ACL
$stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);");
$stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);");
// Fix domain_admins
$stmt = $pdo->query("DELETE FROM `domain_admins` WHERE `domain` = 'ALL';");
if (php_sapi_name() == "cli") {
echo "DB initialization completed" . PHP_EOL;
} else {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__),
'msg' => 'db_init_complete'
);
}
}
catch (PDOException $e) {
if (php_sapi_name() == "cli") {
echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;
} else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => array('mysql_error', $e)
);
}
}
}
if (php_sapi_name() == "cli") {
include '/web/inc/vars.inc.php';
// $now = new DateTime();
// $mins = $now->getOffset() / 60;
// $sgn = ($mins < 0 ? -1 : 1);
// $mins = abs($mins);
// $hrs = floor($mins / 60);
// $mins -= $hrs * 60;
// $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins);
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
//PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;",
];
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
$stmt = $pdo->query("SELECT COUNT('OK') AS OK_C FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view' OR TABLE_NAME = '_sogo_static_view';");
$res = $stmt->fetch(PDO::FETCH_ASSOC);
if (intval($res['OK_C']) === 2) {
// Be more precise when replacing into _sogo_static_view, col orders may change
try {
$stmt = $pdo->query("REPLACE INTO _sogo_static_view (`c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings`)
SELECT `c_uid`, `domain`, `c_name`, `c_password`, `c_cn`, `mail`, `aliases`, `ad_aliases`, `kind`, `multiple_bookings` from sogo_view");
$stmt = $pdo->query("DELETE FROM _sogo_static_view WHERE `c_uid` NOT IN (SELECT `username` FROM `mailbox` WHERE `active` = '1');");
echo "Fixed _sogo_static_view" . PHP_EOL;
}
catch ( Exception $e ) {
// Dunno
}
}
try {
$m = new Memcached();
$m->addServer('memcached', 11211);
$m->flush();
echo "Cleaned up memcached". PHP_EOL;
}
catch ( Exception $e ) {
// Dunno
}
init_db_schema();
}
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index b61679a9..0601cf8e 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -1,251 +1,252 @@
<?php
// Slave does not serve UI
/* if (!preg_match('/y|yes/i', getenv('MASTER'))) {
header('Location: /SOGo', true, 307);
exit;
}*/
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
$default_autodiscover_config = $autodiscover_config;
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
include_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php';
}
unset($https_port);
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
header_remove("X-Powered-By");
// Yubi OTP API
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/Yubico.php';
// Autoload composer
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/vendor/autoload.php';
// Load Sieve
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/sieve/SieveParser.php';
// minifierExtended
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/JSminifierExtended.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/CSSminifierExtended.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/lib/array_merge_real.php';
// Minify JS
use MatthiasMullie\Minify;
$js_minifier = new JSminifierExtended();
$js_dir = array_diff(scandir('/web/js/build'), array('..', '.'));
foreach ($js_dir as $js_file) {
$js_minifier->add('/web/js/build/' . $js_file);
}
// Minify CSS
$css_minifier = new CSSminifierExtended();
$css_dir = array_diff(scandir('/web/css/build'), array('..', '.'));
foreach ($css_dir as $css_file) {
$css_minifier->add('/web/css/build/' . $css_file);
}
// U2F API + T/HOTP API
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
$qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
// Redis
$redis = new Redis();
try {
if (!empty(getenv('REDIS_SLAVEOF_IP'))) {
$redis->connect(getenv('REDIS_SLAVEOF_IP'), getenv('REDIS_SLAVEOF_PORT'));
}
else {
$redis->connect('redis-mailcow', 6379);
}
}
catch (Exception $e) {
?>
<center style='font-family:sans-serif;'>Connection to Redis failed.<br /><br />The following error was reported:<br/><?=$e->getMessage();?></center>
<?php
exit;
}
// PDO
// Calculate offset
// $now = new DateTime();
// $mins = $now->getOffset() / 60;
// $sgn = ($mins < 0 ? -1 : 1);
// $mins = abs($mins);
// $hrs = floor($mins / 60);
// $mins -= $hrs * 60;
// $offset = sprintf('%+d:%02d', $hrs*$sgn, $mins);
$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
//PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone = '" . $offset . "', group_concat_max_len = 3423543543;",
];
try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
// Stop when SQL connection fails
?>
<center style='font-family:sans-serif;'>Connection to database failed.<br /><br />The following error was reported:<br/> <?=$e->getMessage();?></center>
<?php
exit;
}
// Stop when dockerapi is not available
if (fsockopen("tcp://dockerapi", 443, $errno, $errstr) === false) {
?>
<center style='font-family:sans-serif;'>Connection to dockerapi container failed.<br /><br />The following error was reported:<br/><?=$errno;?> - <?=$errstr;?></center>
<?php
exit;
}
// OAuth2
class mailcowPdo extends OAuth2\Storage\Pdo {
public function __construct($connection, $config = array()) {
parent::__construct($connection, $config);
$this->config['user_table'] = 'mailbox';
}
public function checkUserCredentials($username, $password) {
if (check_login($username, $password) == 'user') {
return true;
}
return false;
}
public function getUserDetails($username) {
return $this->getUser($username);
}
}
$oauth2_scope_storage = new OAuth2\Storage\Memory(array('default_scope' => 'profile', 'supported_scopes' => array('profile')));
$oauth2_storage = new mailcowPdo(array('dsn' => $dsn, 'username' => $database_user, 'password' => $database_pass));
$oauth2_server = new OAuth2\Server($oauth2_storage, array(
'refresh_token_lifetime' => $REFRESH_TOKEN_LIFETIME,
'access_lifetime' => $ACCESS_TOKEN_LIFETIME,
));
$oauth2_server->setScopeUtil(new OAuth2\Scope($oauth2_scope_storage));
$oauth2_server->addGrantType(new OAuth2\GrantType\AuthorizationCode($oauth2_storage));
$oauth2_server->addGrantType(new OAuth2\GrantType\UserCredentials($oauth2_storage));
$oauth2_server->addGrantType(new OAuth2\GrantType\RefreshToken($oauth2_storage, array(
'always_issue_new_refresh_token' => true
)));
function exception_handler($e) {
if ($e instanceof PDOException) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => array('mysql_error', $e)
);
return false;
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__),
'msg' => 'An unknown error occured: ' . print_r($e, true)
);
return false;
}
}
set_exception_handler('exception_handler');
// TODO: Move function
function get_remote_ip($anonymize = null) {
global $ANONYMIZE_IPS;
if ($anonymize === null) {
$anonymize = $ANONYMIZE_IPS;
}
elseif ($anonymize !== true && $anonymize !== false) {
$anonymize = true;
}
$remote = $_SERVER['REMOTE_ADDR'];
if (filter_var($remote, FILTER_VALIDATE_IP) === false) {
return '0.0.0.0';
}
if ($anonymize) {
if (strlen(inet_pton($remote)) == 4) {
return inet_ntop(inet_pton($remote) & inet_pton("255.255.255.0"));
}
elseif (strlen(inet_pton($remote)) == 16) {
return inet_ntop(inet_pton($remote) & inet_pton('ffff:ffff:ffff:ffff:0000:0000:0000:0000'));
}
}
else {
return $remote;
}
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
// IMAP lib
// use Ddeboer\Imap\Server;
// $imap_server = new Server('dovecot', 143, '/imap/tls/novalidate-cert');
// Set language
if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$header_lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
if (in_array($header_lang, $AVAILABLE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $header_lang;
}
}
else {
$_SESSION['mailcow_locale'] = strtolower(trim($DEFAULT_LANG));
}
}
if (isset($_COOKIE['mailcow_locale'])) {
(preg_match('/^[a-z]{2}$/', $_COOKIE['mailcow_locale'])) ? $_SESSION['mailcow_locale'] = $_COOKIE['mailcow_locale'] : setcookie("mailcow_locale", "", time() - 300);
}
if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) {
$_SESSION['mailcow_locale'] = $_GET['lang'];
setcookie("mailcow_locale", $_GET['lang'], time()+30758400); // one year
}
/*
* load language
*/
$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.json'), true);
$langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json';
if(file_exists($langFile)) {
$lang = array_merge_real($lang, json_decode(file_get_contents($langFile), true));
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.app_passwd.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.admin.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quarantine.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.quota_notification.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailq.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.oauth2.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.pushover.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.transports.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rspamd.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.presets.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
init_db_schema();
if (isset($_SESSION['mailcow_cc_role'])) {
// if ($_SESSION['mailcow_cc_role'] == 'user') {
// list($master_user, $master_passwd) = explode(':', file_get_contents('/etc/sogo/sieve.creds'));
// $imap_connection = $imap_server->authenticate($_SESSION['mailcow_cc_username'] . '*' . trim($master_user), trim($master_passwd));
// $master_user = $master_passwd = null;
// }
acl('to_session');
}
$UI_TEXTS = customize('get', 'ui_texts');
diff --git a/data/web/inc/sessions.inc.php b/data/web/inc/sessions.inc.php
index e3840d2c..683ebf17 100644
--- a/data/web/inc/sessions.inc.php
+++ b/data/web/inc/sessions.inc.php
@@ -1,134 +1,140 @@
<?php
// Start session
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_httponly", 1);
ini_set('session.gc_maxlifetime', $SESSION_LIFETIME);
}
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == "https") {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
elseif (isset($_SERVER['HTTPS'])) {
if (session_status() !== PHP_SESSION_ACTIVE) {
ini_set("session.cookie_secure", 1);
}
$IS_HTTPS = true;
}
else {
$IS_HTTPS = false;
}
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION['CSRF']['TOKEN'])) {
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
}
// Set session UA
if (!isset($_SESSION['SESS_REMOTE_UA'])) {
$_SESSION['SESS_REMOTE_UA'] = $_SERVER['HTTP_USER_AGENT'];
}
// Keep session active
if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $SESSION_LIFETIME)) {
session_unset();
session_destroy();
}
$_SESSION['LAST_ACTIVITY'] = time();
// API
if (!empty($_SERVER['HTTP_X_API_KEY'])) {
$stmt = $pdo->prepare("SELECT * FROM `api` WHERE `api_key` = :api_key AND `active` = '1';");
$stmt->execute(array(
':api_key' => preg_replace('/[^a-zA-Z0-9-]/', '', $_SERVER['HTTP_X_API_KEY'])
));
$api_return = $stmt->fetch(PDO::FETCH_ASSOC);
if (!empty($api_return['api_key'])) {
$skip_ip_check = ($api_return['skip_ip_check'] == 1);
$remote = get_remote_ip(false);
$allow_from = array_map('trim', preg_split( "/( |,|;|\n)/", $api_return['allow_from']));
if (in_array($remote, $allow_from) || $skip_ip_check === true) {
$_SESSION['mailcow_cc_username'] = 'API';
$_SESSION['mailcow_cc_role'] = 'admin';
$_SESSION['mailcow_cc_api'] = true;
+ if ($api_return['api_key'] == 'rw') {
+ $_SESSION['mailcow_cc_api_access'] = 'rw';
+ }
+ else {
+ $_SESSION['mailcow_cc_api_access'] = 'ro';
+ }
}
else {
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'api access denied for ip ' . $_SERVER['REMOTE_ADDR']
));
unset($_POST);
exit();
}
}
else {
$redis->publish("F2B_CHANNEL", "mailcow UI: Invalid password for API_USER by " . $_SERVER['REMOTE_ADDR']);
error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
http_response_code(401);
echo json_encode(array(
'type' => 'error',
'msg' => 'authentication failed'
));
unset($_POST);
exit();
}
}
// Handle logouts
if (isset($_POST["logout"])) {
if (isset($_SESSION["dual-login"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]);
header("Location: /mailbox");
exit();
}
else {
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
header("Location: /");
}
}
// Check session
function session_check() {
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
return true;
}
if (!isset($_SESSION['SESS_REMOTE_UA']) || ($_SESSION['SESS_REMOTE_UA'] != $_SERVER['HTTP_USER_AGENT'])) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_ua'
);
return false;
}
if (!empty($_POST)) {
if ($_SESSION['CSRF']['TOKEN'] != $_POST['csrf_token']) {
$_SESSION['return'][] = array(
'type' => 'warning',
'msg' => 'session_token'
);
return false;
}
unset($_POST['csrf_token']);
$_SESSION['CSRF']['TOKEN'] = bin2hex(random_bytes(32));
$_SESSION['CSRF']['TIME'] = time();
}
return true;
}
if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
$_POST = array();
$_FILES = array();
}
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
index 03f64f91..132e0bd2 100644
--- a/data/web/inc/triggers.inc.php
+++ b/data/web/inc/triggers.inc.php
@@ -1,121 +1,130 @@
<?php
if (isset($_POST["verify_tfa_login"])) {
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST["token"])) {
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_method']);
header("Location: /user");
}
}
if (isset($_POST["quick_release"])) {
quarantine('quick_release', $_POST["quick_release"]);
}
if (isset($_POST["quick_delete"])) {
quarantine('quick_delete', $_POST["quick_delete"]);
}
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
$login_user = strtolower(trim($_POST["login_user"]));
$as = check_login($login_user, $_POST["pass_user"]);
if ($as == "admin") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "admin";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
header("Location: /admin");
}
elseif ($as == "domainadmin") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "domainadmin";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
header("Location: /mailbox");
}
elseif ($as == "user") {
$_SESSION['mailcow_cc_username'] = $login_user;
$_SESSION['mailcow_cc_role'] = "user";
$_SESSION['mailcow_cc_last_login'] = last_login($login_user);
$http_parameters = explode('&', $_SESSION['index_query_string']);
unset($_SESSION['index_query_string']);
if (in_array('mobileconfig', $http_parameters)) {
if (in_array('only_email', $http_parameters)) {
header("Location: /mobileconfig.php?email_only");
die();
}
header("Location: /mobileconfig.php");
die();
}
header("Location: /user");
}
elseif ($as != "pending") {
unset($_SESSION['pending_mailcow_cc_username']);
unset($_SESSION['pending_mailcow_cc_role']);
unset($_SESSION['pending_tfa_method']);
unset($_SESSION['mailcow_cc_username']);
unset($_SESSION['mailcow_cc_role']);
}
}
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") {
if (isset($_GET["duallogin"])) {
$duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {
if (!empty(mailbox('get', 'mailbox_details', $duallogin))) {
$_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
$_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role'];
$_SESSION['mailcow_cc_username'] = $duallogin;
$_SESSION['mailcow_cc_role'] = "user";
header("Location: /user");
}
}
else {
if (!empty(domain_admin('details', $duallogin))) {
$_SESSION["dual-login"]["username"] = $_SESSION['mailcow_cc_username'];
$_SESSION["dual-login"]["role"] = $_SESSION['mailcow_cc_role'];
$_SESSION['mailcow_cc_username'] = $duallogin;
$_SESSION['mailcow_cc_role'] = "domainadmin";
header("Location: /user");
}
}
}
}
if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "admin" || $_SESSION['mailcow_cc_role'] == "domainadmin")) {
if (isset($_POST["set_tfa"])) {
set_tfa($_POST);
}
if (isset($_POST["unset_tfa_key"])) {
unset_tfa_key($_POST);
}
}
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
// TODO: Move file upload to API?
if (isset($_POST["submit_main_logo"])) {
if ($_FILES['main_logo']['error'] == 0) {
customize('add', 'main_logo', $_FILES);
}
}
if (isset($_POST["reset_main_logo"])) {
customize('delete', 'main_logo');
}
- // API and license cannot be controlled by API
+ // Some actions will not be available via API
if (isset($_POST["license_validate_now"])) {
license('verify');
}
if (isset($_POST["admin_api"])) {
- admin_api('edit', $_POST);
+ if (isset($_POST["admin_api"]["ro"])) {
+ admin_api('ro', 'edit', $_POST);
+ }
+ elseif (isset($_POST["admin_api"]["rw"])) {
+ admin_api('rw', 'edit', $_POST);
+ }
}
- if (isset($_POST["admin_api_regen_key"])) {
- admin_api('regen_key', $_POST);
+ if (isset($_POST["admin_api_regen_key"])) {
+ if (isset($_POST["admin_api_regen_key"]["ro"])) {
+ admin_api('ro', 'regen_key', $_POST);
+ }
+ elseif (isset($_POST["admin_api_regen_key"]["rw"])) {
+ admin_api('rw', 'regen_key', $_POST);
+ }
}
- // Not available via API
if (isset($_POST["rspamd_ui"])) {
rspamd_ui('edit', $_POST);
}
if (isset($_POST["mass_send"])) {
sys_mail($_POST);
}
}
?>
diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js
index 32f76888..62a918df 100644
--- a/data/web/js/site/admin.js
+++ b/data/web/js/site/admin.js
@@ -1,493 +1,520 @@
// Base64 functions
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
jQuery(function($){
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function jq(myid) {return "#" + myid.replace( /(:|\.|\[|\]|,|=|@)/g, "\\$1" );}
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function validateRegex(e){var t=e.split("/"),n=e,r="";t.length>1&&(n=t[1],r=t[2]);try{return new RegExp(n,r),!0}catch(e){return!1}}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
function hashCode(t){for(var n=0,r=0;r<t.length;r++)n=t.charCodeAt(r)+((n<<5)-n);return n}
function intToRGB(t){var n=(16777215&t).toString(16).toUpperCase();return"00000".substring(0,6-n.length)+n}
$("#dkim_missing_keys").on('click', function(e) {
e.preventDefault();
var domains = [];
$('.dkim_missing').each(function() {
domains.push($(this).val());
});
$('#dkim_add_domains').val(domains);
});
$(".arrow-toggle").on('click', function(e) { e.preventDefault(); $(this).find('.arrow').toggleClass("animation"); });
$("#mass_exclude").change(function(){ $("#mass_include").selectpicker('deselectAll'); });
$("#mass_include").change(function(){ $("#mass_exclude").selectpicker('deselectAll'); });
$("#mass_disarm").click(function() { $("#mass_send").attr("disabled", !this.checked); });
$(".validate_rspamd_regex").click(function( event ) {
event.preventDefault();
var regex_map_id = $(this).data('regex-map');
var regex_data = $(jq(regex_map_id)).val().split(/\r?\n/);
var regex_valid = true;
for(var i = 0;i < regex_data.length;i++){
if(regex_data[i].startsWith('#') || !regex_data[i]){
continue;
}
if(!validateRegex(regex_data[i])) {
mailcow_alert_box('Cannot build regex from line ' + (i+1), 'danger');
var regex_valid = false;
break;
}
if(!regex_data[i].startsWith('/') || !/\/[ims]?$/.test(regex_data[i])){
mailcow_alert_box('Line ' + (i+1) + ' is invalid', 'danger');
var regex_valid = false;
break;
}
}
if (regex_valid) {
mailcow_alert_box('Regex OK', 'success');
$('button[data-id="' + regex_map_id + '"]').attr({"disabled": false});
}
+ });
+ $('.btn-api-ro').click(function() {
+ $('#api_rw').hide()
+ $('#api_ro').show()
+ $(this).addClass('active')
+ $('.btn-api-rw, .btn-api-hide').removeClass('active')
+ });
+ $('.btn-api-rw').click(function() {
+ $('#api_ro').hide()
+ $('#api_rw').show()
+ $(this).addClass('active')
+ $('.btn-api-ro, .btn-api-hide').removeClass('active')
+ });
+ $('.btn-api-hide').click(function() {
+ $('#api_ro').hide()
+ $('#api_rw').hide()
+ $(this).addClass('active')
+ $('.btn-api-ro, .btn-api-rw').removeClass('active')
});
$('.textarea-code').on('keyup', function() {
$('.submit_rspamd_regex').attr({"disabled": true});
});
$("#show_rspamd_global_filters").click(function() {
$.get("inc/ajax/show_rspamd_global_filters.php");
$("#confirm_show_rspamd_global_filters").hide();
$("#rspamd_global_filters").removeClass("hidden");
});
$("#super_delete").click(function() { return confirm(lang.queue_ays); });
$(".refresh_table").on('click', function(e) {
e.preventDefault();
var table_name = $(this).data('table');
$('#' + table_name).find("tr.footable-empty").remove();
draw_table = $(this).data('draw');
eval(draw_table + '()');
});
function table_admin_ready(ft, name) {
heading = ft.$el.parents('.panel').find('.panel-heading')
var ft_paging = ft.use(FooTable.Paging)
$(heading).children('.table-lines').text(function(){
return ft_paging.totalRows;
})
}
function draw_domain_admins() {
ft_domainadmins = FooTable.init('#domainadminstable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"username","title":lang.username,"style":{"width":"250px"}},
{"name":"selected_domains","title":lang.admin_domains,"breakpoints":"xs sm"},
{"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/domain-admin/all',
jsonp: false,
error: function () {
console.log('Cannot draw domain admin table');
},
success: function (data) {
return process_table_data(data, 'domainadminstable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"state": {"enabled": true},
"filtering": {"enabled": true,"delay": 1200,"position": "left","connectors": false,"placeholder": lang.filter_table},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_oauth2_clients() {
ft_oauth2clientstable = FooTable.init('#oauth2clientstable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"40px","width":"40px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"id","type":"text","title":"ID","style":{"width":"50px"}},
{"name":"client_id","type":"text","title":lang.oauth2_client_id,"style":{"width":"200px"}},
{"name":"client_secret","title":lang.oauth2_client_secret,"breakpoints":"xs sm md","style":{"width":"200px"}},
{"name":"redirect_uri","title":lang.oauth2_redirect_uri, "type": "text"},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/oauth2-client/all',
jsonp: false,
error: function () {
console.log('Cannot draw oauth2 clients table');
},
success: function (data) {
return process_table_data(data, 'oauth2clientstable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_admins() {
ft_admins = FooTable.init('#adminstable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"sorted": true,"name":"usr","title":lang.username,"style":{"width":"250px"}},
{"name":"tfa_active","title":"TFA", "filterable": false,"style":{"maxWidth":"80px","width":"80px"}},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"250px","width":"250px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/admin/all',
jsonp: false,
error: function () {
console.log('Cannot draw admin table');
},
success: function (data) {
return process_table_data(data, 'adminstable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"filtering": {"enabled": false},
"state": {"enabled": true},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_fwd_hosts() {
ft_forwardinghoststable = FooTable.init('#forwardinghoststable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"host","type":"text","title":lang.host,"style":{"width":"250px"}},
{"name":"source","title":lang.source,"breakpoints":"xs sm"},
{"name":"keep_spam","title":lang.spamfilter, "type": "text","style":{"maxWidth":"80px","width":"80px"}},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"180px","width":"180px"},"type":"html","title":lang.action,"breakpoints":"xs sm"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/fwdhost/all',
jsonp: false,
error: function () {
console.log('Cannot draw forwarding hosts table');
},
success: function (data) {
return process_table_data(data, 'forwardinghoststable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_relayhosts() {
ft_relayhoststable = FooTable.init('#relayhoststable', {
"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":"hostname","type":"text","title":lang.host,"style":{"width":"250px"}},
{"name":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"used_by_domains","title":lang.in_use_by,"style":{"width":"110px"}, "type": "text","breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/relayhost/all',
jsonp: false,
error: function () {
console.log('Cannot draw forwarding hosts table');
},
success: function (data) {
return process_table_data(data, 'relayhoststable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_transport_maps() {
ft_relayhoststable = FooTable.init('#transportstable', {
"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":"username","title":lang.username,"breakpoints":"xs sm"},
{"name":"active","filterable": false,"style":{"maxWidth":"80px","width":"80px"},"title":lang.active},
{"name":"action","filterable": false,"sortable": false,"style":{"text-align":"right","maxWidth":"220px","width":"220px"},"type":"html","title":lang.action,"breakpoints":"xs sm md"}
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/transport/all',
jsonp: false,
error: function () {
console.log('Cannot draw transports table');
},
success: function (data) {
return process_table_data(data, 'transportstable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle"
});
}
function draw_queue() {
ft_queuetable = FooTable.init('#queuetable', {
"columns": [
{"name":"chkbox","title":"","style":{"maxWidth":"60px","width":"60px"},"filterable": false,"sortable": false,"type":"html"},
{"name":"queue_id","type":"text","title":"QID","style":{"width":"50px"}},
{"name":"queue_name","type":"text","title":"Queue","style":{"width":"120px"}},
{"name":"arrival_time","sorted": true,"direction": "DESC","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.arrival_time,"style":{"width":"170px"}},
{"name":"message_size","style":{"whiteSpace":"nowrap"},"title":lang.message_size,"formatter": function(value){
return humanFileSize(value);
}},
{"name":"sender","title":lang.sender, "type": "text","breakpoints":"xs sm"},
{"name":"recipients","title":lang.recipients, "type": "text","style":{"word-break":"break-all","min-width":"300px"},"breakpoints":"xs sm md"},
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/mailq/all',
jsonp: false,
error: function () {
console.log('Cannot draw forwarding hosts table');
},
success: function (data) {
return process_table_data(data, 'queuetable');
}
}),
"empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": log_pagination_size},
"sorting": {"enabled": true},
"toggleSelector": "table tbody span.footable-toggle",
"on": {
"ready.ft.table": function(e, ft){
table_admin_ready(ft, 'queuetable');
}
}
});
}
function process_table_data(data, table) {
if (table == 'relayhoststable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="#" data-toggle="modal" data-target="#testTransportModal" data-transport-id="' + encodeURI(item.id) + '" data-transport-type="sender-dependent" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' +
'<a href="/edit/relayhost/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-rlyhost" data-api-url="delete/relayhost" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="rlyhosts" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'transportstable') {
$.each(data, function (i, item) {
if (item.username) {
item.username = '<span style="border-left:3px solid #' + intToRGB(hashCode(item.nexthop)) + ';padding-left:5px;">' + item.username + '</span>';
}
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"><span class="glyphicon glyphicon-triangle-right"></span> Test</a>' +
'<a href="/edit/transport/' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-transport" data-api-url="delete/transport" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.chkbox = '<input type="checkbox" data-id="transports" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'queuetable') {
$.each(data, function (i, item) {
item.chkbox = '<input type="checkbox" data-id="mailqitems" name="multi_select" value="' + item.queue_id + '" />';
rcpts = $.map(item.recipients, function(i) {
return escapeHtml(i);
});
item.recipients = rcpts.join('<hr style="margin:1px!important">');
});
} else if (table == 'forwardinghoststable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="#" data-action="delete_selected" data-id="single-fwdhost" data-api-url="delete/fwdhost" data-item="' + encodeURI(item.host) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
if (item.keep_spam == "yes") {
item.keep_spam = lang.no;
}
else {
item.keep_spam = lang.yes;
}
item.chkbox = '<input type="checkbox" data-id="fwdhosts" name="multi_select" value="' + item.host + '" />';
});
} else if (table == 'oauth2clientstable') {
$.each(data, function (i, item) {
item.action = '<div class="btn-group">' +
'<a href="/edit.php?oauth2client=' + encodeURI(item.id) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-oauth2-client" data-api-url="delete/oauth2-client" data-item="' + encodeURI(item.id) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
item.scope = "profile";
item.grant_types = 'refresh_token password authorization_code';
item.chkbox = '<input type="checkbox" data-id="oauth2_clients" name="multi_select" value="' + item.id + '" />';
});
} else if (table == 'domainadminstable') {
$.each(data, function (i, item) {
item.selected_domains = escapeHtml(item.selected_domains);
item.selected_domains = item.selected_domains.toString().replace(/,/g, "<br>");
item.chkbox = '<input type="checkbox" data-id="domain_admins" name="multi_select" value="' + item.username + '" />';
item.action = '<div class="btn-group">' +
'<a href="/edit/domainadmin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-domain-admin" data-api-url="delete/domain-admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'<a href="/index.php?duallogin=' + encodeURIComponent(item.username) + '" class="btn btn-xs btn-success"><span class="glyphicon glyphicon-user"></span> Login</a>' +
'</div>';
});
} else if (table == 'adminstable') {
$.each(data, function (i, item) {
if (admin_username.toLowerCase() == item.username.toLowerCase()) {
item.usr = '→ ' + item.username;
} else {
item.usr = item.username;
}
item.chkbox = '<input type="checkbox" data-id="admins" name="multi_select" value="' + item.username + '" />';
item.action = '<div class="btn-group">' +
'<a href="/edit/admin/' + encodeURI(item.username) + '" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-pencil"></span> ' + lang.edit + '</a>' +
'<a href="#" data-action="delete_selected" data-id="single-admin" data-api-url="delete/admin" data-item="' + encodeURI(item.username) + '" class="btn btn-xs btn-danger"><span class="glyphicon glyphicon-trash"></span> ' + lang.remove + '</a>' +
'</div>';
});
}
return data
};
// Initial table drawings
draw_domain_admins();
draw_admins();
draw_fwd_hosts();
draw_relayhosts();
draw_oauth2_clients();
draw_transport_maps();
draw_queue();
// API IP check toggle
- $("#skip_ip_check").click(function( event ) {
- $("#skip_ip_check").not(this).prop('checked', false);
- if ($("#skip_ip_check:checked").length > 0) {
- $('#allow_from').prop('disabled', true);
+ $("#skip_ip_check_ro").click(function( event ) {
+ $("#skip_ip_check_ro").not(this).prop('checked', false);
+ if ($("#skip_ip_check_ro:checked").length > 0) {
+ $('#allow_from_ro').prop('disabled', true);
+ }
+ else {
+ $("#allow_from_ro").removeAttr('disabled');
+ }
+ });
+ $("#skip_ip_check_rw").click(function( event ) {
+ $("#skip_ip_check_rw").not(this).prop('checked', false);
+ if ($("#skip_ip_check_rw:checked").length > 0) {
+ $('#allow_from_rw').prop('disabled', true);
}
else {
- $("#allow_from").removeAttr('disabled');
+ $("#allow_from_rw").removeAttr('disabled');
}
});
// Relayhost
$('#testRelayhostModal').on('show.bs.modal', function (e) {
$('#test_relayhost_result').text("-");
button = $(e.relatedTarget)
if (button != null) {
$('#relayhost_id').val(button.data('relayhost-id'));
}
})
$('#test_relayhost').on('click', function (e) {
e.preventDefault();
prev = $('#test_relayhost').text();
$(this).prop("disabled",true);
$(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
$.ajax({
type: 'GET',
url: 'inc/ajax/relay_check.php',
dataType: 'text',
data: $('#test_relayhost_form').serialize(),
complete: function (data) {
$('#test_relayhost_result').html(data.responseText);
$('#test_relayhost').prop("disabled",false);
$('#test_relayhost').text(prev);
}
});
})
// Transport
$('#testTransportModal').on('show.bs.modal', function (e) {
$('#test_transport_result').text("-");
button = $(e.relatedTarget)
if (button != null) {
$('#transport_id').val(button.data('transport-id'));
$('#transport_type').val(button.data('transport-type'));
}
})
$('#test_transport').on('click', function (e) {
e.preventDefault();
prev = $('#test_transport').text();
$(this).prop("disabled",true);
$(this).html('<span class="glyphicon glyphicon-refresh glyphicon-spin"></span> ');
$.ajax({
type: 'GET',
url: 'inc/ajax/transport_check.php',
dataType: 'text',
data: $('#test_transport_form').serialize(),
complete: function (data) {
$('#test_transport_result').html(data.responseText);
$('#test_transport').prop("disabled",false);
$('#test_transport').text(prev);
}
});
})
// DKIM private key modal
$('#showDKIMprivKey').on('show.bs.modal', function (e) {
$('#priv_key_pre').text("-");
p_related = $(e.relatedTarget)
if (p_related != null) {
var decoded_key = Base64.decode((p_related.data('priv-key')));
$('#priv_key_pre').text(decoded_key);
}
})
// App links
function add_table_row(table_id) {
var row = $('<tr />');
cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>';
cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">Remove row</a></td>';
row.append(cols);
table_id.append(row);
}
$('#app_link_table').on('click', 'tr a', function (e) {
e.preventDefault();
$(this).parents('tr').remove();
});
$('#add_app_link_row').click(function() {
add_table_row($('#app_link_table'));
});
});
$(window).load(function(){
$('.sidebar').affix({
offset: {
top: 0
}
}).on('affix.bs.affix',function(){
setAffixContainerSize();
});
/*Setting the width of the sidebar (I took 10px of its value which is the margin between cols in my Bootstrap CSS*/
function setAffixContainerSize(){
$('.sidebar').width($('.sidebar').parent().innerWidth()-10);
}
$(window).resize(function(){
setAffixContainerSize();
});
initial_width_config = $("#sidebar-admin-config").width();
initial_width_maps = $("#sidebar-admin-maps").width();
$("#scrollbox-config").css("width", initial_width_config);
$("#scrollbox-maps").css("width", initial_width_maps);
if (sessionStorage.scrollTop > 70) {
$('#scrollbox-config').addClass('scrollboxFixed');
$('#scrollbox-maps').addClass('scrollboxFixed');
}
$(window).bind('scroll', function() {
if ($(window).scrollTop() > 70) {
$('#scrollbox-config').addClass('scrollboxFixed');
$('#scrollbox-maps').addClass('scrollboxFixed');
} else {
$('#scrollbox-config').removeClass('scrollboxFixed');
$('#scrollbox-maps').removeClass('scrollboxFixed');
}
});
});
function resizeScrollbox() {
on_resize_width_config = $("#sidebar-admin-config").width();
on_resize_width_maps = $("#sidebar-admin-maps").width();
$("#scrollbox-config").removeAttr("style");
$("#scrollbox-config").css("width", on_resize_width_config);
$("#scrollbox-maps").removeAttr("style");
$("#scrollbox-maps").css("width", on_resize_width_maps);
}
$(window).on('resize', resizeScrollbox);
$('a[data-toggle="tab"]').on('shown.bs.tab', resizeScrollbox);
diff --git a/data/web/json_api.php b/data/web/json_api.php
index a04cb491..8490f3e4 100644
--- a/data/web/json_api.php
+++ b/data/web/json_api.php
@@ -1,1514 +1,1538 @@
<?php
/*
-edit/alias => POST data:
- {
- address: {a, b, c}, (where a, b, c represent alias addresses)
- active: 1 (0 or 1)
- }
-
-delete/alias => POST data:
- {
- address: {a, b, c}, (where a, b, c represent alias addresses)
- }
-
+ see /api
*/
+
header('Content-Type: application/json');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
error_reporting(0);
function api_log($_data) {
global $redis;
$data_var = array();
foreach ($_data as $data => &$value) {
if ($data == 'csrf_token') {
continue;
}
if ($value = json_decode($value, true)) {
unset($value["csrf_token"]);
foreach ($value as $key => &$val) {
if(preg_match("/pass/i", $key)) {
$val = '*';
}
}
$value = json_encode($value);
}
$data_var[] = $data . "='" . $value . "'";
}
try {
$log_line = array(
'time' => time(),
'uri' => $_SERVER['REQUEST_URI'],
'method' => $_SERVER['REQUEST_METHOD'],
'remote' => get_remote_ip(),
'data' => implode(', ', $data_var)
);
$redis->lPush('API_LOG', json_encode($log_line));
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_username'])) {
if (isset($_GET['query'])) {
$query = explode('/', $_GET['query']);
$action = (isset($query[0])) ? $query[0] : null;
$category = (isset($query[1])) ? $query[1] : null;
$object = (isset($query[2])) ? $query[2] : null;
$extra = (isset($query[3])) ? $query[3] : null;
// accept json in request body
if($_SERVER['HTTP_CONTENT_TYPE'] === 'application/json') {
$request = file_get_contents('php://input');
$requestDecoded = json_decode($request, true);
// check for valid json
if ($action != 'get' && $requestDecoded === null) {
http_response_code(400);
echo json_encode(array(
'type' => 'error',
'msg' => 'Request body doesn\'t contain valid json!'
));
exit;
}
// add
if ($action == 'add') {
$_POST['attr'] = $request;
}
// edit
if ($action == 'edit') {
$_POST['attr'] = json_encode($requestDecoded['attr']);
$_POST['items'] = json_encode($requestDecoded['items']);
}
// delete
if ($action == 'delete') {
$_POST['items'] = $request;
}
}
api_log($_POST);
$request_incomplete = json_encode(array(
'type' => 'error',
'msg' => 'Cannot find attributes in post data'
));
switch ($action) {
case "add":
+ if ($_SESSION['mailcow_cc_api_access'] == 'ro' || isset($_SESSION['pending_mailcow_cc_username'])) {
+ http_response_code(403);
+ echo json_encode(array(
+ 'type' => 'error',
+ 'msg' => 'API read/write access denied'
+ ));
+ exit();
+ }
function process_add_return($return) {
$generic_failure = json_encode(array(
'type' => 'error',
'msg' => 'Cannot add item'
));
$generic_success = json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
if ($return === false) {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_failure;
}
else {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
}
}
if (!isset($_POST['attr'])) {
echo $request_incomplete;
exit;
}
else {
$attr = (array)json_decode($_POST['attr'], true);
unset($attr['csrf_token']);
}
// only allow POST requests to POST API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(405);
echo json_encode(array(
'type' => 'error',
'msg' => 'only POST method is allowed'
));
exit();
}
+
switch ($category) {
case "time_limited_alias":
process_add_return(mailbox('add', 'time_limited_alias', $attr));
break;
case "relayhost":
process_add_return(relayhost('add', $attr));
break;
case "transport":
process_add_return(transport('add', $attr));
break;
case "rsetting":
process_add_return(rsettings('add', $attr));
break;
case "mailbox":
process_add_return(mailbox('add', 'mailbox', $attr));
break;
case "oauth2-client":
process_add_return(oauth2('add', 'client', $attr));
break;
case "domain":
process_add_return(mailbox('add', 'domain', $attr));
break;
case "resource":
process_add_return(mailbox('add', 'resource', $attr));
break;
case "alias":
process_add_return(mailbox('add', 'alias', $attr));
break;
case "filter":
process_add_return(mailbox('add', 'filter', $attr));
break;
case "global-filter":
process_add_return(mailbox('add', 'global_filter', $attr));
break;
case "domain-policy":
process_add_return(policy('add', 'domain', $attr));
break;
case "mailbox-policy":
process_add_return(policy('add', 'mailbox', $attr));
break;
case "alias-domain":
process_add_return(mailbox('add', 'alias_domain', $attr));
break;
case "fwdhost":
process_add_return(fwdhost('add', $attr));
break;
case "dkim":
process_add_return(dkim('add', $attr));
break;
case "dkim_duplicate":
process_add_return(dkim('duplicate', $attr));
break;
case "dkim_import":
process_add_return(dkim('import', $attr));
break;
case "domain-admin":
process_add_return(domain_admin('add', $attr));
break;
case "admin":
process_add_return(admin('add', $attr));
break;
case "syncjob":
process_add_return(mailbox('add', 'syncjob', $attr));
break;
case "bcc":
process_add_return(bcc('add', $attr));
break;
case "recipient_map":
process_add_return(recipient_map('add', $attr));
break;
case "tls-policy-map":
process_add_return(tls_policy_maps('add', $attr));
break;
case "app-passwd":
process_add_return(app_passwd('add', $attr));
break;
// return no route found if no case is matched
default:
http_response_code(404);
echo json_encode(array(
'type' => 'error',
'msg' => 'route not found'
));
exit();
}
break;
case "get":
function process_get_return($data) {
echo (!isset($data) || empty($data)) ? '{}' : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
// only allow GET requests to GET API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'GET') {
http_response_code(405);
echo json_encode(array(
'type' => 'error',
'msg' => 'only GET method is allowed'
));
exit();
}
- switch ($category) {
- case "rspamd":
- switch ($object) {
- case "actions":
- $curl = curl_init();
- curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
- curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat");
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
- $data = curl_exec($curl);
- if ($data) {
- $return = array();
- $stats_array = json_decode($data, true)['actions'];
- $stats_array['soft reject'] = $stats_array['soft reject'] + $stats_array['greylist'];
- unset($stats_array['greylist']);
- foreach ($stats_array as $action => $count) {
- $return[] = array($action, $count);
- }
- echo json_encode($return, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
- }
- elseif (!isset($data) || empty($data)) {
- echo '{}';
- }
- break;
- }
- break;
-
- case "domain":
- switch ($object) {
- case "all":
- $domains = mailbox('get', 'domains');
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- if ($details = mailbox('get', 'domain_details', $domain)) {
- $data[] = $details;
+ if (!isset($_SESSION['pending_mailcow_cc_username'])) {
+ switch ($category) {
+ case "u2f-registration":
+ header('Content-Type: application/javascript');
+ if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
+ list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
+ $_SESSION['regReq'] = json_encode($req);
+ $_SESSION['regSigs'] = json_encode($sigs);
+ echo 'var req = ' . json_encode($req) . ';';
+ echo 'var registeredKeys = ' . json_encode($sigs) . ';';
+ echo 'var appId = req.appId;';
+ echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
+ }
+ else {
+ return;
+ }
+ break;
+ case "u2f-authentication":
+ header('Content-Type: application/javascript');
+ if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
+ $auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
+ $challenge = $auth_data[0]->challenge;
+ $appId = $auth_data[0]->appId;
+ foreach ($auth_data as $each) {
+ $key = array(); // Empty array
+ $key['version'] = $each->version;
+ $key['keyHandle'] = $each->keyHandle;
+ $registeredKey[] = $key;
+ }
+ $_SESSION['authReq'] = json_encode($auth_data);
+ echo 'var appId = "' . $appId . '";';
+ echo 'var challenge = ' . json_encode($challenge) . ';';
+ echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
+ }
+ else {
+ return;
+ }
+ break;
+ case "rspamd":
+ switch ($object) {
+ case "actions":
+ $curl = curl_init();
+ curl_setopt($curl, CURLOPT_UNIX_SOCKET_PATH, '/var/lib/rspamd/rspamd.sock');
+ curl_setopt($curl, CURLOPT_URL,"http://rspamd/stat");
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+ $data = curl_exec($curl);
+ if ($data) {
+ $return = array();
+ $stats_array = json_decode($data, true)['actions'];
+ $stats_array['soft reject'] = $stats_array['soft reject'] + $stats_array['greylist'];
+ unset($stats_array['greylist']);
+ foreach ($stats_array as $action => $count) {
+ $return[] = array($action, $count);
}
- else {
- continue;
+ echo json_encode($return, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
+ }
+ elseif (!isset($data) || empty($data)) {
+ echo '{}';
+ }
+ break;
+ }
+ break;
+
+ case "domain":
+ switch ($object) {
+ case "all":
+ $domains = mailbox('get', 'domains');
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ if ($details = mailbox('get', 'domain_details', $domain)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = mailbox('get', 'domain_details', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = mailbox('get', 'domain_details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "app-passwd":
- switch ($object) {
- case "all":
- if (empty($extra)) {
- $app_passwds = app_passwd('get');
- }
- else {
- $app_passwds = app_passwd('get', array('username' => $extra));
- }
- if (!empty($app_passwds)) {
- foreach ($app_passwds as $app_passwd) {
- $details = app_passwd('details', array('id' => $app_passwd['id']));
- if ($details !== false) {
- $data[] = $details;
- }
- else {
- continue;
+ case "app-passwd":
+ switch ($object) {
+ case "all":
+ if (empty($extra)) {
+ $app_passwds = app_passwd('get');
+ }
+ else {
+ $app_passwds = app_passwd('get', array('username' => $extra));
+ }
+ if (!empty($app_passwds)) {
+ foreach ($app_passwds as $app_passwd) {
+ $details = app_passwd('details', array('id' => $app_passwd['id']));
+ if ($details !== false) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = app_passwd('details', array('id' => $object['id']));
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = app_passwd('details', array('id' => $object['id']));
+ process_get_return($data);
+ break;
+ }
+ break;
- case "mailq":
- switch ($object) {
- case "all":
- $mailq = mailq('get');
- if (!empty($mailq)) {
- echo $mailq;
- }
- else {
- echo '{}';
- }
- break;
- }
- break;
+ case "mailq":
+ switch ($object) {
+ case "all":
+ $mailq = mailq('get');
+ if (!empty($mailq)) {
+ echo $mailq;
+ }
+ else {
+ echo '{}';
+ }
+ break;
+ }
+ break;
- case "global_filters":
- $global_filters = mailbox('get', 'global_filter_details');
- switch ($object) {
- case "all":
- if (!empty($global_filters)) {
- process_get_return($global_filters);
- }
- else {
- echo '{}';
- }
- break;
- case "prefilter":
- if (!empty($global_filters['prefilter'])) {
- process_get_return($global_filters['prefilter']);
- }
- else {
- echo '{}';
- }
- break;
- case "postfilter":
- if (!empty($global_filters['postfilter'])) {
- process_get_return($global_filters['postfilter']);
- }
- else {
- echo '{}';
- }
- break;
- }
- break;
+ case "global_filters":
+ $global_filters = mailbox('get', 'global_filter_details');
+ switch ($object) {
+ case "all":
+ if (!empty($global_filters)) {
+ process_get_return($global_filters);
+ }
+ else {
+ echo '{}';
+ }
+ break;
+ case "prefilter":
+ if (!empty($global_filters['prefilter'])) {
+ process_get_return($global_filters['prefilter']);
+ }
+ else {
+ echo '{}';
+ }
+ break;
+ case "postfilter":
+ if (!empty($global_filters['postfilter'])) {
+ process_get_return($global_filters['postfilter']);
+ }
+ else {
+ echo '{}';
+ }
+ break;
+ }
+ break;
- case "rl-domain":
- switch ($object) {
- case "all":
- $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- if ($details = ratelimit('get', 'domain', $domain)) {
- $details['domain'] = $domain;
- $data[] = $details;
- }
- else {
- continue;
+ case "rl-domain":
+ switch ($object) {
+ case "all":
+ $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ if ($details = ratelimit('get', 'domain', $domain)) {
+ $details['domain'] = $domain;
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = ratelimit('get', 'domain', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = ratelimit('get', 'domain', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "rl-mbox":
- switch ($object) {
- case "all":
- $domains = mailbox('get', 'domains');
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $mailboxes = mailbox('get', 'mailboxes', $domain);
- if (!empty($mailboxes)) {
- foreach ($mailboxes as $mailbox) {
- if ($details = ratelimit('get', 'mailbox', $mailbox)) {
- $details['mailbox'] = $mailbox;
- $data[] = $details;
- }
- else {
- continue;
+ case "rl-mbox":
+ switch ($object) {
+ case "all":
+ $domains = mailbox('get', 'domains');
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $mailboxes = mailbox('get', 'mailboxes', $domain);
+ if (!empty($mailboxes)) {
+ foreach ($mailboxes as $mailbox) {
+ if ($details = ratelimit('get', 'mailbox', $mailbox)) {
+ $details['mailbox'] = $mailbox;
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = ratelimit('get', 'mailbox', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = ratelimit('get', 'mailbox', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "relayhost":
- switch ($object) {
- case "all":
- $relayhosts = relayhost('get');
- if (!empty($relayhosts)) {
- foreach ($relayhosts as $relayhost) {
- if ($details = relayhost('details', $relayhost['id'])) {
- $data[] = $details;
- }
- else {
- continue;
+ case "relayhost":
+ switch ($object) {
+ case "all":
+ $relayhosts = relayhost('get');
+ if (!empty($relayhosts)) {
+ foreach ($relayhosts as $relayhost) {
+ if ($details = relayhost('details', $relayhost['id'])) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = relayhost('details', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = relayhost('details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "transport":
- switch ($object) {
- case "all":
- $transports = transport('get');
- if (!empty($transports)) {
- foreach ($transports as $transport) {
- if ($details = transport('details', $transport['id'])) {
- $data[] = $details;
- }
- else {
- continue;
+ case "transport":
+ switch ($object) {
+ case "all":
+ $transports = transport('get');
+ if (!empty($transports)) {
+ foreach ($transports as $transport) {
+ if ($details = transport('details', $transport['id'])) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = transport('details', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = transport('details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "rsetting":
- switch ($object) {
- case "all":
- $rsettings = rsettings('get');
- if (!empty($rsettings)) {
- foreach ($rsettings as $rsetting) {
- if ($details = rsettings('details', $rsetting['id'])) {
- $data[] = $details;
- }
- else {
- continue;
+ case "rsetting":
+ switch ($object) {
+ case "all":
+ $rsettings = rsettings('get');
+ if (!empty($rsettings)) {
+ foreach ($rsettings as $rsetting) {
+ if ($details = rsettings('details', $rsetting['id'])) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = rsetting('details', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = rsetting('details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "oauth2-client":
- switch ($object) {
- case "all":
- $clients = oauth2('get', 'clients');
- if (!empty($clients)) {
- foreach ($clients as $client) {
- if ($details = oauth2('details', 'client', $client)) {
- $data[] = $details;
- }
- else {
- continue;
+ case "oauth2-client":
+ switch ($object) {
+ case "all":
+ $clients = oauth2('get', 'clients');
+ if (!empty($clients)) {
+ foreach ($clients as $client) {
+ if ($details = oauth2('details', 'client', $client)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = oauth2('details', 'client', $object);
- process_get_return($data);
- break;
- }
- break;
+ default:
+ $data = oauth2('details', 'client', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
- case "logs":
- switch ($object) {
- case "dovecot":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('dovecot-mailcow', $extra);
- }
- else {
- $logs = get_logs('dovecot-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "ratelimited":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('ratelimited', $extra);
- }
- else {
- $logs = get_logs('ratelimited');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "netfilter":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('netfilter-mailcow', $extra);
- }
- else {
- $logs = get_logs('netfilter-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "postfix":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('postfix-mailcow', $extra);
- }
- else {
- $logs = get_logs('postfix-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "autodiscover":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('autodiscover-mailcow', $extra);
- }
- else {
- $logs = get_logs('autodiscover-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "sogo":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('sogo-mailcow', $extra);
- }
- else {
- $logs = get_logs('sogo-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "ui":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('mailcow-ui', $extra);
- }
- else {
- $logs = get_logs('mailcow-ui');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "watchdog":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('watchdog-mailcow', $extra);
- }
- else {
- $logs = get_logs('watchdog-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "acme":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('acme-mailcow', $extra);
- }
- else {
- $logs = get_logs('acme-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "api":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('api-mailcow', $extra);
- }
- else {
- $logs = get_logs('api-mailcow');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- case "rspamd-history":
- // 0 is first record, so empty is fine
- if (isset($extra)) {
- $extra = preg_replace('/[^\d\-]/i', '', $extra);
- $logs = get_logs('rspamd-history', $extra);
- }
- else {
- $logs = get_logs('rspamd-history');
- }
- echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
- break;
- // return no route found if no case is matched
- default:
- http_response_code(404);
- echo json_encode(array(
- 'type' => 'error',
- 'msg' => 'route not found'
- ));
- exit();
- }
- break;
- case "mailbox":
- switch ($object) {
- case "all":
- if (empty($extra)) {
- $domains = mailbox('get', 'domains');
- }
- else {
- $domains = array($extra);
- }
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $mailboxes = mailbox('get', 'mailboxes', $domain);
- if (!empty($mailboxes)) {
- foreach ($mailboxes as $mailbox) {
- if ($details = mailbox('get', 'mailbox_details', $mailbox)) {
- $data[] = $details;
- }
- else {
- continue;
+ case "logs":
+ switch ($object) {
+ case "dovecot":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('dovecot-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('dovecot-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "ratelimited":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('ratelimited', $extra);
+ }
+ else {
+ $logs = get_logs('ratelimited');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "netfilter":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('netfilter-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('netfilter-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "postfix":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('postfix-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('postfix-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "autodiscover":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('autodiscover-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('autodiscover-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "sogo":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('sogo-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('sogo-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "ui":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('mailcow-ui', $extra);
+ }
+ else {
+ $logs = get_logs('mailcow-ui');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "watchdog":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('watchdog-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('watchdog-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "acme":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('acme-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('acme-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "api":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('api-mailcow', $extra);
+ }
+ else {
+ $logs = get_logs('api-mailcow');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ case "rspamd-history":
+ // 0 is first record, so empty is fine
+ if (isset($extra)) {
+ $extra = preg_replace('/[^\d\-]/i', '', $extra);
+ $logs = get_logs('rspamd-history', $extra);
+ }
+ else {
+ $logs = get_logs('rspamd-history');
+ }
+ echo (isset($logs) && !empty($logs)) ? json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) : '{}';
+ break;
+ // return no route found if no case is matched
+ default:
+ http_response_code(404);
+ echo json_encode(array(
+ 'type' => 'error',
+ 'msg' => 'route not found'
+ ));
+ exit();
+ }
+ break;
+ case "mailbox":
+ switch ($object) {
+ case "all":
+ if (empty($extra)) {
+ $domains = mailbox('get', 'domains');
+ }
+ else {
+ $domains = array($extra);
+ }
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $mailboxes = mailbox('get', 'mailboxes', $domain);
+ if (!empty($mailboxes)) {
+ foreach ($mailboxes as $mailbox) {
+ if ($details = mailbox('get', 'mailbox_details', $mailbox)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $data = mailbox('get', 'mailbox_details', $object);
- process_get_return($data);
- break;
- }
- break;
- case "syncjobs":
- switch ($object) {
- case "all":
- $domains = mailbox('get', 'domains');
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $mailboxes = mailbox('get', 'mailboxes', $domain);
- if (!empty($mailboxes)) {
- foreach ($mailboxes as $mailbox) {
- $syncjobs = mailbox('get', 'syncjobs', $mailbox);
- if (!empty($syncjobs)) {
- foreach ($syncjobs as $syncjob) {
- if (isset($extra)) {
- $details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
- }
- else {
- $details = mailbox('get', 'syncjob_details', $syncjob);
- }
- if ($details) {
- $data[] = $details;
- }
- else {
- continue;
+ default:
+ $data = mailbox('get', 'mailbox_details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "syncjobs":
+ switch ($object) {
+ case "all":
+ $domains = mailbox('get', 'domains');
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $mailboxes = mailbox('get', 'mailboxes', $domain);
+ if (!empty($mailboxes)) {
+ foreach ($mailboxes as $mailbox) {
+ $syncjobs = mailbox('get', 'syncjobs', $mailbox);
+ if (!empty($syncjobs)) {
+ foreach ($syncjobs as $syncjob) {
+ if (isset($extra)) {
+ $details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
+ }
+ else {
+ $details = mailbox('get', 'syncjob_details', $syncjob);
+ }
+ if ($details) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
}
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $syncjobs = mailbox('get', 'syncjobs', $object);
- if (!empty($syncjobs)) {
- foreach ($syncjobs as $syncjob) {
- if (isset($extra)) {
- $details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
- }
- else {
- $details = mailbox('get', 'syncjob_details', $syncjob);
- }
- if ($details) {
- $data[] = $details;
- }
- else {
- continue;
+ default:
+ $syncjobs = mailbox('get', 'syncjobs', $object);
+ if (!empty($syncjobs)) {
+ foreach ($syncjobs as $syncjob) {
+ if (isset($extra)) {
+ $details = mailbox('get', 'syncjob_details', $syncjob, explode(',', $extra));
+ }
+ else {
+ $details = mailbox('get', 'syncjob_details', $syncjob);
+ }
+ if ($details) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "active-user-sieve":
+ if (isset($object)) {
+ $sieve_filter = mailbox('get', 'active_user_sieve', $object);
+ if (!empty($sieve_filter)) {
+ $data[] = $sieve_filter;
}
- process_get_return($data);
- break;
- }
- break;
- case "active-user-sieve":
- if (isset($object)) {
- $sieve_filter = mailbox('get', 'active_user_sieve', $object);
- if (!empty($sieve_filter)) {
- $data[] = $sieve_filter;
}
- }
- process_get_return($data);
- break;
- case "filters":
- switch ($object) {
- case "all":
- $domains = mailbox('get', 'domains');
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $mailboxes = mailbox('get', 'mailboxes', $domain);
- if (!empty($mailboxes)) {
- foreach ($mailboxes as $mailbox) {
- $filters = mailbox('get', 'filters', $mailbox);
- if (!empty($filters)) {
- foreach ($filters as $filter) {
- if ($details = mailbox('get', 'filter_details', $filter)) {
- $data[] = $details;
- }
- else {
- continue;
+ process_get_return($data);
+ break;
+ case "filters":
+ switch ($object) {
+ case "all":
+ $domains = mailbox('get', 'domains');
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $mailboxes = mailbox('get', 'mailboxes', $domain);
+ if (!empty($mailboxes)) {
+ foreach ($mailboxes as $mailbox) {
+ $filters = mailbox('get', 'filters', $mailbox);
+ if (!empty($filters)) {
+ foreach ($filters as $filter) {
+ if ($details = mailbox('get', 'filter_details', $filter)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
}
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- $filters = mailbox('get', 'filters', $object);
- if (!empty($filters)) {
- foreach ($filters as $filter) {
- if ($details = mailbox('get', 'filter_details', $filter)) {
- $data[] = $details;
- }
- else {
- continue;
+ default:
+ $filters = mailbox('get', 'filters', $object);
+ if (!empty($filters)) {
+ foreach ($filters as $filter) {
+ if ($details = mailbox('get', 'filter_details', $filter)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
- }
- process_get_return($data);
- break;
- }
- break;
- case "bcc":
- switch ($object) {
- case "all":
- $bcc_items = bcc('get');
- if (!empty($bcc_items)) {
- foreach ($bcc_items as $bcc_item) {
- if ($details = bcc('details', $bcc_item)) {
- $data[] = $details;
- }
- else {
- continue;
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "bcc":
+ switch ($object) {
+ case "all":
+ $bcc_items = bcc('get');
+ if (!empty($bcc_items)) {
+ foreach ($bcc_items as $bcc_item) {
+ if ($details = bcc('details', $bcc_item)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
- }
- process_get_return($data);
- break;
- default:
- $data = bcc('details', $object);
- if (!empty($data)) {
- $data[] = $details;
- }
- process_get_return($data);
- break;
- }
- break;
- case "recipient_map":
- switch ($object) {
- case "all":
- $recipient_map_items = recipient_map('get');
- if (!empty($recipient_map_items)) {
- foreach ($recipient_map_items as $recipient_map_item) {
- if ($details = recipient_map('details', $recipient_map_item)) {
- $data[] = $details;
- }
- else {
- continue;
- }
+ process_get_return($data);
+ break;
+ default:
+ $data = bcc('details', $object);
+ if (!empty($data)) {
+ $data[] = $details;
}
- }
- process_get_return($data);
- break;
- default:
- $data = recipient_map('details', $object);
- if (!empty($data)) {
- $data[] = $details;
- }
- process_get_return($data);
- break;
- }
- break;
- case "tls-policy-map":
- switch ($object) {
- case "all":
- $tls_policy_maps_items = tls_policy_maps('get');
- if (!empty($tls_policy_maps_items)) {
- foreach ($tls_policy_maps_items as $tls_policy_maps_item) {
- if ($details = tls_policy_maps('details', $tls_policy_maps_item)) {
- $data[] = $details;
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "recipient_map":
+ switch ($object) {
+ case "all":
+ $recipient_map_items = recipient_map('get');
+ if (!empty($recipient_map_items)) {
+ foreach ($recipient_map_items as $recipient_map_item) {
+ if ($details = recipient_map('details', $recipient_map_item)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
- else {
- continue;
+ }
+ process_get_return($data);
+ break;
+ default:
+ $data = recipient_map('details', $object);
+ if (!empty($data)) {
+ $data[] = $details;
+ }
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "tls-policy-map":
+ switch ($object) {
+ case "all":
+ $tls_policy_maps_items = tls_policy_maps('get');
+ if (!empty($tls_policy_maps_items)) {
+ foreach ($tls_policy_maps_items as $tls_policy_maps_item) {
+ if ($details = tls_policy_maps('details', $tls_policy_maps_item)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
- }
- process_get_return($data);
- break;
- default:
- $data = tls_policy_maps('details', $object);
- if (!empty($data)) {
- $data[] = $details;
- }
- process_get_return($data);
- break;
- }
- break;
- case "policy_wl_mailbox":
- switch ($object) {
- default:
- $data = policy('get', 'mailbox', $object)['whitelist'];
- process_get_return($data);
- break;
- }
- break;
- case "policy_bl_mailbox":
- switch ($object) {
- default:
- $data = policy('get', 'mailbox', $object)['blacklist'];
- process_get_return($data);
- break;
- }
- break;
- case "policy_wl_domain":
- switch ($object) {
- default:
- $data = policy('get', 'domain', $object)['whitelist'];
- process_get_return($data);
- break;
- }
- break;
- case "policy_bl_domain":
- switch ($object) {
- default:
- $data = policy('get', 'domain', $object)['blacklist'];
- process_get_return($data);
- break;
- }
- break;
- case "time_limited_aliases":
- switch ($object) {
- default:
- $data = mailbox('get', 'time_limited_aliases', $object);
- process_get_return($data);
- break;
- }
- break;
- case "fail2ban":
- switch ($object) {
- default:
- $data = fail2ban('get');
- process_get_return($data);
- break;
- }
- break;
- case "resource":
- switch ($object) {
- case "all":
- $domains = mailbox('get', 'domains');
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $resources = mailbox('get', 'resources', $domain);
- if (!empty($resources)) {
- foreach ($resources as $resource) {
- if ($details = mailbox('get', 'resource_details', $resource)) {
- $data[] = $details;
- }
- else {
- continue;
+ process_get_return($data);
+ break;
+ default:
+ $data = tls_policy_maps('details', $object);
+ if (!empty($data)) {
+ $data[] = $details;
+ }
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "policy_wl_mailbox":
+ switch ($object) {
+ default:
+ $data = policy('get', 'mailbox', $object)['whitelist'];
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "policy_bl_mailbox":
+ switch ($object) {
+ default:
+ $data = policy('get', 'mailbox', $object)['blacklist'];
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "policy_wl_domain":
+ switch ($object) {
+ default:
+ $data = policy('get', 'domain', $object)['whitelist'];
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "policy_bl_domain":
+ switch ($object) {
+ default:
+ $data = policy('get', 'domain', $object)['blacklist'];
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "time_limited_aliases":
+ switch ($object) {
+ default:
+ $data = mailbox('get', 'time_limited_aliases', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "fail2ban":
+ switch ($object) {
+ default:
+ $data = fail2ban('get');
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "resource":
+ switch ($object) {
+ case "all":
+ $domains = mailbox('get', 'domains');
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $resources = mailbox('get', 'resources', $domain);
+ if (!empty($resources)) {
+ foreach ($resources as $resource) {
+ if ($details = mailbox('get', 'resource_details', $resource)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
+ process_get_return($data);
}
+ else {
+ echo '{}';
+ }
+ break;
+ default:
+ $data = mailbox('get', 'resource_details', $object);
process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
- default:
- $data = mailbox('get', 'resource_details', $object);
- process_get_return($data);
- break;
- }
- break;
- case "fwdhost":
- switch ($object) {
- case "all":
- process_get_return(fwdhost('get'));
- break;
- default:
- process_get_return(fwdhost('details', $object));
- break;
- }
- break;
- case "quarantine":
- // "all" will not print details
- switch ($object) {
- case "all":
- process_get_return(quarantine('get'));
- break;
- default:
- process_get_return(quarantine('details', $object));
- break;
- }
- break;
- case "alias-domain":
- switch ($object) {
- case "all":
- $alias_domains = mailbox('get', 'alias_domains');
- if (!empty($alias_domains)) {
- foreach ($alias_domains as $alias_domain) {
- if ($details = mailbox('get', 'alias_domain_details', $alias_domain)) {
- $data[] = $details;
- }
- else {
- continue;
+ break;
+ }
+ break;
+ case "fwdhost":
+ switch ($object) {
+ case "all":
+ process_get_return(fwdhost('get'));
+ break;
+ default:
+ process_get_return(fwdhost('details', $object));
+ break;
+ }
+ break;
+ case "quarantine":
+ // "all" will not print details
+ switch ($object) {
+ case "all":
+ process_get_return(quarantine('get'));
+ break;
+ default:
+ process_get_return(quarantine('details', $object));
+ break;
+ }
+ break;
+ case "alias-domain":
+ switch ($object) {
+ case "all":
+ $alias_domains = mailbox('get', 'alias_domains');
+ if (!empty($alias_domains)) {
+ foreach ($alias_domains as $alias_domain) {
+ if ($details = mailbox('get', 'alias_domain_details', $alias_domain)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
- }
- process_get_return($data);
- break;
- default:
- process_get_return(mailbox('get', 'alias_domain_details', $object));
- break;
- }
- break;
- case "alias":
- switch ($object) {
- case "all":
- if (empty($extra)) {
- $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
- }
- else {
- $domains = array($extra);
- }
- if (!empty($domains)) {
- foreach ($domains as $domain) {
- $aliases = mailbox('get', 'aliases', $domain);
- if (!empty($aliases)) {
- foreach ($aliases as $alias) {
- if ($details = mailbox('get', 'alias_details', $alias)) {
- $data[] = $details;
- }
- else {
- continue;
+ process_get_return($data);
+ break;
+ default:
+ process_get_return(mailbox('get', 'alias_domain_details', $object));
+ break;
+ }
+ break;
+ case "alias":
+ switch ($object) {
+ case "all":
+ if (empty($extra)) {
+ $domains = array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains'));
+ }
+ else {
+ $domains = array($extra);
+ }
+ if (!empty($domains)) {
+ foreach ($domains as $domain) {
+ $aliases = mailbox('get', 'aliases', $domain);
+ if (!empty($aliases)) {
+ foreach ($aliases as $alias) {
+ if ($details = mailbox('get', 'alias_details', $alias)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
}
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- process_get_return(mailbox('get', 'alias_details', $object));
- break;
- }
- break;
- case "domain-admin":
- switch ($object) {
- case "all":
- $domain_admins = domain_admin('get');
- if (!empty($domain_admins)) {
- foreach ($domain_admins as $domain_admin) {
- if ($details = domain_admin('details', $domain_admin)) {
- $data[] = $details;
- }
- else {
- continue;
+ default:
+ process_get_return(mailbox('get', 'alias_details', $object));
+ break;
+ }
+ break;
+ case "domain-admin":
+ switch ($object) {
+ case "all":
+ $domain_admins = domain_admin('get');
+ if (!empty($domain_admins)) {
+ foreach ($domain_admins as $domain_admin) {
+ if ($details = domain_admin('details', $domain_admin)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- process_get_return(domain_admin('details', $object));
- break;
- }
- break;
- case "admin":
- switch ($object) {
- case "all":
- $admins = admin('get');
- if (!empty($admins)) {
- foreach ($admins as $admin) {
- if ($details = admin('details', $admin)) {
- $data[] = $details;
- }
- else {
- continue;
+ default:
+ process_get_return(domain_admin('details', $object));
+ break;
+ }
+ break;
+ case "admin":
+ switch ($object) {
+ case "all":
+ $admins = admin('get');
+ if (!empty($admins)) {
+ foreach ($admins as $admin) {
+ if ($details = admin('details', $admin)) {
+ $data[] = $details;
+ }
+ else {
+ continue;
+ }
}
+ process_get_return($data);
}
- process_get_return($data);
- }
- else {
- echo '{}';
- }
- break;
+ else {
+ echo '{}';
+ }
+ break;
- default:
- process_get_return(admin('details', $object));
- break;
- }
- break;
- case "u2f-registration":
- header('Content-Type: application/javascript');
- if (($_SESSION["mailcow_cc_role"] == "admin" || $_SESSION["mailcow_cc_role"] == "domainadmin") && $_SESSION["mailcow_cc_username"] == $object) {
- list($req, $sigs) = $u2f->getRegisterData(get_u2f_registrations($object));
- $_SESSION['regReq'] = json_encode($req);
- $_SESSION['regSigs'] = json_encode($sigs);
- echo 'var req = ' . json_encode($req) . ';';
- echo 'var registeredKeys = ' . json_encode($sigs) . ';';
- echo 'var appId = req.appId;';
- echo 'var registerRequests = [{version: req.version, challenge: req.challenge}];';
- }
- else {
- return;
- }
- break;
- case "u2f-authentication":
- header('Content-Type: application/javascript');
- if (isset($_SESSION['pending_mailcow_cc_username']) && $_SESSION['pending_mailcow_cc_username'] == $object) {
- $auth_data = $u2f->getAuthenticateData(get_u2f_registrations($object));
- $challenge = $auth_data[0]->challenge;
- $appId = $auth_data[0]->appId;
- foreach ($auth_data as $each) {
- $key = array(); // Empty array
- $key['version'] = $each->version;
- $key['keyHandle'] = $each->keyHandle;
- $registeredKey[] = $key;
+ default:
+ process_get_return(admin('details', $object));
+ break;
}
- $_SESSION['authReq'] = json_encode($auth_data);
- echo 'var appId = "' . $appId . '";';
- echo 'var challenge = ' . json_encode($challenge) . ';';
- echo 'var registeredKeys = ' . json_encode($registeredKey) . ';';
- }
- else {
- return;
- }
- break;
- case "dkim":
- switch ($object) {
- default:
- $data = dkim('details', $object);
- process_get_return($data);
+ break;
+ case "dkim":
+ switch ($object) {
+ default:
+ $data = dkim('details', $object);
+ process_get_return($data);
+ break;
+ }
+ break;
+ case "presets":
+ switch ($object) {
+ case "rspamd":
+ process_get_return(presets('get', 'rspamd'));
break;
- }
- break;
- case "presets":
- switch ($object) {
- case "rspamd":
- process_get_return(presets('get', 'rspamd'));
- break;
- case "sieve":
- process_get_return(presets('get', 'sieve'));
- break;
- }
- break;
- case "status":
- switch ($object) {
- case "containers":
- $containers = (docker('info'));
- foreach ($containers as $container => $container_info) {
- $container . ' (' . $container_info['Config']['Image'] . ')';
- $containerstarttime = ($container_info['State']['StartedAt']);
- $containerstate = ($container_info['State']['Status']);
- $containerimage = ($container_info['Config']['Image']);
- $temp[$container] = array(
+ case "sieve":
+ process_get_return(presets('get', 'sieve'));
+ break;
+ }
+ break;
+ case "status":
+ switch ($object) {
+ case "containers":
+ $containers = (docker('info'));
+ foreach ($containers as $container => $container_info) {
+ $container . ' (' . $container_info['Config']['Image'] . ')';
+ $containerstarttime = ($container_info['State']['StartedAt']);
+ $containerstate = ($container_info['State']['Status']);
+ $containerimage = ($container_info['Config']['Image']);
+ $temp[$container] = array(
+ 'type' => 'info',
+ 'container' => $container,
+ 'state' => $containerstate,
+ 'started_at' => $containerstarttime,
+ 'image' => $containerimage
+ );
+ }
+ echo json_encode($temp, JSON_UNESCAPED_SLASHES);
+ break;
+ case "vmail":
+ $exec_fields_vmail = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
+ $vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields_vmail), true));
+ $temp = array(
'type' => 'info',
- 'container' => $container,
- 'state' => $containerstate,
- 'started_at' => $containerstarttime,
- 'image' => $containerimage
+ 'disk' => $vmail_df[0],
+ 'used' => $vmail_df[2],
+ 'total'=> $vmail_df[1],
+ 'used_percent' => $vmail_df[4]
);
- }
- echo json_encode($temp, JSON_UNESCAPED_SLASHES);
+ echo json_encode($temp, JSON_UNESCAPED_SLASHES);
break;
- case "vmail":
- $exec_fields_vmail = array('cmd' => 'system', 'task' => 'df', 'dir' => '/var/vmail');
- $vmail_df = explode(',', json_decode(docker('post', 'dovecot-mailcow', 'exec', $exec_fields_vmail), true));
- $temp = array(
+ case "solr":
+ $solr_status = solr_status();
+ $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
+ $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
+ if (strtolower(getenv('SKIP_SOLR')) != 'n') {
+ $solr_enabled = false;
+ }
+ else {
+ $solr_enabled = true;
+ }
+ echo json_encode(array(
'type' => 'info',
- 'disk' => $vmail_df[0],
- 'used' => $vmail_df[2],
- 'total'=> $vmail_df[1],
- 'used_percent' => $vmail_df[4]
- );
- echo json_encode($temp, JSON_UNESCAPED_SLASHES);
- break;
- case "solr":
- $solr_status = solr_status();
- $solr_size = ($solr_status['status']['dovecot-fts']['index']['size']);
- $solr_documents = ($solr_status['status']['dovecot-fts']['index']['numDocs']);
- if (strtolower(getenv('SKIP_SOLR')) != 'n') {
- $solr_enabled = false;
- }
- else {
- $solr_enabled = true;
+ 'solr_enabled' => $solr_enabled,
+ 'solr_size' => $solr_size,
+ 'solr_documents' => $solr_documents
+ ));
+ break;
}
- echo json_encode(array(
- 'type' => 'info',
- 'solr_enabled' => $solr_enabled,
- 'solr_size' => $solr_size,
- 'solr_documents' => $solr_documents
- ));
break;
- }
break;
- break;
- // return no route found if no case is matched
- default:
- http_response_code(404);
+ // return no route found if no case is matched
+ default:
+ http_response_code(404);
+ echo json_encode(array(
+ 'type' => 'error',
+ 'msg' => 'route not found'
+ ));
+ exit();
+ }
+ }
+ break;
+ case "delete":
+ if ($_SESSION['mailcow_cc_api_access'] == 'ro' || isset($_SESSION['pending_mailcow_cc_username'])) {
+ http_response_code(403);
echo json_encode(array(
- 'type' => 'error',
- 'msg' => 'route not found'
+ 'type' => 'error',
+ 'msg' => 'API read/write access denied'
));
exit();
}
- break;
- case "delete":
function process_delete_return($return) {
$generic_failure = json_encode(array(
'type' => 'error',
'msg' => 'Cannot delete item'
));
$generic_success = json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
if ($return === false) {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_failure;
}
else {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
}
}
if (!isset($_POST['items'])) {
echo $request_incomplete;
exit;
}
else {
$items = (array)json_decode($_POST['items'], true);
}
// only allow POST requests to POST API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(405);
echo json_encode(array(
'type' => 'error',
'msg' => 'only POST method is allowed'
));
exit();
}
switch ($category) {
case "alias":
process_delete_return(mailbox('delete', 'alias', array('id' => $items)));
break;
case "oauth2-client":
process_delete_return(oauth2('delete', 'client', array('id' => $items)));
break;
case "app-passwd":
process_delete_return(app_passwd('delete', array('id' => $items)));
break;
case "relayhost":
process_delete_return(relayhost('delete', array('id' => $items)));
break;
case "transport":
process_delete_return(transport('delete', array('id' => $items)));
break;
case "rsetting":
process_delete_return(rsettings('delete', array('id' => $items)));
break;
case "syncjob":
process_delete_return(mailbox('delete', 'syncjob', array('id' => $items)));
break;
case "filter":
process_delete_return(mailbox('delete', 'filter', array('id' => $items)));
break;
case "mailq":
process_delete_return(mailq('delete', array('qid' => $items)));
break;
case "qitem":
process_delete_return(quarantine('delete', array('id' => $items)));
break;
case "bcc":
process_delete_return(bcc('delete', array('id' => $items)));
break;
case "recipient_map":
process_delete_return(recipient_map('delete', array('id' => $items)));
break;
case "tls-policy-map":
process_delete_return(tls_policy_maps('delete', array('id' => $items)));
break;
case "fwdhost":
process_delete_return(fwdhost('delete', array('forwardinghost' => $items)));
break;
case "dkim":
process_delete_return(dkim('delete', array('domains' => $items)));
break;
case "domain":
file_put_contents('/tmp/dssaa', $items);
process_delete_return(mailbox('delete', 'domain', array('domain' => $items)));
break;
case "alias-domain":
process_delete_return(mailbox('delete', 'alias_domain', array('alias_domain' => $items)));
break;
case "mailbox":
process_delete_return(mailbox('delete', 'mailbox', array('username' => $items)));
break;
case "resource":
process_delete_return(mailbox('delete', 'resource', array('name' => $items)));
break;
case "mailbox-policy":
process_delete_return(policy('delete', 'mailbox', array('prefid' => $items)));
break;
case "domain-policy":
process_delete_return(policy('delete', 'domain', array('prefid' => $items)));
break;
case "time_limited_alias":
process_delete_return(mailbox('delete', 'time_limited_alias', array('address' => $items)));
break;
case "eas_cache":
process_delete_return(mailbox('delete', 'eas_cache', array('username' => $items)));
break;
case "sogo_profile":
process_delete_return(mailbox('delete', 'sogo_profile', array('username' => $items)));
break;
case "domain-admin":
process_delete_return(domain_admin('delete', array('username' => $items)));
break;
case "admin":
process_delete_return(admin('delete', array('username' => $items)));
break;
case "rlhash":
echo ratelimit('delete', null, implode($items));
break;
// return no route found if no case is matched
default:
http_response_code(404);
echo json_encode(array(
'type' => 'error',
'msg' => 'route not found'
));
exit();
}
break;
case "edit":
+ if ($_SESSION['mailcow_cc_api_access'] == 'ro' || isset($_SESSION['pending_mailcow_cc_username'])) {
+ http_response_code(403);
+ echo json_encode(array(
+ 'type' => 'error',
+ 'msg' => 'API read/write access denied'
+ ));
+ exit();
+ }
function process_edit_return($return) {
$generic_failure = json_encode(array(
'type' => 'error',
'msg' => 'Cannot edit item'
));
$generic_success = json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
if ($return === false) {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_failure;
}
else {
echo isset($_SESSION['return']) ? json_encode($_SESSION['return']) : $generic_success;
}
}
if (!isset($_POST['attr'])) {
echo $request_incomplete;
exit;
}
else {
$attr = (array)json_decode($_POST['attr'], true);
unset($attr['csrf_token']);
$items = isset($_POST['items']) ? (array)json_decode($_POST['items'], true) : null;
}
// only allow POST requests to POST API endpoints
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
http_response_code(405);
echo json_encode(array(
'type' => 'error',
'msg' => 'only POST method is allowed'
));
exit();
}
switch ($category) {
case "bcc":
process_edit_return(bcc('edit', array_merge(array('id' => $items), $attr)));
break;
+ case "pushover":
+ process_edit_return(pushover('edit', array_merge(array('username' => $items), $attr)));
+ break;
+ case "pushover-test":
+ process_edit_return(pushover('test', array_merge(array('username' => $items), $attr)));
+ break;
case "oauth2-client":
process_edit_return(oauth2('edit', 'client', array_merge(array('id' => $items), $attr)));
break;
case "recipient_map":
process_edit_return(recipient_map('edit', array_merge(array('id' => $items), $attr)));
break;
case "app-passwd":
process_edit_return(app_passwd('edit', array_merge(array('id' => $items), $attr)));
break;
case "tls-policy-map":
process_edit_return(tls_policy_maps('edit', array_merge(array('id' => $items), $attr)));
break;
case "alias":
process_edit_return(mailbox('edit', 'alias', array_merge(array('id' => $items), $attr)));
break;
case "rspamd-map":
process_edit_return(rspamd('edit', array_merge(array('map' => $items), $attr)));
break;
case "app_links":
process_edit_return(customize('edit', 'app_links', $attr));
break;
case "relayhost":
process_edit_return(relayhost('edit', array_merge(array('id' => $items), $attr)));
break;
case "transport":
process_edit_return(transport('edit', array_merge(array('id' => $items), $attr)));
break;
case "rsetting":
process_edit_return(rsettings('edit', array_merge(array('id' => $items), $attr)));
break;
case "delimiter_action":
process_edit_return(mailbox('edit', 'delimiter_action', array_merge(array('username' => $items), $attr)));
break;
case "tls_policy":
process_edit_return(mailbox('edit', 'tls_policy', array_merge(array('username' => $items), $attr)));
break;
case "quarantine_notification":
process_edit_return(mailbox('edit', 'quarantine_notification', array_merge(array('username' => $items), $attr)));
break;
case "qitem":
process_edit_return(quarantine('edit', array_merge(array('id' => $items), $attr)));
break;
case "quarantine":
process_edit_return(quarantine('edit', $attr));
break;
case "quota_notification":
process_edit_return(quota_notification('edit', $attr));
break;
case "mailq":
process_edit_return(mailq('edit', array_merge(array('qid' => $items), $attr)));
break;
case "time_limited_alias":
process_edit_return(mailbox('edit', 'time_limited_alias', array_merge(array('address' => $items), $attr)));
break;
case "mailbox":
process_edit_return(mailbox('edit', 'mailbox', array_merge(array('username' => $items), $attr)));
break;
case "syncjob":
process_edit_return(mailbox('edit', 'syncjob', array_merge(array('id' => $items), $attr)));
break;
case "filter":
process_edit_return(mailbox('edit', 'filter', array_merge(array('id' => $items), $attr)));
break;
case "resource":
process_edit_return(mailbox('edit', 'resource', array_merge(array('name' => $items), $attr)));
break;
case "domain":
process_edit_return(mailbox('edit', 'domain', array_merge(array('domain' => $items), $attr)));
break;
case "rl-domain":
process_edit_return(ratelimit('edit', 'domain', array_merge(array('object' => $items), $attr)));
break;
case "rl-mbox":
process_edit_return(ratelimit('edit', 'mailbox', array_merge(array('object' => $items), $attr)));
break;
case "user-acl":
process_edit_return(acl('edit', 'user', array_merge(array('username' => $items), $attr)));
break;
case "da-acl":
process_edit_return(acl('edit', 'domainadmin', array_merge(array('username' => $items), $attr)));
break;
case "alias-domain":
process_edit_return(mailbox('edit', 'alias_domain', array_merge(array('alias_domain' => $items), $attr)));
break;
case "spam-score":
process_edit_return(mailbox('edit', 'spam_score', array_merge(array('username' => $items), $attr)));
break;
case "domain-admin":
process_edit_return(domain_admin('edit', array_merge(array('username' => $items), $attr)));
break;
case "admin":
process_edit_return(admin('edit', array_merge(array('username' => $items), $attr)));
break;
case "fwdhost":
process_edit_return(fwdhost('edit', array_merge(array('fwdhost' => $items), $attr)));
break;
case "fail2ban":
process_edit_return(fail2ban('edit', array_merge(array('network' => $items), $attr)));
break;
case "ui_texts":
process_edit_return(customize('edit', 'ui_texts', $attr));
break;
case "self":
if ($_SESSION['mailcow_cc_role'] == "domainadmin") {
process_edit_return(domain_admin('edit', $attr));
}
elseif ($_SESSION['mailcow_cc_role'] == "user") {
process_edit_return(edit_user_account($attr));
}
break;
// return no route found if no case is matched
default:
http_response_code(404);
echo json_encode(array(
'type' => 'error',
'msg' => 'route not found'
));
exit();
}
break;
// return no route found if no case is matched
default:
http_response_code(404);
echo json_encode(array(
'type' => 'error',
'msg' => 'route not found'
));
exit();
}
}
if ($_SESSION['mailcow_cc_api'] === true) {
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
unset($_SESSION['return']);
}
}
}
diff --git a/data/web/lang/lang.de.json b/data/web/lang/lang.de.json
index 0f8200ef..38d29e43 100644
--- a/data/web/lang/lang.de.json
+++ b/data/web/lang/lang.de.json
@@ -1,954 +1,975 @@
{
"footer": {
"loading": "Einen Moment bitte...",
"restart_container": "Container neustarten",
"restart_now": "Jetzt neustarten",
"restarting_container": "Container wird neugestartet, bitte warten...",
"restart_container_info": "<b>Wichtig:</b> Der Neustart eines Containers kann eine Weile in Anspruch nehmen.",
"confirm_delete": "Löschen bestätigen",
"delete_these_items": "Sind Sie sicher, dass die Änderungen an Elementen mit folgender ID durchgeführt werden sollen?",
"delete_now": "Jetzt löschen",
"cancel": "Abbrechen",
"hibp_nok": "Übereinstimmung gefunden! Dieses Passwort ist potenziell gefährlich!",
"hibp_ok": "Keine Übereinstimmung gefunden."
},
"header": {
"restart_sogo": "SOGo neustarten",
"restart_netfilter": "Netfilter neustarten",
"mailcow_settings": "Konfiguration",
"administration": "Server-Konfiguration",
"mailboxes": "E-Mail-Setup",
"user_settings": "Benutzereinstellungen",
"quarantine": "Quarantäne",
"debug": "Systeminformation",
"apps": "Apps"
},
"danger": {
+ "pushover_token": "Pushover Token hat das falsche Format",
+ "pushover_key": "Pushover Key hat das falsche Format",
+ "pushover_credentials_missing": "Pushover Token und/oder Key fehlen",
"invalid_filter_type": "Ungültiger Filtertyp",
"file_open_error": "Datei kann nicht zum Schreiben geöffnet werden",
"transport_dest_exists": "Transport Maps Ziel \"%s\" existiert bereits",
"unlimited_quota_acl": "Unendliche Quota untersagt durch ACL",
"mysql_error": "MySQL Fehler: %s",
"redis_error": "Redis Fehler: %s",
"unknown_tfa_method": "Unbekannte TFA Methode",
"totp_verification_failed": "TOTP Verifizierung fehlgeschlagen",
"u2f_verification_failed": "U2F Verifizierung fehlgeschlagen: %s",
"yotp_verification_failed": "Yubico OTP Verifizierung fehlgeschlagen: %s",
"ip_list_empty": "Liste erlaubter IPs darf nicht leer sein",
"invalid_destination": "Ziel-Format \"%s\" ist ungültig",
"invalid_nexthop": "Next Hop ist ungültig",
"invalid_nexthop_authenticated": "Dieser Next Hop existiert bereits mit abweichenden Authentifizierungsdaten. Die bestehenden Authentifizierungsdaten dieses \"Next Hops\" müssen vorab angepasst werden.",
"next_hop_interferes": "%s verhindert das Hinzufügen von Next Hop %s",
"next_hop_interferes_any": "Ein vorhandener Eintrag verhindert das Hinzufügen von Next Hop %s",
"rspamd_ui_pw_length": "Rspamd UI Passwort muss mindestens 6 Zeichen lang sein",
"unknown": "Ein unbekannter Fehler trat auf",
"malformed_username": "Benutzername hat ein falsches Format",
"login_failed": "Anmeldung fehlgeschlagen",
"set_acl_failed": "ACL konnte nicht gesetzt werden",
"no_user_defined": "Kein Benutzer definiert",
"script_empty": "Script darf nicht leer sein",
"sieve_error": "Sieve Parser: %s",
"value_missing": "Bitte alle Felder ausfüllen",
"filter_type": "Falscher Filtertyp",
"domain_cannot_match_hostname": "Domain darf nicht dem Hostnamen entsprechen",
"rl_timeframe": "Ratelimit Zeitraum ist inkorrekt",
"invalid_bcc_map_type": "Ungültiger BCC Map-Typ",
"bcc_empty": "BCC Ziel darf nicht leer sein",
"bcc_must_be_email": "BCC Ziel %s ist keine gültige E-Mail-Adresse",
"bcc_exists": "Ein BCC Map Eintrag %s existiert bereits als Typ %s",
"private_key_error": "Schlüsselfehler: %s",
"map_content_empty": "Inhalt darf nicht leer sein",
"app_name_empty": "App Name darf nicht leer sein",
"settings_map_invalid": "Regel ID %s ist ungültig",
"app_passwd_id_invalid": "App Passwort ID %s ist ungültig",
"global_map_invalid": "Rspamd Map %s ist ungültig",
"global_filter_write_error": "Kann Filterdatei nicht schreiben: %s",
"global_map_write_error": "Kann globale Map ID %s nicht schreiben: %s",
"invalid_host": "Ungültiger Host: %s",
"relayhost_invalid": "Mapeintrag %s ist ungültig",
"dkim_domain_or_sel_invalid": "DKIM-Domain oder Selector nicht korrekt: %s",
"access_denied": "Zugriff verweigert oder unvollständige/ungültige Daten",
"domain_invalid": "Domainname ist leer oder ungültig",
"mailbox_quota_exceeds_domain_quota": "Maximale Größe für Mailboxen überschreitet das Domain Speicherlimit",
"object_is_not_numeric": "Wert %s ist nicht numerisch",
"alias_empty": "Alias-Adresse darf nicht leer sein",
"goto_empty": "Eine Alias-Adresse muss auf mindestens eine gütlige Ziel-Adresse zeigen",
"policy_list_from_exists": "Ein Eintrag mit diesem Wert existiert bereits",
"policy_list_from_invalid": "Eintrag hat ein ungültiges Format",
"alias_invalid": "Alias-Adresse %s ist ungültig",
"goto_invalid": "Ziel-Adresse %s ist ungültig",
"last_key": "Letzter Key kann nicht gelöscht werden, bitte stattdessen die 2FA deaktivieren.",
"alias_domain_invalid": "Alias-Domain %s ist ungültig",
"target_domain_invalid": "Ziel-Domain %s ist ungültig",
"object_exists": "Objekt %s existiert bereits",
"domain_exists": "Domain %s existiert bereits",
"alias_goto_identical": "Alias- und Ziel-Adresse dürfen nicht identisch sein",
"aliasd_targetd_identical": "Alias-Domain darf nicht gleich Ziel-Domain sein: %s",
"maxquota_empty": "Max. Speicherplatz pro Mailbox darf nicht 0 sein.",
"targetd_not_found": "Ziel-Domain %s nicht gefunden",
"targetd_relay_domain": "Ziel-Domain %s ist eine Relay-Domain",
"username_invalid": "Benutzername %s kann nicht verwendet werden",
"password_mismatch": "Passwort-Wiederholung stimmt nicht überein",
"password_complexity": "Passwort entspricht nicht den Richtlinien",
"password_empty": "Passwort darf nicht leer sein",
"mailbox_invalid": "Mailboxname ist ungültig",
"resource_invalid": "Ressourcenname %s ist ungültig",
"description_invalid": "Ressourcenbeschreibung für %s ist ungültig",
"is_alias": "%s lautet bereits eine Alias-Adresse",
"is_alias_or_mailbox": "Eine Mailbox, ein Alias oder eine sich aus einer Alias-Domain ergebende Adresse mit dem Namen %s ist bereits vorhanden",
"is_spam_alias": "%s lautet bereits eine Spam-Alias-Adresse",
"quota_not_0_not_numeric": "Speicherplatz muss numerisch und >= 0 sein",
"domain_not_found": "Domain %s nicht gefunden",
"max_mailbox_exceeded": "Anzahl an Mailboxen überschritten (%d von %d)",
"max_alias_exceeded": "Anzahl an Alias-Adressen überschritten",
"mailbox_quota_exceeded": "Speicherplatz überschreitet das Limit (max. %d MiB)",
"mailbox_quota_left_exceeded": "Nicht genügend Speicherplatz vorhanden (Speicherplatz anwendbar: %d MiB)",
"max_quota_in_use": "Mailbox Speicherplatzlimit muss größer oder gleich %d MiB sein",
"domain_quota_m_in_use": "Domain Speicherplatzlimit muss größer oder gleich %d MiB sein",
"mailboxes_in_use": "Maximale Anzahl an Mailboxen muss größer oder gleich %d sein",
"aliases_in_use": "Maximale Anzahl an Aliassen muss größer oder gleich %d sein",
"sender_acl_invalid": "Sender ACL %s ist ungültig",
"domain_not_empty": "Domain %s ist nicht leer",
"validity_missing": "Bitte geben Sie eine Gültigkeitsdauer an",
"img_tmp_missing": "Grafik konnte nicht validiert werden: Erstellung temporärer Datei fehlgeschlagen",
"comment_too_long": "Kommentarfeld darf maximal 160 Zeichen enthalten",
"img_invalid": "Grafik konnte nicht validiert werden",
"invalid_mime_type": "Grafik konnte nicht validiert werden: Ungültiger MIME-Type",
"imagick_exception": "Fataler Bildverarbeitungsfehler",
"spam_learn_error": "Spam Lernfehler: %s",
"ham_learn_error": "Ham Lernfehler: %s",
"release_send_failed": "Die Nachricht konnte nicht versendet werden: %s",
"invalid_recipient_map_new": "Neuer Empfänger \"%s\" ist ungültig",
"invalid_recipient_map_old": "Originaler Empfänger \"%s\" ist ungültig",
"recipient_map_entry_exists": "Eine Empfängerumschreibung für Objekt \"%s\" existiert bereits",
"tls_policy_map_entry_exists": "Eine TLS-Richtlinie \"%s\" existiert bereits",
"tls_policy_map_parameter_invalid": "Parameter ist ungültig",
"tls_policy_map_dest_invalid": "Ziel ist ungültig",
"temp_error": "Temporärer Fehler",
"text_empty": "Text darf nicht leer sein",
"subject_empty": "Betreff darf nicht leer sein",
"from_invalid": "Die Absenderadresse muss eine gültige E-Mail-Adresse sein",
"network_host_invalid": "Netzwerk oder Host ungültig: %s",
"mailbox_defquota_exceeds_mailbox_maxquota": "Standard-Quota überschreitet das Limit der maximal erlaubten Größe einer Mailbox",
"defquota_empty": "Standard-Quota darf nicht 0 sein",
"extra_acl_invalid": "Externe Absenderadresse \"%s\" ist ungültig",
"extra_acl_invalid_domain": "Externe Absenderadresse \"%s\" verwendet eine ungültige Domain"
},
"success": {
+ "pushover_settings_edited": "Pushover Konfiguration gespeichert, bitte den Zugang im Anschluss verifizieren.",
"global_filter_written": "Filterdatei wurde erfolreich geschrieben",
"learned_ham": "ID %s wurde erfolreich als Ham gelernt",
"verified_totp_login": "TOTP Anmeldung verifiziert",
"verified_u2f_login": "U2F Anmeldung verifiziert",
"verified_yotp_login": "Yubico OTP Anmeldung verifiziert",
"rspamd_ui_pw_set": "Rspamd UI Passwort wurde gesetzt",
"queue_command_success": "Queue-Aufgabe erfolgreich ausgeführt",
"logged_in_as": "Eingeloggt als %s",
"rl_saved": "Ratelimit für Objekt %s wurde gesetzt",
"acl_saved": "ACL für Objekt %s wurde gesetzt",
"deleted_syncjobs": "Syncjobs gelöscht: %s",
"deleted_syncjob": "Syncjobs ID %s gelöscht",
"delete_filters": "Filter gelöscht: %s",
"delete_filter": "Filter ID %s wurde gelöscht",
"bcc_saved": "BCC Map Eintrag wurde gespeichert",
"bcc_edited": "BCC Map Eintrag %s wurde geändert",
"bcc_deleted": "BCC Map Einträge gelöscht: %s",
"settings_map_added": "Regel wurde gespeichert",
"app_passwd_added": "App Password wurde gespeichert",
"settings_map_removed": "Regeln wurden entfernt: %s",
"app_passwd_removed": "App Passwort ID %s wurde entfernt",
"saved_settings": "Regel wurde gespeichert",
"dkim_removed": "DKIM-Key %s wurde entfernt",
"dkim_added": "DKIM-Key %s wurde hinzugefügt",
"dkim_duplicated": "DKIM-Key der Domain %s wurde auf Domain %s kopiert",
"domain_added": "Domain %s wurde angelegt",
"items_deleted": "Objekt(e) %s wurde(n) erfolgreich entfernt",
"item_deleted": "Objekt %s wurde entfernt",
"alias_added": "Alias-Adresse %s wurden angelegt",
"alias_modified": "Änderungen an Alias %s wurden gespeichert",
"aliasd_modified": "Änderungen an Alias-Domain %s wurden gespeichert",
"mailbox_modified": "Änderungen an Mailbox %s wurden gespeichert",
"resource_modified": "Änderungen an Ressource %s wurden gespeichert",
"object_modified": "Änderungen an Objekt %s wurden gespeichert",
"f2b_modified": "Änderungen an Fail2ban-Parametern wurden gespeichert",
"aliasd_added": "Alias-Domain %s wurde angelegt",
"domain_modified": "Änderungen an Domain %s wurden gespeichert",
"domain_admin_modified": "Änderungen an Domain-Administrator %s wurden gespeichert",
"domain_admin_added": "Domain-Administrator %s wurde angelegt",
"admin_added": "Administrator %s wurde angelegt",
"admin_modified": "Änderungen am Administrator wurden gespeichert",
"admin_api_modified": "Änderungen an API wurden gespeichert",
"license_modified": "Änderungen an Lizenz wurden gespeichert",
"mailbox_added": "Mailbox %s wurde angelegt",
"resource_added": "Ressource %s wurde angelegt",
"domain_removed": "Domain %s wurde entfernt",
"alias_removed": "Alias-Adresse %s wurde entfernt",
"alias_domain_removed": "Alias-Domain %s wurde entfernt",
"domain_admin_removed": "Domain-Administrator %s wurde entfernt",
"admin_removed": "Administrator %s wurde entfernt",
"mailbox_removed": "Mailbox %s wurde entfernt",
"eas_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt",
"sogo_profile_reset": "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt",
"resource_removed": "Ressource %s wurde entfernt",
"hash_deleted": "Hash wurde gelöscht",
"forwarding_host_removed": "Weiterleitungs-Host %s wurde entfernt",
"forwarding_host_added": "Weiterleitungs-Host %s wurde hinzugefügt",
"relayhost_removed": "Mapeintrag %s wurde entfernt",
"relayhost_added": "Mapeintrag %s wurde hinzugefügt",
"upload_success": "Datei wurde erfolgreich hochgeladen",
"app_links": "Änderungen an App Links wurden gespeichert",
"ui_texts": "Änderungen an UI-Texten",
"reset_main_logo": "Standardgrafik wurde wiederhergestellt",
"items_released": "Ausgewählte Objekte wurden an Mailbox versendet",
"qlearn_spam": "Nachricht ID %s wurde als Spam gelernt und gelöscht",
"recipient_map_entry_saved": "Empfängerumschreibung für Objekt \"%s\" wurde gespeichert",
"recipient_map_entry_deleted": "Empfängerumschreibung mit der ID %s wurde gelöscht",
"tls_policy_map_entry_saved": "TLS-Richtlinieneintrag \"%s\" wurde gespeichert",
"tls_policy_map_entry_deleted": "TLS-Richtlinie mit der ID %s wurde gelöscht",
"item_released": "Objekt %s freigegeben",
"db_init_complete": "Datenbankinitialisierung abgeschlossen"
},
"info": {
"awaiting_tfa_confirmation": "Warte auf TFA Verifizierung",
"session_expires": "Die Sitzung wird in etwa 15 Sekunden beendet",
"no_action": "Keine Aktion anwendbar"
},
"warning": {
"domain_added_sogo_failed": "Domain wurde hinzugefügt, aber SOGo konnte nicht neugestartet werden",
"cannot_delete_self": "Kann derzeit eingeloggten Benutzer nicht entfernen",
"no_active_admin": "Kann letzten aktiven Administrator nicht deaktivieren",
"hash_not_found": "Hash nicht gefunden. Möglicherweise wurde dieser bereits gelöscht.",
"fuzzy_learn_error": "Fuzzy Lernfehler: %s",
"ip_invalid": "Ungültige IP übersprungen: %s",
"session_token": "Formular-Token ungültig: Token stimmt nicht überein",
"session_ua": "Formular-Token ungültig: User-Agent-Validierungsfehler",
"quota_exceeded_scope": "Domain-Quota erschöpft: Es können nur noch unlimiterte Mailboxen in dieser Domain erstellt werden."
},
"user": {
+ "pushover_info": "Push-Benachrichtungen werden angewendet auf alle nicht-Spam Nachrichten zugestellt an <b>%s</b>, einschließlich Alias-Adressen (shared, non-shared, tagged).",
+ "verify": "Verifizieren",
+ "pushover_verify": "Verbindung verifizieren",
+ "title": "Title",
+ "pushover_title": "Notification Titel",
+ "text": "Text",
+ "pushover_text": "Notification Text ({SUBJECT} entspricht dem Mail-Betreff)",
+ "last_mail_login": "Letzter Mail-Login",
"last_mail_login": "Letzter Mail-Login",
"no_last_login": "Keine letzte UI Anmeldung gespeichert",
+ "save": "Änderungen speichern",
"generate": "generieren",
"apple_connection_profile": "Apple Verbindungsprofil",
"apple_connection_profile_mailonly": "Dieses Verbindungsprofil beinhaltet IMAP und SMTP Konfigurationen für ein Apple Gerät.",
"apple_connection_profile_complete": "Dieses Verbindungsprofil beinhaltet neben IMAP und SMTP Konfigurationen auch Pfade für die Konfiguration von CalDAV (Kalender) und CardDAV (Adressbücher) für ein Apple Gerät.",
"email": "E-Mail",
"email_and_dav": "E-Mail, Kalender und Adressbücher",
"create_app_passwd": "Erstelle App Passwort",
"app_passwds": "App Passwörter",
"app_name": "App Name",
"app_hint": "App Passwörter sind alternative Passwörter für den <b>IMAP und SMTP</b> Login am Mailserver. Der Benutzername bleibt unverändert.<br>SOGo (und damit ActiveSync) ist mit diesem Kennwort nicht verwendbar.",
"loading": "Lade...",
"force_pw_update": "Das Passwort für diesen Benutzer <b>muss</b> geändert werden, damit die Zugriffssperre auf die Groupwarekomponenten wieder freigeschaltet wird.",
"active_sieve": "Aktiver Filter",
"show_sieve_filters": "Zeige aktiven Filter des Benutzers",
"no_active_filter": "Kein aktiver Filter vorhanden",
"messages": "Nachrichten",
"in_use": "Verwendet",
"user_settings": "Benutzereinstellungen",
"mailbox_details": "Mailbox-Details",
"change_password": "Passwort ändern",
"client_configuration": "Konfigurationsanleitungen für E-Mail-Programme und Smartphones anzeigen",
"new_password": "Neues Passwort",
"password": "Passwort",
"save_changes": "Änderungen speichern",
"password_now": "Aktuelles Passwort (Änderungen bestätigen)",
"password_repeat": "Passwort (Wiederholung)",
"new_password_repeat": "Neues Passwort (Wiederholung)",
"new_password_description": "Mindestanforderung: 6 Zeichen lang, Buchstaben und Zahlen.",
"spam_aliases": "Temporäre E-Mail Aliasse",
"alias": "Alias",
"shared_aliases": "Geteilte Alias-Adressen",
"shared_aliases_desc": "Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.",
"direct_aliases": "Direkte Alias-Adressen",
"direct_aliases_desc": "Nur direkte Alias-Adressen werden für benutzerdefinierte Einstellungen berücksichtigt.",
"is_catch_all": "Ist Catch-All Adresse für Domain(s)",
"aliases_also_send_as": "Darf außerdem versenden als Benutzer",
"aliases_send_as_all": "Absender für folgende Domains und zugehörige Alias-Domains nicht prüfen",
"alias_create_random": "Zufälligen Alias generieren",
"alias_extend_all": "Gültigkeit +1h",
"alias_valid_until": "Gültig bis",
"alias_remove_all": "Alle entfernen",
"alias_time_left": "Zeit verbleibend",
"alias_full_date": "d.m.Y, H:i:s T",
"alias_select_validity": "Bitte Gültigkeit auswählen",
"sync_jobs": "Sync Jobs",
"expire_in": "Ungültig in",
"hour": "Stunde",
"hours": "Stunden",
"day": "Tag",
"week": "Woche",
"weeks": "Wochen",
"spamfilter": "Spamfilter",
"spamfilter_wl": "Whitelist",
"spamfilter_wl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>nicht</b> erfasst werden sollen. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
"spamfilter_bl": "Blacklist",
"spamfilter_bl_desc": "Für E-Mail-Adressen, die vom Spamfilter <b>immer</b> als Spam erfasst und abgelehnt werden. Die Verwendung von Wildcards ist gestattet. Ein Filter funktioniert lediglich für direkte nicht-\"Catch All\" Alias-Adressen (Alias-Adressen mit lediglich einer Mailbox als Ziel-Adresse) sowie die Mailbox-Adresse selbst.",
"spamfilter_table_rule": "Regel",
"spamfilter_table_action": "Aktion",
"spamfilter_table_empty": "Keine Einträge vorhanden",
"spamfilter_table_remove": "Entfernen",
"spamfilter_table_add": "Eintrag hinzufügen",
"spamfilter_behavior": "Bewertung",
"spamfilter_green": "Grün: Die Nachricht ist kein Spam",
"spamfilter_yellow": "Gelb: Die Nachricht ist vielleicht Spam, wird als Spam markiert und in den Junk-Ordner verschoben",
"spamfilter_red": "Rot: Die Nachricht ist eindeutig Spam und wird vom Server abgelehnt",
"spamfilter_default_score": "Standardwert",
"spamfilter_hint": "Der erste Wert beschreibt den \"low spam score\", der zweite Wert den \"high spam score\".",
"spamfilter_table_domain_policy": "n.v. (Domainrichtlinie)",
"waiting": "Warte auf Ausführung",
"status": "Status",
"running": "Wird ausgeführt",
"tls_policy_warning": "<strong>Vorsicht:</strong> Entscheiden Sie sich unverschlüsselte Verbindungen abzulehnen, kann dies dazu führen, dass Kontakte Sie nicht mehr erreichen.<br>Nachrichten, die die Richtlinie nicht erfüllen, werden durch einen Hard-Fail im Mailsystem abgewiesen.<br>Diese Einstellung ist aktiv für die primäre Mailbox, für alle Alias-Adressen, die dieser Mailbox <b>direkt zugeordnet</b> sind (lediglich eine einzige Ziel-Adresse) und der Adressen, die sich aus Alias-Domains ergeben. Ausgeschlossen sind temporäre Aliasse (\"Spam-Alias-Adressen\"), Catch-All Alias-Adressen sowie Alias-Adressen mit mehreren Zielen.",
"tls_policy": "Verschlüsselungsrichtlinie",
"tls_enforce_in": "TLS eingehend erzwingen",
"tls_enforce_out": "TLS ausgehend erzwingen",
"no_record": "Kein Eintrag",
"tag_handling": "Umgang mit getaggten E-Mails steuern",
"tag_in_subfolder": "In Unterordner",
"tag_in_subject": "In Betreff",
"tag_in_none": "Nichts tun",
"tag_help_explain": "Als Unterordner: Es wird ein Ordner mit dem Namen des Tags unterhalb der Inbox erstellt (\"INBOX/Facebook\").<br>\r\nIn Betreff: Der Name des Tags wird dem Betreff angefügt, etwa \"[Facebook] Meine Neuigkeiten\".",
"tag_help_example": "Beispiel für eine getaggte E-Mail-Adresse: ich<b>+Facebook</b>@example.org",
"eas_reset": "ActiveSync Geräte-Cache zurücksetzen",
"eas_reset_now": "Jetzt zurücksetzen",
"eas_reset_help": "In vielen Fällen kann ein ActiveSync Profil durch das Zurücksetzen des Caches repariert werden.<br><b>Vorsicht:</b> Alle Elemente werden erneut heruntergeladen!",
"sogo_profile_reset": "SOGo Profil zurücksetzen",
"sogo_profile_reset_now": "Profil jetzt zurücksetzen",
"sogo_profile_reset_help": "Das Profil wird inklusive <b>aller</b> Kalender- und Kontaktdaten <b>unwiederbringlich gelöscht</b>.",
"encryption": "Verschlüsselung",
"username": "Benutzername",
"last_run": "Letzte Ausführung",
"excludes": "Ausschlüsse",
"interval": "Intervall",
"active": "Aktiv",
"action": "Aktion",
"edit": "Bearbeiten",
"remove": "Entfernen",
"create_syncjob": "Neuen Sync-Job erstellen",
"sender_acl_disabled": "<span class=\"label label-danger\">Absenderprüfung deaktiviert</span>",
"quarantine_notification": "Quarantäne-Benachrichtigung",
"never": "Niemals",
"hourly": "Stündlich",
"daily": "Täglich",
"weekly": "Wöchentlich",
"quarantine_notification_info": "Wurde über eine E-Mail in Quarantäne informiert, wird sie als \"benachrichtigt\" markiert und keine weitere Benachrichtigung zu dieser E-Mail versendet.",
"spam_score_reset": "Auf Server-Standard zurücksetzen"
},
"admin": {
+ "pushover_info": "Push-Benachrichtungen werden angewendet auf alle nicht-Spam Nachrichten zugestellt an <b>%s</b>, einschließlich Alias-Adressen (shared, non-shared, tagged).",
+ "verify": "Verifizieren",
+ "pushover_verify": "Verbindung verifizieren",
+ "title": "Title",
+ "pushover_title": "Notification Titel",
+ "text": "Text",
+ "pushover_text": "Notification Text ({SUBJECT} entspricht dem Mail-Betreff)",
"spamfilter": "Spamfilter",
"domain_s": "Domain(s)",
"rspamd-com_settings": "Ein Name wird automatisch generiert. Beispielinhalte zur Einsicht stehen nachstehend bereit. Siehe auch <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
"no_new_rows": "Keine weiteren Zeilen vorhanden",
"additional_rows": " zusätzliche Zeilen geladen",
"private_key": "Private Key",
"import": "Importieren",
"duplicate": "Duplizieren",
"import_private_key": "Private Key importieren",
"duplicate_dkim": "DKIM duplizieren",
"f2b_parameters": "Fail2ban-Parameter",
"f2b_ban_time": "Bannzeit (s)",
"f2b_max_attempts": "Max. Versuche",
"f2b_retry_window": "Wiederholungen im Zeitraum von (s)",
"f2b_netban_ipv4": "Netzbereich für IPv4-Bans (8-32)",
"f2b_netban_ipv6": "Netzbereich für IPv6-Bans (8-128)",
"f2b_whitelist": "Whitelist für Netzwerke und Hosts",
"r_inactive": "Inaktive Restriktionen",
"r_active": "Aktive Restriktionen",
"r_info": "Ausgegraute/deaktivierte Elemente sind mailcow nicht bekannt und können nicht in die Liste inaktiver Elemente verschoben werden. Unbekannte Restriktionen werden trotzdem in Reihenfolge der Erscheinung gesetzt.<br>Sie können ein Element in der Datei <code>inc/vars.local.inc.php</code> als bekannt hinzufügen, um es zu bewegen.",
"save": "Änderungen speichern",
"dkim_add_key": "ARC/DKIM-Key hinzufügen",
"dkim_keys": "ARC/DKIM-Keys",
"dkim_key_valid": "Key gültig",
"dkim_key_unused": "Key ohne Zuweisung",
"dkim_key_missing": "Key fehlt",
"dkim_from": "Von",
"dkim_to": "Nach",
"dkim_from_title": "Quellobjekt für Duplizierung",
"dkim_to_title": "Ziel-Objekt(e) werden überschrieben",
"dkim_domains_wo_keys": "Domains mit fehlenden Keys auswählen",
"add": "Hinzufügen",
"queue_manager": "Queue Manager",
"configuration": "Konfiguration",
"password": "Passwort",
"password_repeat": "Passwort (Wiederholung)",
"active": "Aktiv",
"inactive": "Inaktiv",
"action": "Aktion",
"add_domain_admin": "Domain-Administrator hinzufügen",
"domain_admin": "Administrator hinzufügen",
"add_settings_rule": "Rspamd Regel hinzufügen",
"rsetting_desc": "Kurze Beschreibung",
"rsetting_content": "Regelinhalt",
"rsetting_none": "Keine Regel hinterlegt",
"rsetting_no_selection": "Bitte eine Regel auswählen",
"rsettings_preset_1": "Alles außer DKIM und Ratelimits für authentifizierte Benutzer deaktivieren",
"rsettings_preset_2": "Spam an Postmaster-Addressen nicht blockieren",
"rsettings_preset_3": "Nur einem oder vielen Absendern erlauben, eine Mailbox anzuschreiben (etwa interne Mailboxen)",
"rsettings_insert_preset": "Beispiel \"%s\" laden",
"rsetting_add_rule": "Regel hinzufügen",
"admin_domains": "Domain-Zuweisungen",
"queue_ays": "Soll die derzeitige Queue wirklich komplett bereinigt werden?",
"arrival_time": "Ankunftszeit (Serverzeit)",
"message_size": "Nachrichtengröße",
"sender": "Sender",
"recipients": "Empfänger",
"flush_queue": "Flush Queue",
"delete_queue": "Alle löschen",
"domain_admins": "Domain-Administratoren",
"username": "Benutzername",
"edit": "Bearbeiten",
"remove": "Entfernen",
"admin": "Administrator",
"admin_details": "Administrator bearbeiten",
"unchanged_if_empty": "Unverändert, wenn leer",
"access": "Zugang",
"no_record": "Kein Eintrag",
"filter_table": "Tabelle Filtern",
"empty": "Keine Einträge vorhanden",
"time": "Zeit",
"last_applied": "Zuletzt angewendet",
"reset_limit": "Hash entfernen",
"hash_remove_info": "Das Entfernen eines Ratelimit Hashes - sofern noch existent - bewirkt den Reset gezählter Nachrichten dieses Elements.<br>\r\n Jeder Hash wird durch eine eindeutige Farbe gekennzeichnet.",
"authed_user": "Auth. Benutzer",
"priority": "Gewichtung",
"refresh": "Neu laden",
"to_top": "Nach oben",
"in_use_by": "Verwendet von",
"rate_name": "Rate name",
"message": "Nachricht",
"forwarding_hosts": "Weiterleitungs-Hosts",
"forwarding_hosts_hint": "Eingehende Nachrichten werden von den hier gelisteten Hosts bedingungslos akzeptiert. Diese Hosts werden dann nicht mit DNSBLs abgeglichen oder Greylisting unterworfen. Von ihnen empfangener Spam wird nie abgelehnt, optional kann er aber in den Spam-Ordner einsortiert werden. Die übliche Verwendung für diese Funktion ist, um Mailserver anzugeben, auf denen eine Weiterleitung zu Ihrem mailcow-Server eingerichtet wurde.",
"forwarding_hosts_add_hint": "Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.",
"relayhosts_hint": "Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.<br>\r\n Der Transporttyp lautet immer \"smtp:\". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.<br>\r\n Gilt neben ausgewählter Domain auch für untergeordnete Alias-Domains.",
"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:\".<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.",
"add_relayhost_hint": "Bitte beachten Sie, dass Anmeldedaten unverschlüsselt gespeichert werden.<br>\r\n Angelegte Transporte dieser Art sind <b>senderabhängig</b> und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.<br>\r\n Diese Einstellungen entsprechen demnach <i>nicht</i> dem \"relayhost\" Parameter in Postfix.",
"add_transports_hint": "Bitte beachten Sie, dass Anmeldedaten unverschlüsselt gespeichert werden.",
"host": "Host",
"source": "Quelle",
"add_forwarding_host": "Weiterleitungs-Host hinzufügen",
"add_relayhost": "Senderabhängigen Transport hinzufügen",
"add_transport": "Transport hinzufügen",
"relayhosts": "Senderabhängige Transport Maps",
"transport_maps": "Transport Maps",
"routing": "Routing",
"credentials_transport_warning": "<b>Warnung</b>: Das Hinzufügen einer neuen Regel bewirkt die Aktualisierung der Authentifizierungsdaten aller vorhandenen Einträge mit identischem Host.",
"destination": "Ziel",
"nexthop": "Next Hop",
"api_allow_from": "IP-Adressen für Zugriff",
"api_key": "API-Key",
"api_skip_ip_check": "IP-Check für API nicht ausführen",
"activate_api": "API aktivieren",
"regen_api_key": "API-Key regenerieren",
"ban_list_info": "Übersicht ausgesperrter Netzwerke: <b>Netzwerk (verbleibende Banzeit) - [Aktionen]</b>.<br />IPs, die zum Entsperren eingereiht werden, verlassen die Liste aktiver Bans nach wenigen Sekunden.<br />Rote Labels sind Indikatoren für aktive Blacklisteinträge.",
"unban_pending": "ausstehend",
"queue_unban": "Entsperren einreihen",
"no_active_bans": "Keine aktiven Bans",
"quota_notifications": "Quota Benachrichtigungen",
"quota_notifications_vars": "{{percent}} entspricht der aktuellen Quota in Prozent<br>{{username}} entspricht dem Mailbox-Namen",
"rspamd_settings_map": "Rspamd Settings Map",
"quarantine": "Quarantäne",
"active_rspamd_settings_map": "Derzeit aktive Settings Map",
"quota_notifications_info": "Quota Benachrichtigungen werden an Mailboxen versendet, die 80 respektive 95 Prozent der zur Verfügung stehenden Quota überschreiten.",
"quarantine_bcc": "Eine Kopie aller Benachrichtigungen (BCC) an folgendes Postfach senden:<br><small>Leer bedeutet deaktiviert. <b>Unsignierte, ungeprüfte E-Mail. Sollte nur intern zugestellt werden.</b></small>",
"quarantine_retention_size": "Rückhaltungen pro Mailbox:<br><small>0 bedeutet <b>inaktiv</b>.</small>",
"quarantine_max_size": "Maximale Größe in MiB (größere Elemente werden verworfen):<br><small>0 bedeutet <b>nicht</b> unlimitiert.</small>",
"quarantine_max_age": "Maximales Alter in Tagen<br><small>Wert muss größer oder gleich 1 Tag sein.</small>",
"quarantine_exclude_domains": "Domains und Alias-Domains ausschließen",
"quarantine_notification_sender": "Benachrichtigungs-E-Mail Absender",
"quota_notification_sender": "Benachrichtigungs-E-Mail Absender",
"quarantine_notification_subject": "Benachrichtigungs-E-Mail Betreff",
"quota_notification_subject": "Benachrichtigungs-E-Mail Betreff",
"quarantine_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>",
"quota_notification_html": "Benachrichtigungs-E-Mail Inhalt:<br><small>Leer lassen, um Standard-Template wiederherzustellen.</small>",
"quarantine_release_format": "Format freigegebener Mails",
"quarantine_release_format_raw": "Unverändertes Original",
"quarantine_release_format_att": "Als Anhang",
"relay_from": "Absenderadresse",
"relay_run": "Test durchführen",
"ui_texts": "UI Label und Texte",
"help_text": "Hilfstext unter Login-Maske (HTML zulässig)",
"title_name": "\"mailcow UI\" Webseiten Titel",
"main_name": "\"mailcow UI\" Name",
"apps_name": "\"mailcow Apps\" Name",
"ui_footer": "Footer (HTML zulässig)",
"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_client_id": "Client ID",
"oauth2_client_secret": "Client Secret",
"oauth2_redirect_uri": "Redirect URI",
"oauth2_revoke_tokens": "Alle Client Tokens entfernen",
"oauth2_renew_secret": "Neues Client Secret generieren",
"customize": "UI Anpassung",
"change_logo": "Logo ändern",
"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.",
"upload": "Hochladen",
"app_links": "App Links",
"app_name": "App Name",
"link": "Link",
"remove_row": "Entfernen",
"add_row": "Reihe hinzufügen",
"reset_default": "Zurücksetzen auf Standard",
"merged_vars_hint": "Ausgegraute Reihen wurden aus der Datei <code>vars.(local.)inc.php</code> gelesen und können hier nicht verändert werden.",
"sys_mails": "System-E-Mails",
"subject": "Betreff",
"from": "Absender",
"include_exclude": "Ein- und Ausschlüsse",
"include_exclude_info": "Ohne Auswahl werden <b>alle Mailboxen</b> adressiert.",
"excludes": "Diese Empfänger ausschließen",
"includes": "Diese Empfänger einschließen",
"text": "Text",
"activate_send": "Senden-Button freischalten",
"send": "Senden",
"api_info": "Das API befindet sich noch in Entwicklung, die Dokumentation kann unter <a href=\"/api\">/api</a> abgerufen werden.",
"guid_and_license": "GUID & Lizenz",
"guid": "GUID - Eindeutige Instanz-ID",
"license_info": "Eine Lizenz ist nicht erforderlich, hilft jedoch der Entwicklung mailcows.<br><a href=\"https://www.servercow.de/mailcow#sal\" target=\"_blank\" alt=\"SAL Bestellung\">Hier kann die mailcow GUID registriert werden.</a> Alternativ ist <a href=\"https://www.servercow.de/mailcow#support\" target=\"_blank\" alt=\"SAL Bestellung\">die Bestellung von Support-Paketen möglich</a>.",
"validate_license_now": "GUID erneut verifizieren",
"customer_id": "Kunde",
"service_id": "Service",
"sal_level": "Moo-Level",
"lookup_mx": "Ziel gegen MX prüfen (etwa .outlook.com, um alle Ziele mit MX *.outlook.com zu routen)",
"transport_dest_format": "Syntax: example.org, .example.org, *, box@example.org (mehrere Werte getrennt durch Komma einzugeben)",
"rspamd_global_filters_agree": "Ich werde vorsichtig sein!",
"rspamd_global_filters": "Globale Filter-Maps",
"rspamd_global_filters_info": "Globale Filter-Maps steuern globales White- und Blacklisting dieses Servers.",
"rspamd_global_filters_regex": "Die akzeptierte Form für Einträge sind <b>ausschließlich</b> Regular Expressions.\r\n Trotz rudimentärer Überprüfung der Map, kann es zu fehlerhaften Einträgen kommen, die Rspamd im schlechtesten Fall mit unvorhersehbarer Funktionalität bestraft.<br>\r\n Das korrekte Format lautet \"/pattern/options\" (Beispiel: <code>/.+@domain\\.tld/i</code>).<br>\r\n Der Name der Map beschreibt die jeweilige Funktion.<br>\r\n Rspamd versucht die Maps umgehend aufzulösen. Bei Problemen sollte <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">Rspamd manuell neugestartet werden</a>.",
"add_admin": "Administrator hinzufügen",
"dkim_domains_selector": "Selector",
"search_domain_da": "Suche Domains",
"f2b_blacklist": "Blacklist für Netzwerke und Hosts",
"no": "&#10005;",
"yes": "&#10003;",
"dkim_private_key": "Private Key",
"dkim_key_length": "DKIM Schlüssellänge (bits)",
"domain": "Domain",
"queue_deliver_mail": "Ausliefern",
"queue_unhold_mail": "Freigeben",
"queue_hold_mail": "Zurückhalten",
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>"
},
"start": {
"mailcow_apps_detail": "Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender und Kontakte zu verwalten und vieles mehr.",
"mailcow_panel_detail": "<b>Domain-Administratoren</b> erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.<br>\r\n\tAls <b>Mailbox-Benutzer</b> erstellen Sie hier zeitlich limitierte Aliasse, ändern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.",
"imap_smtp_server_auth_info": "Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br>\r\nIhre Anmeldedaten werden durch die obligatorische Verschlüsselung entgegen des Begriffes \"PLAIN\" nicht unverschlüsselt übertragen.",
"help": "Hilfe ein-/ausblenden"
},
"quarantine": {
"refresh": "Neu laden",
"table_size": "Tabellengröße",
"table_size_show_n": "Zeige %s Einträge",
"disabled_by_config": "Die derzeitige Konfiguration deaktiviert die Funktion des Quarantäne-Systems.",
"learn_spam_delete": "Als Spam lernen und löschen",
"quarantine": "Quarantäne",
"qinfo": "Das Quarantänesystem speichert abgelehnte Nachrichten in der Datenbank. Dem Sender wird <em>nicht</em> signalisiert, dass seine E-Mail zugestellt wurde.\r\n <br>\"Als Spam lernen und löschen\" lernt Nachrichten nach bayesscher Statistik als Spam und erstellt Fuzzy Hashes ausgehend von der jeweiligen Nachricht, um ähnliche Inhalte zukünftig zu unterbinden.\r\n <br>Der Prozess des Lernens kann abhängig vom System zeitintensiv sein.",
"download_eml": "Herunterladen (.eml)",
"release": "Freigeben",
"empty": "Keine Einträge",
"toggle_all": "Alle auswählen",
"quick_actions": "Aktionen",
"remove": "Entfernen",
"received": "Empfangen",
"action": "Aktion",
"rcpt": "Empfänger",
"qid": "Rspamd QID",
"sender": "Sender",
"show_item": "Details",
"check_hash": "Checksumme auf VirusTotal suchen",
"qitem": "Quarantäneeintrag",
"rspamd_result": "Rspamd Ergebnis",
"subj": "Betreff",
"recipients": "Empfänger",
"text_plain_content": "Inhalt (text/plain)",
"text_from_html_content": "Inhalt (html, konvertiert)",
"atts": "Anhänge",
"low_danger": "Niedrige Gefahr",
"neutral_danger": "Neutral/ohne Bewertung",
"medium_danger": "Mittlere Gefahr",
"high_danger": "Hohe Gefahr",
"danger": "Gefahr",
"spam_score": "Bewertung",
"qhandler_success": "Aktion wurde an das System übergeben. Sie dürfen dieses Fenster nun schließen.",
"release_body": "Die ursprüngliche Nachricht wurde als EML-Datei im Anhang hinterlegt.",
"release_subject": "Potentiell schädliche Nachricht aus Quarantäne: %s",
"confirm_delete": "Bestätigen Sie die Löschung dieses Elements.",
"notified": "Benachrichtigt"
},
"mailbox": {
"stats": "Statistik",
"last_mail_login": "Letzter Mail-Login",
"table_size": "Tabellengröße",
"table_size_show_n": "Zeige %s Einträge",
"tls_policy_maps": "TLS-Richtlinien",
"sogo_allow_admin_hint": "Administrative SOGo Logins sind nur für Mailboxen verfügbar, die bereits ein SOGo Profil besitzen. Ein Benutzer muss zumindest einmal in SOGo eingeloggt gewesen sein, um ein Profil zu besitzen.",
"tls_policy_maps_long": "Ausgehende TLS-Richtlinien",
"tls_policy_maps_info": "Nachstehende Richtlinien erzwingen TLS-Transportregeln unabhängig von TLS-Richtlinieneinstellungen eines Benutzers.<br>\r\n Für weitere Informationen zur Syntax sollte <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">die \"smtp_tls_policy_maps\" Dokumentation</a> konsultiert werden.",
"tls_enforce_in": "TLS eingehend erzwingen",
"tls_enforce_out": "TLS ausgehend erzwingen",
"tls_map_dest": "Ziel",
"tls_map_dest_info": "Beispiele: example.org, .example.org, [mail.example.org]:25",
"tls_map_policy": "Richtlinie",
"tls_map_parameters": "Parameter",
"tls_map_parameters_info": "Leer oder Parameter, Beispiele: protocols=!SSLv2 ciphers=medium exclude=3DES",
"booking_0": "Immer als verfügbar anzeigen",
"booking_lt0": "Unbegrenzt, jedoch anzeigen, wenn gebucht",
"booking_custom": "Benutzerdefiniertes Limit",
"booking_0_short": "Immer verfügbar",
"booking_lt0_short": "Weiches Limit",
"booking_custom_short": "Hartes Limit",
"alias_domain_alias_hint": "Alias-Adressen werden <b>nicht</b> automatisch auch auf Domain-Alias Adressen angewendet. Eine Alias-Adresse <code>mein-alias@domain</code> bildet demnach <b>nicht</b> die Adresse <code>mein-alias@alias-domain</code> ab.<br>E-Mail-Weiterleitungen an externe Postfächer sollten über Sieve (SOGo Weiterleitung oder im Reiter \"Filter\") angelegt werden.",
"domain": "Domain",
"spam_aliases": "Temp. Alias",
"alias": "Alias",
"aliases": "Aliasse",
"multiple_bookings": "Mehrfachbuchen",
"kind": "Art",
"description": "Beschreibung",
"resources": "Ressourcen",
"domains": "Domains",
"mailbox": "Mailbox",
"mailboxes": "Mailboxen",
"mailbox_quota": "Max. Größe einer Mailbox",
"domain_quota": "Gesamtspeicher",
"active": "Aktiv",
"action": "Aktion",
"backup_mx": "Backup MX",
"domain_admins": "Domain-Administratoren",
"domain_aliases": "Domain-Aliasse",
"target_domain": "Ziel-Domain",
"target_address": "Ziel-Adresse",
"username": "Benutzername",
"fname": "Name",
"filter_table": "Filtern",
"in_use": "Prozentualer Gebrauch",
"msg_num": "Anzahl Nachrichten",
"remove": "Entfernen",
"edit": "Bearbeiten",
"no_record": "Kein Eintrag für Objekt %s",
"no_record_single": "Kein Eintrag",
"add_domain": "Domain hinzufügen",
"add_domain_alias": "Domain-Alias hinzufügen",
"add_mailbox": "Mailbox hinzufügen",
"add_resource": "Ressource hinzufügen",
"add_alias": "Alias hinzufügen",
"empty": "Keine Einträge vorhanden",
"toggle_all": "Alle",
"quick_actions": "Aktionen",
"activate": "Aktivieren",
"deactivate": "Deaktivieren",
"owner": "Besitzer",
"mins_interval": "Intervall (min)",
"last_run": "Letzte Ausführung",
"last_run_reset": "Als nächstes ausführen",
"excludes": "Ausschlüsse",
"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>.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sogo_visible": "Alias Sichtbarkeit in SOGo",
"sogo_visible_y": "Alias in SOGo anzeigen",
"sogo_visible_n": "Alias in SOGo verbergen",
"public_comment": "Öffentlicher Kommentar",
"private_comment": "Privater Kommentar",
"quarantine_notification": "Quarantäne-Benachrichtigung",
"never": "Niemals",
"hourly": "Stündlich",
"daily": "Täglich",
"weekly": "Wöchentlich",
"add_filter": "Filter erstellen",
"set_prefilter": "Als Prefilter markieren",
"set_postfilter": "Als Postfilter markieren",
"filters": "Filter",
"sync_jobs": "Synchronisationen",
"inactive": "Inaktiv",
"waiting": "Wartend",
"status": "Status",
"running": "In Ausführung",
"enable_x": "Aktiviere",
"disable_x": "Deaktiviere",
"bcc_map": "BCC Map",
"bcc_map_type": "BCC Typ",
"bcc_type": "BCC Typ",
"bcc_sender_map": "Senderabhängig",
"bcc_rcpt_map": "Empfängerabhängig",
"bcc_local_dest": "Lokales Ziel",
"bcc_destinations": "BCC-Ziel",
"bcc_destination": "BCC-Ziel",
"bcc": "BCC",
"bcc_maps": "BCC-Maps",
"bcc_to_sender": "Map senderabhängig verwenden",
"bcc_to_rcpt": "Map empfängerabhängig verwenden",
"add_bcc_entry": "BCC-Eintrag hinzufügen",
"bcc_info": "Eine empfängerabhängige Map wird verwendet, wenn die BCC-Map Eintragung auf den Eingang einer E-Mail auf das lokale Ziel reagieren soll. Senderabhängige Maps verfahren nach dem gleichen Prinzip.<br/>\r\n Das lokale Ziel wird bei Fehlzustellungen an ein BCC-Ziel nicht informiert.",
"address_rewriting": "Adressumschreibung",
"recipient_maps": "Empfängerumschreibungen",
"recipient_map": "Empfängerumschreibung",
"recipient_map_info": "Empfängerumschreibung ersetzen den Empfänger einer E-Mail vor dem Versand.",
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_map_new_info": "Der neue Empfänger muss eine E-Mail-Adresse sein.",
"recipient_map_old": "Original Empfänger",
"recipient_map_new": "Neuer Empfänger",
"add_recipient_map_entry": "Empfängerumschreibung hinzufügen",
"add_tls_policy_map": "TLS-Richtlinieneintrag hinzufügen",
"mailbox_defquota": "Standard-Quota",
"alias_domain_backupmx": "Alias-Domain für Relay-Domain inaktiv",
"add_domain_record_first": "Bitte zuerst eine Domain hinzufügen",
"no": "&#10005;",
"yes": "&#10003;",
"insert_preset": "Beispiel \"%s\" laden",
"sieve_preset_header": "Beispielinhalte zur Einsicht stehen nachstehend bereit. Siehe auch <a href=\"https://de.wikipedia.org/wiki/Sieve\" target=\"_blank\">Wikipedia</a>.",
"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",
"sieve_preset_4": "Nach INBOX einsortieren und weitere Filterbearbeitung stoppen",
"sieve_preset_5": "Auto-Responder (Vacation, Urlaub)",
"sieve_preset_6": "E-Mails mit Nachricht abweisen",
"sieve_preset_7": "Weiterleiten und behalten oder verwerfen"
},
"edit": {
"generate": "generieren",
"app_name": "App Name",
"app_passwd": "App Passwörter",
"sogo_visible": "Alias in SOGo sichtbar",
"sogo_visible_info": "Diese Option hat lediglich Einfluss auf Objekte, die in SOGo darstellbar sind (geteilte oder nicht-geteilte Alias-Adressen mit dem Ziel mindestens einer lokalen Mailbox).",
"syncjob": "Sync-Job bearbeiten",
"save": "Änderungen speichern",
"username": "Benutzername",
"hostname": "Servername",
"encryption": "Verschlüsselung",
"maxage": "Maximales Alter in Tagen einer Nachricht, die kopiert werden soll<br><small>(0 = alle Nachrichten kopieren)</small>",
"subfolder2": "Ziel-Ordner<br><small>(leer = kein Unterordner)</small>",
"mins_interval": "Intervall (min)",
"maxbytespersecond": "Max. Übertragungsrate in Bytes/s (0 für unlimitiert)",
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"exclude": "Elemente ausschließen (Regex)",
"max_mailboxes": "Max. Mailboxanzahl",
"title": "Objekt bearbeiten",
"target_address": "Ziel-Adresse(n) <small>(getrennt durch Komma)</small>",
"active": "Aktiv",
"gal": "Globales Adressbuch",
"gal_info": "Das Globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>",
"force_pw_update": "Erzwinge Passwortänderung bei nächstem Login",
"force_pw_update_info": "Dem Benutzer wird lediglich der Zugang zur %s ermöglicht.",
"sogo_access": "SOGo Zugriffsrecht",
"sogo_access_info": "Zugriff auf SOGo erlauben oder verbieten. Diese Einstellung hat weder Einfluss auf den Zugang sonstiger Dienste noch entfernt sie ein vorhandenes SOGo Benutzerprofil.",
"target_domain": "Ziel-Domain",
"password": "Passwort",
"password_repeat": "Passwort (Wiederholung)",
"domain_admin": "Domain-Administrator bearbeiten",
"domain": "Domain bearbeiten",
"edit_alias_domain": "Alias-Domain bearbeiten",
"domains": "Domains",
"alias": "Alias bearbeiten",
"mailbox": "Mailbox bearbeiten",
"description": "Beschreibung",
"max_aliases": "Max. Aliasse",
"max_quota": "Max. Größe per Mailbox (MiB)",
"domain_quota": "Domain Speicherplatz gesamt (MiB)",
"backup_mx_options": "Backup MX Optionen",
"relay_domain": "Diese Domain relayen",
"relay_all": "Alle Empfänger-Adressen relayen",
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.",
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.",
"full_name": "Voller Name",
"quota_mb": "Speicherplatz (MiB)",
"sender_acl": "Darf Nachrichten versenden als",
"sender_acl_disabled": "↳ <span class=\"label label-danger\">Absenderprüfung deaktiviert</span>",
"previous": "Vorherige Seite",
"unchanged_if_empty": "Unverändert, wenn leer",
"dont_check_sender_acl": "Absender für Domain %s u. Alias-Dom. nicht prüfen",
"multiple_bookings": "Mehrfaches Buchen",
"kind": "Art",
"resource": "Ressource",
"public_comment": "Öffentlicher Kommentar",
"private_comment": "Privater Kommentar",
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
"extended_sender_acl": "Externe Absenderadressen",
"extended_sender_acl_info": "Der DKIM Domainkey der externen Absenderdomain sollte in diesen Server importiert werden, falls vorhanden.<br>\r\n Wird SPF verwendet, muss diesem Server der Versand gestattet werden.<br>\r\n Wird eine Domain oder Alias-Domain zu diesem Server hinzugefügt, die sich mit der externen Absenderadresse überschneidet, wird der externe Absender hier entfernt.<br>\r\n Ein Eintrag @domain.tld erlaubt den Versand als *@domain.tld",
"sender_acl_info": "Wird einem Mailbox-Benutzer A der Versand als Mailbox-Benutzer B gestattet, so erscheint der Absender <b>nicht</b> automatisch in SOGo zur Auswahl.<br>\r\n In SOGo muss zusätzlich eine Delegation eingerichtet werden. Dieses Verhalten trifft nicht auf Alias-Adressen zu.",
"nexthop": "Next Hop",
"delete2duplicates": "Lösche Duplikate im Ziel",
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
"sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp",
"validate_save": "Validieren und speichern",
"client_id": "Client ID",
"client_secret": "Client Secret",
"scope": "Scope",
"grant_types": "Grant types",
"redirect_uri": "Redirect/Callback URL",
"spam_score": "Einen benutzerdefiniterten Spam-Score festlegen",
"spam_policy": "Hinzufügen und Entfernen von Einträgen in White- und Blacklists",
"spam_alias": "Anpassen temporärer Alias-Adressen",
"bcc_dest_format": "BCC-Ziel muss eine gültige E-Mail-Adresse sein.",
"mailbox_quota_def": "Standard-Quota einer Mailbox",
"mbox_rl_info": "Dieses Limit wird auf den SASL Loginnamen angewendet und betrifft daher alle Absenderadressen, die der eingeloggte Benutzer verwendet. Bei Mailbox Ratelimit überwiegt ein Domain-weites Ratelimit.",
"timeout2": "Timeout für Verbindung zum lokalen Host",
"timeout1": "Timeout für Verbindung zum Remote-Host",
"relayhost": "Senderabhängige Transport Maps"
},
"add": {
"automap": "Ordner automatisch mappen (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"skipcrossduplicates": "Duplikate auch über Ordner hinweg überspringen (\"first come, first serve\")",
"gal": "Globales Adressbuch",
"gal_info": "Das Globale Adressbuch enthält alle Objekte einer Domain und kann durch keinen Benutzer geändert werden. Die Verfügbarkeitsinformation in SOGo ist nur bei eingeschaltetem globalen Adressbuch ersichtlich! <b>Zum Anwenden einer Änderung muss SOGo neugestartet werden.</b>",
"public_comment": "Öffentlicher Kommentar",
"private_comment": "Privater Kommentar",
"comment_info": "Ein privater Kommentar ist für den Benutzer nicht einsehbar. Ein öffentlicher Kommentar wird als Tooltip im Interface des Benutzers angezeigt.",
"generate": "generieren",
"syncjob": "Syncjob hinzufügen",
"syncjob_hint": "Passwörter werden unverschlüsselt abgelegt!",
"app_password": "App Passwort hinzufügen",
"app_name": "App Name",
"hostname": "Host",
"destination": "Ziel",
"nexthop": "Next Hop",
"port": "Port",
"username": "Benutzername",
"enc_method": "Verschlüsselung",
"mins_interval": "Abrufintervall (Minuten)",
"exclude": "Elemente ausschließen (Regex)",
"delete2duplicates": "Lösche Duplikate im Ziel",
"delete1": "Lösche Nachricht nach Übertragung vom Quell-Server",
"delete2": "Lösche Nachrichten von Ziel-Server, die nicht auf Quell-Server vorhanden sind",
"custom_params": "Eigene Parameter",
"custom_params_hint": "Richtig: --param=xy, falsch: --param xy",
"subscribeall": "Alle synchronisierten Ordner abonnieren",
"timeout1": "Timeout für Verbindung zum Remote-Host",
"timeout2": "Timeout für Verbindung zum lokalen Host",
"domain_matches_hostname": "Domain %s darf nicht dem Hostnamen entsprechen",
"domain": "Domain",
"active": "Aktiv",
"multiple_bookings": "Mehrfaches Buchen möglich",
"description": "Beschreibung",
"max_aliases": "Max. mögliche Aliasse",
"max_mailboxes": "Max. mögliche Mailboxen",
"mailbox_quota_m": "Max. Speicherplatz pro Mailbox (MiB)",
"domain_quota_m": "Domain Speicherplatz gesamt (MiB)",
"backup_mx_options": "Backup MX Optionen",
"relay_all": "Alle Empfänger-Adressen relayen",
"relay_domain": "Diese Domain relayen",
"relay_all_info": "↪ Wenn <b>nicht</b> alle Empfänger-Adressen relayt werden sollen, müssen \"blinde\" Mailboxen für jede Adresse, die relayt werden soll, erstellen werden.",
"relay_unknown_only": "Nur nicht-lokale Mailboxen relayen. Existente Mailboxen werden weiterhin lokal zugestellt.",
"relay_transport_info": "<div class=\"label label-info\">Info</div> Transport Maps können erstellt werden, um individuelle Ziele für eine Relay Domain zu definieren.",
"alias_address": "Alias-Adresse(n)",
"alias_address_info": "<small>Vollständige E-Mail-Adresse(n) oder @example.com, um alle Nachrichten einer Domain weiterzuleiten. Getrennt durch Komma. <b>Nur eigene Domains</b>.</small>",
"alias_domain_info": "<small>Nur gültige Domains. Getrennt durch Komma.</small>",
"target_address": "Ziel-Adresse(n)",
"target_address_info": "<small>Vollständige E-Mail-Adresse(n). Getrennt durch Komma.</small>",
"alias_domain": "Alias-Domain",
"select": "Bitte auswählen",
"target_domain": "Ziel-Domain",
"kind": "Art",
"mailbox_username": "Benutzername (linker Teil der E-Mail-Adresse)",
"full_name": "Vor- und Nachname",
"quota_mb": "Speicherplatz (MiB)",
"select_domain": "Bitte zuerst eine Domain auswählen",
"password": "Passwort",
"password_repeat": "Passwort (Wiederholung)",
"post_domain_add": "Der SOGo Container, \"sogo-mailcow\" muss nach dem Hinzufügen einer Domain neugestartet werden!<br><br>Im Anschluss sollte die DNS-Konfiguration der Domain überprüft- und \"acme-mailcow\" gegebenenfalls neugestartet werden, um Änderungen am Zertifikat zu übernehmen (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Dieser Schritt ist optional und wird alle 24 Stunden automatisch ausgeführt.",
"goto_null": "Nachrichten sofort verwerfen",
"goto_ham": "Nachrichten als <span class=\"text-success\"><b>Ham</b></span> lernen",
"goto_spam": "Nachrichten als <span class=\"text-danger\"><b>Spam</b></span> lernen",
"validation_success": "Erfolgreich validiert",
"activate_filter_warn": "Alle anderen Filter diesen Typs werden deaktiviert, falls dieses Script aktiv markiert wird.",
"validate": "Validieren",
"sieve_desc": "Kurze Beschreibung",
"sieve_type": "Filtertyp",
"add_domain_restart": "Domain hinzufügen und SOGo neustarten",
"add_domain_only": "Nur Domain hinzufügen",
"mailbox_quota_def": "Standard-Quota einer Mailbox",
"relayhost_wrapped_tls_info": "Bitte <b>keine</b> TLS-wrapped Ports verwenden (etwa SMTPS via Port 465/tcp).<br>\r\nDer Transport wird stattdessen STARTTLS anfordern, um TLS zu verwenden. TLS kann unter \"TLS Policy Maps\" erzwungen werden."
},
"acl": {
+ "pushover": "Pushover",
"spam_alias": "Temporäre E-Mail Aliasse",
"tls_policy": "Verschlüsselungsrichtlinie",
"spam_score": "Spam-Bewertung",
"spam_policy": "Blacklist/Whitelist",
"delimiter_action": "Delimiter Aktionen (tags)",
"syncjobs": "Sync Jobs",
"eas_reset": "EAS-Cache zurücksetzen",
"sogo_profile_reset": "SOGo Profil zurücksetzen",
"quarantine": "Quarantäne-Aktionen",
"quarantine_notification": "Ändern der Quarantäne-Benachrichtigung",
"quarantine_attachments": "Anhänge aus Quarantäne",
"alias_domains": "Alias-Domains hinzufügen",
"login_as": "Einloggen als Mailbox-Benutzer",
"bcc_maps": "BCC Maps",
"filters": "Filter",
"ratelimit": "Rate limit",
"recipient_maps": "Empfängerumschreibungen",
"unlimited_quota": "Unendliche Quota für Mailboxen",
"extend_sender_acl": "Eingabe externer Absenderadressen erlauben",
"prohibited": "Untersagt durch Richtlinie",
"sogo_access": "Verwalten des SOGo Zugriffsrechts erlauben",
"app_passwds": "App Passwörter verwalten"
},
"login": {
"username": "Benutzername",
"password": "Passwort",
"login": "Anmelden",
"mobileconfig_info": "Bitte als Mailbox-Benutzer einloggen, um das Verbindungsprofil herunterzuladen.",
"delayed": "Login wurde zur Sicherheit um %s Sekunde/n verzögert."
},
"tfa": {
"tfa": "Zwei-Faktor-Authentifizierung",
"set_tfa": "Konfiguriere Zwei-Faktor-Authentifizierungsmethode",
"yubi_otp": "Yubico OTP Authentifizierung",
"key_id": "Ein Name für diesen YubiKey",
"init_u2f": "Initialisiere, bitte warten...",
"start_u2f_validation": "Starte Validierung",
"error_code": "Fehlercode",
"reload_retry": "- (bei persistierendem Fehler, bitte Browserfenster neuladen)",
"key_id_totp": "Ein eindeutiger Name",
"api_register": "%s verwendet die Yubico Cloud API. Ein API-Key für den Yubico Stick kann <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">hier</a> bezogen werden.",
"u2f": "U2F Authentifizierung",
"totp": "Time-based OTP (Google Authenticator etc.)",
"none": "Deaktiviert",
"delete_tfa": "Deaktiviere 2FA",
"disable_tfa": "Deaktiviere 2FA bis zur nächsten erfolgreichen Anmeldung",
"confirm": "Bestätigen",
"select": "Bitte auswählen",
"waiting_usb_auth": "<i>Warte auf USB-Gerät...</i><br><br>Bitte jetzt den vorgesehenen Taster des U2F USB-Gerätes berühren.",
"waiting_usb_register": "<i>Warte auf USB-Gerät...</i><br><br>Bitte zuerst das obere Passwortfeld ausfüllen und erst dann den vorgesehenen Taster des U2F USB-Gerätes berühren.",
"scan_qr_code": "Bitte scannen Sie jetzt den angezeigten QR-Code:.",
"enter_qr_code": "Falls Sie den angezeigten QR-Code nicht scannen können, verwenden Sie bitte nachstehenden Sicherheitsschlüssel",
"confirm_totp_token": "Bitte bestätigen Sie die Änderung durch Eingabe eines generierten Tokens"
},
"diagnostics": {
"dns_records": "DNS-Einträge",
"dns_records_24hours": "Bitte beachten Sie, dass es bis zu 24 Stunden dauern kann, bis Änderungen an Ihren DNS-Einträgen als aktueller Status auf dieser Seite dargestellt werden. Diese Seite ist nur als Hilfsmittel gedacht, um die korrekten Werte für DNS-Einträge anzuzeigen und zu überprüfen, ob die Daten im DNS hinterlegt sind.",
"dns_records_name": "Name",
"dns_records_type": "Typ",
"dns_records_data": "Korrekte Daten",
"dns_records_status": "Aktueller Status",
"optional": "Dieser Eintrag ist optional.",
"cname_from_a": "Wert abgeleitet von A/AAAA Eintrag. Wird unterstützt, sofern der Eintrag auf die korrekte Ressource zeigt."
},
"oauth2": {
"scope_ask_permission": "Eine Anwendung hat um die folgenden Berechtigungen gebeten",
"profile": "Profil",
"profile_desc": "Persönliche Informationen anzeigen: Benutzername, Name, Erstellzeitpunkt, Änderungszeitpunkt, Status",
"permit": "Anwendung autorisieren",
"authorize_app": "Anwendung autorisieren",
"deny": "Ablehnen",
"access_denied": "Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben."
},
"debug": {
"log_info": "<p>mailcow <b>in-memory Logs</b> werden in Redis Listen gespeichert, die maximale Anzahl der Einträge pro Anwendung richtet sich nach LOG_LINES (%d).\r\n <br>In-memory Logs sind vergänglich und nicht zur ständigen Aufbewahrung bestimmt. Alle Anwendungen, die in-memory protokollieren, schreiben ebenso in den Docker Daemon.\r\n <br>Das in-memory Protokoll versteht sich als schnelle Übersicht zum Debugging eines Containers, für komplexere Protokolle sollte der Docker Daemon konsultiert werden.</p>\r\n <p><b>Externe Logs</b> werden via API externer Applikationen bezogen.</p>\r\n <p><b>Statische Logs</b> sind weitestgehend Aktivitätsprotokolle, die nicht in den Docker Daemon geschrieben werden, jedoch permanent verfügbar sein müssen (ausgeschlossen API Logs).</p>",
"in_memory_logs": "In-memory Logs",
"started_on": "Gestartet am",
"jvm_memory_solr": "JVM Speicherauslastung",
"external_logs": "Externe Logs",
"static_logs": "Statische Logs",
"solr_status": "Solr Status",
"solr_dead": "Solr startet, ist deaktiviert oder temporär nicht erreichbar.",
"solr_uptime": "Uptime",
"solr_started_at": "Gestartet am",
"solr_last_modified": "Zuletzt geändert",
"solr_size": "Größe",
"solr_docs": "Dokumente",
"restart_container": "Neustart",
"disk_usage": "Festplattennutzung",
"logs": "Protokolle",
"containers_info": "Container-Information",
"system_containers": "System & Container"
}
}
diff --git a/data/web/lang/lang.en.json b/data/web/lang/lang.en.json
index 49cfa4a5..cc8d8023 100644
--- a/data/web/lang/lang.en.json
+++ b/data/web/lang/lang.en.json
@@ -1,953 +1,973 @@
{
"header": {
"apps": "Apps",
"restart_sogo": "Restart SOGo",
"restart_netfilter": "Restart netfilter",
"mailcow_settings": "Configuration",
"administration": "Configuration & Details",
"mailboxes": "Mail Setup",
"user_settings": "User Settings",
"quarantine": "Quarantine",
"debug": "System Information"
},
"footer": {
"loading": "Please wait...",
"restart_container": "Restart container",
"restart_now": "Restart now",
"restarting_container": "Restarting container, this may take a while",
"restart_container_info": "<b>Important:</b> A graceful restart may take a while to complete, please wait for it to finish.",
"confirm_delete": "Confirm deletion",
"delete_these_items": "Please confirm your changes to the following object id",
"delete_now": "Delete now",
"cancel": "Cancel",
"hibp_nok": "Matched! This is a potentially dangerous password!",
"hibp_ok": "No match found."
},
"danger": {
"invalid_filter_type": "Invalid filter type",
"file_open_error": "File cannot be opened for writing",
"transport_dest_exists": "Transport destination \"%s\" exists",
"unlimited_quota_acl": "Unlimited quota prohibited by ACL",
"mysql_error": "MySQL error: %s",
"redis_error": "Redis error: %s",
+ "pushover_token": "Pushover token has a wrong format",
+ "pushover_key": "Pushover key has a wrong format",
+ "pushover_credentials_missing": "Pushover token and or key missing",
"unknown_tfa_method": "Unknown TFA method",
"totp_verification_failed": "TOTP verification failed",
"u2f_verification_failed": "U2F verification failed: %s",
"yotp_verification_failed": "Yubico OTP verification failed: %s",
"ip_list_empty": "List of allowed IPs cannot be empty",
"invalid_destination": "Destination format \"%s\" is invalid",
"invalid_nexthop": "Next hop format is invalid",
"invalid_nexthop_authenticated": "Next hop exists with different credentials, please update the existing credentials for this next hop first.",
"next_hop_interferes": "%s interferes with nexthop %s",
"next_hop_interferes_any": "An existing next hop interferes with %s",
"rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long",
"unknown": "An unknown error occurred",
"malformed_username": "Malformed username",
"login_failed": "Login failed",
"set_acl_failed": "Failed to set ACL",
"no_user_defined": "No user defined",
"script_empty": "Script cannot be empty",
"sieve_error": "Sieve parser error: %s",
"value_missing": "Please provide all values",
"filter_type": "Wrong filter type",
"domain_cannot_match_hostname": "Domain cannot match hostname",
"rl_timeframe": "Rate limit time frame is incorrect",
"invalid_bcc_map_type": "Invalid BCC map type",
"bcc_empty": "BCC destination cannot be empty",
"bcc_must_be_email": "BCC destination %s is not a valid email address",
"bcc_exists": "A BCC map %s exists for type %s",
"private_key_error": "Private key error: %s",
"map_content_empty": "Map content cannot be empty",
"app_name_empty": "App name cannot be empty",
"settings_map_invalid": "Settings map ID %s invalid",
"app_passwd_id_invalid": "App password ID %s invalid",
"global_map_invalid": "Global map ID %s invalid",
"global_map_write_error": "Could not write global map ID %s: %s",
"global_filter_write_error": "Could not write filter file: %s",
"invalid_host": "Invalid host specified: %s",
"relayhost_invalid": "Map entry %s is invalid",
"dkim_domain_or_sel_invalid": "DKIM domain or selector invalid: %s",
"access_denied": "Access denied or invalid form data",
"domain_invalid": "Domain name is empty or invalid",
"mailbox_quota_exceeds_domain_quota": "Max. quota exceeds domain quota limit",
"object_is_not_numeric": "Value %s is not numeric",
"alias_empty": "Alias address must not be empty",
"last_key": "Last key cannot be deleted, please deactivate TFA instead.",
"goto_empty": "An alias address must contain at least one valid goto address",
"policy_list_from_exists": "A record with given name exists",
"policy_list_from_invalid": "Record has invalid format",
"alias_invalid": "Alias address %s is invalid",
"goto_invalid": "Goto address %s is invalid",
"alias_domain_invalid": "Alias domain %s is invalid",
"target_domain_invalid": "Target domain %s is invalid",
"object_exists": "Object %s already exists",
"domain_exists": "Domain %s already exists",
"alias_goto_identical": "Alias and goto address must not be identical",
"aliasd_targetd_identical": "Alias domain must not be equal to target domain: %s",
"maxquota_empty": "Max. quota per mailbox must not be 0.",
"targetd_not_found": "Target domain %s not found",
"targetd_relay_domain": "Target domain %s is a relay domain",
"username_invalid": "Username %s cannot be used",
"password_mismatch": "Confirmation password does not match",
"password_complexity": "Password does not meet the policy",
"password_empty": "Password must not be empty",
"mailbox_invalid": "Mailbox name is invalid",
"description_invalid": "Resource description for %s is invalid",
"resource_invalid": "Resource name %s is invalid",
"is_alias": "%s is already known as an alias address",
"is_alias_or_mailbox": "%s is already known as an alias, a mailbox or an alias address expanded from an alias domain.",
"is_spam_alias": "%s is already known as a spam alias address",
"quota_not_0_not_numeric": "Quota must be numeric and >= 0",
"domain_not_found": "Domain %s not found",
"max_mailbox_exceeded": "Max. mailboxes exceeded (%d of %d)",
"max_alias_exceeded": "Max. aliases exceeded",
"mailbox_quota_exceeded": "Quota exceeds the domain limit (max. %d MiB)",
"mailbox_quota_left_exceeded": "Not enough space left (space left: %d MiB)",
"max_quota_in_use": "Mailbox quota must be greater or equal to %d MiB",
"domain_quota_m_in_use": "Domain quota must be greater or equal to %s MiB",
"mailboxes_in_use": "Max. mailboxes must be greater or equal to %d",
"aliases_in_use": "Max. aliases must be greater or equal to %d",
"sender_acl_invalid": "Sender ACL value %s is invalid",
"domain_not_empty": "Cannot remove non-empty domain %s",
"validity_missing": "Please assign a period of validity",
"comment_too_long": "Comment too long, max 160 chars allowed",
"img_tmp_missing": "Cannot validate image file: Temporary file not found",
"img_invalid": "Cannot validate image file",
"invalid_mime_type": "Invalid mime type",
"imagick_exception": "Error: Imagick exception while reading image",
"spam_learn_error": "Spam learn error: %s",
"ham_learn_error": "Ham learn error: %s",
"release_send_failed": "Message could not be released: %s",
"invalid_recipient_map_new": "Invalid new recipient specified: %s",
"invalid_recipient_map_old": "Invalid original recipient specified: %s",
"recipient_map_entry_exists": "A Recipient map entry \"%s\" exists",
"tls_policy_map_entry_exists": "A TLS policy map entry \"%s\" exists",
"tls_policy_map_parameter_invalid": "Policy parameter is invalid",
"tls_policy_map_dest_invalid": "Policy destination is invalid",
"temp_error": "Temporary error",
"text_empty": "Text must not be empty",
"subject_empty": "Subject must not be empty",
"from_invalid": "Sender must not be empty",
"network_host_invalid": "Invalid network or host: %s",
"mailbox_defquota_exceeds_mailbox_maxquota": "Default quota exceeds max quota limit",
"defquota_empty": "Default quota per mailbox must not be 0.",
"extra_acl_invalid": "External sender address \"%s\" is invalid",
"extra_acl_invalid_domain": "External sender \"%s\" uses an invalid domain"
},
"success": {
+ "pushover_settings_edited": "Pushover settings successfully set, please verify credentials.",
"global_filter_written": "Filter was successfully written to file",
"learned_ham": "Successfully learned ID % as ham",
"verified_totp_login": "Verified TOTP login",
"verified_u2f_login": "Verified U2F login",
"verified_yotp_login": "Verified Yubico OTP login",
"rspamd_ui_pw_set": "Rspamd UI password successfully set",
"queue_command_success": "Queue command completed successfully",
"logged_in_as": "Logged in as %s",
"rl_saved": "Rate limit for object %s saved",
"acl_saved": "ACL for object %s saved",
"deleted_syncjobs": "Deleted syncjobs: %s",
"deleted_syncjob": "Deleted syncjob ID %s",
"delete_filters": "Deleted filters: %s",
"delete_filter": "Deleted filters ID %s",
"bcc_saved": "BCC map entry saved",
"bcc_edited": "BCC map entry %s edited",
"bcc_deleted": "BCC map entries deleted: %s",
"settings_map_added": "Added settings map entry",
"app_passwd_added": "Added new app password",
"settings_map_removed": "Removed settings map ID %s",
"app_passwd_removed": "Removed app password ID %s",
"saved_settings": "Saved settings",
"db_init_complete": "Database initialization completed",
"dkim_removed": "DKIM key %s has been removed",
"dkim_added": "DKIM key %s has been saved",
"dkim_duplicated": "DKIM key for domain %s has been copied to %s",
"domain_added": "Added domain %s",
"items_deleted": "Item %s successfully deleted",
"item_deleted": "Item %s successfully deleted",
"alias_added": "Alias address %s has been added",
"alias_modified": "Changes to alias address %s have been saved",
"mailbox_modified": "Changes to mailbox %s have been saved",
"resource_modified": "Changes to mailbox %s have been saved",
"object_modified": "Changes to object %s have been saved",
"f2b_modified": "Changes to Fail2ban parameters have been saved",
"aliasd_added": "Added alias domain %s",
"aliasd_modified": "Changes to alias domain %s have been saved",
"domain_modified": "Changes to domain %s have been saved",
"domain_admin_modified": "Changes to domain administrator %s have been saved",
"domain_admin_added": "Domain administrator %s has been added",
"admin_added": "Administrator %s has been added",
"admin_modified": "Changes to administrator have been saved",
"admin_api_modified": "Changes to API have been saved",
"license_modified": "Changes to license have been saved",
"mailbox_added": "Mailbox %s has been added",
"resource_added": "Resource %s has been added",
"domain_removed": "Domain %s has been removed",
"alias_removed": "Alias %s has been removed",
"alias_domain_removed": "Alias domain %s has been removed",
"domain_admin_removed": "Domain administrator %s has been removed",
"admin_removed": "Administrator %s has been removed",
"mailbox_removed": "Mailbox %s has been removed",
"eas_reset": "ActiveSync devices for user %s were reset",
"sogo_profile_reset": "SOGo profile for user %s was reset",
"resource_removed": "Resource %s has been removed",
"hash_deleted": "Hash deleted",
"forwarding_host_removed": "Forwarding host %s has been removed",
"forwarding_host_added": "Forwarding host %s has been added",
"relayhost_removed": "Map entry %s has been removed",
"relayhost_added": "Map entry %s has been added",
"upload_success": "File uploaded successfully",
"app_links": "Saved changes to app links",
"ui_texts": "Saved changes to UI texts",
"reset_main_logo": "Reset to default logo",
"items_released": "Selected items were released",
"item_released": "Item %s released",
"qlearn_spam": "Message ID %s was learned as spam and deleted",
"recipient_map_entry_saved": "Recipient map entry \"%s\" has been saved",
"recipient_map_entry_deleted": "Recipient map ID %s has been deleted",
"tls_policy_map_entry_saved": "TLS policy map entry \"%s\" has been saved",
"tls_policy_map_entry_deleted": "TLS policy map ID %s has been deleted"
},
"info": {
"awaiting_tfa_confirmation": "Awaiting TFA confirmation",
"session_expires": "Your session will expire in about 15 seconds",
"no_action": "No action applicable"
},
"warning": {
"domain_added_sogo_failed": "Added domain but failed to restart SOGo, please check your server logs.",
"session_ua": "Form token invalid: User-Agent validation error",
"session_token": "Form token invalid: Token mismatch",
"cannot_delete_self": "Cannot delete logged in user",
"no_active_admin": "Cannot deactivate last active admin",
"hash_not_found": "Hash not found or already deleted",
"fuzzy_learn_error": "Fuzzy hash learn error: %s",
"ip_invalid": "Skipped invalid IP: %s",
- "quota_exceeded_scope": "Domain quota exceeded: Only unlimited/unrated mailboxes can be created in this domain scope."
+ "quota_exceeded_scope": "Domain quota exceeded: Only unlimited mailboxes can be created in this domain scope."
},
"user": {
+ "pushover_info": "Push notification settings will apply to all clean (non-spam) mail delivered to <b>%s</b> including aliases (shared, non-shared, tagged).",
+ "verify": "Verify",
+ "pushover_verify": "Verify credentials",
+ "title": "Title",
+ "pushover_title": "Notification title",
+ "text": "Text",
+ "pushover_text": "Notification text ({SUBJECT} will be replaced by mail subject)",
"no_last_login": "No last UI login information",
"last_mail_login": "Last mail login",
+ "save": "Save changes",
"apple_connection_profile": "Apple connection profile",
"apple_connection_profile_mailonly": "This connection profile includes IMAP and SMTP configuration parameters for an Apple device.",
"apple_connection_profile_complete": "This connection profile includes IMAP and SMTP parameters as well as CalDAV (calendars) and CardDAV (contacts) pathes for an Apple device.",
"email": "Email",
"email_and_dav": "Email, calendars and contacts",
"generate": "generate",
"create_app_passwd": "Create app password",
"app_passwds": "App passwords",
"app_name": "App name",
"app_hint": "App passwords are alternative passwords for your <b>IMAP and SMTP</b> login. The username remains unchanged.<br>SOGo (including ActiveSync) is not available through app passwords.",
"loading": "Loading...",
"force_pw_update": "You <b>must</b> set a new password to be able to access groupware related services.",
"active_sieve": "Active filter",
"show_sieve_filters": "Show active user sieve filter",
"no_active_filter": "No active filter available",
"messages": "messages",
"in_use": "Used",
"user_settings": "User settings",
"mailbox_details": "Mailbox details",
"change_password": "Change password",
"client_configuration": "Show configuration guides for email clients and smartphones",
"new_password": "New password",
"password": "Password",
"save_changes": "Save changes",
"password_now": "Current password (confirm changes)",
"new_password_repeat": "Confirmation password (repeat)",
"password_repeat": "Password (repeat)",
"new_password_description": "Requirement: 6 characters long, letters and numbers.",
"spam_aliases": "Temporary email aliases",
"alias": "Alias",
"shared_aliases": "Shared alias addresses",
"shared_aliases_desc": "Shared aliases are not affected by user specific settings such as the spam filter or encryption policy. Corresponding spam filters can only be made by an administrator as a domain-wide policy.",
"direct_aliases": "Direct alias addresses",
"direct_aliases_desc": "Direct alias addresses are affected by spam filter and TLS policy settings.",
"is_catch_all": "Catch-all for domain/s",
"aliases_also_send_as": "Also allowed to send as user",
"aliases_send_as_all": "Do not check sender access for the following domain(s) and its alias domains",
"alias_create_random": "Generate random alias",
"alias_extend_all": "Extend aliases by 1 hour",
"alias_valid_until": "Valid until",
"alias_remove_all": "Remove all aliases",
"alias_time_left": "Time left",
"alias_full_date": "d.m.Y, H:i:s T",
"alias_select_validity": "Period of validity",
"sync_jobs": "Sync jobs",
"expire_in": "Expire in",
"hour": "hour",
"hours": "hours",
"day": "day",
"week": "week",
"weeks": "weeks",
"spamfilter": "Spam filter",
"spamfilter_wl": "Whitelist",
"spamfilter_wl_desc": "Whitelisted email addresses to <b>never</b> classify as spam. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
"spamfilter_bl": "Blacklist",
"spamfilter_bl_desc": "Blacklisted email addresses to <b>always</b> classify as spam and reject. Wildcards may be used. A filter is only applied to direct aliases (aliases with a single target mailbox) excluding catch-all aliases and a mailbox itself.",
"spamfilter_behavior": "Rating",
"spamfilter_table_rule": "Rule",
"spamfilter_table_action": "Action",
"spamfilter_table_empty": "No data to display",
"spamfilter_table_remove": "remove",
"spamfilter_table_add": "Add item",
"spamfilter_green": "Green: this message is not spam",
"spamfilter_yellow": "Yellow: this message may be spam, will be tagged as spam and moved to your junk folder",
"spamfilter_red": "Red: This message is spam and will be rejected by the server",
"spamfilter_default_score": "Default values",
"spamfilter_hint": "The first value describes the \"low spam score\", the second represents the \"high spam score\".",
"spamfilter_table_domain_policy": "n/a (domain policy)",
"waiting": "Waiting",
"status": "Status",
"running": "Running",
"tls_policy_warning": "<strong>Warning:</strong> If you decide to enforce encrypted mail transfer, you may lose emails.<br>Messages to not satisfy the policy will be bounced with a hard fail by the mail system.<br>This option applies to your primary email address (login name), all addresses derived from alias domains as well as alias addresses <b>with only this single mailbox</b> as target.",
"tls_policy": "Encryption policy",
"tls_enforce_in": "Enforce TLS incoming",
"tls_enforce_out": "Enforce TLS outgoing",
"no_record": "No record",
"tag_handling": "Set handling for tagged mail",
"tag_in_subfolder": "In subfolder",
"tag_in_subject": "In subject",
"tag_in_none": "Do nothing",
"tag_help_explain": "In subfolder: a new subfolder named after the tag will be created below INBOX (\"INBOX/Facebook\").<br>\r\nIn subject: the tags name will be prepended to the mails subject, example: \"[Facebook] My News\".",
"tag_help_example": "Example for a tagged email address: me<b>+Facebook</b>@example.org",
"eas_reset": "Reset ActiveSync device cache",
"eas_reset_now": "Reset now",
"eas_reset_help": "In many cases a device cache reset will help to recover a broken ActiveSync profile.<br><b>Attention:</b> All elements will be redownloaded!",
"sogo_profile_reset": "Reset SOGo profile",
"sogo_profile_reset_now": "Reset profile now",
"sogo_profile_reset_help": "This will destroy a users SOGo profile and <b>delete all contact and calendar data irretrievable</b>.",
"encryption": "Encryption",
"username": "Username",
"last_run": "Last run",
"excludes": "Excludes",
"interval": "Interval",
"active": "Active",
"action": "Action",
"edit": "Edit",
"remove": "Remove",
"create_syncjob": "Create new sync job",
"sender_acl_disabled": "<span class=\"label label-danger\">Sender check is disabled</span>",
"quarantine_notification": "Quarantine notifications",
"never": "Never",
"hourly": "Hourly",
"daily": "Daily",
"weekly": "Weekly",
"quarantine_notification_info": "Once a notification has been sent, items will be marked as \"notified\" and no further notifications will be sent for this particular item.",
"spam_score_reset": "Reset to server default"
},
"admin": {
+ "pushover_info": "Push notification settings will apply to all clean (non-spam) mail delivered to <b>%s</b> including aliases (shared, non-shared, tagged).",
+ "verify": "Verify",
+ "pushover_verify": "Verify credentials",
+ "title": "Title",
+ "pushover_title": "Notification title",
+ "text": "Text",
+ "pushover_text": "Notification text ({SUBJECT} will be replaced by mail subject)",
"spamfilter": "Spam filter",
"domain": "Domain",
"domain_s": "Domain/s",
"rspamd-com_settings": "A setting name will be auto-generated, please see the example presets below. For more details see <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd docs</a>",
"no_new_rows": "No further rows available",
"queue_manager": "Queue manager",
"additional_rows": " additional rows were added",
"private_key": "Private key",
"import": "Import",
"duplicate": "Duplicate",
"import_private_key": "Import private key",
"duplicate_dkim": "Duplicate DKIM record",
"dkim_from": "From",
"dkim_to": "To",
"dkim_from_title": "Source domain to copy data from",
"dkim_to_title": "Target domain/s - will be overwritten",
"f2b_parameters": "Fail2ban parameters",
"f2b_ban_time": "Ban time (s)",
"f2b_max_attempts": "Max. attempts",
"f2b_retry_window": "Retry window (s) for max. attempts",
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
"f2b_whitelist": "Whitelisted networks/hosts",
"f2b_blacklist": "Blacklisted networks/hosts",
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
"search_domain_da": "Search domains",
"r_inactive": "Inactive restrictions",
"r_active": "Active restrictions",
"r_info": "Greyed out/disabled elements on the list of active restrictions are not known as valid restrictions to mailcow and cannot be moved. Unknown restrictions will be set in order of appearance anyway. <br>You can add new elements in <code>inc/vars.local.inc.php</code> to be able to toggle them.",
"dkim_key_length": "DKIM key length (bits)",
"dkim_key_valid": "Key valid",
"dkim_key_unused": "Key unused",
"dkim_key_missing": "Key missing",
"dkim_add_key": "Add ARC/DKIM key",
"dkim_keys": "ARC/DKIM keys",
"dkim_private_key": "Private key",
"dkim_domains_wo_keys": "Select domains with missing keys",
"dkim_domains_selector": "Selector",
"add": "Add",
"configuration": "Configuration",
"password": "Password",
"password_repeat": "Confirmation password (repeat)",
"active": "Active",
"inactive": "Inactive",
"action": "Action",
"add_domain_admin": "Add domain administrator",
"add_admin": "Add administrator",
"add_settings_rule": "Add settings rule",
"rsetting_desc": "Short description",
"rsetting_content": "Rule content",
"rsetting_none": "No rules available",
"rsetting_no_selection": "Please select a rule",
"rsettings_preset_1": "Disable all but DKIM and rate limit for authenticated users",
"rsettings_preset_2": "Postmasters want spam",
"rsettings_preset_3": "Only allow specific senders for a mailbox (i.e. usage as internal mailbox only)",
"rsettings_insert_preset": "Insert example preset \"%s\"",
"rsetting_add_rule": "Add rule",
"queue_ays": "Please confirm you want to delete all items from the current queue.",
"arrival_time": "Arrival time (server time)",
"message_size": "Message size",
"sender": "Sender",
"recipients": "Recipients",
"admin_domains": "Domain assignments",
"domain_admins": "Domain administrators",
"flush_queue": "Flush queue",
"delete_queue": "Delete all",
"queue_deliver_mail": "Deliver",
"queue_hold_mail": "Hold",
"queue_unhold_mail": "Unhold",
"username": "Username",
"edit": "Edit",
"remove": "Remove",
"save": "Save changes",
"admin": "Administrator",
"admin_details": "Edit administrator details",
"unchanged_if_empty": "If unchanged leave blank",
"yes": "&#10003;",
"no": "&#10005;",
"access": "Access",
"no_record": "No record",
"filter_table": "Filter table",
"empty": "No results",
"time": "Time",
"last_applied": "Last applied",
"reset_limit": "Remove hash",
"hash_remove_info": "Removing a ratelimit hash (if still existing) will reset its counter completely.<br>\r\n Each hash is indicated by an individual color.",
"authed_user": "Auth. user",
"priority": "Priority",
"message": "Message",
"rate_name": "Rate name",
"refresh": "Refresh",
"to_top": "Back to top",
"in_use_by": "In use by",
"forwarding_hosts": "Forwarding Hosts",
"forwarding_hosts_hint": "Incoming messages are unconditionally accepted from any hosts listed here. These hosts are then not checked against DNSBLs or subjected to greylisting. Spam received from them is never rejected, but optionally it can be filed into the Junk folder. The most common use for this is to specify mail servers on which you have set up a rule that forwards incoming emails to your mailcow server.",
"forwarding_hosts_add_hint": "You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).",
"relayhosts_hint": "Define sender-dependent transports to be able to select them in a domains configuration dialog.<br>\r\n The transport service is always \"smtp:\". A users individual outbound TLS policy setting is taken into account.<br>\r\n Affects selected domains including alias domains.",
"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:\".<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.",
"add_relayhost_hint": "Please be aware that authentication data, if any, will be stored as plain text.",
"add_transports_hint": "Please be aware that authentication data, if any, will be stored as plain text.",
"host": "Host",
"source": "Source",
"add_forwarding_host": "Add forwarding host",
"add_relayhost": "Add sender-dependent transport",
"add_transport": "Add transport",
"relayhosts": "Sender-dependent transports",
"transport_maps": "Transport Maps",
"routing": "Routing",
"credentials_transport_warning": "<b>Warning</b>: Adding a new transport map entry will update the credentials for all entries with a matching nexthop column.",
"destination": "Destination",
"nexthop": "Next hop",
"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\nPathes 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_client_id": "Client ID",
"oauth2_client_secret": "Client secret",
"oauth2_redirect_uri": "Redirect URI",
"oauth2_revoke_tokens": "Revoke all client tokens",
"oauth2_renew_secret": "Generate new client secret",
"relay_from": "\"From:\" address",
"relay_run": "Run test",
"api_allow_from": "Allow API access from these IPs (separated by comma or new line)",
"api_key": "API key",
"api_skip_ip_check": "Skip IP check for API",
"activate_api": "Activate API",
"regen_api_key": "Regenerate API key",
"ban_list_info": "See a list of banned IPs below: <b>network (remaining ban time) - [actions]</b>.<br />IPs queued to be unbanned will be removed from the active ban list within a few seconds.<br />Red labels indicate active permanent bans by blacklisting.",
"unban_pending": "unban pending",
"queue_unban": "queue unban",
"no_active_bans": "No active bans",
"quarantine": "Quarantine",
"rspamd_settings_map": "Rspamd settings map",
"quota_notifications": "Quota notifications",
"quota_notifications_vars": "{{percent}} equals the current quota of the user<br>{{username}} is the mailbox name",
"active_rspamd_settings_map": "Active settings map",
"quota_notifications_info": "Quota notications are sent to users once when crossing 80% and once when crossing 95% usage.",
"quarantine_bcc": "Send a copy of all notifications (BCC) to this recipient:<br><small>Leave empty to disable. <b>Unsigned, unchecked mail. Should be delivered internally only.</b></small>",
"quarantine_retention_size": "Retentions per mailbox:<br><small>0 indicates <b>inactive</b>.</small>",
"quarantine_max_size": "Maximum size in MiB (larger elements are discarded):<br><small>0 does <b>not</b> indicate unlimited.</small>",
"quarantine_max_age": "Maximum age in days<br><small>Value must be equal to or greater than 1 day.</small>",
"quarantine_exclude_domains": "Exclude domains and alias-domains",
"quarantine_release_format": "Format of released items",
"quarantine_release_format_raw": "Unmodified original",
"quarantine_release_format_att": "As attachment",
"quarantine_notification_sender": "Notification email sender",
"quarantine_notification_subject": "Notification email subject",
"quarantine_notification_html": "Notification email template:<br><small>Leave empty to restore default template.</small>",
"quota_notification_sender": "Notification email sender",
"quota_notification_subject": "Notification email subject",
"quota_notification_html": "Notification email template:<br><small>Leave empty to restore default template.</small>",
"ui_texts": "UI labels and texts",
"help_text": "Override help text below login mask (HTML allowed)",
"title_name": "\"mailcow UI\" website title",
"main_name": "\"mailcow UI\" name",
"apps_name": "\"mailcow Apps\" name",
"ui_footer": "Footer (HTML allowed)",
"customize": "Customize",
"change_logo": "Change logo",
"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.",
"upload": "Upload",
"app_links": "App links",
"app_name": "App name",
"link": "Link",
"remove_row": "Remove row",
"add_row": "Add row",
"reset_default": "Reset to default",
"merged_vars_hint": "Greyed out rows were merged from <code>vars.(local.)inc.php</code> and cannot be modified.",
"sys_mails": "System mails",
"subject": "Subject",
"from": "From",
"include_exclude": "Include/Exclude",
"include_exclude_info": "By default - with no selection - <b>all mailboxes</b> are addressed",
"excludes": "Excludes these recipients",
"includes": "Include these recipients",
"text": "Text",
"activate_send": "Activate send button",
"send": "Send",
"api_info": "The API is a work in progress. The documentation can be found at <a href=\"/api\">/api</a>",
"guid_and_license": "GUID & License",
"guid": "GUID - unique instance ID",
"license_info": "A license is not required but helps further development.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"SAL order\">Register your GUID here</a> or <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Support order\">buy support for your mailcow installation.</a>",
"validate_license_now": "Validate GUID against license server",
"customer_id": "Customer ID",
"service_id": "Service ID",
"sal_level": "Moo level",
"lookup_mx": "Match destination against MX (.outlook.com to route all mail targeted to a MX *.outlook.com over this hop)",
"transport_dest_format": "Syntax: example.org, .example.org, *, box@example.org (multiple values can be comma-separated)",
"rspamd_global_filters_agree": "I will be careful!",
"rspamd_global_filters": "Global filter maps",
"rspamd_global_filters_info": "Global filter maps contain different kind of global black and whitelists.",
"rspamd_global_filters_regex": "Their names explain their purpose. All content must contain valid regular expression in the format of \"/pattern/options\" (e.g. <code>/.+@domain\\.tld/i</code>).<br>\r\n Although rudimentary checks are being executed on each line of regex, Rspamds functionality can be broken, if it fails to read the syntax correctly.<br>\r\n Rspamd will try to read the map content when changed. If you experience problems, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restart Rspamd</a> to enforce a map reload."
},
"start": {
"mailcow_apps_detail": "Use a mailcow app to access your mails, calendar, contacts and more.",
"mailcow_panel_detail": "<b>Domain administrators</b> create, modify or delete mailboxes and aliases, change domains and read further information about their assigned domains.<br>\r\n<b>Mailbox users</b> are able to create time-limited aliases (spam aliases), change their password and spam filter settings.",
"imap_smtp_server_auth_info": "Please use your full email address and the PLAIN authentication mechanism.<br>\r\nYour login data will be encrypted by the server-side mandatory encryption.",
"help": "Show/Hide help panel"
},
"quarantine": {
"refresh": "Refresh",
"table_size": "Table size",
"table_size_show_n": "Show %s items",
"disabled_by_config": "The current system configuration disables the quarantine functionality.",
"quarantine": "Quarantine",
"learn_spam_delete": "Learn as spam and delete",
"qinfo": "The quarantine system will save rejected mail to the database, while the sender will <em>not</em> be given the impression of a delivered mail.\r\n <br>\"Learn as spam and delete\" will learn a message as spam via Bayesian theorem and also calculate fuzzy hashes to deny similar messages in the future.\r\n <br>Please be aware that learning multiple messages can be - depending on your system - time consuming.",
"download_eml": "Download (.eml)",
"release": "Release",
"empty": "No results",
"toggle_all": "Toggle all",
"quick_actions": "Actions",
"remove": "Remove",
"received": "Received",
"action": "Action",
"rcpt": "Recipient",
"qid": "Rspamd QID",
"sender": "Sender",
"show_item": "Show item",
"check_hash": "Search file hash @ VT",
"qitem": "Quarantine item",
"rspamd_result": "Rspamd result",
"subj": "Subject",
"recipients": "Recipients",
"text_plain_content": "Content (text/plain)",
"text_from_html_content": "Content (converted html)",
"atts": "Attachments",
"low_danger": "Low danger",
"neutral_danger": "Neutral/no rating",
"medium_danger": "Medium danger",
"high_danger": "High",
"danger": "Danger",
"spam_score": "Score",
"confirm_delete": "Confirm the deletion of this element.",
"qhandler_success": "Request successfully sent to the system. You can now close the window.",
"release_body": "We have attached your message as eml file to this message.",
"release_subject": "Potentially damaging quarantine item %s",
"notified": "Notified"
},
"mailbox": {
"stats": "Statistics",
"last_mail_login": "Last mail login",
"table_size": "Table size",
"table_size_show_n": "Show %s items",
"tls_policy_maps": "TLS policy maps",
"sogo_allow_admin_hint": "Administrative SOGo logins are only available for mailboxes with an existing SOGo profile. A user must have been logged in to SOGo at least once.",
"tls_policy_maps_long": "Outgoing TLS policy map overrides",
"tls_policy_maps_info": "This policy map overrides outgoing TLS transport rules independently of a users TLS policy settings.<br>\r\n Please check <a href=\"http://www.postfix.org/postconf.5.html#smtp_tls_policy_maps\" target=\"_blank\">the \"smtp_tls_policy_maps\" docs</a> for further information.",
"tls_enforce_in": "Enforce TLS incoming",
"tls_enforce_out": "Enforce TLS outgoing",
"tls_map_dest": "Destination",
"tls_map_dest_info": "Examples: example.org, .example.org, [mail.example.org]:25",
"tls_map_policy": "Policy",
"tls_map_parameters": "Parameters",
"tls_map_parameters_info": "Empty or parameters, for example: protocols=!SSLv2 ciphers=medium exclude=3DES",
"booking_0": "Always show as free",
"booking_lt0": "Unlimited, but show as busy when booked",
"booking_custom": "Hard-limit to a custom amount of bookings",
"booking_0_short": "Always free",
"booking_lt0_short": "Soft limit",
"booking_custom_short": "Hard limit",
"alias_domain_alias_hint": "Aliases are <b>not</b> applied on domain aliases automatically. An alias address <code>my-alias@domain</code> <b>does not</b> cover the address <code>my-alias@alias-domain</code> (where \"alias-domain\" is an imaginary alias domain for \"domain\").<br>Please use a sieve filter to redirect mail to an external mailbox (see tab \"Filters\" or use SOGo -> Forwarder).",
"domain": "Domain",
"spam_aliases": "Temp. alias",
"multiple_bookings": "Multiple bookings",
"kind": "Kind",
"description": "Description",
"alias": "Alias",
"aliases": "Aliases",
"domains": "Domains",
"mailboxes": "Mailboxes",
"mailbox": "Mailbox",
"resources": "Resources",
"mailbox_quota": "Max. size of a mailbox",
"domain_quota": "Quota",
"active": "Active",
"action": "Action",
"backup_mx": "Backup MX",
"domain_admins": "Domain administrators",
"domain_aliases": "Domain aliases",
"target_domain": "Target domain",
"target_address": "Goto address",
"username": "Username",
"fname": "Full name",
"filter_table": "Filter table",
"yes": "&#10003;",
"no": "&#10005;",
"in_use": "In use (%)",
"msg_num": "Message #",
"remove": "Remove",
"edit": "Edit",
"no_record": "No record for object %s",
"no_record_single": "No record",
"add_domain": "Add domain",
"add_domain_alias": "Add domain alias",
"add_mailbox": "Add mailbox",
"add_resource": "Add resource",
"add_alias": "Add alias",
"add_domain_record_first": "Please add a domain first",
"empty": "No results",
"toggle_all": "Toggle all",
"quick_actions": "Actions",
"activate": "Activate",
"deactivate": "Deactivate",
"owner": "Owner",
"mins_interval": "Interval (min)",
"last_run": "Last run",
"excludes": "Excludes",
"last_run_reset": "Schedule next",
"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.<br><br>Global sieve prefilter → Prefilter → User scripts → Postfilter → Global sieve postfilter",
"sogo_visible": "Alias is visible in SOGo",
"sogo_visible_y": "Show alias in SOGo",
"sogo_visible_n": "Hide alias in SOGo",
"public_comment": "Public comment",
"private_comment": "Private comment",
"quarantine_notification": "Quarantine notifications",
"never": "Never",
"hourly": "Hourly",
"daily": "Daily",
"weekly": "Weekly",
"add_filter": "Add filter",
"set_prefilter": "Mark as prefilter",
"set_postfilter": "Mark as postfilter",
"filters": "Filters",
"sync_jobs": "Sync jobs",
"inactive": "Inactive",
"waiting": "Waiting",
"status": "Status",
"running": "Running",
"enable_x": "Enable",
"disable_x": "Disable",
"bcc_map": "BCC map",
"bcc_map_type": "BCC type",
"bcc_type": "BCC type",
"bcc_sender_map": "Sender map",
"bcc_rcpt_map": "Recipient map",
"bcc_local_dest": "Local destination",
"bcc_destinations": "BCC destination",
"bcc_destination": "BCC destination",
"bcc": "BCC",
"bcc_maps": "BCC maps",
"bcc_to_sender": "Switch to sender map type",
"bcc_to_rcpt": "Switch to recipient map type",
"add_bcc_entry": "Add BCC map",
"add_tls_policy_map": "Add TLS policy map",
"bcc_info": "BCC maps are used to silently forward copies of all messages to another address. A recipient map type entry is used, when the local destination acts as recipient of a mail. Sender maps conform to the same principle.<br/>\r\n The local destination will not be informed about a failed delivery.",
"address_rewriting": "Address rewriting",
"recipient_maps": "Recipient maps",
"recipient_map": "Recipient map",
"recipient_map_info": "Recipient maps are used to replace the destination address on a message before it is delivered.",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_map_new_info": "Recipient map destination must be a valid email address.",
"recipient_map_old": "Original recipient",
"recipient_map_new": "New recipient",
"add_recipient_map_entry": "Add recipient map",
"mailbox_defquota": "Default mailbox size",
"alias_domain_backupmx": "Alias domain inactive for relay domain",
"insert_preset": "Insert example preset \"%s\"",
"sieve_preset_header": "Please see the example presets below. For more details see <a href=\"https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)\" target=\"_blank\">Wikipedia</a>.",
"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",
"sieve_preset_4": "File into INBOX, skip further processing by sieve filters",
"sieve_preset_5": "Auto responder (vacation)",
"sieve_preset_6": "Reject mail with reponse",
"sieve_preset_7": "Redirect and keep/drop"
},
"edit": {
"generate": "generate",
"app_name": "App name",
"app_passwd": "App password",
"sogo_visible": "Alias is visible in SOGo",
"sogo_visible_info": "This option only affects objects, that can be displayed in SOGo (shared or non-shared alias addresses pointing to at least one local mailbox). If hidden, an alias will not appear as selectable sender in SOGo.",
"syncjob": "Edit sync job",
"hostname": "Hostname",
"encryption": "Encryption",
"maxage": "Maximum age of messages in days that will be polled from remote<br><small>(0 = ignore age)</small>",
"maxbytespersecond": "Max. bytes per second <br><small>(0 = unlimited)</small>",
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"subfolder2": "Sync into subfolder on destination<br><small>(empty = do not use subfolder)</small>",
"mins_interval": "Interval (min)",
"exclude": "Exclude objects (regex)",
"save": "Save changes",
"username": "Username",
"max_mailboxes": "Max. possible mailboxes",
"title": "Edit object",
"target_address": "Goto address/es <small>(comma-separated)</small>",
"active": "Active",
"gal": "Global Address List",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
"force_pw_update": "Force password update at next login",
"force_pw_update_info": "This user will only be able to login to %s.",
"sogo_access": "Grant access to SOGo",
"sogo_access_info": "Grant or permit access to SOGo. This setting does neither affect access to all other services nor does it delete or change a users existing SOGo profile.",
"target_domain": "Target domain",
"password": "Password",
"password_repeat": "Confirmation password (repeat)",
"domain_admin": "Edit domain administrator",
"domain": "Edit domain",
"edit_alias_domain": "Edit Alias domain",
"domains": "Domains",
"alias": "Edit alias",
"mailbox": "Edit mailbox",
"description": "Description",
"max_aliases": "Max. aliases",
"max_quota": "Max. quota per mailbox (MiB)",
"domain_quota": "Domain quota",
"backup_mx_options": "Backup MX options",
"relay_domain": "Relay this domain",
"relay_all": "Relay all recipients",
"relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.",
"relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.",
"relay_transport_info": "<div class=\"label label-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.",
"full_name": "Full name",
"quota_mb": "Quota (MiB)",
"sender_acl": "Allow to send as",
"sender_acl_disabled": "↳ <span class=\"label label-danger\">Sender check is disabled</span>",
"previous": "Previous page",
"unchanged_if_empty": "If unchanged leave blank",
"dont_check_sender_acl": "Disable sender check for domain %s (+ alias domains)",
"multiple_bookings": "Multiple bookings",
"kind": "Kind",
"resource": "Resource",
"relayhost": "Sender-dependent transports",
"public_comment": "Public comment",
"private_comment": "Private comment",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview",
"extended_sender_acl": "External sender addresses",
"extended_sender_acl_info": "A DKIM domain key should be imported, if available.<br>\r\n Remember to add this server to the corresponding SPF TXT record.<br>\r\n Whenever a domain or alias domain is added to this server, that overlaps with an external address, the external address is removed.<br>\r\n Use @domain.tld to allow to send as *@domain.tld.",
"sender_acl_info": "If mailbox user A is allowed to send as mailbox user B, the sender address is not automatically displayed as selectable \"from\" field in SOGo.<br>\r\n Mailbox user B needs to create a delegation in SOGo to allow mailbox user A to select their address as sender. To delegate a mailbox in SOGo, use the menu (three dots) to the right of your mailbox name in the upper left while in mail view. This behaviour does not apply to alias addresses.",
"nexthop": "Next hop",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"delete2duplicates": "Delete duplicates on destination",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"sieve_desc": "Short description",
"sieve_type": "Filter type",
"validate_save": "Validate and save",
"client_id": "Client ID",
"client_secret": "Client secret",
"scope": "Scope",
"grant_types": "Grant types",
"redirect_uri": "Redirect/Callback URL",
"spam_score": "Set a custom spam score",
"spam_policy": "Add or remove items to white-/blacklist",
"spam_alias": "Create or change time limited alias addresses",
"bcc_dest_format": "BCC destination must be a single valid email address.",
"mailbox_quota_def": "Default mailbox quota",
"mbox_rl_info": "This rate limit is applied on the SASL login name, it matches any \"from\" address used by the logged-in user. A mailbox rate limit overrides a domain-wide rate limit."
},
"add": {
"automap": "Try to automap folders (\"Sent items\", \"Sent\" => \"Sent\" etc.)",
"skipcrossduplicates": "Skip duplicate messages across folders (first come, first serve)",
"gal": "Global Address List",
"gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
"public_comment": "Public comment",
"private_comment": "Private comment",
"comment_info": "A private comment is not visible to the user, while a public comment is shown as tooltip when hovering it in a users overview",
"generate": "generate",
"syncjob": "Add sync job",
"syncjob_hint": "Be aware that passwords need to be saved plain-text!",
"hostname": "Host",
"destination": "Destination",
"nexthop": "Next hop",
"port": "Port",
"app_name": "App name",
"app_password": "Add app password",
"username": "Username",
"enc_method": "Encryption method",
"mins_interval": "Polling interval (minutes)",
"exclude": "Exclude objects (regex)",
"delete2duplicates": "Delete duplicates on destination",
"delete1": "Delete from source when completed",
"delete2": "Delete messages on destination that are not on source",
"custom_params": "Custom parameters",
"custom_params_hint": "Right: --param=xy, wrong: --param xy",
"subscribeall": "Subscribe all folders",
"timeout1": "Timeout for connection to remote host",
"timeout2": "Timeout for connection to local host",
"domain_matches_hostname": "Domain %s matches hostname",
"domain": "Domain",
"active": "Active",
"multiple_bookings": "Multiple bookings",
"description": "Description",
"max_aliases": "Max. possible aliases",
"max_mailboxes": "Max. possible mailboxes",
"mailbox_quota_m": "Max. quota per mailbox (MiB)",
"domain_quota_m": "Total domain quota (MiB)",
"backup_mx_options": "Backup MX options",
"relay_all": "Relay all recipients",
"relay_domain": "Relay this domain",
"relay_all_info": "↪ If you choose <b>not</b> to relay all recipients, you will need to add a (\"blind\") mailbox for every single recipient that should be relayed.",
"relay_unknown_only": "Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.",
"relay_transport_info": "<div class=\"label label-info\">Info</div> You can define transport maps for a custom destination for this domain. If not set, a MX lookup will be made.",
"alias_address": "Alias address/es",
"alias_address_info": "<small>Full email address/es or @example.com, to catch all messages for a domain (comma-separated). <b>mailcow domains only</b>.</small>",
"alias_domain_info": "<small>Valid domain names only (comma-separated).</small>",
"target_address": "Goto addresses",
"target_address_info": "<small>Full email address/es (comma-separated).</small>",
"alias_domain": "Alias domain",
"select": "Please select...",
"target_domain": "Target domain",
"kind": "Kind",
"mailbox_username": "Username (left part of an email address)",
"full_name": "Full name",
"quota_mb": "Quota (MiB)",
"select_domain": "Please select a domain first",
"password": "Password",
"password_repeat": "Confirmation password (repeat)",
"post_domain_add": "The SOGo container, \"sogo-mailcow\", needs to be restarted after adding a new domain!<br><br>Additionally the domains DNS configuration should be reviewed. Once the DNS configuration is approved, restart \"acme-mailcow\" to automatically generate certificates for your new domain (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>This step is optional and will be retried every 24 hours.",
"goto_null": "Silently discard mail",
"goto_ham": "Learn as <span class=\"text-success\"><b>ham</b></span>",
"goto_spam": "Learn as <span class=\"text-danger\"><b>spam</b></span>",
"validation_success": "Validated successfully",
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
"validate": "Validate",
"sieve_desc": "Short description",
"sieve_type": "Filter type",
"add_domain_restart": "Add domain and restart SOGo",
"add_domain_only": "Add domain only",
"mailbox_quota_def": "Default mailbox quota",
"relayhost_wrapped_tls_info": "Please do <b>not</b> use TLS-wrapped ports (mostly used on port 465).<br>\r\nUse any non-wrapped port and issue STARTTLS. A TLS policy to enforce TLS can be created in \"TLS policy maps\"."
},
"acl": {
+ "pushover": "Pushover",
"spam_alias": "Temporary aliases",
"tls_policy": "TLS policy",
"spam_score": "Spam score",
"spam_policy": "Blacklist/Whitelist",
"delimiter_action": "Delimiter action",
"syncjobs": "Sync jobs",
"eas_reset": "Reset EAS devices",
"sogo_profile_reset": "Reset SOGo profile",
"quarantine": "Quarantine actions",
"quarantine_notification": "Change quarantine notifications",
"quarantine_attachments": "Quarantine attachments",
"alias_domains": "Add alias domains",
"login_as": "Login as mailbox user",
"bcc_maps": "BCC maps",
"filters": "Filters",
"ratelimit": "Rate limit",
"recipient_maps": "Recipient maps",
"unlimited_quota": "Unlimited quota for mailboxes",
"extend_sender_acl": "Allow to extend sender ACL by external addresses",
"prohibited": "Prohibited by ACL",
"sogo_access": "Allow management of SOGo access",
"app_passwds": "Manage app passwords"
},
"login": {
"username": "Username",
"password": "Password",
"login": "Login",
"mobileconfig_info": "Please login as mailbox user to download the requested Apple connection profile.",
"delayed": "Login was delayed by %s seconds."
},
"tfa": {
"tfa": "Two-factor authentication",
"set_tfa": "Set two-factor authentication method",
"yubi_otp": "Yubico OTP authentication",
"key_id": "An identifier for your YubiKey",
"init_u2f": "Initializing, please wait...",
"start_u2f_validation": "Start validation",
"reload_retry": "- (reload browser if the error persists)",
"key_id_totp": "An identifier for your key",
"error_code": "Error code",
"api_register": "%s uses the Yubico Cloud API. Please get an API key for your key <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">here</a>",
"u2f": "U2F authentication",
"none": "Deactivate",
"delete_tfa": "Disable TFA",
"disable_tfa": "Disable TFA until next successful login",
"confirm": "Confirm",
"totp": "Time-based OTP (Google Authenticator, Authy, etc.)",
"select": "Please select",
"waiting_usb_auth": "<i>Waiting for USB device...</i><br><br>Please tap the button on your U2F USB device now.",
"waiting_usb_register": "<i>Waiting for USB device...</i><br><br>Please enter your password above and confirm your U2F registration by tapping the button on your U2F USB device.",
"scan_qr_code": "Please scan the following code with your authenticator app or enter the code manually.",
"enter_qr_code": "Your TOTP code if your device cannot scan QR codes",
"confirm_totp_token": "Please confirm your changes by entering the generated token"
},
"oauth2": {
"scope_ask_permission": "An application asked for the following permissions",
"profile": "Profile",
"profile_desc": "View personal information: username, full name, created, modified, active",
"permit": "Authorize application",
"authorize_app": "Authorize application",
"deny": "Deny",
"access_denied": "Please login as mailbox owner to grant access via OAuth2."
},
"diagnostics": {
"dns_records": "DNS Records",
"dns_records_24hours": "Please note that changes made to DNS may take up to 24 hours to correctly have their current state reflected on this page. It is intended as a way for you to easily see how to configure your DNS records and to check whether all your records are correctly stored in DNS.",
"dns_records_name": "Name",
"dns_records_type": "Type",
"dns_records_data": "Correct Data",
"dns_records_status": "Current State",
"optional": "This record is optional.",
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource."
},
"debug": {
"system_containers": "System & Containers",
"started_on": "Started on",
"jvm_memory_solr": "JVM memory usage",
"solr_status": "Solr status",
"solr_dead": "Solr is starting, disabled or died.",
"logs": "Logs",
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\r\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\r\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\r\n <p><b>External logs</b> are collected via API of the given application.</p>\r\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
"in_memory_logs": "In-memory logs",
"external_logs": "External logs",
"static_logs": "Static logs",
"solr_uptime": "Uptime",
"solr_started_at": "Started at",
"solr_last_modified": "Last modified",
"solr_size": "Size",
"solr_docs": "Docs",
"disk_usage": "Disk usage",
"containers_info": "Container information",
"restart_container": "Restart"
}
}
diff --git a/data/web/user.php b/data/web/user.php
index a312d5f4..f691b8bf 100644
--- a/data/web/user.php
+++ b/data/web/user.php
@@ -1,523 +1,578 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
/*
/ DOMAIN ADMIN
*/
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$tfa_data = get_tfa();
$username = $_SESSION['mailcow_cc_username'];
?>
<div class="container">
<h3><?=$lang['user']['user_settings'];?></h3>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['user']['user_settings'];?></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-offset-3 col-sm-9">
<p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p>
<p><small>
<?php
if ($_SESSION['mailcow_cc_last_login']['remote']):
?>
<span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>)
<?php
else: echo $lang['user']['no_last_login']; endif;
?>
</small></p>
<p>
</div>
</div>
<hr>
<div class="row">
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['tfa'];?></div>
<div class="col-sm-9 col-xs-7">
<p id="tfa_pretty"><?=$tfa_data['pretty'];?></p>
<div id="tfa_additional">
<?php if (!empty($tfa_data['additional'])):
foreach ($tfa_data['additional'] as $key_info): ?>
<form style="display:inline;" method="post">
<input type="hidden" name="unset_tfa_key" value="<?=$key_info['id'];?>" />
<div class="label label-default">🔑 <?=$key_info['key_id'];?> <a href="#" style="font-weight:bold;color:white" onClick="$(this).closest('form').submit()">[<?=strtolower($lang['admin']['remove']);?>]</a></div>
</form>
<?php endforeach;
endif;?>
</div>
<br />
</div>
</div>
<div class="row">
<div class="col-sm-3 col-xs-5 text-right"><?=$lang['tfa']['set_tfa'];?></div>
<div class="col-sm-9 col-xs-7">
<select id="selectTFA" class="selectpicker" title="<?=$lang['tfa']['select'];?>">
<option value="yubi_otp"><?=$lang['tfa']['yubi_otp'];?></option>
<option value="u2f"><?=$lang['tfa']['u2f'];?></option>
<option value="totp"><?=$lang['tfa']['totp'];?></option>
<option value="none"><?=$lang['tfa']['none'];?></option>
</select>
</div>
</div>
</div>
</div>
</div>
<?php
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
/*
/ USER
*/
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/header.inc.php';
$_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
$username = $_SESSION['mailcow_cc_username'];
$mailboxdata = mailbox('get', 'mailbox_details', $username);
+ $pushover_data = pushover('get', $username);
$clientconfigstr = "host=" . urlencode($mailcow_hostname) . "&email=" . urlencode($username) . "&name=" . urlencode($mailboxdata['name']) . "&ui=" . urlencode(strtok($_SERVER['HTTP_HOST'], ':')) . "&port=" . urlencode($autodiscover_config['caldav']['port']);
if ($autodiscover_config['useEASforOutlook'] == 'yes')
$clientconfigstr .= "&outlookEAS=1";
if (file_exists('thunderbird-plugins/version.csv')) {
$fh = fopen('thunderbird-plugins/version.csv', 'r');
if ($fh) {
while (($row = fgetcsv($fh, 1000, ';')) !== FALSE) {
if ($row[0] == 'sogo-connector@inverse.ca') {
$clientconfigstr .= "&connector=" . urlencode($row[1]);
}
}
fclose($fh);
}
}
?>
<div class="container">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#userSettings" aria-controls="userSettings" role="tab" data-toggle="tab"><?=$lang['user']['mailbox_details'];?></a></li>
<li role="presentation"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li>
<li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li>
<li role="presentation"><a href="#Syncjobs" aria-controls="Syncjobs" role="tab" data-toggle="tab"><?=$lang['user']['sync_jobs'];?></a></li>
<li role="presentation"><a href="#AppPasswds" aria-controls="AppPasswds" role="tab" data-toggle="tab"><?=$lang['user']['app_passwds'];?></a></li>
+ <li role="presentation"><a href="#Pushover" aria-controls="Pushover" role="tab" data-toggle="tab">Pushover API</a></li>
</ul>
<hr>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="userSettings">
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div>
<div class="panel-body">
<div class="row">
<div class="col-sm-offset-3 col-sm-9">
<?php if ($mailboxdata['attributes']['force_pw_update'] == "1"): ?>
<div class="alert alert-danger"><?=$lang['user']['force_pw_update'];?></div>
<?php endif; ?>
<p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p>
<p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p>
<p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
<p><small>
<?php
if ($_SESSION['mailcow_cc_last_login']['remote']):
?>
<span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>)
<?php
else: echo $lang['user']['no_last_login']; endif;
?>
</small></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['apple_connection_profile'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> <a href="/mobileconfig.php?only_email"><?=$lang['user']['email'];?></a> <small>IMAP, SMTP</small></p>
<p class="help-block"><?=$lang['user']['apple_connection_profile_mailonly'];?></p>
<p><span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> <a href="/mobileconfig.php"><?=$lang['user']['email_and_dav'];?></a> <small>IMAP, SMTP, Cal/CardDAV</small></p>
<p class="help-block"><?=$lang['user']['apple_connection_profile_complete'];?></p>
</div>
</div>
<hr>
<?php // Get user information about aliases
$user_get_alias_details = user_get_alias_details($username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:
<p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['direct_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) {
(!empty($direct_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $direct_alias, $direct_alias_meta['public_comment']) :
printf('%s<br>', $direct_alias);
}
}
?>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:
<p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['shared_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) {
(!empty($shared_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $shared_alias, $shared_alias_meta['public_comment']) :
printf('%s<br>', $shared_alias);
}
}
?>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=$user_get_alias_details['aliases_send_as_all'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=$user_get_alias_details['is_catch_all'];?></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div>
<div class="col-md-5 col-xs-7">
<div class="progress">
<div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;">
<?=$mailboxdata['percent_in_use'];?>%
</div>
</div>
<p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=($mailboxdata['quota'] == 0) ? '∞' : formatBytes($mailboxdata['quota'], 2);?><br><?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
</div>
</div>
<hr>
<?php
// Show tagging options
$get_tagging_options = mailbox('get', 'delimiter_action', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['delimiter_action'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tag_help_explain'];?></p>
<p class="help-block"><?=$lang['user']['tag_help_example'];?></p>
</div>
</div>
<?php
// Show TLS policy options
$get_tls_policy = mailbox('get', 'tls_policy', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['tls_policy'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
</div>
</div>
<?php
// Show quarantine_notification options
$quarantine_notification = mailbox('get', 'quarantine_notification', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
</div>
<p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['eas_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['eas_reset_help'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['sogo_profile_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['sogo_profile_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['sogo_profile_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="sogo_profile" data-api-url='delete/sogo_profile' href="#"><?=$lang['user']['sogo_profile_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['sogo_profile_reset_help'];?></p>
</div>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="SpamAliases">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="table-responsive">
<table class="table table-striped" id="tla_table"></table>
</div>
</div>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_alias'];?>">
<div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="tla" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['hour'];?></a></li>
<li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"6"}' href="#"><?=$lang['user']['expire_in'];?> 6 <?=$lang['user']['hours'];?></a></li>
<li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['day'];?></a></li>
<li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#"><?=$lang['user']['expire_in'];?> 1 <?=$lang['user']['week'];?></a></li>
<li><a data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"672"}' href="#"><?=$lang['user']['expire_in'];?> 4 <?=$lang['user']['weeks'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
</div>
<div class="btn-group">
<a class="btn btn-sm btn-success dropdown-toggle" data-toggle="dropdown" href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['alias_create_random'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">1 <?=$lang['user']['hour'];?></a></li>
<li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"6"}' href="#">6 <?=$lang['user']['hours'];?></a></li>
<li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">1 <?=$lang['user']['day'];?></a></li>
<li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">1 <?=$lang['user']['week'];?></a></li>
<li><a data-action="add_item" data-api-url='add/time_limited_alias' data-api-attr='{"validity":"672"}' href="#">4 <?=$lang['user']['weeks'];?></a></li>
</ul>
</div>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="Spamfilter">
<h4><?=$lang['user']['spamfilter_behavior'];?></h4>
<form class="form-horizontal" role="form" data-id="spam_score" method="post">
<div class="form-group">
<div class="col-lg-6 col-sm-12">
<input data-acl="<?=$_SESSION['acl']['spam_score'];?>" name="spam_score" id="spam_score" type="text" style="width: 100%;"
data-provide="slider"
data-slider-min="1"
data-slider-max="2000"
data-slider-scale='logarithmic'
data-slider-step="0.5"
data-slider-range="true"
data-slider-tooltip='always'
data-slider-id="slider1"
data-slider-value="[<?=mailbox('get', 'spam_score', $username);?>]"
data-slider-step="1" />
<br /><br />
<ul>
<li><?=$lang['user']['spamfilter_green'];?></li>
<li><?=$lang['user']['spamfilter_yellow'];?></li>
<li><?=$lang['user']['spamfilter_red'];?></li>
</ul>
<p><?=$lang['user']['spamfilter_hint'];?></p>
</div>
</div>
<div class="form-group">
<div class="col-sm-10">
</div>
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-success" data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="spam_score"
data-api-url='edit/spam-score'
data-api-attr='{}'><?=$lang['user']['save_changes'];?></a>
<a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-default" data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="spam_score_reset"
data-api-url='edit/spam-score'
data-api-attr='{"spam_score":"default"}'><?=$lang['user']['spam_score_reset'];?></a>
</div>
</div>
</form>
<hr>
<div class="row">
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_wl'];?></h4>
<p><?=$lang['user']['spamfilter_wl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="wl_policy_mailbox_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</div>
</div>
<form class="form-inline" data-id="add_wl_policy_mailbox">
<div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" data-action="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
<div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_bl'];?></h4>
<p><?=$lang['user']['spamfilter_bl_desc'];?></p>
<div class="table-responsive">
<table class="table table-striped table-condensed" id="bl_policy_mailbox_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" data-action="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</div>
</div>
<form class="form-inline" data-id="add_bl_policy_mailbox">
<div class="input-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<input type="text" class="form-control" name="object_from" placeholder="*@example.org" required>
<span class="input-group-btn">
<button class="btn btn-default" data-action="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":<?= json_encode($username); ?>,"object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
</span>
</div>
</form>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="Syncjobs">
<div class="table-responsive">
<table class="table table-striped" id="sync_job_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="syncjob" data-api-url='edit/syncjob' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="syncjob" data-api-url='delete/syncjob' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
</div>
</div>
</div>
+ <div role="tabpanel" class="tab-pane" id="Pushover">
+ <form data-id="pushover" class="form well" method="post">
+ <input type="hidden" value="0" name="active">
+ <div class="row">
+ <div class="col-sm-1">
+ <p class="help-block"><a href="https://pushover.net" target="_blank"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAACglBMVEUAAAAAAAEAAAAilecFGigAAAAAAAAAAAAAAAANj+c3n+Ypm+oeYI4KWI4MieAtkdQbleoJcLcjmeswmN4Rit4KgdMKUYQJKUAQSnILL0kMNlMSTngimOoNPF0hlOQBBgkNOlkRS3MHIjUhk+IPf8wKLUYsjM0AAAASTngAAAAAAAAPfckbdLIbdrYUWIgegsgce70knfEAAAAknfENOVkGHi8YaaIjnvEdgMUhkuAQSG8aca0hleQUh9YLjOM4nOEMgtMcbaYWa6YemO02ltkKhNktgLodYZEPXJEyi8kKesktfLUzj84cWYMiluckZ5YJXJYeW4Y0k9YKfs4yjs0pc6YHZaUviskLfMkqmugak+cqkNcViNcqeK4Iaq4XRmYGPmYMKDsFJTstgr0LdL0ti84CCQ4BCQ4Qgc8rlt8XjN8shcQsi8wZSGgEP2cRMEUDKkUAAAD///8dmvEamfExo/EXmPEWl/ERlvElnvEsofEjnfETl/Enn/Ezo/E4pvEvovEfm/E1pPEzpPEvofEOlfEpoPEamPEQlfEYmfE6p/EgnPEVlvEroPE3pfE2pfENk/Ern/E3pPEcmfEfmvEnnvBlufT6/P0soPAknPDd7/zs9vzo9PxBqfItofAqoPD9/f3B4/q43/mx2/l/xfZ6w/Vxv/VtvfVgt/RXtPNTsfNEq/L3+/31+v3a7fvR6vvH5fqs2vmc0/jx+P3v9/3h8fzW7PvV7PvL5/q13fmo1/mh1PiY0fiNy/aHyfZ2wfVou/Vdt/RPsPM3oeoQkuowmeAgjdgcgMQbeLrw9/3k8vy74Pm63/mX0PdYtfNNr/Ikm+4wnOchkuAVjOAfdrMVcrOdoJikAAAAcnRSTlMAIQ8IzzweFwf+/fvw8P79+/Xt7e3p6eji4d7U08y8qZyTiIWDgn53bWxqaWBKQ0JBOjUwMCkoJCEfHBkT/vz8/Pv7+vr69/b29PTy7ezm5ubm5N7e29vQ0M/Pv7+4uLW1pqaWloWDg3x7e21mUVFFRUXdPracAAAEbElEQVRIx4WUZbvaQBCFF+ru7u7u7u7u7t4mvVwSoBC0JIUCLRQolLq7u7vr/+nMLkmQyvlwyfPcd86e3ZldUqwyQ/p329J+XfutPQYOLUP+q55rFtQJRvY79+xxlZTUWbKpz7/xrrMr2+3BoNPpdLn2lJQ4HEeqLOr1d7z7XNkesQed4A848G63Oy4Gmg/6Mz542QvZbqe8C/Ig73CLYiYTrtLmT3zfqbIcAR7y4wIqH/B6M9Fo0+Ldb6sM9ph/v4ozPuz12mxRofaAAr7jCNkuoz/jNf9AGHibkBCm51fsGKvxsAGWx4H+jBcEi6V2birDpCL/9Klrd1KHbiSvPWP8V0tTnTfO03iXi57P6WNHOVUf44IFdFDRz6pV5fw8Zy5z3JVH5+R48OwxqDiGvKJIY9R+9JsCuJ5HPg74OVEMpz+nbdEPUHEWeEk6IDUnTC1l5r+f8uffc0cfxc8fS17kLso24SwUPFDA/6DE82xKDOPliJ7n/GGOOyWK9zD9CdjvOfg9Dv6AH+AX04LW9gj2i8W/APx1UbxwCAu+wPmcpgUKL/EHdvtq4uwaZwCuznPJVY5LHhED15G/isd5Hz4eKui/e/du02YoKFeD5mHzHIN/nxEDe25gQQwKorAid04CfyzwL4XutXvl1Pt1guMOwwKPkU8mYIFT8JHK+vv8prpDScUVL+j8s3lOctw1GIhbWHAS+HgKPk7xPM/4UtNAYmzizJkf6NgTb/gM8jePQLsewMdthS3g95tMpT1IhVm6v1s8fYmLeb13Odwp8Fh5KY048y/d14WUrwrb1e/X/rNp73nkD8kWS+wi/MZ4XuetG4mhKubJm3/WNEvi8SHwB56nPKjUam0LBdp9ARwupFemTYudvgN/L1+A/Ko/LGBuS8pPy+YR1fuCTWNKnUyoeUyYx2o2dyEVGmr5xTD42xzvkD16+Pb9WIIH6fmt1r3mbsTY7Bvw+n23naT8BUWh86bz6G/e259UXPUK3gfAxQDlo7Rpx3Geqb2e3wp83SGEdKpB7zvwYbzvT2n65xLwbH6YP+M9C8vA8E1wxLU8gkCbdhXGUyrMgwVrcbzLHonr78lzDvWM3q/C/HtDlXoSUIe3YkblhRPIX4E8Oo/9siLv8dRjV7SBlkdgTXvKS7nzsA/9AfeEuhKq9T8zWIDv1Sd6ETAP4D6/H/1V+1BojvruNa4SZXz4JhY84dV5MOF5agUvu5OsOo+KRpG30KalEnoeDccFlutPZYs38D5n3zcpr1/0fBhfb3DOY1z2tSAgLxWezz6zuoHhfUmOejf6blHQH/sFuJYfcMZX307ytKvRa3ifoV/586P5j+tICtS77BuJxzxYAPZsntX8k3eSIhlajK4p8b7iefCEKs03kD/I2LnxL9ovH+43y4fAv1YrI/mzDBsavAX/UppfzVOrZT/ydxk6lJ047MfLfVbcb6hS9ZEzWxekKQ5WrtPqZg3rV6tWrX6Tle3KQZj/q6KxQnmDoXwFY0VSrN9e8FRXBCTAvwAAAABJRU5ErkJggg==" class="img img-fluid"></a></p>
+ </div>
+ <div class="col-sm-10">
+ <div class="form-group">
+ <div class="row">
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="token">API Token/Key (Application)</label>
+ <input type="text" class="form-control" name="token" maxlength="30" value="<?=$pushover_data['token'];?>" required>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="key">User/Group Key</label>
+ <input type="text" class="form-control" name="key" maxlength="30" value="<?=$pushover_data['key'];?>" required>
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="title"><?=$lang['user']['pushover_title'];?></label>
+ <input type="text" class="form-control" name="title" value="<?=$pushover_data['title'];?>" placeholder="Mail">
+ </div>
+ </div>
+ <div class="col-sm-6">
+ <div class="form-group">
+ <label for="text"><?=$lang['user']['pushover_text'];?></label>
+ <input type="text" class="form-control" name="text" value="<?=$pushover_data['text'];?>" placeholder="You've got mail 📧">
+ </div>
+ </div>
+ <div class="col-sm-12">
+ <div class="checkbox">
+ <label><input type="checkbox" value="1" name="active" <?=($pushover_data['active']=="1") ? "checked" : null;?>> <?=$lang['edit']['active'];?></label>
+ </div>
+ </div>
+ </div>
+ </div>
+ <hr>
+ <p><?=sprintf($lang['user']['pushover_info'], $username);?></p>
+ <div class="btn-group" data-acl="<?=$_SESSION['acl']['pushover'];?>">
+ <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover' data-api-attr='{}' href="#"><?=$lang['user']['save'];?></a>
+ <a class="btn btn-sm btn-default" data-action="edit_selected" data-id="pushover-test" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover-test' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['user']['pushover_verify'];?></a>
+ <a class="btn btn-sm btn-danger" data-action="edit_selected" data-id="pushover-delete" data-item="<?=htmlspecialchars($username);?>" data-api-url='edit/pushover' data-api-attr='{"delete":"true"}' href="#"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> <?=$lang['user']['remove'];?></a>
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
+
<div role="tabpanel" class="tab-pane" id="AppPasswds">
<p><?=$lang['user']['app_hint'];?></p>
<div class="table-responsive">
<table class="table table-striped" id="app_passwd_table"></table>
</div>
<div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['app_passwds'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="apppasswd" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['mailbox']['quick_actions'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"1"}' href="#"><?=$lang['mailbox']['activate'];?></a></li>
<li><a data-action="edit_selected" data-id="apppasswd" data-api-url='edit/app-passwd' data-api-attr='{"active":"0"}' href="#"><?=$lang['mailbox']['deactivate'];?></a></li>
<li role="separator" class="divider"></li>
<li><a data-action="delete_selected" data-id="apppasswd" data-api-url='delete/app-passwd' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addAppPasswdModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_app_passwd'];?></a>
</div>
</div>
</div>
</div>
</div><!-- /container -->
<div style="margin-bottom:200px;"></div>
<?php
}
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] != 'admin') {
require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/user.php';
?>
<script type='text/javascript'>
<?php
$lang_user = json_encode($lang['user']);
echo "var lang = ". $lang_user . ";\n";
echo "var acl = '". json_encode($_SESSION['acl']) . "';\n";
echo "var csrf_token = '". $_SESSION['CSRF']['TOKEN'] . "';\n";
echo "var mailcow_cc_username = '". $_SESSION['mailcow_cc_username'] . "';\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
?>
</script>
<?php
$js_minifier->add('/web/js/site/user.js');
$js_minifier->add('/web/js/site/pwgen.js');
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/footer.inc.php';
}
else {
header('Location: /');
exit();
}
?>

File Metadata

Mime Type
text/x-diff
Expires
9月 9 Tue, 5:47 AM (7 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5373
默认替代文本
(790 KB)

Event Timeline